Les opérateurs sont les verbes du C : ils prennent des valeurs et en produisent de nouvelles. Il y en a une quarantaine, mais une douzaine couvre la plupart du code, et deux d'entre eux ont des pièges assez tranchants pour mériter leur propre section.
Les opérateurs arithmétiques
(%% dans la chaîne de format affiche un signe pourcent littéral - % seul commencerait un spécificateur de format.)
Les cinq opérateurs arithmétiques binaires sont + - * / %, plus le - unaire pour la négation et le + qui ne fait essentiellement rien.
Le piège de la division entière
a / b avec deux entiers effectue une division entière et jette le reste. Il n'y a pas d'arrondi - le résultat est tronqué vers zéro.
Ce dernier cas est le bug qui survit jusqu'en production. Affecter à un double n'aide pas : la division total / count est déjà terminée, et déjà un int, avant que l'affectation n'ait lieu. La solution est de rendre un opérande flottant dans l'expression, généralement avec un cast.
Deux règles voisines : diviser par zéro avec des entiers est un comportement indéfini et fait typiquement planter le programme, et INT_MIN / -1 déborde.
L'opérateur modulo
% donne le reste et ne fonctionne que sur les entiers :
Tester x % 2 == 0 pour la parité et utiliser % n pour faire boucler un indice sur un tableau sont les deux usages quotidiens.
Avec des nombres négatifs, le % du C suit le signe du dividende :
Donc x % 2 == 1 est un test d'imparité cassé quand x peut être négatif - -7 % 2 vaut -1. Utilisez plutôt x % 2 != 0.
Pour les restes en virgule flottante, % est une erreur de compilation ; utilisez fmod() de math.h.
Affectation et affectation composée
= stocke une valeur. Les formes composées combinent une opération et le stockage :
x += 5 signifie x = x + 5, mais il n'évalue x qu'une fois - ce qui compte quand la cible est quelque chose comme arr[compute_index()].
L'affectation est elle-même une expression qui produit la valeur affectée, c'est pourquoi a = b = c = 0 fonctionne (l'affectation va de droite à gauche) et pourquoi l'erreur = contre == compile silencieusement.
Incrémentation et décrémentation
++ ajoute un, -- en retire un. Chacun existe en forme préfixe et postfixe :
En instruction isolée, les deux sont interchangeables, et i++ est le choix conventionnel dans un en-tête de for sans autre raison que la tradition.
Ce que vous ne devez pas faire, c'est utiliser une variable plus d'une fois dans une expression où elle est aussi modifiée :
int i = 5;
int x = i++ + i++; /* comportement indefini */
arr[i] = i++; /* comportement indefini */
printf("%d %d", i++, i);/* comportement indefini */
Le C ne définit pas l'ordre d'évaluation de ces sous-expressions, donc le résultat n'est pas simplement non spécifié - c'est le comportement de tout le programme qui est indéfini. Coupez l'instruction en deux et l'ambiguïté disparaît.
Les opérateurs de comparaison
Il y en a six, et tous produisent un int : 1 pour vrai et 0 pour faux.
Le fait que le résultat soit un simple int plutôt qu'un type booléen dédié est une caractéristique définitoire du C - voir les booléens en C pour ce qui en découle, y compris le bug = contre ==.
Deux erreurs de comparaison sont propres au C. Le chaînage ne fonctionne pas comme les mathématiques le suggèrent : if (1 < x < 10) est toujours vrai, car 1 < x s'évalue à 0 ou 1 puis cela est comparé à 10. Écrivez if (x > 1 && x < 10). Et comparer des chaînes avec == compare des pointeurs, pas du texte - utilisez strcmp de string.h.
Les opérateurs logiques
&& est le ET, || le OU, ! le NON. Tous trois traitent toute valeur non nulle comme vraie.
Ils évaluent en court-circuit : && s'arrête dès qu'un opérande est faux, et || dès qu'un est vrai. Le reste n'est jamais évalué, ce qui n'est pas qu'une optimisation - c'est une garantie sur laquelle vous pouvez compter pour la sûreté :
Inversez l'ordre de ces deux tests et l'appel avec NULL déréférence un pointeur nul et plante. Protégez d'abord, utilisez ensuite - l'ordre dans une chaîne && fait partie de la logique.
L'opérateur conditionnel
Le seul opérateur à trois opérandes du C choisit entre deux valeurs :
condition ? valeur_si_vrai : valeur_si_faux. C'est une expression, elle tient donc là où une instruction if ne peut pas - dans un argument de printf, dans un initialiseur. Gardez-la courte ; les conditionnels imbriqués deviennent vite illisibles.
Les opérateurs bit à bit, brièvement
Ils travaillent sur les bits individuels d'un entier :
Ils servent aux drapeaux, aux masques et aux registres matériels. Deux mises en garde : & et | ne sont pas && et || (ils n'évaluent pas en court-circuit et travaillent bit à bit), et décaler une valeur signée ou décaler de plus que la largeur du type est indéfini. Utilisez des types non signés pour le travail sur les bits.
La précédence
Quand une expression n'a pas de parenthèses, cette table décide de ce qui s'attache à quoi. La précédence la plus forte d'abord :
| Niveau | Opérateurs | Associativité |
|---|---|---|
| 1 | () [] -> . x++ x-- | de gauche à droite |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | de droite à gauche |
| 3 | * / % | de gauche à droite |
| 4 | + - | de gauche à droite |
| 5 | << >> | de gauche à droite |
| 6 | < <= > >= | de gauche à droite |
| 7 | == != | de gauche à droite |
| 8 | & | de gauche à droite |
| 9 | ^ | de gauche à droite |
| 10 | OU bit à bit | de gauche à droite |
| 11 | && | de gauche à droite |
| 12 | OU logique | de gauche à droite |
| 13 | ?: | de droite à gauche |
| 14 | = += -= *= /= %= et compagnie | de droite à gauche |
| 15 | , | de gauche à droite |
Les conséquences pratiques :
a + b * c /* signifie a + (b * c) */
a < b == c < d /* signifie (a < b) == (c < d) - rarement ce que vous vouliez */
x & 1 == 0 /* signifie x & (1 == 0), c.-a-d. x & 0 - un bug classique */
*p++ /* signifie *(p++) - dereferencer p, puis l'avancer */
Cette troisième ligne mérite d'être mémorisée : == s'attache plus fort que &, donc les tests de masque de bits ont besoin de parenthèses - (x & 1) == 0.
Personne ne retient les quinze niveaux, et personne n'en a besoin. Retenez que * bat +, que les comparaisons battent && qui bat ||, et que tout le reste reçoit des parenthèses :
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
Cette ligne a des parenthèses redondantes et s'en porte mieux.
Questions fréquentes
Que fait % en C ?
% est l'opérateur modulo : il donne le reste de la division entière. 17 % 5 vaut 2. Il ne fonctionne que sur les entiers - l'utiliser sur un float ou un double est une erreur de compilation, et fmod() de math.h en est l'équivalent flottant.
Pourquoi 5 / 2 donne-t-il 2 en C ?
Parce que les deux opérandes sont des entiers, le C fait donc une division entière et jette la fraction. Rendez un côté flottant pour obtenir 2,5 : 5 / 2.0, ou convertissez un opérande avec (double)a / b.
Quelle est la différence entre i++ et ++i ?
Les deux ajoutent un à i. ++i (préfixe) incrémente d'abord et produit la nouvelle valeur ; i++ (postfixe) produit l'ancienne valeur et incrémente ensuite. En instruction isolée, ils sont identiques - la différence ne compte que quand le résultat est utilisé, comme dans int b = a++; face à int b = ++a;.
Qu'est-ce que la précédence des opérateurs en C ?
L'ordre dans lequel les opérateurs s'attachent quand une expression n'a pas de parenthèses. *, / et % s'attachent plus fort que + et -, qui s'attachent plus fort que les comparaisons, qui s'attachent plus fort que &&, puis ||, puis l'affectation. Dans le doute, ajoutez des parenthèses - elles ne coûtent rien et suppriment la question.