Lorsque vous effectuez une opération, par exemple
3 + 4
, le
+
est un opérateur,
3
et
4
sont des
opérandes. Si l’opérateur s’applique à 2 opérandes, on dit qu’il
s’agit d’un opérateur binaire, ou bien d’arité
2. Un opérateur d’arité 1, dit aussi unaire, s’applique à
un seul opérande, par exemple
-x
, le
x
est
l’opérande et le
-
unaire est l’opérateur qui, appliqué à
x
, nous donne l’opposé de celui-ci, c’est-à-dire le nombre
qu’il faut additionner à
x
pour obtenir 0. Il ne faut pas
le confondre avec le
-
binaire, qui appliqué à x et y,
additionne à x l’opposé de y.
En C, les opérateurs sont unaires ou binaires, et il existe un seul opérateur ternaire.
Si vous écrivez une expression de la forme
a + b + c
, où
a
,
b
et
c
sont des variables entières, vous
appliquez deux fois l’opérateur binaire
+
pour calculer la
somme de 3 nombres
a
,
b
et
c
. Dans quel
ordre ces opérations sont-elles effectuées ? Est-ce que l’on a
(a + b) + c
ou
a + (b + c)
? Cela importe peu, car
le
+
entier est associatif, ce qui signifie
qu’il est possible de modifier le parenthèsage d’une somme d’entiers
sans en changer le résultat. Attention : l’associativité est une
rareté ! Peu d’opérateurs sont associatifs, une bonne connaissance des
règles sur les priorités et le parenthèsage par défaut est donc
requise.
Un opérateur unaire est préfixe s’il se place avant son
opérande, postfixe s’il se place après. Un opérateur binaire
est infixe s’il se place entre ses deux opérandes
(
a + b
), préfixe s’il se place avant (
+ a b
),
postfixe s’il se place après (
a b +
). Vous rencontrez
en
C
des opérateurs unaires préfixes et d’autres postfixes (on
imagine difficilement un opérateur unaire infixe), par contre tous les
opérateurs binaires seront infixe.
Les règles des priorités en C sont nombreuses et complexes, nous ne ferons ici que les esquisser. Nous appelerons parenthèsage implicite le parenthèsage adopté par défaut par le C, c’est à dire l’ordre dans lequel il effectue les opérations. La première règle à retenir est qu’un opérateur unaire est toujours prioritaire sur un opérateur binaire.
La négation arithmétique est l’opérateur
-
qui à une
opérande x, associe l’opposé de x, c’est-à-dire le nombre qu’il
faut additionner à x pour obtenir 0.
La négation binaire
~
agit directement sur les bits de son
opérande, tous les bits à 0 deviennent 1, et vice-versa. Par exemple,
~127
(tous les bits à 1 sauf le premier) est égal à
128
(le premier bit à 1 et tous les autres à 0).
Tous les opérateurs unaires sont de priorité équivalente, le
parenthèsage implicite est fait le plus à droite possible, on dit que
ces opérateurs sont associatifs à droite. Par exemple, le
parenthèsage implicite de l’expression
~-~i
est
~(-(~i))
. C’est plutôt logique : si vous parvenez à placer
les parenthèses différement, prevenez-moi parce que je ne vois pas
comment faire...
L’opération
a >> 1
effectue un décalage des bits de la
représentation binaire de
a
vers la droite. Tous les
bits sont décalés d’un cran vers la droite, le dernier
bit disparaît, le premier prend la valeur
0
. L’opération
a << 1
effectue un décalage des bits de la représentation
binaire de
a
vers la gauche. Tous les bits sont décalés d’un
cran vers la gauche, le premier bit disparaît et le dernier devient
0
. Par exemple,
32 << 2
associe à 0010 0000
<< 2 la valeur dont la représentation binaire 1000
0000 et 32 >> 3 associe à 0010 0000
>> 3 la valeur dont la représentation binaire 0000 0100.
L’opérateur
&
associe à deux opérandes le ET logique de
leurs représentations binaires par exemple
60 & 15
donne
12, autrement formulé 0011 1100 ET 0000 1111 = 0000
1100. L’opérateur
|
associe à deux opérandes le OU
logique de leurs représentations binaires par exemple 60 |
15 donne 63, autrement formulé 0011 1100 OU 0000
1111 = 0011 1111. L’opérateur
^
associe à deux
opérandes le OU exclusif logique de leurs représentations
binaires par exemple
60 ^ 15
donne 51, autrement
formulé 0011 1100 OU EXCLUSIF 0000 1111 = 0011 0011. Deux
^
successifs s’annulent, en d’autres termes
a^b^b=a
.
Ne vous en déplaise, le
=
est bien un opérateur
binaire. Celui-ci affecte à l’opérande de gauche (appelée
Lvalue
par le compilateur), qui doit être une
variable, une valeur calculée à l’aide d’une expression, qui est
l’opérande de droite. Attention, il est possible d’effectuer une
affectation pendant l’évaluation d’une expression. Par exemple,
a = b + (c = 3);
Cette expression affecte à
c
la valeur 3, puis affecte à
a
la valeur
b + c
.
Tous les opérateurs binaires ne sont pas de priorités
équivalentes. Ceux de priorité la plus forte sont les opérateurs
arithmétiques (
*
,
/
,
&
,
+
,
-
), puis les opérateus de décalage de bit (
<<
,
>>
), les opérateurs de bit (
&
,
^
,
|
), et enfin l’affectation
=
. Représentons dans une
tableau les opérateurs en fonction de leur priorité, plaçons les plus
prioritaire en haut et les moins prioritaires en bas. Parmi les
opérateurs arithmétiques, les multiplications et divisions sont
prioritaires sur les sommes et différence :
noms | opérateurs |
produit | *, /, % |
sommes | +, - |
Les deux opérateurs de décalage sont de priorité équivalente :
noms | opérateurs |
décalage binaire | >>, << |
L’opérateur
&
est assimilé à un produit,
|
à une
somme. Donc ,
&
est prioritaire sur
|
. Comme
^
se trouve entre les deux, on a
noms | opérateurs |
ET binaire | & |
OU Exlusif binaire | ^ |
OU binaire | | |
Il ne nous reste plus qu’à assembler les tableaux :
noms | opérateurs |
produit | *, /, % |
somme | +, - |
décalage binaire | >>, << |
ET binaire | & |
OU Exlusif binaire | ^ |
OU binaire | | |
affectation | = |
Quand deux opérateurs sont de même priorité le parenthèsage implicite
est fait le plus à gauche possible, on dit que ces opérateurs
sont associatifs à gauche. Par exemple, le parenthèsage
implicite de l’expression
a - b - c
est
(a - b) - c
et certainement pas
a - (b - c)
. Ayez donc cela en
tête lorsque vous manipulez des opérateurs non associatifs ! La seule
exception est le
=
, qui est associatif à droite. Par exemple,
a = b = c;
se décompose en
b = c
suivi de
a = b
.
Le C étant un langage de paresseux, tout à été fait pour que les programmeurs aient le moins de caractères possible à saisir. Je vous préviens : j’ai placé ce chapitre pour que soyez capable de décrypter la bouillie que pondent certains programmeurs, pas pour que vous les imitiez ! Alors vous allez me faire le plaisir de faire usage des formes contractées avec parcimonie, n’oubliez pas qu’il est très important que votre code soit lisible.
Il est possible d’incrémenter (augmenter de 1) la valeur
d’une variable
i
en écrivant
i++
, ou bien
++i
. De la même façon on peut décrémenter (diminuer
de 1)
i
en écrivant
i--
(forme postfixe), ou bien
--i
(forme préfixe). Vous pouvez décider d’incrémenter (ou de
décrémenter) la valeur d’une variable pendant un calcul, par exemple,
a = 1; b = (a++) + a;
évalue successivement les deux opérandes
a++
et
a
,
puis affecte leur somme à
b
. L’opérande
a++
est
évaluée à 1, puis est incrémentée, donc lorsque la deuxième opérande
a
est évaluée, sa valeur est 2. Donc la valeur de b après
l’incrémentation est 3. L’incrémentation contractée sous forme
postfixe s’appelle une post-incrémentation. Si l’on écrit,
a = 1; b = (++a) + a;
On opère une pré-incrémentation,
++a
donne lieu à
une incrémentation avant l’évaluation de
a
, donc la valeur
4 est affectée à
b
. On peut de façon analogue effectuer une
pré-decrémentation ou a post-decrémentation. Soyez
très attentifs au fait que ce code n’est pas portable, il existe des
compilateurs qui évaluent les opérandes dans le désordre ou diffèrent
les incrémentations, donnant ainsi des résultats autres que les
résultats théoriques exposés précédement. Vous n’utiliserez donc les
incrémentation et decrémentation contractées que
lorsque vous serez certain que l’ordre d’évaluation des opérandes ne
pourra pas influer sur le résultat.
Toutes les affectations de la forme
variable = variable operateurBinaire expression
peuvent être contractées sous la forme
variable operateurBinaire= expression
. Par exemple,
avant | après |
a = a + b | a += b |
a = a - b | a -= b |
a = a * b | a *= b |
a = a / b | a /= b |
a = a % b | a %= b |
a = a >> i | a >>= i |
a = a << i | a <<= i |
a = a & b | a &= b |
a = a ^ b | a ^= b |
a = a | b | a |= b |
Vous vous douterez que l’égalité ne peut pas être contractée...
Nous ordonnons de façon grossière les types de la façon suivante :
long double
>
double
>
float
>
unsigned long
>
long
>
unsigned int
>
int
>
unsigned short
>
short
>
char
. Dans un calcul où les opérandes sont de types
hétérogènes, l’opérande dont le type T est de niveau le plus élévé
(conformément à l’ordre énoncé ci-avant) est sélectionné et l’autre
est converti dans le type T.
Il se peut cependant que dans un calcul, cela ne convienne pas. Si par exemple, vous souhaitez calculer l’inverse 1/x d’un nombre entier x, et que vous codez
int i = 4; printf("L'inverse de %d est %d", i, 1/i);
Vous constaterez que résultat est inintéressant au possible. En effet, comme i et 1 sont tout deux de type entier, c’est la division entière est effectuée, et de toute évidence le résultat est 0. Changer la chaîne de format
int i = 4; printf("L'inverse de %d est %f\n", i, 1/i);
se révèlera aussi d’une inefficacité notoire : non seulement vous vous
taperez un warning, mais en plus
printf
lira un entier en
croyant que c’est un flottant. Alors comment on se sort de là ? Ici la
bidouille est simple, il suffit d’écrire le
1
avec un point :
int i = 4; printf("L'inverse de %d est %f\n", i, 1./i);
Le compilateur, voyant un opérande de type flottant, convertit lors du
calcul l’autre opérande,
i
, en flottant. De ce fait, c’est
une division flottante et non entière qui est effectuée. Allons plus
loin : comment faire pour appliquer une division flottante à deux
entiers ? Par exemple :
int i = 4, j= 5; printf("Le quotient de %d et %d est %f\n", i, j, i/j);
Cette fois-ci c’est inextricable, vous pouvez placer des points où
vous voudrez, vous n’arriverez pas à vous débarasser du warning et ce
programme persistera à vous dire que ce quotient est
-0.000000 ! Une solution particulièrement crade serait de
recopier
i
et
j
dans des variables flottantes avant
de faire la division, une autre méthode de bourrin est de calculer
(i + 0.)/j
. Mais j’espère que vous réalisez que seuls les
boeufs procèdent de la sorte.
cast
Le seul moyen de vous sortir de là est d’effectuer un
cast
,
c’est à dire une conversion de type sur commande. On caste en plaçant
entre parenthèse le type dans lequel on veut convertir juste avant
l’opérande que l’on veut convertir. Par exemple,
int i = 4, j= 5; printf("Le quotient de %d et %d est %f\n", i, j, (float)i/j);
Et là, ça fonctionne. La variable valeur contenue dans
i
est
convertie en
float
et de ce fait, l’autre opérande,
j
, est aussi convertie en
float
. La division est
donc une division flottante. Notez bien que le
cast
est un
opérateur unaire, donc prioritaire sur la division qui est un
opérateur binaire, c’est pour ça que la conversion de
i
a
lieu avant la division. Mais si jamais il vous vient l’idée saugrenue
d’écrire
int i = 4, j= 5; printf("Le quotient de %d et %d est %f\n", i, j, (float)(i/j));
Vous constaterez très rapidement que c’est une alternative peu intelligente. En effet, le résulat est flottant, mais comme la division a lieu avant toute conversion, c’est le résultat d’une division entière qui est converti en flottant, vous avez donc le même résultat que si vous n’aviez pas du tout casté.
Ajoutons le
cast
au tableau des priorités de nos opérateurs :
noms | opérateurs |
opérateurs unaires | cast, -, , ++, -- |
produit | *, /, % |
somme | +, - |
décalage binaire | >>, << |
ET binaire | & |
OU Exlusif binaire | ^ |
OU binaire | | |
affectation | = |