Une expression est un calcul qui donne une valeur résultat (exemple : 8+5). Une expression comporte des variables, des appels de fonction et des constantes combinés entre eux par des opérateurs (ex : MaVariable*sin(VarAngle*PI/180) ).
Une expression de base peut donc être un appel à une fonction (exemple sin(3.1416). Une fonction est un bout de programme (que vous avez écrit ou faisant partie d'une bibliothèque) auquel on "donne" des valeurs (arguments), entre parenthèses et séparés par des virgules. La fonction fait un calcul sur ces arguments pour "retourner" un résultat. Ce résultat pourra servir, si nécessaire, dans une autre expression, voire comme argument d'une fonction exemple atan(tan(x)). Les arguments donnés à l'appel de la fonction (dits paramètres réels ou effectifs) sont recopiés dans le même ordre dans des copies (paramètres formels), qui elles ne pourront que modifier les copies (et pas les paramètres réels). Dans le cas de fonctions devant modifier une variable, il faut fournir en argument l'adresse (par l'opérateur &, voir plus bas), comme par exemple pour scanf.
Pour former une expression, les opérateurs possibles sont assez nombreux, nous allons les détailler suivant les types de variables qu'ils gèrent.
Arithmétiques
Ces opérateurs s'appliquent à des valeurs entières ou réelles.
unaires
Ce sont les opérateurs à un seul argument : - et + (ce dernier a été rajouté par la norme ANSI). Le résultat est du même type que l'argument.
deuxaires
Le terme "deuxaire" n'est pas standard, je l'utilise parce que binaire est pour moi associé à la base 2.
Ces opérateurs nécessitent deux arguments, placés de part et d'autre de l'opérateur. Ce sont + (addition), - (soustraction), * (produit), / (division), % (reste de la division). % nécessite obligatoirement deux arguments entiers, les autres utilisent soit des entiers, soit des réels. Les opérandes doivent être du même type, le résultat sera toujours du type des opérandes. Lorsque les deux opérandes sont de type différent (mais numérique évidement), le compilateur prévoit une conversion implicite (vous ne l'avez pas demandée mais il la fait néanmoins) suivant l'ordre : { char -> int -> long -> float -> double } et { signed -> unsigned }. On remarque qu'il considère les char comme des entiers, les opérations sont en fait faites sur les numéros de code (ASCII). Les calculs arithmétiques sont faits uniquement soit en long soit en double, pour éviter des dépassements de capacité.
exemples :
int a=1,b=2,c=32000;
float x=1,y=2;
a=(c*2)/1000; /* que des int, le résultat est 64, même si l'on est passé par un résultat intermédiaire (64000) qui dépassait la capacité des entiers (mais pas celle des long) */
b=7/b; /* signe = donc en premier calcul de l'argument à droite : 7 (entier) / 2 (entier) donne 3 (entier, reste 1, que l'on obtient par 5%2). donc b=3 */
x=7/b; /* 7 et b entiers => passage en réel inutile, calcul de 7/3 donne 2 (entier, reste 1) puis opérateur = (transformation du 2 en 2.0 puis transfert dans X qui vaut donc 2.0) */
x=7/y; /* un int et un float autour de / : transformation implicite de 7 en réel (7.0), division des deux réel (3.5), puis transfert dans x */
x=((float)(a+1))/b; /* calcul (entier) de a+1, puis transformation explicite en float, et donc implicite de b en float, division 65.0/3.0 -> 21.666... */
Relationnels
comparaisons
Ces opérateurs sont deuxaires : = = (égalité), != (différent), <, >, <=, >=. Des deux côtés du signe opératoire, il faut deux opérandes de même type (sinon, transformation implicite) mais numérique (les caractères sont classés suivant leur numéro de code ASCII). Le résultat de l'opération est 0 si faux, 1 si vrai (le résultat est de type int). Exemple : (5<7)+3*((1+1)= =2) donne 4. Attention, le compilateur ne vous prévient pas si vous avez mis = au lieu de = = (= est aussi un opérateur, voir plus loin), mais le résultat sera différent de celui prévu.
logique booléenne
Le résultat est toujours 0 (faux) ou 1 (vrai), les opérandes devant être de type entier (si char conversion implicite), 0 symbolisant faux, toute autre valeur étant considérée vraie.
Opérateur unaire : ! (non). !arg vaut 1 si arg vaut 0, et 0 sinon.
Opérateurs deuxaires : && (ET, vaut 1 si les 2 opérandes sont non nuls, 0 sinon) et || (OU, vaut 0 si les deux opérandes sont nuls, 1 sinon). Le deuxième opérande n'est évalué que si le premier n'a pas suffi pour conclure au résultat (ex (a= =0)&&(x++<0) incrémente x si a est nul, le laisse intact sinon).
binaires
Ces opérateurs ne fonctionnent qu'avec des entiers. Ils effectuent des opérations binaires bit à bit. On peut utiliser ~ (complément, unaire), & (et), | (ou inclusif), ^ (ou exclusif), >> (décalage à droite, le 2ème opérande est le nombre de décalages), << (décalage à gauche). Contrairement aux opérateurs relationnels, les résultats ne se limitent pas à 0 et 1.
exemples : 7&12 donne 4 (car 0111&1100 donne 0100); ~0 donne -1 (tous les bits à 1, y compris celui de signe); 8<<2 donne 32.
Affectation
affectation simple =
En C, l'affectation (signe =) est une opération comme une autre. Elle nécessite deux opérantes, un à droite, appelé Rvalue, qui doit être une expression donnant un résultat d'un type donné, et un à gauche (Lvalue) qui doit désigner l'endroit en mémoire où l'on veut stocker la Rvalue. Les deux opérandes doivent être de même type, dans le cas d'opérandes numériques si ce n'est pas le cas le compilateur effectuera une conversion implicite (la Lvalue doit être de type "plus fort" que la Rvalue). L'opération d'affectation rend une valeur, celle qui a été transférée, et peut donc servir de Rvalue.
Exemples : a=5 (met la valeur 5 dans la variable a. Si a est float, il y a conversion implicite en float); b=(a*5)/2 (calcule d'abord la Rvalue, puis met le résultat dans b); a=5+(b=2) (Le compilateur lit l'expression de gauche à droite. la première affectation nécessite le calcul d'une Rvalue : 5+(b=2). Celle ci comporte une addition, dont il évalue le premier opérande (5) puis le second (b=2). Il met donc 2 dans b, le résultat de l'opération est 2, qui sera donc ajouté à 5 pour être mis dans a. A vaut donc 7 et b, 2. Le résultat de l'expression est 7 (si l'on veut s'en servir).
Remarque : il ne faut pas confondre = et = =. Le compilateur ne peut pas remarquer une erreur (contrairement au Pascal ou Fortran) car les deux sont possibles. Exemple : if (a=0) est toujours faux car quelle que soit la valeur initiale de a, on l'écrase par la valeur 0, le résultat de l'opération vaut 0 et est donc interprété par IF comme faux.
incrémentation / décrémentation
++a : ajoute 1 à la variable a. Le résultat de l'expression est la valeur finale de a (c'est à dire après incrémentation). On l'appelle incrémentation préfixée.
a++ : ajoute 1 à la variable a. Le résultat de l'expression est la valeur initiale de a (c'est à dire avant incrémentation). C'est l'incrémentation postfixée.
de même, la décrémentation --a et a-- soustrait 1 à a.
exemple : j=++i est équivalent à j=(i=i+1). Je vous déconseille les imbrications du genre i=i++ + ++i difficilement compréhensibles.
affectation élargie
+= , -= , *= , /= , %= , <<= , >>= , &= , ^= , |=
a+=5 est équivalent à a=(a+5). Il faut encore ici une Rvalue à droite et une Lvalue à gauche.
Opérateurs d'adresses
Ces opérateurs sont utilisées avec des pointeurs. On utilise
&variable : donne l'adresse d'une variable
*pointeur : réfère à la variable pointée (opérateur d'indirection)
. : champ d'une structure
-> : champ pointé
exemple : supposons déclarer : int i1=1,i2=2; int *p1,*p2; i1 et i2 sont deux mémoires contenant un entier, alors que p1 et p2 sont des pointeurs, puisqu'ils contiennent une adresse d'entier. p1=&i1; met dans p1 l'adresse de i1. p2=p1; met la même adresse (celle de i1) dans p2. printf("%d\n",*p1); affiche ce qui est désigné (pointé) par p1 donc i1 donc 1. p2=&i2;*p2=*p1; à l'adresse pointée par p2 mettre ce qui est pointé par p1, donc copier la valeur de i1 dans i2. printf("%d\n",i2); affiche donc 1.
Autres
conditionnel ? :
C'est un (le seul) opérateur ternaire. L'expression a?b:c vaut la valeur de b si a est vrai (entier, différent de 0), et c si a est faux. Exemple : max=a>b?a:b
séquentiel ,
Cet opérateur permet de regrouper deux sous expressions en une seule. On effectue le premier opérande puis le second, la valeur finale de l'expression étant celle du second opérande. On l'utilise pour évaluer deux (ou plus) expressions là où la syntaxe du C ne nous permettait que d'en mettre une, exemple : for(i=j=0;i>10;i++,j++). Dans le cas d'une utilisation de cet opérateur dans une liste, utilisez les parenthèses pour distinguer les signes , : exemple (inutile) : printf("%d %d",(i++,j++),k) i est modifié mais sa valeur n'est pas affichée.
Ordre de priorité et associativité
opérateurs
associativité
description
() [] -> .
->
! ~ ++ -- - + & * (cast)
<-
unaires (* pointeurs)
* / %
->
multiplicatifs
+ -
->
addition
>> <<
->
décalages
< <= > >=
->
relations d'ordre
= = !=
->
égalité
&
->
binaire
^
->
binaire
|
->
binaire
&&
->
logique
| |
->
logique
? :
->
conditionnel (ternaire)
= += -= *= etc.
<-
affectation
,
<-
séquentiel
Dans ce tableau, les opérateurs sont classés par priorité décroissante (même priorité pour les opérateurs d'une même ligne). Les opérateurs les plus prioritaires seront évalués en premier. L'associativité définit l'ordre d'évaluation des opérandes. La plupart se font de gauche à droite ( 4/2/2 donne (4/2)/2 donc 1 (et pas 4/(2/2))).
Les seules exceptions sont :
les opérateurs unaires, écrits à gauche de l'opérateur. L'opérande est évalué puis l'opération est effectuée, le résultat est celui de l'opération; sauf dans le cas de l'incrémentation / décrémentation postfixée, où le résultat de l'expression est la valeur de l'argument avant l'opération.
L'affectation : on calcule l'opérande de droite, puis on l'affecte à celui de gauche. Le résultat est la valeur transférée.
La virgule : la valeur à droite est calculée avant celle à gauche (en particulier lors d'un appel de fonction). Exemple d'embrouille : fonction(tableau[i],++i);
Les opérateurs logiques et conditionnel évaluent toujours leur premier argument. Le second par contre n'est évalué que si c'est nécessaire. Donc a && i++ ne change pas toujours i, contrairement à a & i++ (mais qui écrirait de telles horreurs ?)
Instructions
Une instruction peut être :
- soit une expression(pouvant comprendre une affectation, un appel de fonction...), terminé par un ; qui en fait signifie "on peut oublier le résultat de l'expression et passer à la suite",
- soit une structure de contrôle (boucle, branchement...),
- soit un bloc d'instructions : ensemble de déclarations et instructions délimités par des accolades {}. Un bloc sera utilisé à chaque fois que l'on désire mettre plusieurs instructions là où on ne peut en mettre qu'une.
Seule la première forme est terminée par un ;. Un cas particulier est l'instruction vide, qui se compose uniquement d'un ; (utilisé là où une instruction est nécessaire d'après la syntaxe).