pdf - e-book - archive - github.com

1.3  Opérateurs

1.3.1  Généralités

Opérandes et arité

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.

Associativité

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.

Formes préfixes, postfixes, infixes

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.

Priorités

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.

1.3.2  Les opérateurs unaires

Négation arithmétique

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.

Négation binaire

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).

Priorités

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...

1.3.3  Les opérateurs binaires

Opérations de décalages de bits

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.

Opérations logiques sur la représentation binaire

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.

Affectation

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.

Priorités

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 :

nomsopérateurs
produit*, /, %
sommes+, -

Les deux opérateurs de décalage sont de priorité équivalente :

nomsopé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

nomsopérateurs
ET binaire&
OU Exlusif binaire^
OU binaire|

Il ne nous reste plus qu’à assembler les tableaux :

nomsopé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.

1.3.4  Formes contractées

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.

Unaires

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.

Binaires

Toutes les affectations de la forme variable = variable operateurBinaire expression peuvent être contractées sous la forme variable operateurBinaire= expression. Par exemple,

avantaprès
a = a + ba += b
a = a - ba -= b
a = a * ba *= b
a = a / ba /= b
a = a % ba %= b
a = a >> ia >>= i
a = a << ia <<= i
a = a & ba &= b
a = a ^ ba ^= b
a = a | ba |= b

Vous vous douterez que l’égalité ne peut pas être contractée...

1.3.5  Opérations hétérogènes

Le fonctionnement par défaut

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.

Le problème

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.

Le 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é.

1.3.6  Les priorités

Ajoutons le cast au tableau des priorités de nos opérateurs :

nomsopérateurs
opérateurs unairescast, -,  , ++, --
produit*, /, %
somme+, -
décalage binaire>>, <<
ET binaire&
OU Exlusif binaire^
OU binaire|
affectation=