Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Les expressions et les opérateurs du langage C

Les types et les variables Les instructions


Accès rapide :
   Quelques définitions
   Les opérateurs arithmétiques
   Les opérateurs bit à bit
   Les opérateurs de comparaisons
   Les opérateurs logiques
   Les opérateurs d'affectations
   Les opérateurs de cast (de transtypage)
   Les autres opérateurs du langage
   Table de précédence des opérateurs C

Nous allons, dans ce chapitre, nous focaliser sur les expressions et les opérateurs du langage C. Mais avant de commencer à énumérer les opérateurs du langage, nous allons introduire quelques points de terminologies. Il est à remarquer que ces termes sont repris des mathématiques et représentent des concepts extrêmement similaires.

Quelques définitions

Un opérateur correspond à une opération permettant de calculer un résultat à partir d'une ou de plusieurs opérandes. Par exemple, l'opérateur d'addition permet de calculer la somme de deux valeurs numériques. En C, les opérateurs sont principalement introduits par des caractères spéciaux et non par des mots clés (à quelques exceptions près): par exemple, l'opérateur && permet de réaliser une opération « ET » logique (alors que certains autres langages de programmation préféreront l'utilisation du mot clé « and »).

Un opérande correspond à une quantité acceptée par un opérateur. Par exemple l'opérateur d'addition + accepte deux opérandes : dans l'exemple 4 + 8, les quantités 4 et 8 correspondent à deux opérandes de l'opérateur +. Le système est récursif : un opérande peut résulter de l'évaluation d'un autre opérateur. Dans l'exemple 3 + 2 * 5, l'opérateur + accepte deux opérandes et le second correspond au calcul de l'opérateur * sur les sous-opérandes 2 et 5.

Nous parlons d'arité d'un opérateur : cela correspond au nombre d'opérandes qu'accepte un opérateur. En C, nous pouvons distinguer :

Une expression correspond à un calcul de valeur. Dans une expression, plusieurs opérateurs (et opérandes) peuvent être mis en relation pour effectuer un calcul complexe. L'utilisation de parenthèses est de plus possible afin de contrôler la priorité d'évaluation de vos opérateurs. Par exemple : (2 + 3) * 5.

Les opérateurs arithmétiques

Le langage C met à notre disposition cinq opérateurs arithmétiques. Attention néanmoins, ils peuvent fonctionner sur des valeurs entières ou des valeurs flottantes. Dans le cas particulier de la division, on peut même affirmer que le résultat produit sera dépendant des types de deux opérandes (division entière et division flottante). Voici un tableau présentant ces cinq opérateurs.

Nom de l'opérateur Opération réalisée Exemple
+ Additionne deux nombres (entiers ou flottants).
3 + 5       // Calcule 8
3.1 + 2.8 // Calcule 5.9
- Soustrait deux nombres (entiers ou flottants).
5 - 3       // Calcule 2
3.1 - 2.8 // Calcule 0.3
* Multiplie deux nombres (entiers ou flottants).
3 * 5       // Calcule 15
3.1 * 2.8 // Calcule 8.68
/ Divise deux nombres (entiers ou flottants).
5 / 3       // Calcule 1 (division entière)
7.44 / 3.1 // Calcule 2.4 (division flottante)
% Calcul le reste de la division entière.
5 % 3       // Calcule 2

Voici un petit exemple d'utilisation de ces opérateurs. Il est important de bien regarder ce qui se passe pour le cas de la division entière : nous reviendrons sur ce cas, juste après l'exemple.

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int a = 1;
    int b = 3;

    printf( "%d + %d == %d\n", a, b, a+b );
    printf( "%d - %d == %d\n", a, b, a-b );
    printf( "%d * %d == %d\n", a, b, a*b );
    printf( "%d / %d == %d\n", a, b, a/b );
    printf( "%d %% %d == %d\n", a, b, a%b );

    double result = a / b;
    printf( "Cas de la division entière : %5.3lf\n", result );

    double d1 = 1;
    double d2 = 3;

    printf( "%5.3lf + %5.3lf == %5.3lf\n", d1, d2, d1+d2 );
    printf( "%5.3lf - %5.3lf == %5.3lf\n", d1, d2, d1-d2 );
    printf( "%5.3lf * %5.3lf == %5.3lf\n", d1, d2, d1*d2 );
    printf( "%5.3lf / %5.3lf == %5.3lf\n", d1, d2, d1/d2 );

    // Attention : ne compile pas
    // printf( "%5.3lf %% %5.3lf == %5.3lf\n", d1, d2, d1%d2 ); 

    return EXIT_SUCCESS;
}

Et voici les résultats produits par cet exemple de code.

$> gcc -Wall -o Sample Sample.c
$> ./Sample 
1 + 3 == 4
1 - 3 == -2
1 * 3 == 3
1 / 3 == 0
1 % 3 == 1
Cas de la division entière : 0.000
1.000 + 3.000 == 4.000
1.000 - 3.000 == -2.000
1.000 * 3.000 == 3.000
1.000 / 3.000 == 0.333
$>

Revenons donc sur le cas de la division entière. Certes, la variable result est typée comme étant un double, mais le problème apparait avant l'affectation de la valeur dans cette variable. En premier lieu, le compilateur évalue l'opérande a : il est de type int. Ensuite, le compilateur évalue l'opérande b : il est aussi de type int. Le compilateur génère donc du code permettant de produire une division entière. Cette valeur entière est ensuite stockée dans une variable typée double, mais c'est trop tard : la partie flottante est perdue. Pour forcer une division flottante entre deux entiers, vous pouvez utiliser un opérateur de cast (transtypage, en français) pour ramener l'une de deux valeur sur un type flottant. En voici un exemple.

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int a = 1;
    int b = 3;

    double result = a / (double) b;
    printf( "%d / %d == %5.3lf\n", a, b, result );

    return EXIT_SUCCESS;
}

Et voici le résultat de cette division flottante entre deux données initialement typées comme étant des entiers.

$> gcc -Wall -o Sample Sample.c
$> ./Sample 
1 / 3 == 0.333
$>

Pour rappel, les opérations de multiplication et de division sont prioritaires par rapport aux opérations d'ajout et de soustraction. Il est néanmoins possible d'utiliser les parenthèses pour mieux contrôler vos calculs.

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int result = 2 + 3 * 5;
    printf( "%d\n", result );       // Affiche 17

    result =  2 + ( 3 * 5 );
    printf( "%d\n", result );       // Affiche 17

    result =  (2 + 3) * 5;
    printf( "%d\n", result );       // Affiche 25

    return EXIT_SUCCESS;
}

Les opérateurs bit à bit

Ces opérateurs permettent de manipuler la représentation binaire de vos valeurs numériques. Il existe quatre opérateurs binaires principaux.

Nom de l'opérateur Opération réalisée Exemple
& Le « et » (and) binaire
int first  = 0b1010101;
int second = 0b1111110;
int result = first & second; // 84 == 0b1010100
| Le « ou » (or) inclusif binaire
int first  = 0b01010101;
int second = 0b01111110;
int result = first | second; // 127 == 0b1111111
^ Le « ou exclusif » (xor) binaire
int first  = 0b111000;
int second = 0b101010;
int result = first ^ second; // 18 == 0b010010
~ Le « non » (not) binaire
int first  = 0b1010101;
int result = ~ first;
<< Un opérateur de décalage de bit de n position vers la gauche.
int first  = 0b00100;
int result = first << 2; // 16 == 0b10000
>> Un opérateur de décalage de bit de n position vers la droite.
int first  = 0b00100;
int result = first >> 2; // 1 == 0b00001

Le programme ci-dessous vous montre un exemple d'utilisation de ces opérateurs binaires. Nous cherchons notamment à afficher une valeur entière en représentation binaire (l'opérateur >> est bien utile dans ce cas).

#include <stdio.h>
#include <stdlib.h>

void binaryput( unsigned int value ) {
    if ( value > 1 ) binaryput( value >> 1 );
    putc( value & 1 ? '1' : '0', stdout );
    return;
}

int main() {

    unsigned int first  = 0b1010101;
    unsigned int second = 0b1111110;

    int result = first & second;
    fputs( "0b1010101 & 0b1111110 = 0b", stdout );
    binaryput( result );
    putc( '\n', stdout );
   
    result = ~first;

    fputs( "~0b1010101 = 0b", stdout );
    binaryput( result );
    putc( '\n', stdout );
   
    return EXIT_SUCCESS;
}

Voici ce que produit cet exemple après compilation.

$> gcc -o Essai Essai.c
$> Essai
0b1010101 & 0b1111110 = 0b1010100
~0b1010101 = 0b11111111111111111111111110101010
$> 

Les opérateurs de comparaisons

Le langage C définit aussi un certain nombre d'opérateurs permettant de comparer deux valeurs entre elles. Tous ces opérateurs renvoient un pseudo booléen (une valeur numérique pour laquelle 0 correspondra à l'état faux et toute autre valeur sera associée à l'état vrai). Voici la liste de ces opérateurs de comparaison.

Nom de l'opérateur Opération réalisée Exemple
< Opérateur d'infériorité stricte
printf( "3 < 7 : %d\n", (3 < 7) )
<= Opérateur d'infériorité
printf( "3 <= 3 : %d\n", (3 <= 3) )
== Opérateur d'égalité
printf( "3 == 3 : %d\n", (3 == 3) )
!= Opérateur de non égalité : not (!) equal (=)
printf( "3 != 3 : %d\n", (3 != 3) )
>= Opérateur de supériorité
printf( "3 >= 7 : %d\n", (3 >= 7) )
> Opérateur de supériorité stricte
printf( "3 > 7 : %d\n", (3 > 7) )

Voici un petit exemple d'utilisation de certains de ces opérateurs (avec en plus une petite référence culturelle ;-) ).

#include <stdio.h>
#include <stdlib.h>


int main() {

    int age;
    printf( "Quel est votre age : " );    
    scanf( "%d", &age );

    if ( age < 7 ) {
        puts( "Trop jeune pour lire les Aventures de Tintin\n" );
        return EXIT_FAILURE;
    } else if ( age > 77 ) {
        puts( "Trop vieux pour lire les Aventures de Tintin\n" );
        return EXIT_FAILURE;
    }

    puts( "Vous pouvez lire les Aventures de Tintin" );
    // Pour explication du 7 à 77 ans:   ;-)
    puts( "https://fr.wikipedia.org/wiki/Tintin_%28p%C3%A9riodique%29" );
   
    return EXIT_SUCCESS;
}

Voici ce que produit cet exemple après compilation.

$> gcc -o Essai Essai.c
$> Essai
Quel est votre age : 42
Vous pouvez lire les Aventures de Tintin
https://fr.wikipedia.org/wiki/Tintin_%28p%C3%A9riodique%29
$> 

Les opérateurs logiques

Les opérateurs logiques permettent principalement de combiner des petites expressions pour en produire une plus complexe. Trois opérateurs logiques sont disponibles : le && (« et » logique), le || (« ou » logique) et le ! (« non » logique).

Nom de l'opérateur Opération réalisée Exemple
&& « et » logique if ( age >= 7 && age <= 77 ) puts( "yes" );
|| « ou » logique if ( age < 7 || age > 77 ) puts( "no" );
! « non » logique if ( ! ( age >= 7 && age <= 77 ) ) puts( "no" );

Voici un petit exemple d'utilisation de certains de ces opérateurs (avec encore la même référence culturelle ;-) ).

#include <stdio.h>
#include <stdlib.h>


int main() {

    int age;
    printf( "Quel est votre age : " );    
    scanf( "%d", &age );

    if ( age < 7 || age > 77 ) {
        puts( "Vous n'avez pas l'âge pour lire les Aventures de Tintin\n" );
        return EXIT_FAILURE;
    }

    puts( "Vous pouvez lire les Aventures de Tintin" );
    // Pour explication du 7 à 77 ans:   ;-)
    puts( "https://fr.wikipedia.org/wiki/Tintin_%28p%C3%A9riodique%29" );
   
    return EXIT_SUCCESS;
}

Voici ce que produit cet exemple après compilation.

$> gcc -o Essai Essai.c
$> Essai
Quel est votre age : 78
Vous n'avez pas l'age pour lire les Aventures de Tintin
$> Essai
Quel est votre age : 42
Vous pouvez lire les Aventures de Tintin
https://fr.wikipedia.org/wiki/Tintin_%28p%C3%A9riodique%29
$> 

Les opérateurs d'affectations

En premier lieu, il est à signaler que le langage C, ainsi que les langages pour lesquels la syntaxe dérive de C, présente la caractéristique que l'affectation est un opérateur et non une instruction : il en découle qu'une affectation calcule un résultat. C'est pour cela que le code suivant fonctionne très bien en C.

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    // On déclare quatre variables.
    int a, b, c, d;

    // On affecte 55 à la variable d, ce qui calcule la valeur 55
    // qu'on réaffecte à c, ce qui calcule de nouveau la valeur 55, ...
    a = b = c = d = 55;

    // Affiche : 55 - 55 - 55 - 55
    printf( "%d - %d - %d - %d\n", a, b, c, d );
                                        
    return EXIT_SUCCESS;
}

Pour information, l'opérateur = est évalué de la droite vers la gauche. Cela veut dire que dans l'exemple ci-dessus il y a d'abord évaluation de d = 55, ce qui calcule la valeur 55 (la valeur affectée). Ensuite cette valeur est de nouveau affectée à c, ce qui calcule la valeur 55. Cette valeur est réaffectée à b et le résultat de cette affectation est transféré dans la variable a.

Il est aussi à noter que ce n'est pas le seul opérateur qui réalise une affectation. En effet, il existe un ensemble d'autres opérateurs qui sont des combinaisons entre un opérateur arithmétique (ou binaire), vu précédemment, et l'opérateur d'affectation. Le tableau ci-dessous les reprend un à un.

Nom de l'opérateur Opération réalisée Exemple
+= a += 10;
Plus ou moins équivalent à : a = a + 10;
for( int i=0; i<10; i+=2 )
    printf( "%d\n", i );
-= a -= 10;
Plus ou moins équivalent à : a = a - 10;
for( int i=10; i>=0; i-=2 )
    printf( "%d\n", i );
*= a *= 2;
Plus ou moins équivalent à : a = a * 2;
for( int i=1; i<=100; i*=2 )
    printf( "%d\n", i );
/= a /= 2;
Plus ou moins équivalent à : a = a / 2;
int a = 100;
a /= 2;
printf( "%d\n", a );
%= a %= 2;
Plus ou moins équivalent à : a = a % 2;
int a = 101;
a %= 10;
printf( "%d\n", a );
&= a &= 2;
Plus ou moins équivalent à : a = a & 2;
 
|= a |= 2;
Plus ou moins équivalent à : a = a | 2;
 
^= a ^= 2;
Plus ou moins équivalent à : a = a ^ 2;
 
<<= a <<= 2;
Plus ou moins équivalent à : a = a << 2;
 
>>= a >>= 2;
Plus ou moins équivalent à : a = a >> 2;
 

Pour finir cette section, notez qu'il existe deux autres opérateurs pouvant modifier le contenu d'une variable : l'opérateur ++ qui réalise une incrémentation et l'opérateur -- qui réalise une décrémentation.

Mais attention : l'utilisation des opérateurs ++ et -- peut vous jouer des tours en fonction que vous les utilisiez en préfixés ou en post-fixés. Effectivement, si l'un de ces deux opérateur est utilisé en préfixé, il est exécuté en premier ensuite votre programme évaluera le reste de l'expression. Par contre, si vous utilisez l'opérateur en post-fixé, l'incrémentation (ou la décrémentation) sera jouée à la fin du traitement de l'instruction en cours (jusqu'au ;). Le programme suivant vous présente, entre autre, cette problématique.

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int a = 10;
    int b = 5;
    int c = a++ + b++;
    // Affiche Postfix: 11 6 15
    printf( "Postfix: %d %d %d\n", a, b, c );

    a = 10;
    b = 5;
    c = ++a + ++b;
    // Affiche Prefix: 11 6 17
    printf( "Prefix: %d %d %d\n", a, b, c );
                                        
    return EXIT_SUCCESS;
}

Les opérateurs de cast (de transtypage)

Les opérateurs de cast (de transtypage, en français) permettent de transformer des données d'un type vers un autre type. Un opérateur de cast porte le nom du type de destination mis entre parenthèses. Les opérateurs de cast peuvent être utilisés de manière implicite par le compilateur pour, par exemple, transformer en int en un long : c'est normal, le type de destination étant plus grand que le type d'origine, il n'y aura aucune perte d'information. Par contre, pour transformer un double en un float, une utilisation explicite de l'opérateur sera requise : il signifiera au compilateur qu'en cas de perte de précision sur une valeur, vous accepterez la chose.

Attention : certains compilateurs produisent par défaut des warnings lors de cast implicites pouvant aboutir à des pertes de précision. D'autres, par défaut, ne disent rien : c'est notamment le cas de gcc. Par contre, gcc fournit une option permettant de détecter ces cas et alors de produire un warning. Cette option est -Wconversion et je vous conseille de l'utiliser systématiquement afin que vous soyez certains d'être conscient de toutes les pertes de précision possibles dans votre code. Testons ce premier programme en le compilant avec cette option.

#include <stdio.h>
#include <stdlib.h>


int main() {

    int a = 10;
    long b = a + 1;
    int c = b * 2;
    printf( "c == %d\n", c );

    float f = 3.3f;
    double d = f / 7;
    float final = d * 3.1f;
    printf( "final == %f\n", final );            

    return EXIT_SUCCESS;
}

Lançons maintenant la compilation :

$> gcc -Wconversion -o Essai Essai.c
Essai.c: Dans la fonction 'main':
Essai.c:9:13: attention : conversion to 'int' from 'long int' may alter its value [-Wconversion]
     int c = b * 2;
             ^
Essai.c:14:19: attention : conversion to 'float' from 'double' may alter its value [-Wfloat-conversion]
     float final = d * 3.1f; 
                   ^
$> 

Voici maintenant le même exemple, avec l'utilisation de d'opérateurs de cast explicites et donc ne produisant plus de warnings.

#include <stdio.h>
#include <stdlib.h>


int main() {

    int a = 10;
    long b = a + 1;                     // Implicit cast
    int c = (int) (b * 2);              // Explicit cast
    printf( "c == %d\n", c );

    float f = 3.3f;
    double d = f / 7;                   // Implicit cast
    float final = (float) (d * 3.1f);   // Explicit cast
    printf( "final == %f\n", final );            

    return EXIT_SUCCESS;
}

Relançons la compilation :

$> gcc -Wconversion -o Essai Essai.c
$> ./Essai
c == 22
final == 1.461429
$> 

Le tableau ci-dessous, énumère quelques opérateurs de cast utilisables en C.

Nom de l'opérateur Opération réalisée Exemple
(char) Retype une donnée en char char c = (char) aLongValue;
(unsigned char) Retype une donnée en unsigned char unsigned char c = (unsigned char) aLongValue;
(short) Retype une donnée en short short c = (short) aLongValue;
(unsigned short) Retype une donnée en unsigned short unsigned short c = (unsigned short) aLongValue;
(int) Retype une donnée en int int c = (int) aLongValue;
(unsigned int) Retype une donnée en unsigned int unsigned int c = (unsigned int) aLongValue;
(long) Retype une donnée en long long c = (long) aDoubleValue;
(unsigned long) Retype une donnée en unsigned long unsigned long c = (unsigned long) aDoubleValue;
(float) Retype une donnée en float float c = (float) aDoubleValue;
(int *) Retype un pointeur en pointeur sur entiers int * ptrInt = (int *) malloc( 10 * sizeof( int ) )

Les autres opérateurs du langage

Sachez qu'il existe quelques autres opérateurs dans la syntaxe C. En voici quelques-uns.

L'opérateur conditionnel

L'opérateur conditionnel permet d'évaluer une valeur en fonction d'une condition. C'est le seul opérateur du langage qui travaille sur trois opérandes : la condition, la valeur à calculer si la condition est vraie et la valeur à calculer si la condition est fausse. En conséquence cet opérateur se décompose en deux parties : le caractère « ? » permet de séparer la condition de l'expression à évaluer si la condition est vraie et le caractère « : » pour séparer l'expression associée à la condition vraie et celle associée à la condition fausse.

condition ? ifTrueExpression : ifFalseExpression

Voici un petit exemple d'utilisation :

#include <stdio.h>
#include <stdlib.h>


int main() {

    int a, b;
    printf( "Veuillez saisir une première valeur : " );
    scanf( "%d", &a );

    printf( "Veuillez saisir une seconde valeur : " );
    scanf( "%d", &b );
               
    printf( "Les deux valeurs sont %s\n", a==b ? "égales" : "différentes" );

    return EXIT_SUCCESS;
}

Testons ce programme :

$> gcc -o Essai Essai.c
$> ./Essai
Veuillez saisir une première valeur : 10
Veuillez saisir une seconde valeur : 5
Les deux valeurs sont différentes
$> ./Essai
Veuillez saisir une première valeur : 10
Veuillez saisir une seconde valeur : 10
Les deux valeurs sont égales
$> 

L'opérateur sizeof

L'opérateur sizeof (nous l'avons déjà vu dans un chapitre précédent) permet de calculer la taille, en nombre d'octets, occupée par un type ou par une variable.

#include <stdio.h>
#include <stdlib.h>


int main() {

    int a = 10;
    printf( "Tailles int :    %d %d\n", sizeof( int ), sizeof( a ) ) ;

    double f = 3.141592654;
    printf( "Tailles double : %d %d\n", sizeof( double ), sizeof( f ) ) ;

    return EXIT_SUCCESS;
}

Voici les résultats produits par cet exemple.

$> gcc -o Essai Essai.c
$> ./Essai
Tailles int    : 4 4
Tailles double : 8 8
$> 

Les opérateurs sur pointeurs

Il existe aussi un certains nombre d'opérateurs liés à la gestion des pointeurs. Nous reviendrons plus précisément sur ces opérateurs plus tard dans ce cours. En attendant, voici juste une présentation rapide de ces opérateurs.

Nom de l'opérateur Opération réalisée Exemple
& Calcule l'adresse (le pointeur) de l'élément passé en opérande. int a = 10;
int * ptrA = & a;
* Permet de récupérer la valeur pointée. int a = 10;
int * ptrA = & a;
printf( "%d\n", *a );
-> Permet de récupérer la valeur d'un champ d'une structure gérée par pointeur. // Deux lignes strictement équivalentes.
int value = (* ptrOnStruct).member;
int value2 = ptrOnStruct->member;

Le séparateur d'expression

La virgule peut être utilisée comme séparateur d'expression. Les sous-expressions considérées seront évaluées de la gauche vers la droite. Voici un petit exemple d'utilisation.

#include <stdio.h>
#include <stdlib.h>


int main() {

    int a = 10;
    int b = 20;
    int c = (++a, ++b, a+b);
    
    printf( "%d %d %d\n", a, b, c );

    return EXIT_SUCCESS;
}

Voici les résultats produits par cet exemple.

$> gcc -o Essai Essai.c
$> ./Essai
11 21 32
$> 

Table de précédence des opérateurs C

Le tableau suivant montre la table de précédence des opérateurs : il s'agit d'un classement des opérateurs du langage C des plus prioritaires (en haut du tableaux) aux moins prioritaires (en bas). Notez aussi la seconde colonne qui précise si l'opérateur s'évalue de la gauche (+, par exemple) vers la droite ou l'inverse (opérateur d'affectation =, par exemple)

Commentaire Opérateurs Sens d'évaluation
fonction, tableau, membre de structure, pointeur sur un membre de structure () [] . -> G -> D
Opérateurs unaires - ++ -- ! ~ * & sizeof D -> G
Opérateur arithmétiques * / % G -> D
Opérateur arithmétiques + - G -> D
Opérateurs binaires de décalage << >> G -> D
Opérateurs de comparaison < <= >= > G -> D
Opérateurs de comparaison == != G -> D
« et » binaire & G -> D
« ou exclusif » (XOR) binaire ^ G -> D
« ou » binaire | G -> D
« et » logique && G -> D
« ou » logique || G -> D
Opérateur conditionnel ?: D -> G
Opérateurs d'affectation = += -= *= /= %= &= ^= |= <<= >>= D -> G
Séparateur d'expression , G -> D


Les types et les variables Les instructions