CHAPITRE II

Expressions de Base

A person who is more than casually interested in computers should be well
schooled in machine language, since it is a fundamental part of a computer.
-- Donald Knuth


Najib TOUNSI,   Original: http://www.mescours.ma/C/c2.html

ntounsi@emi.ac.ma
Version: Sept. 2000, Dernière MàJ: Dec. 2021



< Précédent Sommaire Suivant >

0. Introduction

En C une instruction est une expression suivie du caractère point-virgule «;»

<expression> ;

«;» qui a, en quelque sorte, le sens «évaluer cette expression».

Une expression est une combinaison bien formée[1] d'opérandes et d'opérateurs. Les opérandes sont des variables, constantes ou des appels de fonctions. Les opérateurs sont ceux classiquement connus. Cependant, on remarquera un opérateur spécial qui est l'affectation, symbole =. Pour bien comprendre cela il faut imaginer qu'en C une expression a non seulement une valeur, celle résultant de son évaluation, et un type, celui hérité de ses opérandes, mais aussi un effet --de bord en quelque sorte.

Voici des exemples d'expressions/instructions: soit la déclaration int i;

2 + 3 ; est une expression de type entier évaluée à 5, son résultat.
i + 5 ; idem, expression entière évaluée à i + 5.
i++ ; est une expression qui a pour type et valeur résultat ceux de i, et pour effet d'incrémenter i de 1.
i = 2 + 3; est une expression entière de valeur 5 (2 + 3) et qui a pour effet d'affecter ce résultat 5 à i.

C'est l'usage le plus fréquent de l'opérateur =, i.e. instruction d'affectation. L'expression précédente ayant comme valeur 5 de type int, l'expression

x = (i = 2 +3); a pour effet d'affecter 5 à x (en plus de i).

On aura noté que les deux premières écritures sont sans intérêt (résultat perdu) quoique correctes.

1. Objets de Base de C

1.1. Caractère: char

char est le type d'un caractère quelconque ASCII codé sur un octet. c'est l'objet C le plus élémentaire.

Exemple:

char c, b;
c = 'b' ;
b = '+';
c = b;
c = '\'';

Les constantes sont entre quotes simples '' comme 'a' ou en code octal '\134' (valeur Ascii en base 8 de \). Le caractère quote lui-même est introduit par le symbole \ (considéré méta-caractère) comme dans '\''. En quelque sorte,\x signifie traiter le caractère x spécialement s'il est normal, ou normalement s'il est spécial. Ainsi '\\' est le caractère normal backslash \ et '\n' est le caractère spécial newline. La figure II-1 donne la liste des caractères spéciaux (avec leur code Ascii en décimal et en octal).

\0 pour caractère "null" 0 (le premier du code ascii)
\a pour son bip  BELL code ascii  7 ('\007')
\b pour espace arrière BACKSPACE  code ascii  8 ('\010')
\t pour tabulation  TAB   code ascii  9 ('\011')
\n pour nouvelle  ligne LINEFEED  code ascii 10 ('\012')
\f pour nouvelle page FORMFEED  code ascii 12 ('\014')
\r pour retour chariot RETURN  code ascii 13 ('\015')
\" pour double quote ou guillemets "  code ascii 34 ('\042')
\' pour quote ou apostrophe '  code ascii 39 ('\047')
\\ pour le \ lui-même   code ascii 92 ('\134')
\ddd  pour une constante octale ddd   désignant une valeur ASCII
( 0 =< d <= 7 ) ( 0 =< d <= 7 )

Exercice: Expérimenter printf() de \xx est de votre choix (e.g. \a \b \j \l ...)

Des fonctions utiles sur le type char sont données par la figure II-2. Elles sont macro-définies dans ctype.h. Ces fonctions retournent 0 pour faux et une valeur différente de 0 pour vrai. Elles servent essentiellement à connaître le type de caractère: chiffre, lettre, majuscule etc...

La fonction isalpha(c), par exemple, teste si le caractère c, est alphabétique. Elle rend 1 si oui, 0 sinon.

#include <ctype.h>  /* l'entête à mettre */ 
isalpha(c) c est une lettre 
isupper(c) c est une lettre majuscule 
islower(c) c est minuscule 
isdigit(c) c est chiffre 
isxdigit(c)c est hexadécimal [0-9], [A-F], ou [a-f]
isalnum(c) c est alphanumérique (lettre ou chiffre) 
isspace(c) c est blanc, tab, retour-chariot, newline ou formfeed 
ispunct(c) c est caractère de ponctuation (non alnum, ctrl ou space) 
isprint(c) c est caractère imprimable (de 32(040) a 126(0176)tilde)
isgraph(c) c est caractère imprimable différent de espace
iscntrl(c) c est car. de contrôle (<32) ou delete(0177) et non space 
isascii(c) c est caractère ASCII (0 <= c <128 

1.2. Entiers: int, short int, long int, unsigned

Les entiers sont du type int pour l'usage courant, shortint (ou short tout court, sans jeu de mots) pour les entiers codés sur deux octets (16 bits) appartenant à l'intervalle:

[-215 .. 215 - 1] , i.e. [-32768 .. 32767]

et long int (ou long ) pour des entiers sur 4 octets (32 bits) appartenant à l'intervalle:

[-231 .. 231 - 1], i.e. [-2147483648 .. 2147483647]

En général int équivaut à short sur PCs et à long sur stations et mainframes. On peut utiliser aussi le qualificatif unsigned pour des entiers non signés, i.e. positifs. Par exemple unsigned short appartient à [0 .. 65535] (216 cette fois).

Exemples de déclarations :

int i;
long int j;
(en plus court long j;)
short int j;
(en plus court short j;)
unsigned int k;
unsigned short l, m, n;

Exemples de constantes:

1 25 189     en notation décimale.
0123 0717     en notation octale (0 suivi de chiffres à base 8 ).
0xfF 0XaC 0xfAc     en notation hexadécimale (0 suivi de x ou X suivi de chiffres dans [0..9] ∪ [A..F] ∪ [a..f]).

Exemple de programme:

Le programme de la figure II-3 calcule le pgcd de deux entiers a et b.

main () {
    int pgcd (int, int); 
     printf(" PGCD = %4d\n",pgcd(18,12)); 
     printf(" PGCD = %4d\n",pgcd(72,81)); 
     printf(" PGCD = %4d\n",pgcd(233,144)); 
} 
int pgcd(int a, int b) { /* PGCD de a b , par EUCLIDE. Version itérative. */ int reste; printf(" a | b \n"); printf("-----------\n"); printf("%4d | %4d\n",a,b); reste = b; while (reste != 0){ /* si a<b, le premier tour de boucle permute a et b */ reste = a % b; /* % est le modulo */ a = b; b = reste; printf("%4d | %4d\n",a,b); } return a; }

Voici les résultats du programme sur les couples d'entiers (a, b):(18,12) (72,81) (233,144)

  a  |  b
-----------
  18 |   12
  12 |    6
   6 |    0
 PGCD =    6 
  a  |  b
-----------
  72 |   81
  81 |   72
  72 |    9
   9 |    0
 PGCD =    9 
  a  |  b
-----------
 233 |  144
 144 |   89
  89 |   55
  55 |   34
  34 |   21
  21 |   13
  13 |    8
   8 |    5
   5 |    3
   3 |    2
   2 |    1
   1 |    0
 PGCD =    1 

Le dernier jeu d'essai (233,144) a donné l'itération la plus longue. En effet ce sont deux termes successifs de la suite de Fibonacci, comme le montrent les différentes valeurs de a (exercice: montrer pourquoi).

1.3. Réels: short float , long float

Ils sont short float (ou float), long float (ou double ). La précision est de 6 ou de 16 chiffres décimaux selon le cas. Soit 4 ou 8 octets en général.

Exemples de déclarations:

short float delta;   /* ou float delta; */
long float pi;       /* ou double pi; */

Exemples de constantes:

3.14 -5.0 5e17 -3E-12 etc...

Remarque: On peut utiliser la fonction sizeof(<type>) ou sizeof (<expr>) pour connaître la taille d'un objet. Par exemple sizeof(short) est 2, et sizeof(3.14) est 8.

Exercice: tester sizeof() pour certains types ou expressions.

1.4. Initialisations

On peut initialiser une variable dès sa déclaration comme dans:

int i = 3;

qui déclare i entier et lui donne 3 comme valeur initiale; ou encore:

double pi = 3.1415926535897932384626433;
(
mais seulement 16 chiffres sont retenus)

char c = 'c';

Ci-après les fonctions mathématiques courantes de C:

 #include<math.h> 
 abs(i) int i;                valeur absolue de i 
 double fabs(x) double x;     valeur absolue de x réel double 
 double floor(x) double x;    plus grand entier ? x (partie entière) 
 double ceil(x) double x;     plus petit entier ? x 
 double exp(x) double x;      e puissance x 
 double log(x)double x;       le logarithme de x 
 double log10(x) double x;    log à base 10 de x 
 double pow(x,y) double x,y;  x puissance y (power)  
 double sqrt(x) double x ;    racine carré de x (Square Root) 
 double sin(x)double x;       sinus de x 
     /* les autres fonctions trigonométriques sont 
      * cos, tan, acos, asin, atan, sinh, cosh, tanh
*/ void srand (germe) int germe; initie la fonction aléatoire
rand() int rand (void) génère une valeur aléatoire (random)
dans [0 .. 2^31 - 1]

 Le générateur est initialisé par srand(1), ensuite réinitialisé par srand(i) i quelconque.

2. Opérateurs Arithmétiques

C offre les opérateurs arithmétiques classiques à savoir:

+ et -    addition et soustraction binaires (ou - unaire)

* et /   pour la multiplication et la division

%              pour modulo

Quand il s'agit d'entiers dans la division, la partie décimale est tronquée! Ainsi

5.0/2.0 donne 2.5 et

5/2 donne 2, i.e. le quotient entier.

% est l'opérateur modulo (reste de la division entière) sur les entiers.

5%2  donne 1.

L'élévation à la puissance n'existe pas. Il faut utiliser la fonction pow(x,y) pour avoir xy en général. La figure II-4 liste des fonctions mathématiques de C. On doit se rappeler que les fonctions retournent par défaut un entier (e.g. abs(i)). Il est très utile de jeter un coup d'oeil sur le fichier UNIX /usr/include/math.h pour ces fonctions et d'autres.

L'exemple suivant, est un programmes sur des réels. C'est le calcul d'un nombre réel y par fractions continues.

y = x1 + 1 / ( x2 + 1 / (x3 + 1 / (x4 + ... + 1 /(xn))))

Il a pour données: n et xi, pour i de n à 1. Le résultat est y, le réel cherché. Nous l'expérimenterons sur des réels connus. Il donne pour les valeurs de xi suivantes:

[1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
résultat = 1.61805556 = nombre d'or k [2]

[1; 2, 2, 2, 2, 2, 2, 2]
résultat = 1.41421569 = racine de 2

[2; 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8]
résultat = 2.71828184 = nombre e

La notation [x1; x2, ..., xn] représente les termes successifs du calcul, qu'on commence à l'envers de xn à x1 (à la saisie, rentrer xn , xn-1, ..., x1 ).

main(){
      float x; 
      double y; 
     /* y est le résultat de chaque itération, i.e. le cumul 
      * intermédiaire et final 
      */ 
      int i,n; 
      printf("combien de termes?"); scanf("%d",&n);printf("\n"); 
      printf("dernier terme >"); scanf("%f",&x);printf("\n"); 
      y = x; 
      for (i=1; i < n; i++){ 
            y = 1.0 / y; 
            printf("nouveau terme >"); 
            scanf("%f",&x);printf("\n"); 
            y = x + y ;
      } 
      printf("résultat = %12.8f\n",y); 
}

Exercices:

II.2.1 Faire un programme qui lit et écrit des entiers short et long, des réels float et double. Utiliser les formats %ld %lf pour les long int et long float .

II.2.2 Faire un programme qui lit deux entiers représentant respectivement une mantisse m et un exposant a, et qui imprime le réel correspondant. Exemple m=314, a=-2, réel résultat = 3.14. Calculé par 314 * 10-2, i.e. m * pow(10, a).

II.2.3 Vérifier que:

[3; 7, 15, 292, 1, 1, 6, 1, 3]donne le nombre pi et que
[1; 1, 2, 1, 2, 1, 2, 1, 2]donne racine de 3.

Chercher l'algorithme inverse qui, pour un réel donné, donne les termes successif de la suite
xn, xn-1, ..., x1 .

3. Les Tableaux

Les tableaux sont des variables indicées, à une ou plusieurs dimensions. La notation [] sert à introduire l'indexation.

int t[20];

déclare un tableau de 20 entiers.

float m[3][5];

déclare une matrice de réels ayant 3 lignes et 5 colonnes.

L'accès à un élément se fait par la notation x[entier], x[entier][entier]...

t[i], t[5] etc...

pour une dimension, avec i dans 0..19 ! (t déclaré t[20])

m[2][3]

en deux dimension, pour l'élément de la 3ième ligne 4ième colonne !

En effet les indices de tableau commencent à 0 en C. Un tableau t[5] contient 5 éléments et il est indicé de 0 à 4. De même pour toute dimension. On se rendra compte vite de cet aspect pratique. On verra pourquoi plus loin quand on étudiera les pointeurs.

remarque importante: Les tableaux en C ne supportent pas l'opération d'affectation. On ne peut pas affecter en bloc un tableau à un autre. Le compilateur vérifie cela et n'acceptera pas que le nom d'un tableau figure en partie gauche d'une affectation. Si on a

int r[5], s[5];

on ne peut pas faire

r = s;

Il faut le faire dans une boucle par exemple:

for (i=0; i<5; i++)
        r[i] = s[i];

Cela est dû au fait qu'un nom de tableau n'est pas une variable comme les autres qui contient une valeur --qui serait dans ce cas tout le tableau. Elle contient en fait l'adresse du tableau et pour cela elle ne doit pas changer sinon on ne peut plus accéder au tableau. Nous en reparlerons au chapitre IV.

On peut initialiser un tableau, totalement ou partiellement, à la déclaration.

int t[4] = {1, 2, 3, 0};

On énumère entre {} la liste-virgule des éléments.

float u[5] = {0., 3.1415};

Les deux premiers éléments sont initialisés, les autres étant nuls.

float mat[3][2] = { {0, 1}, {1, 0}, {2, 5}};

Liste de liste d'initialisations, i.e. listes des lignes.

char s[5] = {'t', 'o', 't', 'o'}; qu'on peut écrire en bref comme

char s[5] = "toto";

Remarque: Dans les anciennes versions de C, l'initialisation d'un tableau n'est possible que si ce dernier est déclaré static, ou en dehors de toute fonction, dans la partie déclarations de noms externes (voir Section 3 ChapitreI).

4. Les Chaînes de Caractères char* idf ou char idf[n]

Les chaînes sont des tableaux de caractères, qu'on peut manipuler par indexation comme un tableau ou à travers un pointeur (qui pointe vers le premier caractère). Une discussion plus détaillée sur les pointeurs et les tableaux se trouve plus loin avec les opérateurs * et & (Voir Chapitre IV). Contentons nous dès à présent de donner quelques cas simples qui illustrent l'usage d'un tableau de type char:

char s[20];

Déclare s un tableau de 20 caractères maximum [3].

char* ps ;

Déclare ps un pointeur vers (le premier caractère d') une chaîne.

char x[10] = "toto\0";

Déclare une chaîne d'au maximum 10 caractères et l'initialise avec le mot toto (ou plus exactement, avec une adresse contenant le mot toto). On a

x[0]='t' x[1]='o' x[2]='t' x[3]='o' et x[4]='\0'.

Le caractère \0 est, par convention, le terminateur d'une chaîne. Penser à ne pas l'omettre. Souvent, il est rajouté par le compilateur. En tout cas il faut prévoir sa place dans la taille du tableau lors de l'initialisation (char s[20]; est en fait une chaîne de 19 caractères utiles maximum)

Il est plus pratique d'utiliser les chaînes de caractères avec les pointeurs que comme tableaux. L'affectation

ps = "toto\0";

marche si ps est déclaré char* ps; mais elle n'est pas permise si ps est déclaré char ps[n]; Un tableau ne supporte pas d'affectation globale. Il faut procéder élément par élément. Mais la notation est permise pour initialiser une déclaration tableau de caractères.

#include <strings.h> 
char *strcat (s1, s2) char *s1, *s2; 
    Concatène s2 au bout de s1. Le résultat est identique à s1.

char *strncat(s1, s2, n) char *s1, *s2; int n; 
    idem avec une limite totale égale à n.

char *strcpy(s1, s2) char *s1, *s2; 
    Copie s2 dans s1 jusqu'au caractère null '\0' compris. 
    Résultat identique dans s1.

char *strncpy(s1, s2, n) char *s1, *s2; int n; 
    idem avec copie de n caractères.  
    Il y a troncature ou remplissage avec \0 (null).

char *index(s, c) char *s, c; 
    Retourne la chaîne à partir de 1ère occurrence de c dans s.

char *rindex(s, c) char *s, c; 
    idem mais dernière occurrence de c.

int strcmp (s1, s2) char *s1, *s2; 
    Compare s1 et s2. 
    résultat=0 (s1 = s2), <0 (s1 < s2), >0 (s1 > s2).

int strlen(s) char *s; 
    Retourne la longueur de s.

Remarque: Une différence (à ne pas oublier) existe toutefois entre une chaîne considérée comme tableau de caractères (char[]) ou comme pointeur vers caractère (char*): dans le cas d'un tableau, la chaîne de caractères se trouve dans l'espace d'adressage normal du programme, et dans le cas d'un pointeur, la chaîne se trouve dans un espace dynamique, appelé tas (zone Heap). Par conséquent, le pointeur en question doit être initialisé pour lui allouer de la place . Plus exactement, allouer la place à l'objet pointé et affecter l'adresse de cet objet au pointeur. On utilise pour cela la fonction C malloc() définie dans #include <malloc.h>. (Nous détaillons cela dans le Chapitre IV).

Exemple: soit char* ps;

ps = malloc (80);

alloue 80 octets pour ps. On peut alors écrire :

ps =  expression chaîne

Noter au passage que "chaine", constante tableau de caractères, est une expression à valeur d'adresse . Dans l'écriture:

PointeurChar = "chaine";

le compilateur alloue un espace pour la chaîne chaine et affecte son adresse (celle du premier caractère) au pointeur. Ce dernier peut dans ce cas se passer d'une initialisation préalable par malloc().

L'écriture, inhabituelle:

"chaine"[3]

quand à elle, est le 4e caractère 'i' de la chaîne chaine. Remarquer aussi que l'expression

"chaine"[3] = 'î'

est correcte (exercice: que fait-elle et quel est son type ?)

Les fonctions sur chaînes de caractères sont données dans la figure II-6. La plupart rend un pointeur sur une chaîne. Les trois dernières fonctions retournent, on l'aura compris, un entier. Pour les autres fonctions les paramètres, ainsi que le résultat, sont des pointeurs sur (le 1er caractère de) la chaîne. Les paramètres effectifs peuvent être des tableaux de char ou des pointeurs char*.

Exemple:

char *x, *y, *z ;
x = "toto";
y = "titi";
z = strcat (x,y);

donnera pour z (et x!) "tototiti". Donc on peut se contenter d'écrire

strcat(x,y);

et récupérer le résultat dans x.

Remarques:

1) On ne peut pas comparer (<, >, ==, <=, >=) deux chaînes de caractères (les opérateurs de comparaison ne marchent que pour des objets scalaires). Il faut utiliser la fonction strcmp(). Exemple:

strcmp("claustrophobie", "claustrophobe") est >0, car "claustrophobie" > "claustrophobe"

2) Comme les tableaux en C ne sont pas affectables, strcpy() peut servir pour affecter un tableau de caractères à un autre. Exemple

char t[10];
strcpy(t,"snoopy");

3) Toutefois, quand x et y sont des chaînes, x = y; n'est pas la même chose que strcpy(x, y). Dans un cas, (figure II-7), x et y désigneront le même objet chaîne, celui pointé par y auparavant, dans l'autre, il y a copie de l'objet. x et y désigneront deux objets différents --mais ayant la même valeur. (Exercice le vérifier).

Affectation vs Copie

  Fig-II-7 Affectation vs Copie

(a) est la situation avant; (b) est la situation après x = y; (c) est la situation après strcpy(x,y).

Noter que dans le cas (b), le premier espace alloué à x est inutilisable [4].

5. Des Opérateurs de C

Dans la figure II-8 on peut voir les opérateurs qu'on peut retenir dès à présent. Ils sont dressés par ordre de priorité décroissante et par groupe de même priorité. L'associativité signifie qu'en l'absence de parenthèses et pour des opérateurs de même priorité, l'évaluation d'une expression se fait dans le sens droite-gauche ( <--- ) ou gauche-droite ( ---> ).

Opérateur Signification Exemple Associa-
tivité
&
*
-
++
--
sizeof()
Adresse de
Indirection
- unaire
Incrémentation
Décrémentation
Taille de
&a
*p
-4
, -(b*b)
++i
, j++
--i
, j--
sizeof(i)
sizeof(float)
<---
*
/
/
%
Multiplication
Division (réelle)
Quotient (entier)
Modulo (reste)
2 * 4
4.0 / 5
9 / 3
9 % 7

--->
+
-
Addition
Soustraction
2 + 4
3 - 5
--->
=
op=
Affectation
Changement
x = 8x=y=0
 x += 5
y /= 2
<---

Opérateur =

Comme il a déjà été mentionné, l'affectation est considérée en C comme un opérateur normal. Quoiqu'à l'usage il sert à écrire l'instruction d'affectation classique comme dans

delta = b*b - 4*a*c;

Mais il n'est pas rare de voir des écritures comme

x = y = 1;

qui affecte 1 à x et à y ou

while ( s[i] = s[j] )

qui affecte s[j] à s[i] tout en testant si s[j], résultat de l'expression, devient nulle... Cette écriture condensée s'est révélée très pratique (mais pas moins dangereuse : se rappeler qu'il ne faut pas confondre while (s[i] = s[j]) avec while (s[i] == s[j]) !).

Opérateur ++

i++ (resp. i--)

incrémente (resp. décrémente) i de 1. Mais la valeur de l'expression i++ est celle de i avant l'incrémentation. De même pour i--. On parle de post-incrémentation ou post-décrémentation.

++i (resp. --i)

incrémente (resp. décremente) i de 1 aussi. Mais la valeur de l'expression est celle après incrémentation (resp. decrémentation). Ainsi

i = 5;
x = i++;

donne 5 pour x et 6 pour i.

i = 5;
x = ++i;

donne 6 pour x et pour i.

On notera que les deux instructions suivantes sont «identiques» (pourquoi?).

i++;
++i;

6. Règles de Conversion

Le résultat d'une expression est en générale de même type que ses opérandes. Mais il arrive que des fois on combine plusieurs types dans une même expression. Quel type doit l'emporter alors?

6.1. Conversion Implicite

Il existe des règles de conversions implicites[5] faites par le compilateur. L'entier l'emporte sur le caractère et le réel l'emporte sur l'entier.

En C, de façon générale dans une expression, pour chaque opérateur arithmétique binaire, si deux opérandes sont de type différents, alors il y a conversion. Celle-ci s'applique dans le sens: caractère vers entier vers réel. Plus exactement:

- S'il existe un opérande de type double ou float alors le calcul s'effectue en double, et le résultat est de type double.

- Sinon (on n'a pas de réels), s'il existe un opérande de type long alors calcul en long.

- Sinon, s'il existe un opérande unsigned alors calcul en unsigned.

- Autrement le calcul est en int.

Mais il faut noter aussi qu'avant un calcul, short et char sont toujours convertis en int et float est toujours converti en double. Il s'agit de calcul en CPU.

La conversion se fait aussi lors de l'affectation. la valeur de la partie droite est convertie vers le type de la partie gauche qui est aussi celui du résultat final.

Exemples:

float x, y;
x = 7;        x devient 7.0, conversion d'affectation
y = x / 2;         y devient 3.5 ( 7.0 / 2.0, calcul effectué en double)

float x, y;
y = 5 / 3 ;       y devient 1.0 car (division entière de 5 par 3 = 1)
x = y + 1;              x devient 2.0

float x, y; int i;
y = 3 / 2;          devient 1.0
i = y + 1.5;      i devient 2 (troncature de 2.5)
x = i + y;          x devient 3.0

La conversion la plus importante à noter, du point de vue réversibilité, est celle de char (8 bits) vers int (16 ou 32 bits). En général c'est une extension du signe. Mais cela dépend de l'architecture de la machine. Si le caractère a une valeur ASCII < 128, (dernier bit = 0), alors l'entier a la même valeur. Sinon (valeur ASCII >=128) le bit de gauche est à 1, et il est recopié à gauche pour faire un entier négatif! Valeur ASCII perdue.

La conversion inverse, de int vers char est une simple troncature des bits d'ordre > 7, qui ne fait pas perdre la valeur ASCII (entier positif).

int i; char c;
c = 'd';
i = c;     extension de signe (positif)
c = i;           troncature

La valeur de c n'a pas changé, c = 'd' toujours.

Par contre l'inverse, char vers int, n'est pas toujours juste, comme le montre l'exemple suivant :

main(){ 
       int i; 
       char c; 
       i = 200; 
       c = i; /* c = ascii(200) */ 
       i = c; /* extension de signe pour i */ 
       printf("%c %d\n",c,i); 
       i = 100; 
       c = i; 
       i = c; 
       printf("%c %d\n",c,i); 
}

qui imprime :

    H -56 
     d 100

Dans la première partie de cet exemple on a

i= 200 = 128 + 64 + 8 = 00000000 11001000 et

c=11001000 = 100 1000 = 72 = 'H' (le code ASCII ne considère que 7 bits)

i = 11111111 11001000 = -56 (extension de signe)

et dans la deuxième partie

i = 100 = 64 + 32 + 4 =00000000 01100100 et

c = 01100100 = 110 0100 = 'd'

i redevient 00000000 01100100 (extension de signe)donc100

Sachez aussi que si x est float et i est int, alors x = i et i = x causent des conversions. float vers int est alors une troncature de la partie décimale. De même double est converti vers float par arrondi et troncature de la mantisse, et float est converti vers double par allongement avec des 0 de la mantisse. Long int est converti vers short ou char par troncature des bits de poids fort.

Remarque: Les arguments des fonctions étant des expressions, la conversion se fait et les valeurs passées à une fonction sont toujours double ou int selon le cas.

En programmation, il est de bonne discipline de ne pas trop se fier aux conversions implicites. Avec float x, écrire plutôt x = x + 5.0 que x = x + 5 . La compréhension des programmes est meilleure et, surtout, on contrôle soi-même ses instructions.

6.2. Conversion Explicite

C offre aussi un moyen de forcer une conversion en cas de necessité. La ligne suivante convertit l'entier 2 en réél float.

(float ) 2

et

int n;
sqrt ( (double) n )

fait passer un flottant double à la fonction de bibliothèque sqrt() dont l'argument est double. De façon générale on écrit:

(type) expression

pour convertir le résultat de l'expression vers le type nommé. Par exemple:

(float ) (i+5)

convertit le résultat de i+5 vers float et

pt = (int*) malloc(sizeof(int));

convertit un pointeur char* , retour de malloc(), vers un pointeur int* sur entier.

Mais ce type de conversion, appelé cast, ne touche pas à la valeur de son argument:

(double) n

ne modifie pas la valeur de n. Elle produit la même valeur dans le type double.

7. Expressions Conditionnelles

On aura remarqué que le type booléen est absent de C. Qu'importe, dans les tests on emploie des expressions numériques avec la signification FAUX si le résultat est 0, VRAI sinon (i.e. différent de 0).

C permet l'utilisation des opérateurs relationnels et logiques pour former des expressions conditionnelles. Les voici par groupe de priorité décroissante (table ci-dessous). Il s'associent tous de gauche à droite (-->). Pour certains, la notation n'est pas très classique... Faire attention surtout au test d'égalité. Il n'est par rare d'écrire par erreur:

if (a = b)

au lieu de

if (a == b)

Symbole Signification
==
!=
égal à (deux signes =)
différent de
>
<
>=
<=
supérieur à
inférieur à
supérieur ou égal à
inférieur ou égal à
&&
||
!
et logique
ou logique
non logique

Exemples:

x == y               x égale y
(x + 4) != 3         (x + 4) différent de 3
(a < b) && (b < c)   b supérieur à a et inférieur à c
a < b && b < c       même chose car < est prioritaire sur &&
!b < c               (non b) est inférieur à c
!(b < c)             b n'est pas inférieur à c
a < b < c            a un sens! (exercice: quel est-il?)

Une syntaxe particulièrement utile, quoiqu'illisible parfois est

exp_c ? exp_1 : exp_0

qui signifie: évaluer exp_c et si elle est non-zero alors évaluer exp_1, qui est alors le résultat, sinon évaluer exp_0 et c'est le résultat. Exemples:

max = (a > b) ? a : b ;

signifie:

max = si a > b alors a sinon b

On peut imbriquer ces expressions

z = (a > b) ? (a > c) ? a : c : b;

Une expression conditionnelle de ce genre peut être utilisée partout à la place d'une expression. Si les types de exp_1 et exp_0 sont différents il y a conversion selon les règles précitées indifféremment de la valeur du test.

L'exemple suivant imprime les 100 premiers entiers à raison de dix par ligne, séparés par un espace.

for (i=1; i <= 100 ; i++) 
      printf("%3d%c", i, (i % 10) == 0 ? '\n' : ' ');

Résultat:

  1   2   3   4   5   6   7   8   9  10
 11  12  13  14  15  16  17  18  19  20
 21  22  23  24  25  26  27  28  29  30
 31  32  33  34  35  36  37  38  39  40
 41  42  43  44  45  46  47  48  49  50
 51  52  53  54  55  56  57  58  59  60
 61  62  63  64  65  66  67  68  69  70
 71  72  73  74  75  76  77  78  79  80
 81  82  83  84  85  86  87  88  89  90
 91  92  93  94  95  96  97  98  99 100

7.1. Opérateurs Logiques sur les Bits.

En C on peut manipuler la configuration binaire d'un objet. On a les opérateurs:

Opérateur Signification
&
|
^
<<
>>
~
et bit à bit (& binaire)
ou logique bit à bit
ou exclusif bit à bit
décalage à gauche
décalage à droite
complément à un
(les 1 deviennent 0
et inversement). 

~ est un opérateur unaire, les autres binaires.

Ces opérateurs doivent s'appliquer quand ils ont un sens. Ils n'en ont pas sur float ou double par exemple.

On peut utiliser & pour masquer des bits comme par exemple

c = i & 0177 (0177 étant 01 111 111)

qui met à 0 tous les bits de i sauf les 7 bits de poids faible. Ainsi c devient ASCII internationalement valide (entre 0 et 127).

x = x | MASK

met à 1 les bit de x situés vis à vis des 1 de MASK, une constante donnée.

x & ~077

met à 0 les 6 bits de droite de x, car ~077 = 11 000 000.

Les opérateurs << et >> servent à faire des décalages de n bits où n est le deuxième argument.

x << 5     décale x de 5 bits à gauche

x >> 2     décale x de 2 bits à droite

Dans le premier cas, x reçoit des 0 à droite et dans le second cas, des 0 ou des 1 selon son signe. Mais cela dépend des architectures machine.

Pour illustrer un usage de ces opérateurs, la figure II-9, est une fonction bin(n) qui retournent la configuration binaire de son argument entier n. Le résultat est une chaîne de caractères 0 ou 1.

char b[16]; /* tableau résultat */  
char *bin(short n) 
{
      /* routine qui convertit n en sa représentation  
       * binaire b tableau de '0' ou '1' .  
       */ short i,j,k; 
       k = 16;  
       i = n; /* i variable à décaler */  
       for(j=0; j<=15; j++){  
              if ((i % 2) == 0) /* si i est pair */  
                     b[k--]='0'; /* donc son 1er bit est 0 */  
              else  
                     b[k--]='1'; /* sinon il est 1 */  
              /* decaler i de 1 bit à droite */  
              i >>= 1;  
       } 
       return (b);  
}

Sur la séquence:

int i
char *s;
s = bin(i);
printf("%s\n",s);

ce programme donne:

0000000000000000  pour i = 0
0000000000000100 pour i = 4
1111111111111111 pour i = -1

8. Récapitulatif des Opérateurs de C

La table de la figure II-10, extraite du manuel C original (KERNIGHAN & RITCHIE), récapitule l'ensemble des opérateurs de C. Elle est donnée dans l'ordre décroissant de priorité des opérateurs avec leur associativité. Les opérateurs de même priorité sont sur une même ligne , comme *, / ou % qui sont prioritaires sur + et -. En l'absence de parenthèses et à priorité égale, l'associativité indique l'ordre d'évaluation des opérateurs. Nous y avons inclus aussi l'arité, qui est 1 pour unaire, 2 pour binaire et 3 pour ternaire.

Certains opérateurs ne sont pas encore étudiés (comme l'opérateur -> de sélection de champs de structure,voir Chapitre IV). Les parenthèses() de la première ligne désignent l'appel de fonction f(...) aussi bien que le parenthèsage des expressions, et les [] de la première ligne, l'indexation t[n]. (<type>) est l'opérateur de conversion forcée comme (float)3. Le contexte d'apparition de certains symboles identiques , *, - et &, implique leur signification:

Arité Opérateurs  Associativité 
2
1

2
2
2
2
2
2
2
2
2
2
3
2
2
() []   .    ->
! ~ ++ -- - (<type>)
* & sizeof() 

* / % 
+ - 
<< >>
< <= > >= 
== != 



&&
|| 
? :
=  +=  -= etc.
, (virgule)
---> 
<--- 

---> 
---> 
---> 
---> 
---> 
---> 
---> 
---> 
---> 
---> 
<--- 
<--- 
--->

Le dernier opérateur , virgule, sert pour une liste_virgule d'expressions.

x=3, p++, f(x,p)

qui est une expression évaluée de gauche à droite et dont le résultat est celui de la plus à droite, ici f(x,p). Dans un appel de fonction, il faut parenthéser une telle expression et écrire par exemple

g (a, (x=3,p++),b)

pour ne pas confondre avec la virgule qui sépare les arguments.


[1] Sans donner une syntaxe formelle, bien formée veut dire écrite correctement: 2 * (a - b) est bien formée, /2 + )a - b par exemple ne l'est pas.

[2] k = (L+l) / L = L/l. L et l sont les dimensions d'un rectangle parfait.

[3] En C le débordement de tableaux n'est pas toujours vérifié. C'est au programmeur(se), généralement averti(e), de faire attention.

[4] En terme plus modernes, le cas (c) est appelé shallow copy (copie superficielle). Le cas (b) est alors une simple affectation.

[5] Y faire très attention, c'est parfois source d'erreurs


< Précédent Sommaire Suivant >


Copyleft © Najib TOUNSI
ntounsi@emi.ac.ma
Version : Sept 2000 Dernière MàJ: Dec 2021