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
http://www.mescours.ma/C/c2.html
ntounsi@emi.ac.ma
Version: Sept. 2000, Dernière MàJ: Dec. 2021
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.
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 \x où x
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
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;
(en plus court
long int j;long j;
)
(en plus court
short int j;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).
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.
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.
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.
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:
Chercher l'algorithme inverse qui, pour un
réel donné, donne les termes successif de la suite
xn, xn-1, ..., x1
.
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).
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 fonctionstrcmp()
. 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
ety
sont des chaînes,x = y;
n'est pas la même chose questrcpy(x, y).
Dans un cas, (figure II-7),x
ety
désigneront le même objet chaîne, celui pointé pary
auparavant, dans l'autre, il y a copie de l'objet.x
ety
désigneront deux objets différents --mais ayant la même valeur. (Exercice le vérifier).
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].
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é |
---|---|---|---|
& |
Adresse
de |
&a , -(b*b) , j++ , j-- |
<---
|
* |
Multiplication |
2 * 4 |
---> |
+ |
Addition |
2 + 4 3 - 5 |
---> |
= |
Affectation |
x = 8 , x=y=0 , 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;
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?
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'affectationy = 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;
y
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.
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
.
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 =) |
> |
supérieur à |
&& |
et
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
En C on peut manipuler la configuration binaire d'un objet. On a les opérateurs:
Opérateur | Signification |
---|---|
& | ^ << >> ~ |
et
bit à bit ( |
~
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
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:
*
est l'indirection, -
est le
moins et &
est l'adresse;*
et -
désignent respectivement
la multiplication et la soustraction, et &
est le ET
sur les bits.Arité | Opérateurs | Associativité |
---|---|---|
2 1 2 2 2 2 2 2 2 2 2 2 3 2 2 |
() [] . -> ! ~ ++ -- - (<type>) * / % + - << >> < <= > >= == != & ^ | && || ? : = += -= 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