wiki:InsiaProgCSyntaxe4

Sommaire

Syntaxe - Opérateurs

Le langage C permet d'utiliser les opérations naturelles du processeur et les représente de manière concise avec des symloles divers (&, !, |, etc.). Leur saupoudrage peut rapidement rendre votre programme illisible, mais d'un autre côté leur notation concise peut aider à une lecture efficace du programme.

1. Opérateurs relationnels

Ils interviennent dans les comparaisons de valeur. Chaque membre peut être un litéral (constante), une variable, une fonction, ou une opération arithmétique combinant le tout:

if (a <= 5) printf("a est plus petit que 5");
if (a <= factorielle(b) * 2) printf("a est plus petit que 2b!");

Voici la liste des opérateurs disponibles:

<, <= inférieur et strictement inférieur
>, >= supérieur et strictement supérieur
==, != égal et différent

Attention: la comparaison se fait sur les valeurs donc == ne peut pas comparer des chaînes de caractère (utiliser strcmp).

Attention à la confusion entre l'assignation et la comparaison. Ceci doit produire un warning lors de la compilation car c'est en général une erreur de programmation:

if (x = 0) ...

Si vous voulez vraiment effectuer une assignation et une comparaison en même temps, il est possible d'écrire ceci (notez les doubles parenthèses pour signaler au compilateur que l'assignation est voulue):

FILE* f;
if ((f = fopen("test.txt", "r"))) ...

2. Opérateurs booléens

Une valeur booléenne en C est représentée par un entier avec pour convention: la valeur 0 est fausse, et toute autre valeur est vraie. Le résultat des opérateurs de comparaison renvoie en particulier une valeur booléenne:

int est_positif = (a >= 0);
if (est_positif) printf("a est positif"); /* Equivalent à: if (est_positif != 0) ... */

Attention: par abus, on utilise la même convention sur les pointeurs en assimilant 0 à NULL (mais c'est incorrect, à réserver au C++):

FILE* f = fopen("test.txt", "r");
if (f) printf("Fichier ouvert avec succès.");
/* Implique: if (f != 0)... mais devrait être: if (f != NULL)... */

Les opérateurs disponibles:

&& Et logique
|| Ou logique
! Négation logique

Pour combiner les opérateurs, il vaut mieut utiliser les parenthèses plutot que d'apprendre par coeur leur règles d'associativité:

if (cond1 && !(cond2 || cond3)) ...

Il est important de comprendre que les deux opérandes ne sont pas forcément évalués: ceci a un effet direct si l'opérande est une fonction. Par exemple dans le cas du ET logique, si le membre gauche est faux, alors le membre droit n'est pas évalué car le résultat de l'opérateur est déjà connu. Ceci peut servir à faire des raccourcis subtils, comme ci-dessous où on est sûr que fprintf ne sera exécuté que si le fichier a bien été ouvert:

FILE* f = fopen("test.txt", "w");
if (f != NULL && fprintf(f, "Hello !")) printf("Ecriture fichier OK\n");

Le raisonnement est similaire avec le OU logique: si le membre gauche est vrai, alors le membre droit n'est pas évalué (car on sait déjà que le résultat de l'opération est vrai).

3. Opérateurs binaires

Les opérations binaires effectuent des opérations logique "bit à bit", c'est à dire sur l'ensemble des bits de la valeur, tous pris indépendamment les uns des autres. Opérateurs disponibles:

& Et binaire
| Ou binaire
^ Ou exclusif binaire
~ Inversion binaire (dit aussi complément à 1)
<<, >> décalage à gauche et à droite

La manipulation bit à bit est en particulier utilisée pour fournir un ensemble de valeurs booléennes de manière compacte (ex: jusqu'à 32 valeurs dans un entier classique 32 bits):

int boisson = 1 << 0; /* binaire: ..0001 */
int plat    = 1 << 1; /* binaire: ..0010 */
int dessert = 1 << 2; /* binaire: ..0100 */
...
int repas = boisson | dessert; /* binaire: ..0101 */
if (repas & plat) printf("Le repas comporte un plat");

4. Opérateurs arithmétiques

Ces opérateurs peuvent travailler sur des types différents et éventuellement fonctionner différement suivant le type en question:

+, - entiers, flottants, pointeurs
*, / entiers, flottants
% (modulo) entiers

Le résultat d'une opération arithmétique dépend des types de chaque membre. Si les deux membres sont du même type, le résultat est également du même type. Pour les cas mixtes on peut consulter ce tableau:

Membre 1 Membre 2 Résultat
entier entier entier
entier flottant flottant
entier pointeur pointeur

Les cas non mentionnés sont invalides (ex: un flottant et un pointeur) car ils n'ont pas de sens.

L'opérateur le plus délicat est la division, qui est euclidienne si les deux membres sont des entiers, et réelle si l'un des membre est un flottant. Si on veut diviser deux entiers et obtenir un résultat flottant, on peut utiliser une constante explicitement flottante ou un transtypage (voir plus loin):

int a = 5, b = 3;
float f1 = a / 3;        /* x vaut 1 */
float f2 = a / 3.0;      /* x vaut 1.6666... */
float f3 = a / b;        /* x vaut 1 */
float f4 = a / (float)b; /* x vaut 1.6666... */

5. Affectations composées

Parfois on veut appliquer un opérateur et stocker le résultat dans l'un des membres. On peut alors raccourcir la notation dans la plupart des cas:

a += 5, a -= 5; /* Equivalent à: a = a + 5, a = a - 5 */
a *= 5, a /= 5; /* Equivalent à: a = a * 5, a = a / 5 */
/* Ainsi de suite avec:
  %=
  &=
  ^=
  |=
  <<=
  >>=
*/

6. Incrémentation/décrémentation

Il s'agit d'opérateurs unaires utilisable sur les énumérables (entiers, pointeurs et enums). Ils existent en deux variantes subtiles (pré- et post-):

int a = 3, b = 5;
int post = a++; /* post vaut 3, a vaut 4 */
int pre  = ++b; /* pre vaut 6, b vaut 6 */

Une post-incrémentation est effectuée après l'exécution de l'instruction courante (dans l'exemple, l'assignation à la variable post). Dans le cas d'une pré-incrémentation, l'opération est effectuée avant l'instruction. Autre illustration avec les tests:

int i = 0, j = 0;

while (++i < 5); /* En fin de boucle, i vaut 5 */
while (j++ < 5); /* En fin de boucle, j vaut 6 */

7. Opérateur ternaire

Il s'agit d'un raccourci du if () ... else ... dans le cas d'une assignation à une même variable:

char* signe = (a >= 0) ? "positif" : "négatif";

Si la condition est vraie, le premier membre après le ? est retourné, sinon c'est le deuxième après le :. On peut également omettre un opérande, dans ce cas la variable assignée n'est pas modifée:

x = (x >= 0) ?: 0; /* Met x à zéro s'il est négatif */

8. Transtypage

Il est possible de modifier le type de certaines variables au cas par cas lors de leur utilisation. Cette fonctionnalité est délicate: rompre le typage peut facilement créer des programmes qui plantent, et le transtypage n'a pas toujours l'effet escompté.

Le cas le plus simple sert à passer des entiers vers les flottants et vice-versa:

int a = 5, b = 3;
float f = (float)a / (float)b; /* f vaut 1.6666... */
int c = (int)f; /* c vaut 1 */

Transtyper un flottant en entier effectue une troncature: la partie décimale est retirée. Si vous voulez une autre méthode d'arrondi, il faut l'effectuer dans l'espace des réels avec les fonctions floor et ceil par exemple, puis enfin transtyper en entier.

Un autre cas courant consiste à passer d'un pointeur d'un type A à un pointeur d'un type B. On conserve la notion de pointeur (c'est donc bien toujours une adresse mémoire que l'on manipule), mais on modifie la façon dont l'espace mémoire est accédé. En général les fonctions qui manipulent les adresses sans se soucier du contenu (ex: fread, fwrite)) utilisent le type spécial void* qui est compatible avec tous les pointeurs (pas besoin de transtyper). Vous verrez toutefois cette écriture désuète régulièrement:

struct toto_t* ptr;

ptr = (struct toto_t*)malloc(sizeof(struct toto_t) * 10);
Last modified 14 years ago Last modified on Dec 18, 2006, 11:03:36 AM