Cours ebook : langage C un premier langage de Jacques le Maitre
1.1 C, son origine et son importance
Le langage C a été créé au début des années 70 par des chercheurs du laboratoire de la compagnie Bell aux USA. Il a été conçu, à l'origine, pour être le langage de programmation du système d'exploitation UNIR.
C est un langage très largement diffusé, en tant que tel, mais aussi comme noyau des langages C++, un langage de programmation objet et Java, le langage de programmation du Web .
1.2 Exemple de programme C
Le programme suivant est écrit en C. Il demande à son utilisateur d'entrer deux nombres x et y, puis calcule le plus grand, max, de ces deux nombres et l'affiche.
(1) #include <stdio.h>
(2) /*
(3) * Maximum de deux nombres entiers
(4) */
(5) main()
(6) {
(7) int x, y, max;
(8) printf("x ? ");
(9) scanf("%d", &x);
(10) printf("y ? ");
(11) scanf("%d", &y);
(12) if (x >= y)
(13) max = x;
(14) else
(15) max = y;
(16) printf("max = %d", max);
(17) }
Ce programme est construit de la façon suivante ;
1.3 Réalisation d'un programme C
La réalisation d'un programme C se déroule en trois étapes :
1.4 Livres sur C
Nous conseillons tout particulièrement les deux livres suivants :
Le premier est celui des inventeurs de C. Le second, que nous avons largement utilisé pour rédiger ce cours, est le fruit de nombreuses années d'enseignement de la programmation à l'Université de la Méditerranée.
2 Données
Un programme C manipule des données. Une donnée a un type et une valeur, elle peut avoir un nom et peut être constante ou modifiable. La valeur d'une donnée modifiable peut changer au cours de l'exécution d'un programme alors que celle d'une donnée constante ne le peut pas.
On peut voir une donnée modifiable comme une case de la mémoire accessible à partir du nom de la donnée et qui contient la valeur de cette donnée.
Supposons que l'on veuille écrire un programme qui calcule le volume d'une sphère dont le rayon est donné par l'utilisateur du programme. Le nombre n sera représenté comme une donnée constante. Le rayon et le volume d'une sphère seront représentées comme des données modifiables. On considérera donc qu'il y a dans la mémoire du programme une case nommée rayon dans laquelle sera enregistrée le rayon de la sphère dont le volume doit être calculé et une case nommée volume dans laquelle le volume de la sphère sera enregistré une fois qu'il aura été calculé.
Une valeur peut être :
Les valeurs sont classées par types. Toute valeur a un et un seul type.
Il n'existe pas de type spécifique pour les booléens, les caractères et les chaines de caractères. Le booléen « faux » est représenté par le nombre 0 et le booléen « vrai » par toute valeur non nulle. Un caractère est codé par un nombre entier (en ASCII, en général). Une chaine de caractères est représentée comme un tableau de caractères (c.-à-d. comme un tableau de nombres entiers).
Dans ce chapitre, nous n'étudierons que les types numériques. Les structures et les tableaux seront étudiés au chapitre 5 et les pointeurs au chapitre 9.
2.1 Types numériques
Le tableau suivant présente les types numériques du langage C.
...
3 Expressions
3.1 Qu'est-ce qu'une expression ?
Une expression est une phrase formée à l'aide de constantes littérales, de noms de variables et d'opérateurs. Par exemple, dans un environnement où la variable x est visible, les phrases suivantes :
12x(x + 12) - 3
(x > 4) && (x > 10)
sont des expressions. Les deux premières sont des expressions simples et les deux dernières sont des expressions composées.
En C, toute expression a obligatoirement:
On distingue deux catégories d'expression:
Si e est une expression nous noterons :
Une expression peut être mise entre parenthèses. Si exp est une expression, alors (exp) est une expression de même type et de même valeur que exp.
3.2 Expressions atomiques
- type(c) = type de la valeur représentée par c, - val(c) = valeur représentée par c.
- type(v) = type déclaré pour v,
- val(v) = valeur de v dans l'environnement courant.
Un nom de variable est une valeur gauche, puisqu'il est le nom d'une donnée modifiable.
3.3 Expressions composées
Les expressions composées, le sont à l'aide d'opérateurs. Dans ce chapitre nous étudierons :
Les opérateurs de manipulation des valeurs composées (structures et tableaux) seront étudiés au chapitre 5 et ceux de manipulation des pointeurs seront étudiés au chapitre 9.
3.3.1 Moins unaire
L'opérateur — calcule l'opposé d'un nombre.
Si exp est une expression de type numérique, alors : -exp est une expression telle que :
3.3.2 Opérateurs arithmétiques binaires
Les opérateurs + - * / calculent respectivement la somme, la différence, le produit et le quotient de deux nombres. L'opérateur % calcule le reste de la division de deux nombres entiers.
Si expl et exp2 sont des expressions de type numérique, alors
expl + exp2 expl - exp2 expl * exp2 expl / exp2 expl % exp2
sont des expressions telles que :
Par exemple :
val(((4.0 + 2.25) * 2.0)) = 12.5
val(15 / 2) = 7
val(15 % 2) = 1
3.3.3 Comparateurs
Les opérateurs == != < <= > >= testent respectivement l'égalité, la différence, l'infériorité, l'infériorité ou l'égalité, la supériorité et la supériorité ou l'égalité de deux nombres.
Si expl et exp2 sont des expressions de type numérique, alors :
expl == exp2 expl != exp2 expl < exp2 expl <= exp2 expl > exp2 expl >= exp2
sont des expressions telles que :
Par exemple: val(1 == 9) = 0
3.3.4 Négation
L'opérateur ! calcule la négation d'un booléen. Si exp est une expression, alors : !exp est une expression telle que :
3.3.5 Conjonction et disjonction.
Les opérateurs || et && calculent respectivement la conjonction et la disjonction de deux booléens.
Si expl et exp2 sont des expressions, alors :
expl || exp2 expl && exp2
sont des expressions telles que :
si val(expl) 7É 0 alors
l
sinon
si val(exp2) 7É 0 alors
l
sinon
0
fin-si
fin-si
si val(expl) = 0 alors
0
sinon
si val(exp2) 7É 0 alors
l
sinon
0
fin-si
fin-si
Par exemple :
val(!((1 || 0) && (0 || 1))) = 0
3.3.6 Affectation
L'opérateur = affecte une valeur à une donnée modifiable. Plus exactement, il enregistre cette valeur dans la case mémoire attachée à cette donnée.
Si expl est une expression qui est le nom d'une donnée modifiable (une valeur gauche) et exp2 est une expression expressions, alors :
expl = exp2
est une expression telle que :
Par exemple, si x et y sont des variables telles que type(x) = type(y) = int et val(y) = 5, l'expression :
(x = y) > 2
a la valeur 5 et a pour effet de bord d'affecter la valeur 5 à x.
Attention ! il ne faut pas confondre l'opérateur d'affectation = avec l'opérateur d'égalité ==.
Signalons enfin deux abréviations classiques. Si ident est un nom de variable numérique, alors :
ident++ = ident = ident + 1 ident-- = ident = ident - 1
3.4 Conversion de type
On distingue :
3.4.1 Conversion explicite : opérateur de "cast"
Si T est un nom de type et exp est une expression dont la valeur est convertible en T, alors : (T) exp
est une expression telle que :
3.4.1.1 Conversion entier - entier
Les types entiers sont ordonnés de la façon suivante :
char < unsigned char < short < unsigned short < long < unsigned long
Soit deux types entiers Ti et T2 et une valeur v de type Ti. La conversion de v en une valeur de type T2 est réalisée :
3.4.1.2 Conversion entier - flottant et flottant - entier
3.4.2 Conversion implicite
3.4.2.1 Opérations arithmétiques et opérations de comparaison
Si op est un opérateur arithmétique, l'expression: expl op exp2
est évaluée de la façon suivante :
int < unsigned int < long < unsigned long < float < double
Si l'un des opérandes est de type Tl et l'autre de type T2 et que Tl < T2 alors l'opérande de type Tl est converti en T2, puis l'opérateur est appliqué fournissant une valeur de type T2.
3.4.2.2 Affectation
L'expression : expl = exp2
où expl est une expression de type Tl et exp2 est une expression de type T2, est évaluée de la façon suivante :
Par exemple, l'évaluation de l'expression: y = 5.0 + 3 * x
dans l'état :
(float) x
(int) y
produit la valeur 12 de type int et le nouvel état :
(float) x
(int) y
3.5 Priorité et associativité des opérateurs
La priorité des opérateurs est utilisée lors de l'évaluation d'expressions dans lesquelles l'ordre des opérations n'a pas été explicitement indiquée par des paires de parenthèses.
Le tableau suivant indique la priorité et l'associativité des opérateurs qui ont été ou seront étudiés dans ce cours.
...
Si opl et ope sont des opérateurs infixes, on a :
Si op, est un opérateur préfixe et ope est un opérateur infixe, on a :
≡
≡
≡ (!a) && b ||
((!a) && b)
(((!a) && b) x >
|| x
|| 3, car la priorité de ! est supérieure à celle de &&,
> 3, car la priorité de && est supérieure à celle de ||, (x > 3)), car la priorité de > est supérieure à celle de ||.
3.6 Ordre d'évaluation des opérandes
L'ordre d'évaluation des opérandes n'est pas spécifié par la norme ANSI excepté pour les opérateurs || et && (voir ci-dessus §3.3.5). Il faut en tenir compte lorsque ces opérandes contiennent des opérateurs à effet de bord, car l'ordre dans lequel se produiront ces effets est indéterminé.
4 Instructions
4.1 Qu'est-ce qu'une instruction
Une instruction est un ordre donné à l'ordinateur de réaliser une suite d'actions dont chacune a pour effet de modifier le déroulement du programme, son environnement ou son état.
Les instructions offertes par C sont les suivantes :
4.2 Présentation des instructions
Chaque instruction sera définie par sa syntaxe et par l'action que déclenche son exécution.
4.2.1 Instruction vide
L'instruction :
;
est une instruction vide qui ne réalise aucune action.
4.2.2 Instruction "expression"
Si exp est une expression, alors exp;
est une instruction qui réalise l'effet de bord de l'expression exp. Une telle instruction n'a donc de sens que si cet effet de bord n'est pas nul.
Par exemple :
12 + 4;
est une instruction qui ne déclenche aucune action. Elle est équivalente à une instruction vide. Par contre, l'instruction :
x = 12 + 4;
en déclenche une: affecter 16 à x.
4.2.3 Bloc d'instructions
Si decl1, ..., declm sont des déclarations de variables et inst1, ..., instn sont des instructions, alors :
{ decl1
decln
inst1 ...
instn
}
est une instruction appelé bloc d'instructions ou plus simplement bloc.
Un bloc est composé d'une suite, éventuellement vide, de déclarations, suivie d'une suite d'instructions qui peuvent elles-mêmes être des blocs d'instructions.
Les variables déclarées dans un bloc sont des variables locales à ce bloc. Elles sont visibles dans ce bloc et dans tous les blocs englobés qui ne les redéfinissent pas. La valeur initiale d'une variable locale non initialisée dans sa déclaration, est indéterminée.
L'exécution d'un bloc d'instructions se déroule de la façon suivante :
- soit parce que la fin du bloc a été atteinte,
- soit par l'exécution de l'instruction break ou return (voir ci-dessous §4.2.7 et §4.2.8).
Considérons par exemple le bloc suivant:
{
int x = 5, d = 1;
float y;
{
float d = 0.5;
y = x + d;
}
...
}
Les déclarations de x et y dans le bloc externe sont visibles dans le bloc interne. La déclaration de d dans le bloc interne masque celle du bloc externe : d est un entier dans le bloc externe et un flottant dans le bloc interne. Notons que la valeur initiale de y est indéterminée. L'évolution de l'état du programme lors de l'exécution du bloc interne est le suivant :
...
4.2.4 Instruction conditionnelle
Une instruction conditionnelle permet de choisir l'instruction à exécuter parmi deux possibles, en fonction de la valeur d'une expression booléenne de choix.
Si exp est une expression et inst, inst~ et inst2 sont des instructions alors
if (exp) inst
if (exp)
inst~ else
inst2
sont des instructions conditionnelles. La première réalise l'action :
si val(exp) 7É 0 alors
exécuter l'instruction inst fin-si
et la seconde l'action :
si val(exp) 7É 0 alors
exécuter l'instruction inst~ sinon
exécuter l'instruction inst2 fin-si
Par exemple :
if (mois >= 4 && mois < 10)
saison = "chaude" else
saison = "froide"
Lorsque des instructions conditionnelles sont imbriquées, la règle suivante s'applique : chaque clause else se rapporte au dernier if ayant une condition suivie d'exactement une instruction. En application de cette règle, dans l'instruction :
Jacques Le Maitre, Université de Toulon et du Var
if (condl) if (cond2)
inst2l else
inst22
la clause else est celle du if interne. Cette règle a pour conséquence qu'un if sans clause else imbriqué dans la clause then d'un if avec clause else, devra être inséré dans un bloc, pour éviter toute ambiguité. Dans l'instruction :
if (condl) {
if (cond2)
inst2l
}
else
inst12
la clause else se rapporte bien au if externe, en application de la règle d'imbrication des instructions conditionnelles. On aurait pu aussi ajouter au if interne une clause else vide :
if (condl) if (cond2)
inst2l else
;
else
inst12
4.2.5 Itération
Une instruction d'itération, permet de répéter l'exécution d'une instruction tant qu'une condition est vérifiée. Par exemple, ajouter 2 à la valeur d'une variable tant que cette valeur est inférieure à 100.
Trois instructions d'itération sont disponibles : while, do...while et for.
4.2.5.1 while
Si exp est une expression et inst est une instruction alors :
while (exp) inst
est une instruction d'itération (on dit aussi une boucle) dans laquelle exp est le test de continuation et inst, le corps de la boucle, est une instruction à répéter.
L'action réalisée est la suivante :
boucle :
si val(exp) 7É 0 alors
exécuter inst
aller-à boucle fin-si
Le corps de la boucle est exécuté tant que le test de continuation est vrai. Il peut donc ne jamais être exécuté.
Une instruction while doit être précédée d'une initialisation des variables dont dépend le test de continuation et dont les valeurs évolueront à chaque exécution du corps de la boucle.
Par exemple, l'instruction suivante calcule la somme s des entiers de 1 à 9.
{
int s, i;
s = 0;
i = 1;
while (i <= 9)
{
s = s + i;
i = i + 1;
}
}
4.2.5.2 do... while
Si inst est une instruction et exp est une expression alors :
do
inst
while (exp);
est une instruction d'itération. Comme dans l'instruction while, inst est le corps de la boucle et exp est le test de continuation.
L'action réalisée est la suivante :
boucle :
exécuter inst
si val(exp) 7É 0 alors
aller-à boucle
fin-si
Le corps de la boucle est exécuté jusqu'à ce que le test de continuation soit faux. Il est donc exécuté au moins une fois.
De même qu'une instruction while, une instruction do...while doit être précédée d'une initialisation des variables dont dépend le test de continuation et dont les valeurs évolueront à chaque exécution du corps de la boucle.
Par exemple, l'instruction suivante calcule le plus petit entier n tel que la somme des entiers de 1 à n soit supérieure à 50.
{
int s, n;
s = 0;
n = 0;
do
{
n = n + 1;
s = s + n;
}
while(s <= 50);
}
4.2.5.3 for
Si exp1, exp2, exp3 sont des expressions et inst est une instruction, alors
for (expl; exp2; exp3) inst
est une instruction d'itération équivalente, par définition, à : expi;
while (exp2) {
inst
exp3; }
Cette instruction intègre donc l'initialisation (exp,) et l'évolution (exp2) de la variable dont dépend le test de continuation (exp3).
Par exemple, l'instruction suivante calcule la somme s des 10 premiers entiers.
{
int s, n;
s = 0;
for (n = 1; n <= 10; n = n + 1)
s = s + n;
}
Les expressions exp, et exp3 peuvent être absentes. Le test de continuation (exp2) peut lui-aussi être absent, on considère alors qu'il est toujours vrai. Par exemple, l'instruction :
for (;;) inst
est une boucle infinie.
4.2.6 Choix multiple
Une instruction de choix multiple permet de choisir une instruction à réaliser parmi un ensemble d'instructions possibles, en fonction de la valeur d'une expression de choix.
Une instruction de choix multiple a la forme suivante :
switch (exp-choix) {
inst-switch, ...
inst-switchn }
où :
case exp-cas: inst-switch default: inst-switch inst
où case exp-cas: et default: sont des étiquettes de cas, exp-cas est une expression constante et inst est une instruction.
L'action réalisée par une instruction switch est la suivante :
Jacques Le Maitre, Université de Toulon et du Var
si dans le corps il existe une étiquette case exp-cas: telle que val(exp-cas) = val(exp choix) alors se brancher à l'instruction qui suit immédiatement cette étiquette.
sinon
si dans le corps il existe une étiquette default: alors
se brancher à l'instruction qui suit immédiatement cette étiquette.
sinon
ne rien faire.
fin-si
fin-si
Une fois qu'un branchement a été effectué, les instructions sont exécutées en séquence jusqu'à la dernière instruction du corps. Pour éviter d'enchaîner l'exécution de deux cas exclusifs consécutifs, il faut terminer le premier par une instruction break (voir ci-dessous §4.2.7) qui a pour effet d'abandonner l'exécution de l'instruction.
Par exemple, l'instruction suivante calcule le nom du jour (jour) correspondant à un numéro de jour (num_jour) dans la semaine:
switch(num_jour)
{
case 1:
jour = "lundi";
break;
case 2:
jour = "mardi";
break;
...
case 7:
jour = "dimanche";
break;
default:
printf("Il n’y a que 7 jours dans la semaine !");
}
Il est possible de définir des cas à plusieurs étiquettes. Par exemple, l'instruction suivante détermine si un jour de la semaine est un jour d'activité ou de repos :
switch(num_jour)
{
case 6:
case 7:
activite = "repos";
break;
default:
activite = "travail";
}
4.2.7 Rupture de séquence
L'instruction : break;
provoque l'abandon de l'instruction en cours. Elle ne peut apparaître que dans le corps d'une instruction while, do, for ou switch.
4.2.8 Retour d'une fonction
Si exp est une instruction alors
Jacques Le Maitre, Université de Toulon et du Var
return exp;
est une instruction qui provoque, comme nous le verrons au chapitre 6, l'abandon de l'exécution du bloc qui constitue le corps d'une fonction et retourne la valeur val(exp) de l'application de cette fonction.