III. Expressions et instructions▲
III-A. Introduction aux types de données du langage C▲
III-A-1. Les types de base du langage C▲
Au niveau du processeur, toutes les données sont représentées sous leur forme binaire et la notion de type n'a pas de sens. Cette notion n'a été introduite que par les langages de haut niveau dans le but de rendre les programmes plus rationnels et structurés. Mais même parmi les langages de haut niveau, on distingue ceux qui sont dits fortement typés (qui offrent une grande variété de types), comme le Pascal par exemple, de ceux qui sont dits faiblement ou moyennement typés (et plus proches de la machine) comme le C par exemple. Le langage C ne dispose que de 4 types de base :
Type C | Type correspondant |
---|---|
char | caractère (entier de petite taille) |
int | entier |
float | nombre flottant (réel) en simple précision |
double | nombre flottant (réel) en double précision |
Devant char ou int, on peut mettre le modificateur signed ou unsigned selon que l'on veut
avoir un entier signé (par défaut) ou non signé. Par exemple :
2.
3.
char
ch;
unsigned
char
c;
unsigned
int
n; /* ou tout simplement : unsigned n */
La plus petite valeur possible que l'on puisse affecter à une variable de type entier non signé
est 0 alors que les entiers signés acceptent les valeurs négatives.
Devant int, on peut mettre également short ou long auxquels cas on obtiendrait un entier
court (short int ou tout simplement short) respectivement un entier long (long int ou tout
simplement long). Voici des exemples de déclarations valides :
2.
3.
4.
int
n =
10
, m =
5
;
short
a, b, c;
long
x, y, z =
-
1
;
unsigned
long
p =
2
;
long peut être également mis devant double, le type résultant est alors long double (quadruple précision).
III-A-2. Règle d'écriture des constantes littérales▲
Nombres entiers▲
Toute constante littérale « pure » de type entier (ex : 1, -3, 60, 40, -20, ...) est considérée par
le langage comme étant de type int.
Pour expliciter qu'une constante littérale de type entier est de type unsigned, il suffit d'ajouter
à la constante le suffixe u ou U. Par exemple : 2u, 30u, 40U, 50U, ...
De même, il suffit d'ajouter le suffixe l ou L pour expliciter qu'une constante littérale de type
entier est de type long (on pourra utiliser le suffixe UL par exemple pour unsigned long).
Une constante littérale de type entier peut également s'écrire en octal (base 8) ou en
hexadécimal (base 16). L'écriture en hexa est évidement beaucoup plus utilisée.
Une constante littérale écrite en octal doit être précédée de 0 (zéro). Par exemple : 012, 020,
030UL, etc.
Une constante littérale écrite en hexadécimal doit commencer par 0x (zéro x). Par exemple
0x30, 0x41, 0x61, 0xFFL, etc.
Nombres flottants▲
Toute constante littérale « pure » de type flottant (ex : 0.5, -1.2, ...) est considérée comme
étant de type double.
Le suffixe f ou F permet d'expliciter un float. Attention, 1f n'est pas valide car 1 est une
constante entière. Par contre 1.0f est tout à fait correcte. Le suffixe l ou L permet d'expliciter
un long double.
Une constante littérale de type flottant est constituée, dans cet ordre :
- d'un signe (+ ou -)
- d'une suite de chiffres décimaux : la partie entière
- d'un point : le séparateur décimal
- d'une suite de chiffres décimaux : la partie décimale
- d'une des deux lettres e ou E : symbole de la puissance de 10 (notation scientifique)
- d'un signe (+ ou -)
- d'une suite de chiffres décimaux : la puissance de 10
Par exemple, les constantes littérales suivantes représentent bien des nombres flottants : 1.0, -1.1f, 1.6E-19, 6.02e23L, 0.5 3e8
Caractères et chaînes de caractères▲
Les caractères sont placés entre simple quottes (ex : 'A', 'b', 'c', ...) et les chaînes de caractères entre double quottes (ex : "Bonjour", "Au revoir", ...). Certains caractères sont spéciaux et il faut utiliser la technique dite d'échappement pour les utiliser. En langage C, le caractère d'échappement est le caractère \. Exemples :
Caractère | Valeur |
---|---|
'\t' | Le caractère 'tabulation' |
'\r' | Le caractère 'retour chariot' |
'\n' | Le caractère 'fin de ligne' |
'\\' | Le caractère 'antislash' |
III-A-3. Spécification de format dans printf▲
III-A-3-a. Généralités▲
Voici la liste des codes format que nous utiliserons les plus souvent par la suite :
Code format | Utilisation |
---|---|
c | Afficher un caractère |
d | Afficher un int |
u | Afficher un unsigned int |
x, X | Afficher un entier dans le format hexadécimal |
f | Afficher un float ou un double en notation décimale |
e | Afficher un float ou un double en notation scientifique avec un petit e |
E | Afficher un float ou un double en notation scientifique avec un grand E |
g, G | Afficher un float ou un double (utilise le format le plus adapté) |
% | Afficher le caractère '%' |
Lorsqu'on affiche une donnée de type flottant dans le format "scientifique", il n'y a qu'un chiffre, ni moins ni plus,
devant le point décimal (et ce chiffre est 0 si et seulement si la donnée vaut 0), le nombre de chiffres après ce point est variable
et le nombre de chiffres utilisés pour représenter l'exposant dépend de l'implémentation mais il y en a toujours au moins deux.
D'autre part :
- h devant d ou u indique que l'argument est un short
- l devant d, u, x ou X indique que l'argument est de type long
- L devant f, e, E ou g indique que l'argument est de type long double
La spécification de format dans printf va cependant beaucoup plus loin qu'une simple spécification de type.
En règle générale, une spécification de format a la structure suivante : %[options]specificateur_de_type.
Les "options" ont été mis entre crochets car elles sont toujours facultatives. Les options disponibles dépendent
du code de conversion (le "spécificateur de type") utilisé. Nous n'allons pas toutes les présenter (la documentation
de référence est là pour ça) mais allons quand même voir quelques options que nous aurons assez souvent l'occasion d'utiliser.
Enfin, pour afficher un caractère, il vaut mieux utiliser putchar (ex : putchar('*')) qui est plus simple plutôt que printf.
III-A-3-b. Les "options"▲
Une spécification de format dans printf a le plus souvent (cas pratique) la structure suivante :
%[[+][-][0]][gabarit][.precision][modificateur_de_type]specificateur_de_type
Les drapeaux + et 0 n'ont de sens que pour les données à imprimer sous forme numérique.
Découvrons le rôle des paramètres les plus utiles pour les types les plus utilisés.
III-A-3-c. Le type entier▲
Pour le type entier (code de conversion d, u, x ou X) :
- h devant le spécificateur de type indique que l'argument attendu est de type short et l indique que l'argument est de type long.
- La précision indique le nombre maximum de caractères à utiliser pour imprimer la donnée.
- Le gabarit indique le nombre minimum de caractères à utiliser.
- Le drapeau 0 indique qu'il faut ajouter autant de 0 qu'il en faut avant le premier chiffre significatif de sorte que tout l'espace reservé au texte (défini par le gabarit) soit occupé.
- Le drapeau - indique que le texte doit être aligné à gauche. L'alignement par défaut est donc bien évidemment à droite.
- Le drapeau + indique qu'on veut que le signe du nombre soit toujours affiché, même si le nombre est positif ou nul.
Voici un programme proposant quelques applications :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
#include <stdio.h>
int
main
(
)
{
int
n =
1234
;
printf
(
"
+------------+
\n
"
);
printf
(
"
|%12d|
\n
"
, n); /* 12 caracteres minimum, alignement a droite */
printf
(
"
+------------+
\n
"
);
printf
(
"
|%-12d|
\n
"
, n); /* 12 caracteres minimum, alignement a gauche */
printf
(
"
+------------+
\n
"
);
printf
(
"
|%012d|
\n
"
, n); /* 12 caracteres minimum, caractere de remplissage : '0' */
printf
(
"
+------------+
\n
"
);
/* Affichage obligatoire du signe */
printf
(
"
|%+12d|
\n
"
, n);
printf
(
"
+------------+
\n
"
);
printf
(
"
|%+-12d|
\n
"
, n);
printf
(
"
+------------+
\n
"
);
printf
(
"
|%+012d|
\n
"
, n);
printf
(
"
+------------+
\n
"
);
return
0
;
}
Voici la sortie de ce programme :
+------------+
| 1234|
+------------+
|1234 |
+------------+
|000000001234|
+------------+
| +1234|
+------------+
|+1234 |
+------------+
|+00000001234|
+------------+
III-A-3-d. Le type flottant▲
Pour le type flottant (code de conversion f, e, E, g ou G) :
- L devant le spécificateur de type indique que l'argument attendu est de type long double.
- La précision indique le nombre de caractères à afficher après le point décimal pour les codes f, e et E (la valeur par défaut est 6) et le nombre maximum de chiffres significatifs pour les codes g et G.
- Le gabarit indique le nombre minimum de caractères à utiliser.
- Le drapeau 0 indique qu'il faut ajouter autant de 0 qu'il en faut avant le premier chiffre significatif de sorte que tout l'espace reservé au texte (défini par le gabarit) soit occupé.
- Le drapeau - indique que le texte doit être aligné à gauche. L'alignement par défaut est donc bien évidemment à droite.
- Le drapeau + indique qu'on veut que le signe du nombre soit toujours affiché, même si le nombre est positif ou nul.
Voici un programme proposant quelques applications :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
#include <stdio.h>
int
main
(
)
{
double
x =
12
.34
;
/* Affichage de flottant avec 4 chiffres apres le point decimal. */
printf
(
"
%.4f
\n
"
, x);
printf
(
"
%.4e
\n
"
, x);
/* Affichage dans le format le plus approprie. */
printf
(
"
%g
\n
"
, x);
return
0
;
}
Voici la sortie de ce programme (en supposant que l'implémentation utilise 3 chiffres pour imprimer l'exposant) :
12.3400
1.2340e+001
12.34
III-A-4. Les variables et les constantes▲
Si vous avez déjà fait un peu de math dans votre vie, vous devriez connaître ce que c'est qu'une variable. La notion est pratiquement la même en programmation. La déclaration d'une variable en langage C, dans sa forme la plus simple, se fait de la manière suivante :
<
type>
variable;
Par exemple :
int
n;
Voici d'autres exemples beaucoup plus généralistes :
2.
3.
int
a, b, c;
int
i =
0
, j, n =
10
;
double
x, y, z =
0
.5
;
Une déclaration de variable de la forme :
int
n =
0
;
est appelée une déclaration avec initialisation. Ce n'est pas la même chose que :
2.
int
n;
n =
0
;
qui est une déclaration suivie d'une instruction d'affectation. Pour vous en convaincre rapidement, une variable constante (ou varaible en lecture seule) est une variable qui ne peut être qu'initialisée puis lue (la valeur d'une constante ne peut pas être modifiée). On obtient une constante en ajoutant le mot-clé const devant une déclaration. Par exemple :
const
int
n =
10
;
On ne pourra jamais écrire :
n =
<
quoi que ce soit>
;
Le mot-clé const être placé avant ou après le type ou encore avant le nom de la variable. Voici deux exemples :
2.
const
int
n =
10
;
int
const
m =
20
;
III-A-5. Définition de nouveaux types▲
Le C dispose d'un mécanisme très puissant permettant au programmeur de créer de nouveaux types de données en utilisant le mot clé typedef. Par exemple :
typedef
int
ENTIER;
Définit le type ENTIER comme n'étant autre que le type int. Rien ne nous empêche donc désormais d'écrire par exemple :
ENTIER a, b;
Bien que dans ce cas, un simple #define aurait pu suffire, il est toujours recommandé d'utiliser typedef qui est beaucoup plus sûr.
III-B. Les pointeurs▲
III-B-1. Définition▲
Comme nous le savons très bien, l'endroit où s'exécute un programme est la mémoire donc
toutes les données du programme (les variables, les fonctions, ...) se trouvent en mémoire. Le
langage C dispose d'un opérateur - & - permettant de récupérer l'adresse en mémoire d'une
variable ou d'une fonction quelconque. Par exemple, si n est une variable, &n désigne
l'adresse de n.
Le C dispose également d'un opérateur - * - permettant d'accéder au contenu de la mémoire
dont l'adresse est donnée. Par exemple, supposons qu'on ait :
int
n;
Alors les instructions suivantes sont strictement identiques.
n =
10
;
*(
&
n ) =
10
;
Un pointeur (ou une variable de type pointeur) est une variable destinée à recevoir une
adresse. On dit alors qu'elle pointe sur un emplacement mémoire. L'accès au contenu de la
mémoire se fait par l'intermédiaire de l'opérateur *.
Les pointeurs en langage C sont typés et obéissent à l'arithmétique des pointeurs que nous
verrons un peu plus loin. Supposons que l'on veuille créer une variable - p - destinée à
recevoir l'adresse d'une variable de type int. p s'utilisera alors de la façon suivante :
2.
3.
4.
p =
&
n;
...
*
p =
5
;
...
*p est un int. Vous suivez ? Alors comment devrait-on déclarer le pointeur ? Si vous avez bien suivi, vous auriez répondu :
int
*
p;
Et de plus, si vous avez très bien suivi, vous auriez certainement ajouté : et cela est strictement équivalent à :
int
*
p;
Car en C, les espaces sont totalement gratuites ! Donc voilà, le type d'un pointeur sur int est int *. Mais cela peut être moins évident dans certaines déclarations. Par exemple, dans :
2.
/* cas 1 */
int
*
p1, p2, p3;
seul p1 est de type int * ! Le reste, p2 et p3, sont de type int. Si vous voulez obtenir 3 pointeurs, vous devrez utilisez la syntaxe suivante :
2.
/* cas 2 */
int
*
p1, *
p2, *
p3;
Ou avec un typedef :
2.
typedef
int
*
PINT;
PINT p1, p2, p3;
Par contre cela n'aurait pas marché si on avait défini PINT à l'aide d'un #define car cela nous amènerait au cas 1.
III-B-2. Saisir des données tapées au clavier avec la fonction scanf▲
On va encore faire un petit exercice de logique, plutôt de bon sens.
Si nous voulons afficher un entier par exemple, que doit-on fournir à la fonction d'affichage
(printf par exemple) : la valeur de l'entier que nous voulons afficher ou l'adresse de la
variable contenant le nombre que nous voulons afficher ?
La bonne réponse est : l'entier que nous voulons afficher bien sûr, autrement dit sa valeur.
Personne n'a demandé son adresse !
Maintenant, si nous voulons demander à l'utilisateur (celui qui utilise notre programme) de
taper un nombre puis ranger le nombre ainsi tapé dans une variable, que devons-nous fournir
à la fonction permettant la saisie : l'adresse de la variable dans laquelle nous souhaitons
ranger le nombre que l'utilisateur a entré, ou la valeur que contient actuellement cette
variable ?
La bonne réponse est bien sûr : l'adresse de la variable dans laquelle nous souhaitons ranger le
nombre entré
La fonction scanf, déclarée dans le fichier stdio.h, permet de lire des données formatées sur
l'entrée standard, par défaut le clavier.
Voici un programme qui demande à l'utilisateur d'entrer 2 nombres puis affiche leur somme :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
#include <stdio.h>
int
main
(
)
{
int
a, b, c;
printf
(
"
Ce programme calcule la somme de 2 nombres.
\n
"
);
printf
(
"
Entrez la valeur de a :
"
);
scanf
(
"
%d
"
, &
a);
printf
(
"
Entrez la valeur de b :
"
);
scanf
(
"
%d
"
, &
b);
c =
a +
b;
printf
(
"
%d + %d = %d
\n
"
, a, b, c);
return
0
;
}
Mais faites gaffe avec la fonction scanf ! "%d" par exemple n'a rien à voir avec " %d ". En effet dans ce dernier cas, le format attendu par la fonction est :
<
un espace>
<
un entier>
<
puis un autre espace>
Où espace désigne en fait n'importe quel caractère ou série de caractères blancs (espace, tabulation, etc.). Voici un exemple beaucoup plus convaincant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
#include <stdio.h>
int
main
(
)
{
int
mon_age =
23
, votre_age;
printf
(
"
Bonjour, j'ai %d ans. Et vous?
"
, mon_age);
scanf
(
"
%d ans
"
, &
votre_age);
printf
(
"
Ah! vous avez %d ans?
\n
"
, votre_age);
return
0
;
}
L'utilisateur devra alors taper par exemple :
22
ans
Peu importe le nombre de caractères blancs entre 22 et ans.
C'est ce qu'on appelle une saisie formatée. scanf retourne le nombre de conversions de format réussies.
La valeur de retour de scanf permet donc déjà de voir si l'utilisateur a bien respecté le format attendu ou non. Dans notre
exemple, il n'y a qu'une seule conversion de format à faire (%d). Si l'utilisateur respecte bien le format, scanf retournera 1.
Les fonctions telles que scanf sont plutôt
destinées à être utilisées pour lire des données provenant d'un programme sûr (par
l'intermédiaire d'un fichier par exemple), pas celles provenant d'un humain, qui sont sujettes
à l'erreur. Les codes format utilisés dans scanf sont à peu près les mêmes que dans printf, sauf
pour les flottants notamment.
Code format | Utilisation |
---|---|
f, e, g | float |
lf, le, lg | double |
Lf, Le, Lg | long double |
Voici un programme qui permet de calculer le volume d'un cône droit à base circulaire selon
la formule : V = 1/3 * (B * h) où B est la surface de base soit pour une base circulaire : B =
PI*R2, où R est le rayon de la base.
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.
#include <stdio.h>
double
Volume
(
double
r_base, double
hauteur);
int
main
(
)
{
double
R, h, V;
printf
(
"
Ce programme calcule le volume d'un cone.
\n
"
);
printf
(
"
Entrez le rayon de la base :
"
);
scanf
(
"
%lf
"
, &
R);
printf
(
"
Entrez la hauteur du cone :
"
);
scanf
(
"
%lf
"
, &
h);
V =
Volume
(
R, h);
printf
(
"
Le volume du cone est : %f
"
, V);
return
0
;
}
double
Volume
(
double
r_base, double
hauteur)
{
return
(
3
.14
*
r_base *
r_base *
hauteur) /
3
;
}
III-B-3. Exemple de permutation des contenus de deux variables▲
Voici un programme simple qui permute le contenu de deux variables.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
include <
stdio.h>
int
main
(
)
{
int
a =
10
, b =
20
, c;
/* on sauvegarde quelque part le contenu de a */
c =
a;
/* on met le contenu de b dans a */
a =
b;
/* puis le contenu de a que nous avons sauvegardé dans c dans b */
b =
c;
/* affichons maintenant a et b */
printf
(
"
a = %d
\n
b = %d
\n
"
, a, b);
return
0
;
}
Maintenant, réécrivons le même programme en utilisant une fonction. Cette fonction doit donc pouvoir localiser les variables en mémoire autrement dit nous devons passer à cette fonction les adresses des variables dont on veut permuter le contenu.
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.
#include <stdio.h>
void
permuter
(
int
*
addr_a, int
*
addr_b);
int
main
(
)
{
int
a =
10
, b =
20
;
permuter
(&
a, &
b);
printf
(
"
a = %d
\n
b = %d
\n
"
, a, b);
return
0
;
}
void
permuter
(
int
*
addr_a , int
*
addr_b)
/**
*************\
* addr_a
<
-- &a *
* addr_b
<
-- &b *
\**************
*/
{
int
c;
c =
*
addr_a;
*
addr_a =
*
addr_b;
*
addr_b =
c;
}
III-C. Les expressions▲
III-C-1. Introduction▲
En langage C, la notion d'expression est beaucoup plus riche que dans n'importe quel autre langage. De ce fait en donner une définition est longue et nous allons donc plutôt l'introduire (comme nous l'avons d'ailleurs toujours fait) de manière intuitive. Pour cela, analysons l'instruction suivante :
c =
a +
b;
Dans cette instruction (où nous supposons que les 3 variables sont tous de type int), il y a tout d'abord évaluation de a + b, puis affection du résultat dans c. On dit que :
a +
b
est une expression, plus précisément une expression de type int (car a et b sont de type int) et dont la valeur est la somme de a et b (a + b), et que :
c =
a +
b;
est une instruction (remarquez bien le point-virgule).
L'expression a + b est composée de deux expressions à savoir a et b. On dit alors que c'est
une expression complexe. En langage C, les opérateurs permettent de construire des
expressions complexes. Dans notre exemple, l'opérateur d'addition à savoir + nous a permis
de construire une expression complexe, a + b, à partir des expressions simples a et b.
Or en langage C, = est également un opérateur, l'opérateur d'affectation, et permet donc de
construire des expressions complexes. Ainsi, dans notre exemple :
c =
a +
b
est une expression (remarquez bien l'absence de point-virgule, sinon ce serait une instruction) et dont la valeur et le type sont celle et celui de l'expression situé à gauche à savoir c.
III-C-2. lvalue et rvalue▲
Une lvalue (de left value) est tout ce qui peut figurer à gauche de l'opérateur d'affectation.
Une variable, par exemple, est une lvalue. Si p est un pointeur (disons vers int), p est une lvalue (en effet un pointeur est une
variable comme toutes les autres) et *p est également une lvalue.
Une rvalue (de right value) est tout ce qui peut figurer à droite de l'opérateur d'affectation.
Une variable, une constante littérale, l'adresse d'une variable en sont des exemples. Si a
est une variable, disons de type int, alors a est une lvalue tandis que a, -a ou a + 1 par exemple
sont des rvalues. Cet exemple nous montre bien qu'une variable est à la fois une lvalue et une
rvalue.
Une rvalue possède une valeur mais pas une adresse. Ainsi par exemple : a étant une
lvalue, &a a bien un sens (désigne l'adresse mémoire de la variable a).
Par contre, une expression insensée telle que &(-a) ou &(a + 1) sera rejetée (avec
plaisir) par le compilateur.
III-C-3. Opérations usuelles▲
III-C-3-a. Les opérateurs arithmétiques courants▲
Les opérateurs arithmétiques courants +, -, * et / existent en langage C. Toutefois, la division entière est un tout petit peu délicate. En effet, si a et b sont des entiers, a / b vaut le quotient de a et b c'est-à-dire par exemple, 29 / 5 vaut 5. Le reste d'une division entière s'obtient avec l'opérateur modulo %, c'est-à-dire, en reprenant l'exemple précédent, 29 % 5 vaut 4.
III-C-3-b. Les opérateurs de comparaison▲
Les opérateurs de comparaison sont
Opérateur | Rôle |
---|---|
< | Inférieur à |
> | Supérieur à |
== | Egal à |
<= | Inférieur ou égal à |
>= | Supérieur ou égal à |
!= | Différent de |
Par exemple, l'expression :
1
<
1000
est vraie. Vraie ? Et que vaut « vrai » au juste ? Quel est le type d'une expression « vraie » ? En langage C, la valeur d'une expression « vraie » est non nulle et celle d'une expression « fausse » zéro. Et réciproquement, toute valeur non nulle (24, -107, 2.9, ...) peut être interprétée comme vrai et zéro comme faux. Par exemple :
2.
int
prop;
prop =
(
1
<
1000
); /* alors prop = VRAI */
III-C-3-c. Les opérateurs logiques▲
On peut construire des expressions logiques complexes à l'aide des opérateurs
Opérateur | Rôle |
---|---|
&& | ET |
|| | OU |
! | NON |
Par exemple :
2.
3.
4.
5.
6.
int
prop1, prop2, prop_ou, prop_et, prop_vrai;
prop1 =
(
1
<
1000
);
prop2 =
(
2
==
-
6
);
prop_ou =
prop1 ||
prop2; /* VRAI, car prop1 est VRAI */
prop_et =
prop1 &&
prop2; /* FAUX, car prop2 est FAUX */
prop_vrai =
prop1 &&
!
prop_2 /* VRAI car prop1 et !prop2 sont VRAI */
Les opérateurs logiques jouissent des propriétés suivantes :
- Dans une opération ET, l'évaluation se fait de gauche à droite. Si l'expression à
gauche de l'opérateur est fausse, l'expression à droite ne sera plus évaluée car on sait
déjà que le résultat de l'opération sera toujours FAUX.
- Dans une opération OU, l'évaluation se fait de gauche à droite. Si l'expression à gauche de l'opérateur est vrai, l'expression à droite ne sera plus évaluée car on sait déjà que le résultat de l'opération sera toujours VRAI.
III-C-4. L'opérateur virgule▲
On peut séparer plusieurs expressions à l'aide de l'opérateur virgule. Le résultat est une expression dont la valeur est celle de l'expression la plus à droite. L'expression est évaluée de gauche à droite. Par exemple, l'expression :
(
a =
-
5
, b =
12
, c =
a +
b) *
2
vaut 14.
III-C-5. Taille des données. L'opérateur sizeof▲
La taille d'une donnée désigne la taille, en octets, que celle-ci occupe en mémoire. Par
extension de cette définition, la taille d'un type de données désigne la taille d'une donnée de
ce type. Attention ! octet désigne ici, par abus de langage, la taille d'un élément de mémoire
sur la machine cible (la machine abstraite), c'est-à-dire la taille d'une case mémoire (qui vaut 8 bits dans la plupart des architectures
actuelles), et non un groupe de 8 bits. En langage C, un octet (une case mémoire) est représenté par un char. La taille d'un char n'est donc pas forcément
8 bits, même si c'est le cas dans de nombreuses architectures, mais dépendante de la machine. La norme requiert toutefois qu'un char doit faire
au moins 8 bits et que la macro CHAR_BIT, déclarée dans limits.h, indique la taille exacte d'un char sur la machine cible.
Le C dispose d'un opérateur, sizeof, permettant de connaître la taille, en octets, d'une donnée ou d'un
type de données. La taille d'un char vaut donc évidemment 1 puisqu'un char représente un octet. Par ailleurs, il ne peut y avoir de type
dont la taille n'est pas multiple de celle d'un char. Le type de la valeur retournée par l'opérateur sizeof est size_t, déclaré dans stddef.h,
qui est inclus par de nombreux fichiers d'en-tête dont stdio.h.
Comme nous l'avons déjà dit plus haut, la taille des données est dépendante de la machine cible. En langage
C, la taille des données n'est donc pas fixée. Néanmoins la norme stipule qu'on doit avoir :
sizeof
(
char
) <=
sizeof
(
short
) <=
sizeof
(
int
) <=
sizeofd
(
long
)
Sur un processeur Intel (x86) 32 bits par exemple, un char fait 8 bits, un short 16 bits, et les int et les long 32 bits.
III-C-6. Les opérateurs d'incrémentation et de décrémentation▲
Il s'agit des opérateurs ++ (opérateur d'incrémentation) et -- (opérateur de décrémentation). Ils existent sous forme préfixée ou postfixée. L'opérande doit être une lvalue de type numérique ou pointeur. Soient i et j deux variables de type int :
j =
i++
;
est équivalent à :
2.
j =
i;
i =
i +
1
;
On voit donc bien que la valeur de i++ est celle de i avant l'évaluation effective de
l'expression (post incrémentation).
Inversement :
j =
++
i;
est équivalent à :
2.
i =
i +
1
;
j =
i;
Où l'on voit bien que la valeur de ++i est celle de i après l'évaluation effective de
l'expression (pré incrémentation).
L'opérateur -- s'utilise de la même manière que ++ mais l'opérande est cette fois-ci
décrémentée.
III-C-7. Expressions conditionnelles▲
Une expression conditionnelle est une expression dont la valeur dépend d'une condition. L'expression :
p ? a : b
vaut a si p est vrai et b si p est faux.
III-C-8. Autres opérateurs d'affectation▲
Ce sont les opérateurs : +=, -=, *=, /=, ...
x +=
a;
par exemple est équivalent à :
x =
x +
a;
III-C-9. Ordre de priorité des opérateurs▲
Les opérateurs sont classés par ordre de priorité. Voici les opérateurs que nous avons étudiés jusqu'ici classés dans cet ordre. Opérateur Associativité
Opérateur | Associativité |
---|---|
Parenthèses | de gauche à droite |
! ++ -- - (signe) sizeof | de gauche à droite |
* / % | de gauche à droite |
+ - | de gauche à droite |
< <= > >= | de gauche à droite |
== != | de gauche à droite |
& (adresse de) | de gauche à droite |
&& | de gauche à droite |
|| | de gauche à droite |
Opérateurs d'affectation (= += ...) | de droite à gauche |
, | de gauche à droite |
Ce n'est pas parce que cet ordre existe qu'il faut le retenir par coeur. Pour du code lisible, il est
même conseillé de ne pas trop en tenir compte et d'utiliser des parenthèses dans les situations
ambiguës.
III-D. Considérations liées à la représentation binaire▲
III-D-1. Généralités▲
L'ordinateur traite les données uniquement sous forme numérique. A l'intérieur de l'ordinateur, ces nombres sont représentés en binaire. La manière de représenter une donnée dans la mémoire de l'ordinateur est appelée codage. Pour bien comprendre le C, vous devez connaître comment les données sont représentées en mémoire, c'est-à-dire sous leur forme binaire. Voir à ce sujet votre cours de numérique.
III-D-2. Les caractères▲
La représentation numérique des caractères définit ce qu'on appelle un jeu de caractères. Par
exemple, dans le jeu de caractères ASCII (American Standard Code for Information
Interchange) qui est un jeu de caractères qui n'utilise que 7 bits et qui est à la base de
nombreux codes populaires de nos jours, le caractère 'A' est représenté par le code 65, le
caractère 'a' par 97 et '0' par 48. Hélas, même ASCII n'est pas adopté par le langage C. En effet si le
C dépendait d'un jeu de caractères particulier, il ne serait alors pas totalement portable. Le
standard définit néanmoins un certain nombre de caractères que tout environnement
compatible avec le C doit posséder parmi lesquels les 26 lettres de l'alphabet latin (donc en
fait 52 puisqu'on différencie les majuscules et les minuscules), les 10 chiffres décimaux, les
caractères # < > ( ) etc. Le programmeur (mais pas le compilateur) n'a pas besoin de connaître
comment ces caractères sont représentés dans le jeu de caractères de l'environnement. Le
standard ne définit donc pas un jeu de caractères mais seulement un ensemble de caractères
que chaque environnement compatible est libre d'implémenter à sa façon (plus
éventuellement les caractères spécifiques à cet environnement). La seule contrainte imposée
est que leur valeur doit pouvoir tenir dans un char.
Concernant la technique d'échappement, sachez également qu'on peut insérer
du code octal (commençant par 0) ou hexadécimal (commençant par x) après le caractère
d'échappement \ pour obtenir un caractère dont le code dans le jeu de caractères est donné.
L'hexadécimal est de loin le plus utilisé. Par exemple : '\x30', '\x41', '\x61', ...
Et enfin pour les caractères de code 0, 1, ... jusqu'à 7, on peut utiliser les raccourcis '\0',
'\1', ... '\7'.
III-D-3. Dépassement de capacité▲
Le dépassement de capacité a lieu lorsqu'on tente d'affecter à une lvalue une valeur plus grande que ce qu'elle peut contenir. Par exemple, en affectant une valeur sur 32 bits à une variable ne pouvant contenir que 16 bits.
III-E. La conversion de type▲
III-E-1. Conversion implicite▲
En langage C, des règles de conversion dite implicite s'appliquent aux données qui composent une expression complexe lorsqu'ils ne sont pas de même type (entier avec un flottant, entier court avec entier long, entier signé avec un entier non signé, etc.). Par exemple, dans l'expression :
'
A
'
+
2
'A' est de type char et 2 de type int. Dans ce cas, 'A' est tout d'abord converti en int avant que
l'expression ne soit évaluée. Le résultat de l'opération est de type int (car un int + un int
donne un int). Ici, il vaut 67 (65 + 2). En fait, les char et les short sont toujours systématiquement
convertis en int c'est-à-dire que dans l'addition de deux char par exemple, tous deux sont tout d'abord convertis en int
avant d'être additionnés, et le résultat est un int (pas un char). Un unsigned char sera converti en unsigned int, et ainsi de suite.
En règle générale : le type le plus « faible » est convertit dans le type le plus
« fort ». Par exemple, les entiers sont plus faibles que les flottants donc
1 mélangé à un flottant par exemple sera tout d'abord converti en 1.0 avant que l'opération n'ait effectivement lieu.
III-E-2. Conversion explicite (cast)▲
Il suffit de préciser le type de destination entre parenthèses devant l'expression à convertir. Par exemple :
2.
float
f;
f =
(
float
)3
.1416
;
Dans cet exemple, on a converti explicitement 3.1416, qui est de type double, en float. Lorsqu'on affecte un flottant à un entier, seule la partie entière, si elle peut être représentée, est retenue.
III-F. Les instructions▲
III-F-1. Introduction▲
L'instruction est la plus petite unité qui compose une fonction. Le seul type d'instruction que nous avons vu jusqu'ici est l'expression-instruction qui n'est autre qu'une expression suivie d'un point-virgule. Comprendre les notions d'expression et d'instruction est indispensable pour bien comprendre et maîtriser la syntaxe du langage C. Nous allons maintenant généraliser la notion d'instruction en langage C.
III-F-2. Bloc d'instructions▲
Un bloc, abusivement appelé bloc d'instructions, ou plus précisément une instruction-bloc
consiste en un groupe de déclarations et d'instructions (qui peuvent être également des
blocs). Un bloc est délimité par des accolades. Une fonction est toujours constituée d'une
instruction-bloc.
Une variable déclarée à l'intérieur d'un bloc est locale à (c'est-à-dire n'est connue qu'à
l'intérieur de) ce bloc et masque toute variable de même nom déclarée à l'extérieur de celui-ci
(une variable globale est une variable déclarée à l'extérieur de toute fonction et est connue
partout dans le fichier source). D'autre part, une déclaration doit toujours se faire en début de
bloc. Les arguments effectifs d'une fonction sont également des variables locales à cette
fonction.
Voici un exemple de programme (présentant peu d'intérêt je le conçois) comportant plusieurs blocs :
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.
#include <stdio.h>
int
main
(
)
{
printf
(
"
Les instructions en langage C
\n
"
);
printf
(
"
-----------------------------
\n\n
"
);
{
/* Un bloc d'instructions */
printf
(
"
**********
\n
"
);
printf
(
"
* Bloc 1 *
\n
"
);
printf
(
"
**********
\n
"
);
}
putchar
(
'
\n
'
);
{
/* Un autre bloc */
printf
(
"
**********
\n
"
);
printf
(
"
* Bloc 2 *
\n
"
);
printf
(
"
**********
\n
"
);
{
/* Encore un autre bloc */
printf
(
"
**********
\n
"
);
printf
(
"
* Bloc 3 *
\n
"
);
printf
(
"
**********
\n
"
);
}
}
return
0
;
}
Retenez donc bien ceci : un bloc constitue bien une et une seule instruction. Bien que cette instruction soit en fait un bloc d'instructions (et de déclarations), il est conseillé de la voir comme une et une seule instruction plutôt que comme un bloc d'instructions. Une instruction-bloc ne se termine pas par un point-virgule. Conclusion : Il est faux de dire qu'une instruction doit toujours se terminer par un point-virgule. Le point-virgule marque tout simplement la fin de certaines instructions, dont les expressions-instructions.
III-F-3. L'instruction if▲
Permet d'effectuer des choix conditionnels. La syntaxe de l'instruction est la suivante :
2.
3.
4.
if
(
<
expression>
)
<
une et une seule instruction>
else
<
une et une seule instruction>
Une instruction if peut ne pas comporter de else. Lorsqu'on a plusieurs instructions if imbriquées, un else se rapporte toujours au dernier if suivi d'une et une seule instruction. Par exemple : écrivons un programme qui compare deux nombres.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
#include <stdio.h>
int
main
(
)
{
int
a, b;
printf
(
"
Ce programme compare deux nombres.
\n
"
);
printf
(
"
Entrez la valeur de a :
"
);
scanf
(
"
%d
"
, &
a);
printf
(
"
Entrez la valeur de b :
"
);
scanf
(
"
%d
"
, &
b);
if
(
a <
b)
printf
(
"
a est plus petit que b.
\n
"
);
else
if
(
a >
b)
printf
(
"
a est plus grand que b.
\n
"
);
else
printf
(
"
a est egal a b.
\n
"
);
return
0
;
}
III-F-4. L'instruction do▲
Permet d'effectuer une boucle. La syntaxe de l'instruction est la suivante :
2.
3.
do
<
une et une seule instruction>
while
(
<
expression>
);
L'instruction do permet d'exécuter une instruction tant que <expression> est vraie. Le test est fait après chaque exécution de l'instruction. Voici un programme qui affiche 10 fois Bonjour.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
#include <stdio.h>
int
main
(
)
{
int
nb_lignes_affichees =
0
;
do
{
printf
(
"
Bonjour.
\n
"
);
nb_lignes_affichees++
;
}
while
(
nb_lignes_affichees <
10
);
return
0
;
}
III-F-5. L'instruction while▲
Permet d'effectuer une boucle. La syntaxe de l'instruction est la suivante :
2.
while
(
<
expression>
)
<
une et une seule instruction>
L'instruction while permet d'exécuter une instruction tant que <expression> est vraie. Le test est fait avant chaque exécution de l'instruction. Donc si la condition (<expression>) est fausse dès le départ, la boucle ne sera pas exécutée.
III-F-6. L'instruction for▲
Permet d'effectuer une boucle. La syntaxe de l'instruction est la suivante :
2.
for
(
<
init>
; <
condition>
; <
step>
)
<
instruction>
Elle est pratiquement identique à :
2.
3.
4.
5.
6.
<
init>
;
while
(
<
condition>
)
{
<
instruction>
<
step>
}
Par exemple, écrivons un programme qui affiche la table de multiplication par 5.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
#include <stdio.h>
int
main
(
)
{
int
n;
for
(
n =
0
; n <=
10
; n++
)
printf
(
"
5 x - -
\n
"
, n, 5
*
n);
return
0
;
}
III-F-7. Les instructions switch et case▲
Ces instructions permettent d'éviter des instructions if trop imbriquées comme illustré par l'exemple suivant :
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.
#include <stdio.h>
int
main
(
)
{
int
n;
printf
(
"
Entrez un nombre entier :
"
);
scanf
(
"
%d
"
, &
n);
switch
(
n)
{
case
0
:
printf
(
"
Cas de 0.
\n
"
);
break
;
case
1
:
printf
(
"
Cas de 1.
\n
"
);
break
;
case
2
: case
3
:
printf
(
"
Cas de 2 ou 3.
\n
"
);
break
;
case
4
:
printf
(
"
Cas de 4.
\n
"
);
break
;
default
:
printf
(
"
Cas inconnu.
\n
"
);
}
return
0
;
}
III-F-8. L'instruction break▲
Permet de sortir immédiatement d'une boucle ou d'un switch. La syntaxe de cette instruction est :
break
;
III-F-9. L'instruction continue▲
Dans une boucle, permet de passer immédiatement à l'itération suivante. Par exemple, modifions le programme table de multiplication de telle sorte qu'on affiche rien pour n = 4 ou n = 6.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
#include <stdio.h>
int
main
(
)
{
int
n;
for
(
n =
0
; n <=
10
; n++
)
{
if
((
n ==
4
) ||
(
n ==
6
))
continue
;
printf
(
"
5 x %2d %2d
\n
"
, n, 5
*
n);
}
return
0
;
}
III-F-10. L'instruction return▲
Permet de terminer une fonction. La syntaxe de cette instruction est la suivante :
return
<
expression>
; /* termine la fonction et retourne <expression> */
ou :
return
; /* termine la fonction sans spécifier de valeur de retour */
III-F-11. L'instruction vide▲
Une instruction peut être vide. Par exemple :
2.
3.
/* Voici une instruction vide * / ;
/* Voici une autre instruction vide */ ;
{ /* Encore une instruction vide */ }
III-G. Exercices▲
III-G-1. Valeur absolue▲
Ecrire un programme qui demande à l'utilisateur d'entrer un nombre et qui affiche ensuite la "valeur absolue" de ce nombre. Il y a plusieurs façons de définir la valeur absolue d'un nombre x mais nous utiliserons la suivante : c'est x si x est supérieur ou égal à 0 et -x si x est inférieur à 0. La valeur absolue d'un nombre est donc un nombre toujours positif ou nul. Voici un exemple d'exécution :
Ce programme permet de determiner la valeur absolue d'un nombre.
Entrez un nombre : -6
La valeur absolue de -6 est 6.
Merci d'avoir utilise ce programme. A bientot !
III-G-2. Moyenne▲
Ecrire un programme qui demande à l'utilisateur d'entrer une note. La note doit être comprise entre 0 et 20 sinon le programme affichera "Cette note n'est pas valide" puis se terminera. Après que l'utilisateur ait entré une note valide, si la note est supérieure ou égale à 10, le programme affichera "Vous avez eu la moyenne." sinon, il affichera "Vous n'avez pas eu la moyenne.". Voici quelques exemples d'exécution :
Ce programme permet de determiner si vous avez eu la moyenne ou non.
Entrez votre note (0 a 20) : 12
Vous avez eu la moyenne.
Merci d'avoir utilise ce programme. A bientot !
Ce programme permet de determiner si vous avez eu la moyenne ou non.
Entrez votre note (0 a 20) : 22
Cette note n'est pas valide.
Merci d'avoir utilise ce programme. A bientot !
III-G-3. L'heure dans une minute▲
Ecrire un programme qui demande à l'utilisateur d'entrer l'heure et la minute actuelles et qui affiche ensuite l'heure et la minute qu'il fera une minute après. Voici quelques exemples d'exécution :
Ce programme permet de determiner l'heure qu'il sera une minute plus tard.
Entrez l'heure actuelle : 14
Entrez la minute actuelle : 38
Dans une minute il sera : 14:39.
Merci d'avoir utilise ce programme. A bientot !
Ce programme permet de determiner l'heure qu'il sera une minute plus tard.
Entrez l'heure actuelle : 23
Entrez la minute actuelle : 59
Dans une minute il sera : 00:00.
Merci d'avoir utilise ce programme. A bientot !
Pour simplifier l'exercice, on supposera que les valeurs entrées par l'utilisateur seront toujours correctes.
III-G-4. Rectangle▲
a. Ecrire une fonction affiche_car qui reçoit en arguments un caractère c et un entier n - void affiche_car(char c, int n) - et qui imprime ce caractère n fois. Utilisez cette fonction pour écrire un programme qui demande à l'utilisateur d'entrer un nombre n puis qui affiche une ligne de longueur n en utilisant le caractère '*', c'est-à-dire une ligne composée de n '*'. n doit être compris entre 0 et 20. Voici un exemple d'exécution :
Ce programme dessine une ligne.
Entrez la longueur de la ligne (0 a 20) : 10
**********
Merci d'avoir utilise ce programme. A bientot !
b. En utilisant la fonction affiche_car, écrire un programme qui demande à l'utilisateur d'entrer deux nombres L et h puis qui dessine un rectangle de longueur L et
de hauteur h à l'aide du caractère '*'. L et h doivent être compris entre 0 et 20. Voici un exemple d'exécution :
Ce programme dessine un rectangle plein.
Entrez la longueur du rectangle (0 a 20) : 10
Entrez la hauteur du rectangle (0 a 20) : 4
**********
**********
**********
**********
Merci d'avoir utilise ce programme. A bientot !
c. Reprendre l'exercice b en dessinant cette fois ci un rectanle creux. Voici un exemple d'exécution :
Ce programme dessine un rectangle creux.
Entrez la longueur du rectangle (0 a 20) : 10
Entrez la hauteur du rectangle (0 a 20) : 4
**********
* *
* *
**********
Merci d'avoir utilise ce programme. A bientot !
III-G-5. Triangle isocèle▲
a. Ecrire un programme qui demande à l'utilisateur d'entrer un nombre h puis qui dessine un triangle isocèle de hauteur h et de surface minimale à l'aide du caractère '*'. h doit être compris entre 0 et 20. b. Afficher après le dessin le nombre d'étoiles affichées. Voici un exemple d'exécution :
Ce programme dessine un triangle.
Entrez la hauteur du triangle (0 a 20) : 4
*
***
*****
*******
Il y a 16 etoile(s) affichee(s).
Merci d'avoir utilise ce programme. A bientot !
III-G-6. Somme▲
Ecrire un programme qui demande à l'utilisateur d'entrer au plus 10 nombres puis qui affiche la somme des nombres entrés. L'utilisateur peut à tout moment terminer sa liste en entrant 0. Voici quelques exemples d'utilisation :
Ce programme permet de calculer une somme.
Entrez la liste des nombres a additionner (terminez en entrant 0).
1
3
5
7
0
Le total est : 16.
Merci d'avoir utilise ce programme. A bientot !
Ce programme permet de calculer une somme.
Entrez la liste des nombres a additionner (terminez en entrant 0).
1
2
3
4
5
6
7
8
9
10
Le total est : 55.
Merci d'avoir utilise ce programme. A bientot !