Académique Documents
Professionnel Documents
Culture Documents
Eric Berthomier
Daniel Schang
2
Table des matières
1 Avant de commencer... 1
2 Premiers pas 3
2.1 Prologue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.7 Compléments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.12 Astuce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.14 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2 Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
i
ii TABLE DES MATIÈRES
3.12 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.8 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
TABLE DES MATIÈRES iii
5 Les conditions 29
5.6 Combinaison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.7 Astuce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.9 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.10.3 Tester si un caractère saisi au clavier est une consonne ou une voyelle . . . . . . . 36
5.12 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6 Mise au point 39
6.1 Prologue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.2 Exercice 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.5 Exercice 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.6 Exercice 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.1 While . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.10 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8 Les boucles 57
8.2 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
8.8 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
9 Pointeurs et Fonctions 65
9.1.1 Binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
9.1.2 Octet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
TABLE DES MATIÈRES v
9.2.2 Pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
9.3.2 void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.3.5 Exercice 3 : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
9.3.6 Piège ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
9.3.7 Exercice 4 : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
9.3.10 Exercice 5 : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
9.6 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.1 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
10.1.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
10.1.2 Déclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
10.1.3 Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.2.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10.7 A retenir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
11 Structures et Fichiers 99
11.2 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
11.2.1 Déclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
13 Complements 119
13.6 Un exemple pour se rafraîchir la mémoire sur l’utilité des pointeurs . . . . . . . . . . . . 125
17.11switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Avant de commencer...
Ce document va vous permettre de découvrir le Langage C. Allez à votre rythme, le but n’est pas de tout
faire le plus vite possible mais au contraire de bien assimiler les choses.
Pour ce faire, vous aurez à votre disposition environ 20 heures. En sachant que ce polycopié fait aux
alentours de 180 pages, cela fait environ 9-10 pages par heure (en rythme de croisière...). Dans la pratique,
vous irez sans doute plus vite au départ... Par endroits, on vous demandera de retaper des petits bouts de
programme, ainsi plutôt que de vous les donner ou faire du copier-coller, cela vous permettra de ne pas
aller trop vite et de mieux assimiler ces programmes lorsque vous les saisirez.
Pour mémoire, quand on parlera de la touche entrée, c’est la même chose que la touche return (voir
figure).
Touche entrée/return :
entrée
barre espace
1
2 CHAPITRE 1. AVANT DE COMMENCER...
Chapitre 2
Premiers pas
2.1 Prologue
Ce cours est destiné à vous faire connaître le C sous Linux. Linux, ou GNU/Linux, est un système d’ex-
ploitation (façon Windows). Linux est un logiciel libre créé en 1991 par Linus Torvalds sur un ordinateur
compatible PC.
Voici une manipulation pour lancer un éditeur : un éditeur est un programme (façon Word) qui nous servira
à taper nos programmes :
- sous Ubuntu, vous faites Applications −→ Accessoires −→ Terminal. Dans un autre environnement
recherchez, dans les menus, quelque chose qui pourrait s’appeler terminal ou console.
Dans cette nouvelle fenêtre qui ressemble à la figure ci-dessous tapez gedit. Vous verrez par la suite que
taper gedit & serait encore mieux, car cela vous permettra de gagner du temps en évitant d’ouvrir une
seconde fenêtre du type "Terminal".
3
4 CHAPITRE 2. PREMIERS PAS
Voici pour exemple un premier programme qui fonctionne malgré le fait qu’il ne soit pas normalisé 1 .
Celui-ci affiche le mot Bonjour à l’écran. À l’aide de votre éditeur de texte (dans la fenêtre gedit donc),
tapez le texte qui se trouve à l’intérieur du rectangle suivant :
main () {
puts ("Bonjour");
getchar ();
}
Une fois le texte du programme tapé, il faut le compiler, c’est à dire en analyser la syntaxe (on verra que la
compilation fait d’autres choses aussi...). Nous allons donc ouvrir une seconde fenêtre dans laquelle nous
allons compiler notre programme : comme tout à l’heure lancez une fenêtre du type terminal.
Si vous n’avez pas fait d’erreur, rien ne va s’afficher (pas de nouvelles bonnes nouvelles...) En fait, cette
commande vient de créer un fichier nommé programme1. Vous pouvez le vérifier en tapant ls -l
(attention, tapez bien ℓs − ℓ) qui devrait vous renvoyer quelque chose du type :
Si on y regarde de plus près on peut voir le fichier intitulé programme1.c qui a été sauvegardé à 11h10
1. On verra par la suite qu’un programme écrit en Langage C doit respecter certaines normes...
2.4. NORMALISATION DU PROGRAMME 5
et qui contient 44 octets. En dessous se trouve le fichier programme1 qui vient d’être créé et qui contient
6525 octets. Ce dernier fichier contient le code machine (code qui est compréhensible par la machine).
Le fichier programme1 est nommé le fichier exécutable. Le fichier programme1.c se nomme le fichier
source (source de tous les bonheurs de Linux ...). En fait, un fichier source désigne un fichier qu’un être
humain peut comprendre par opposition à un exécutable que seule la machine arrive à comprendre. Il ne
reste plus qu’à exécuter le programme :
./programme1 .
Et comme par magie, s’affichera alors Bonjour et attendra que vous appuyez sur la touche Entrée.
Remarque : nous reviendrons par la suite sur le fait qu’il faille taper ./programme1 et pas programme1...
Remarque : vous avez pu constater que la fin des lignes se terminait par un ; sauf pour la ligne du main...
nous reviendrons là dessus... disons pour l’instant que c’est un peu comme en français, chaque ligne se
termine par un "." alors qu’en Langage C, chaque ligne se termine généralement par un ;
Bon, jusqu’à présent, nous avons fait un peu "dans le sale". Pourquoi me direz–vous ? Eh bien rendons
notre compilateur bavard en lançant la commande gcc −o programme1 programme1.c −Wall
Observez les insultes.
En fait, l’option de compilation −Wall permet de « déclencher la production de messages soulignant toute
technique autorisée mais discutable », en deux mots à éviter.
À sa base, le langage C n’est qu’un ensemble de bibliothèques à partir desquelles le compilateur trouve les
fonctions et les applications qui lui permettent de créer un programme exécutable. Exactement ce que vous
faîtes si vous recherchez dans une encyclopédie pour réaliser un exposé.
Certaines bibliothèques (les plus courantes) sont incluses dans des compilateurs ce qui permet à notre
programme de se compiler. Normalement puts a besoin de la bibliothèque stdio.h. Pour ajouter une
bibliothèque, il suffit d’ajouter #include <nom de la bibliothèque> en début de programme.
Un autre point à corriger est l’ajout de la ligne return 0. En fait, le programme renvoie une valeur de retour
(0), tout à la fin. Cette valeur de retour permet à un programme ou à l’utilisateur de savoir si le programme
que l’on exécute s’est correctement terminé. En général 0 signifie une terminaison sans erreur. Enfin, il
faut transformer la ligne main () en int main(). Ce point sera détaillé par la suite lorsque nous parlerons de
fonctions...
6 CHAPITRE 2. PREMIERS PAS
#include <stdio.h>
int main () {
puts ("Bonjour");
getchar (); /* Permet d’attendre la frappe d’une touche */
return 0;
}
À l’instar de l’étudiant qui recherche dans des livres, on peut dire que le « .h » représente l’index du livre
et le « .c » le contenu du chapitre concerné.
Exemple : Lorsque le compilateur C rencontre le mot puts, il regarde dans chacun des « .h » déclaré
par l’instruction #include si ce mot y est défini. Il trouve celui-ci dans la bibliothèque stdio.h. À
l’inverse, s’il ne le trouve pas, celui-ci émettra une erreur de syntaxe.
2.7 Compléments
#include <stdio.h>
int main () {
puts ("Bonjour");
getchar (); /* Permet d’attendre la frappe d’une touche */
return 0;
}
Notre programme affiche bonjour et attend que l’on appuie sur la touche entrée ou sur une autre touche
puis la touche entrée.
2. Un retour chariot désigne la même chose que la touche entrée ou return. C’est une touche qui sert en quelque sorte à en-
trer/valider des données.
8 CHAPITRE 2. PREMIERS PAS
int main () {
/* Déclaration des variables */ // cf. chapitres suivants...
/* Corps du programme */
getchar(); /* Facultatif mais permet d’attendre l’appui d’une touche */
return 0; /* Aucune erreur renvoyée */
}
La partie de programme située entre deux accolades est appelée un bloc. On conseille de prendre l’habitude
de faire une tabulation 3 après l’accolade. Puis retirer cette tabulation après l’accolade fermante du bloc.
Ainsi, on obtient :
int main () {
Tabulation
Tout le code est frappé à cette hauteur
}
Retrait de la tabulation
Tout le texte est maintenant frappé à cette hauteur.
Commenter signifie qu’une personne ne connaissant pas votre programme doit pouvoir lire le programme
et le comprendre. Les commentaires sont indispensables dans tout bon programme. Les commentaires
peuvent être placés à n’importe quel endroit dans le programme. Ils commencent par /* et se terminent
par */.
/* Commentaire */
Vous trouverez aussi :
Une fois que vous aurez proposé/tapé/testé votre solution, vous pouvez comparer avec la solution qui
apparaît au paragraphe suivant...
2.12 Astuce
Sous Linux, afin d’éviter de retaper à chaque fois la commande suivante : gcc -o programme programme.c,
ce qui peut être laborieux à force, il suffit d’appuyer une ou deux fois sur la flèche vers le haut, ce qui fera
réapparaître (comme par magie), la commande gcc -o programme programme.c...
10 CHAPITRE 2. PREMIERS PAS
#include <stdio.h>
int main () {
puts ("Salut toi, appuie sur une touche s’il te plaît");
/* Affiche le message Salut toi, ... s’il te plaît */
return 0;
}
2.14 A retenir
– l’éditeur que l’on utilise s’appelle gedit
– connaître les fonctions puts et getchar qui apparaissent dans le programme suivant :
#include <stdio.h>
int main () {
puts ("Bonjour");
getchar (); /* Permet d’attendre la frappe d’une touche */
return 0;
}
Remarque : lorsque vous quitterez votre session (à la fin du TP), il ne faut pas arrêter brutalement la
machine en appuyant sur le bouton on/off, au contraire, il faut chercher dans les différents menus, quelque
chose qui ressemble à : clore la session oubien quitter oubien logout...
Chapitre 3
#include <stdio.h>
int main () {
printf ("Coucou c’est moi\n");
/* Affiche Coucou c’est moi à l’écran puis saute une ligne */
getchar();
/* Attendre l’appui d’une touche */
return 0;
}
On pourrait dire que la fonction printf est la même que l’instruction puts vue précédemment mais il
n’en est rien... Celle-ci est beaucoup, beaucoup, beaucoup plus puissante.
Syntaxe : La syntaxe de printf est très complexe et pourrait à elle seule faire l’objet d’un cours, nous
en verrons donc des applications au fur et à mesure des besoins.
3.2 Variable
Comme son nom l’indique une variable est quelque chose qui varie. C’est vrai mais ce n’est pas suffisant.
Une variable peut être considérée comme une boîte dans laquelle on met des données que l’on peut lire ou
écrire.
La manière la plus facile de lire le contenu d’une variable est la fonction printf que l’on a aperçue
précédemment. La manière la plus simple de donner une valeur à une variable est l’opérateur mathématique
=.
11
12 CHAPITRE 3. LES VARIABLES (1ERE PARTIE)
Écrire dans une variable ayant déjà une valeur revient à la modifier.
Une variable ne peut contenir qu’une seule chose à la fois. Si vous mettez une seconde donnée dans la
variable, la précédente est effacée.
Remarque importante : comme le montre le bout de programme suivant qui déclare deux variables :
#include <stdio.h>
int main () {
int i; /* déclare un entier de nom i */
char c; /* déclare un caractère de nom c */
}
Il ne faut pas mettre les < et > comme cela apparaissait dans
<son type> <son nom>; !!!
on met ceci pour rendre les programmes plus lisibles... Vous ne mettrez jamais ces < et > exceptés
pour des cas du type #include <stdio.h>.
Inutile de retaper cet exemple, lisez le programme puis les explications ci-dessous :
i=22; /* i vaut 22 */
j=i; /* on recopie la valeur de i dans j */
/* donc j vaut aussi 22 à présent */
Explications :
3.5. UTILISATION MULTIPLE DU % 13
1. printf ("i vaut %d\n", i); : %d signifie que l’on attend une valeur entière, le programme
va donc remplacer le %d par la valeur de i. Ce début de programme provoquera donc l’affichage
suivant : i vaut 22
2. printf ("i+j vaut %d\n", i+j); : ici, on voit que des calculs sont possibles. On aura
donc l’affichage suivant : i+j vaut 44
Voici un second exemple avec des variables du type caractère (lisez le programme puis les explications
ci-dessous) :
#include <stdio.h>
int main () {
char car; /* car: variable de type caractère */
car = ’E’;
char e;
e=’e’;
printf("car=%c e=%c",car,e);
getchar();
return 0;
}
Explications :
1. car = ’E’ : on met dans la variable car la valeur du caractère E.
Note : En informatique, tout n’est que nombre, je dis donc la valeur de E et non E car c’est le code
ASCII 1 de E qui est sauvegardé dans cette variable. Nous reviendrons là dessus un peu plus tard.
2. e=’e’ : on met dans la variable e la valeur du caractère e. On voit bien ici que si l’on avait écrit
e=e, cela n’aurait pas fait grand chose ! On aurait juste recopié la valeur de la variable e dans la
variable e. Cela aurait juste consommé un peu de temps...
car=E e=e
Le code « %x » signifie que le compilateur C doit remplacer ce code par la valeur correspondante (qui lui
est fournie dans la suite de l’instruction) en la transformant dans le type x. Cette transformation est appelée
un cast.
Exemple :
int i;
i =65;
printf ("Le caractère %d est %c",i,i);
Une fois que vous avez votre propre solution, vous pouvez comparer avec celle qui se trouve à la fin du
chapitre... Eventuellement, vous pouvez même taper le premier exercice, le compiler puis le tester...
On peut réutiliser une variable autant de fois que l’on veut, la précédente valeur étant effacée :
i = 3;
i = 5;
i = 7; donnera au final pour valeur de i la valeur 7.
car = ’E’;
car = ’G’;
car = ’h’; donnera au final pour valeur de car la valeur de h.
Certains caractères utilisés par la fonction printf (« % » par exemple) ou même tout simplement pour
déclarer une variable (’ pour les caractères par exemple) obligent à utiliser le caractère de suffixe \ pour
pouvoir être affichés.
Exemple : Pour déclarer un caractère avec la valeur ’ (prononcée quote en informatique et non pas
apostrophe (français)), on écrira :
3.9. EXERCICE À RÉALISER 15
char car;
car = ’\’’;
char car;
car = ’’’;
Rappel : Pour pouvoir afficher un caractère réservé à la syntaxe du C comme par exemple ", on utilise
le caractère \ comme préfixe à ce caractère. Pour obtenir un ", on utilise donc \".
16 CHAPITRE 3. LES VARIABLES (1ERE PARTIE)
Faites afficher, en utilisant ce qui a été fait précédemment, les valeurs 70, 82, 185 et 30.
#include <stdio.h>
int main () {
int i,a,b,c;
i=70;
a=82;
b=185;
c=30;
return 0;
}
#include <stdio.h>
int main () {
char a,b,c,d,e,f;
a=’c’;
b=’o’;
c=’u’;
d=’C’;
e=’O’;
f=’U’;
getchar();
return 0;
}
3.11. UN EXERCICE POUR FINIR 17
#include <stdio.h>
int main () {
char car;
car = ’C’;
int i;
i = 1;
car = ’C’;
printf ("\n%c",car);
car = ’\’’;
printf ("\n%c",car);
car = ’e’;
printf ("\n%c",car);
car = ’s’;
printf ("\n%c",car);
car = ’t’;
printf ("%c",car);
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
3.12 A retenir
#include <stdio.h>
int main () {
char caractere;
caractere = ’c’;
int variable;
variable = 1;
return 0;
}
Chapitre 4
Durant tout ce chapitre, nous aurons pour but simple, mais complet dans sa programmation, de faire une
malheureuse calculatrice.
Attention : Nous allons voir qu’afin de pouvoir utiliser la bibliothèque mathématique du C (#include
<math.h>), il est nécessaire d’ajouter au moment de la compilation ceci :-lm (lisez bien ℓm) ; ce qui nous
donne :
Pas à pas, nous allons maintenant réaliser notre petit programme de calculatrice.
Complétez le programme en :
– déclarant 2 variables a et b de type int (entier) ;
– assignant à ces deux variables les valeurs 36 et 54 ;
19
20 CHAPITRE 4. LES VARIABLES (2EME PARTIE)
– faisant afficher le résultat de la somme de a + b (et non pas en faisant afficher 90 !).
Pour faire afficher le résultat il est possible d’utiliser la fonction printf en utilisant une troisième variable.
Pour plus de facilité, nous allons utiliser un affichage direct. Celui-ci s’effectue de la façon suivante :
printf ("Valeur de a + b : %d",a+b);
Le %d sera remplacé par la valeur de a+b.
Si une calculatrice se limitait à exécuter la somme de deux nombres fixes, le boulier serait encore de mise.
Pour saisir une variable 1 , il est nécessaire d’utiliser la fonction scanf. La fonction scanf s’utilise de la
façon suivante :
Saisie de la valeur a : scanf ("%d", &a);
Comme pour printf, on reconnaît le %d pour la saisie d’un nombre entier. Le & devant le a signifie que
l’on va écrire dans la variable a.
Aïe... En fait &a signifie « l’adresse mémoire de a ». La fonction scanf va donc écrire dans l’empla-
cement mémoire (la petite boîte contenant la variable) de a. Si l’on oublie le &, on écrira chez quelqu’un
d’autre, à une autre adresse. Et là ça peut faire mal, très mal... Mais ceci est une autre histoire sur laquelle
nous reviendrons longuement par la suite... Pour l’instant, n’oubliez pas le &.
Nous allons maintenant saisir les variables a et b. Pour exemple, voici le code pour la saisie de a, la saisie
de b reste à faire par vos soins à titre d’exercice.
/* Saisie de la valeur de a */
printf ("Valeur de a :\n");
scanf ("%d",&a);
Il est conseillé d’initialiser les variables avant de les utiliser. Au début du programme, après leur déclara-
tion, assignez à a et à b la valeur 0.
Exemple : a = 0;
Si tout s’est bien passé, vous avez maintenant entre les mains une super calculatrice qui réalise des addi-
tions !
N.B. Appuyez sur la touche Enter (Entrée) après avoir tapé la valeur de a puis à nouveau sur la
touche Enter après avoir tapé la valeur de b.
Pour aller plus rapidement, il est possible d’initialiser une variable dans le même temps que sa déclaration.
Pour cela, rien de plus simple, ajoutez à la fin de la déclaration = suivi de la valeur d’initialisation :
Exemple : int i = 10;
Donc à peu de chose près, votre programme devrait ressembler à ceci (lisez le programme puis la remarque
importante qui se trouve en dessous) :
1. Quand on dit "saisir", on veut dire que l’ordinateur va attendre que l’utilisateur entre une valeur au clavier puis qu’il appuie sur
la touche entrée.
4.3. SAISIE DES VARIABLES 21
#include <stdio.h>
#include <math.h>
int main () {
int a,b; /* Déclaration des variables */
printf("Calculatrice :\n\n");
printf("Valeur de a : ");
scanf("%d",&a);
printf("\n");
printf("Valeur de b : ");
scanf("%d",&b);
getchar ();
return 0;
}
Remarque importante : vous pouvez tester ce programme et vous vous apercevrez que curieusement, le
programme se finit alors qu’on n’a même pas eu le temps d’appuyer sur la touche entrée ; c’est comme
si le getchar() était pûrement et simplement oublié ? ! En fait, il s’agit d’une petite "sournoiserie" du
Langage C ; en effet le scanf("%d",&b) qui se situe au dessus attend que vous entriez une valeur
au clavier. Pour fixer les idées, supposons que vous entriez la valeur 1234, la chose à bien comprendre
est que pour entrer cette valeur, vous appuyez également sur la touche entrée. La subtilité tient au
fait que le scanf("%d",&b) "veut" juste une valeur entière. Il laissera donc cet \n disponible pour la
prochaine instruction qui ira lire quelque chose au clavier (on dit que le retour chariot reste disponible dans
le tampon clavier) ; c’est précisément le getchar() qui va le récupérer et qui permettra donc la sortie du
programme.
Evolution du logiciel : exercice Aïe aïe aïe, dur dur d’être informaticien. J’ai envie de faire une super
calculatrice et je me dis qu’en fait la simplicité qui a eu lieu tout à l’heure n’est pas forcément adaptée à
l’évolution désirée.
Déclarer une troisième valeur de type int (penser à l’initialiser à 0) que l’on nommera bêtement s comme
somme. Une fois les valeurs de a et b saisies, initialisez s avec la valeur de a + b. Comme en mathé-
matiques élémentaires. Affichez la valeur de s. On devrait avoir les mêmes résultats qu’auparavant, bien
sûr.
Un nouveau type : les nombres à virgule ou flottants (float). Le type float permet de déclarer un
nombre à virgule. Transformez les 3 précédents programmes en utilisant le type float au lieu du type
int. Enfin, si pour les int, on utilisait le format %d pour les printf et les scanf, à présent, nous allons
utiliser le format %f pour les flottants.
float a;
printf ("Saisie de a :");
scanf ("%f",&a);
printf ("\n a vaut : %f",a);
Pour un affichage plus agréable il est possible de fixer le nombre de chiffres après la virgule de la façon
suivante : %.[nombre de chiffres après la virgule]f
Exemple : printf ("%.2f",a);
Ouah, les 4 opérations ! ! ! Créer un quatrième programme qui réalise la division de deux nombres.
Vous pourrez vous amuser à tester avec une division par 0 ! ! ! La solution à ce petit problème sera vu dans
le cours sur les conditions (if).
Exercices à réaliser Compléter les 4 programmes afin de réaliser les opérations suivantes :
s = a + b + c
s = a - b - c
s = a / b / c
où a,b et c sont des nombres à virgules (float).
La fonction abs permet d’obtenir la valeur absolue d’un nombre entier (il faut inclure stdlib.h dans ce
cas). La fonction fabs permet d’obtenir la valeur absolue d’un nombre flottant.
Remarque : la fonction fabs doit être utilisée si on manipule des nombres flottants. Pour des nombres
entiers (du type int par exemple), il faudrait utiliser la fonction abs et non fabs !
La fonction ceil permet d’obtenir l’arrondi entier supérieur d’un nombre réel. Utiliser cette fonction pour
calculer l’arrondi supérieur de (a / b).
Exemple d’utilisation de ceil : s = ceil (a/b);
! ! ! On notera que pour utiliser ces 2 fonctions mathématiques, il faut ajouter #include <math.h> et
bien sûr compiler en ajoutant l’option -lm lors de la compilation.
4.6. CORRIGÉS DES EXERCICES DU CHAPITRE 23
#include <stdio.h>
#include <math.h>
int main () {
printf("Calculatrice :\n\n");
printf("Valeur de a : \n");
getchar();
printf("Valeur de b : ");
getchar();
#include <stdio.h>
#include <math.h>
int main () {
int a,b; /* Déclaration des variables */
getchar ();
return 0;
}
#include <stdio.h>
#include <math.h>
int main () {
int a,b; /* Déclaration des variables */
int d; /* Différence entre les 2 nombres */
printf("Calculatrice :\n\n");
printf("Valeur de a : ");
scanf("%d",&a);
printf("\n");
printf("Valeur de b : ");
scanf("%d",&b);
d=a-b;
printf("Valeur de a-b : %d\n",d); /* Affichage de la différence */
getchar ();
return 0;
}
#include <stdio.h>
#include <math.h>
int main () {
int a,b; /* Déclaration des variables */
int m; /* Résultat de la multiplication */
a=0; /* Initialisation des variables */
b=0;
printf("Calculatrice :\n\n");
printf("Valeur de a : ");
scanf("%d",&a);
printf("\n");
printf("Valeur de b : ");
scanf("%d",&b);
m = a*b;
printf("Valeur de a*b : %d\n", m);
/* Affichage de la multiplication */
getchar ();
return 0;
}
Aucune difficulté dans cet exercice, vous ne trouverez que la correction que de la multiplication, les autres
opérations se réalisent sur le même schéma.
26 CHAPITRE 4. LES VARIABLES (2EME PARTIE)
#include <stdio.h>
#include <math.h>
int main () {
float a,b; /* Déclaration des variables */
float m; /* Résultat de la multiplication */
printf("Calculatrice :\n\n");
printf("Valeur de a : ");
scanf("%f",&a);
printf("\n");
printf("Valeur de b : ");
scanf("%f",&b);
m = a*b;
printf("Valeur de a*b : %f\n", m);
/* Affichage de la multiplication */
getchar ();
return 0;
}
Pour l’addition :
m = a + b;
printf ("Valeur de a+b : %f", m);
Pour la soustraction :
m = a - b;
printf ("Valeur de a-b : %f", m);
Calculer la somme a + b + c
4.6. CORRIGÉS DES EXERCICES DU CHAPITRE 27
#include <stdio.h>
#include <math.h>
int main () {
float a,b,c,s; /* Déclaration des variables */
printf("Saisie de a : ");
scanf("%f",&a);
printf("Saisie de b : "); /* Saisie variables flottantes */
scanf("%f",&b);
printf("Saisie de c : ");
scanf("%f",&c);
getchar ();
return 0;
}
Calculer la différence a - b - c
Calculer la division a / b / c
Remarque : la fonction fabs doit être utilisée si on manipule des nombres flottants. Pour des nombres
entiers (du type int par exemple), il faudrait utiliser la fonction abs et non fabs !
28 CHAPITRE 4. LES VARIABLES (2EME PARTIE)
Calculer l’arrondi de a + b
Il aurait été possible d’utiliser %d du fait que l’arrondi est un nombre entier !
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
4.8 A retenir
– Exemple de programme :
#include <stdio.h>
int main () {
float pi; // déclaration d’un nombre à virgule
pi=3.14159;
printf ("pi vaut: %f",pi); // ne pas se tromper, bien
// mettre %f et non pas %d
// par exemple !
int j;
printf("\n entrez une valeur:");
scanf("%d",&j); // !!!!!!!!! ne pas oublier le &
printf("\n vous venez d’entrer %d",j);
return 0;
}
Les conditions
Ecrire un programme qui met en application le théorème de Pythagore pour calculer l’hypothénuse d’un
triangle rectangle.
Rappel : Dans un triangle rectangle, l’hypothénuse (le plus grand côté) peut se calculer en appliquant la
formule suivante : √
hypothénuse = a2 + b2
où a et b sont les longueurs des côtés adjacents à l’angle droit.
Notes : – La racine carrée s’obtient par l’utilisation de la fonction sqrt(valeur) contenue dans la
bibliothèque math.h. (#include <math.h>)
2
– a peut s’obtenir par a*a.
Méthodologie : 1. Rechercher les variables nécessaires et les déclarer dans le programme.
2. Faire saisir a au clavier.
3. Faire saisir b au clavier.
4. Effectuer l’opération de la racine carrée et afficher le résultat.
Une fois que vous avez proposé/tapé/compilé/testé votre solution, vous pourrez comparer avec la solution
qui se trouve à la fin de ce chapitre.
29
30 CHAPITRE 5. LES CONDITIONS
if (condition vraie) {
instructions 1
}
else {
instructions 2
}
si (condition vraie) {
alors faire instructions 1
}
sinon {
faire instructions 2
}
Les conditions s’expriment avec des opérateurs logiques dont nous allons expliquer tout de suite la signi-
fication et l’utilisation.
Libellé Opérateur
Inférieur <
Supérieur >
Egal ==
Différent !=
Inférieur ou égal <=
Supérieur ou égal >=
Voici un exemple de programme qui demande à l’utilisateur de saisir 2 valeurs puis il affiche la valeur qui
est la plus grande :
5.4. OPÉRATEURS LOGIQUES PURS 31
#include <stdio.h>
int main () {
int valeur1; /* valeur 1 */
int valeur2; /* valeur 2 */
/* Saisie de valeur1 */
printf ("entrez une 1ere valeur : ");
scanf ("%d",&valeur1);
/* Saisie de valeur2 */
printf ("entrez 2eme valeur : ");
scanf ("%d",&valeur2);
if (valeur1<valeur2)
printf("la plus grande valeur vaut:%d",valeur2);
else
printf("la plus grande valeur vaut:%d",valeur1);
return 0;
}
Libellé Opérateur
Et (and) &&
Ou (or) ||
Non (not) !
" | " se nomme en informatique un pipe (prononcer païpe). Des exemples suivront bientôt...
La valeur Vrai peut être assimilée à la valeur numérique 1 ou à toute valeur non nulle.
La valeur Faux peut être assimilée à la valeur numérique 0.
L’opérateur Ou (||) correspond alors à une addition.
Ou Vrai Faux + 1 0
Vrai Vrai Vrai 1 2 1
Faux Vrai Faux 0 1 0
Et Vrai Faux * 1 0
Vrai Vrai Faux 1 1 0
Faux Faux Faux 0 0 0
int i1;
int i2;
int res;
i1=1;
i2=0;
printf("i1 || i2 = %d",i1||i2);
/* renverra 1 car : vrai||faux=vrai et vrai vaut 1 */
printf("contraire(1)=%d",!(1));
/* renverra 0 car : !(vrai)=faux et faux vaut 0 */
5.6 Combinaison
Toutes les opérations logiques peuvent se combiner entre elles. La seule condition d’utilisation d’un si (if)
avec de telles combinaisons est de l’entourer de ( ).
5.7 Astuce
if (er) {
/* Alors faire quelque chose */
}
si (er != 0) /* si er différent de 0 */
{
/* Alors faire quelque chose */
}
if (er != 0) { /* si er différent de 0 */
/* Alors faire quelque chose */
}
Les accolades entourant les blocs d’instructions d’une condition peuvent être omises si le bloc n’est consti-
tué que d’une seule instruction.
Exemple :
/* VERSION LOURDE:...*/
if (car == ’b’) {
printf ("car vaut b.");
}
else {
printf ("car est différent de b.");
}
/* VERSION LEGERE:...*/
if (car == ’b’)
printf ("car vaut b.");
else
printf ("car est différent de b.");
5.9 Exercices
1. Faites saisir une variable de type entier et indiquez à l’utilisateur si celle-ci est strictement positive
ou strictement négative ou nulle.
Aide :
if (a>0)
printf ("Valeur positive");
else
printf ("Valeur négative");
2. Faites saisir une variable de type caractère et indiquez à l’utilisateur si celle-ci est une voyelle ou une
consonne. On considèrera que le caractère saisi est en minuscule.
5.10. CORRECTIONS DES EXERCICES DU CHAPITRE 35
#include <stdio.h>
#include <math.h>
int main () {
float a; /* base du triangle */
float b; /* côté du triangle rectangle */
float h; /* valeur de l’hypoténuse */
/* Saisie de a */
printf ("Valeur de la base : ");
scanf ("%f",&a);
/* Saisie de b */
printf ("Valeur du côté : ");
scanf ("%f",&b);
/* Affichage du résultat */
printf ("L’hypoténuse mesure : %.2f\n",h);
return 0;
}
#include <stdio.h>
int main () {
/* Valeur que l’on va saisir */
int a = 0;
/* Saisie de a */
printf("Saisie de a : ");
scanf("%d",&a);
else {
/* Test condition a>0 */
if (a>0)
printf("la variable a est positive\n");
/* Sinon a est nulle */
else
printf("la variable a est nulle\n");
}
getchar ();
return 0;
}
5.10.3 Tester si un caractère saisi au clavier est une consonne ou une voyelle
#include <stdio.h>
int main () {
/* Valeur que l’on va saisir */
char car;
/* Saisie du caractère a */
printf("Saisie du caractère : ");
scanf("%c",&car);
getchar ();
return 0;
}
5.11. UN EXERCICE POUR FINIR 37
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
5.12 A retenir
– La valeur Vrai peut être assimilée à la valeur numérique 1 ou à toute valeur non nulle.
– La valeur Faux peut être assimilée à la valeur numérique 0.
Mise au point
6.1 Prologue
L’objet de ce cours est de réaliser un petit break dans l’apprentissage du C et de s’attacher à voir ce que
l’on est capable de réaliser avec le peu de moyens que l’on a.
Ce cours sera donc constitué de 3 exercices de difficulté croissante avec apprentissage d’une nouvelle
fonction et d’un exercice complet de programmation.
6.2 Exercice 1
Réaliser un programme qui saisit un nombre et indique à l’utilisateur si celui-ci est plus grand ou plus petit
qu’un autre nombre fixé dans le programme.
Exemple :
si (nbre_saisi<10)
alors "plus petit"
Reprendre l’exercice du chapitre 4 qui disait si un nombre est strictement positif, strictement négatif ou
nul.
La fonction getchar() permet d’attendre la frappe d’un caractère au clavier, de le lire et de le renvoyer.
Deux utilisations peuvent être faites de getchar(), la première est celle permettant d’attendre la frappe
d’une touche sans se soucier de sa valeur, la seconde est celle permettant de lire un caractère au clavier.
39
40 CHAPITRE 6. MISE AU POINT
Exemples :
1. Attente
getchar();
2. Saisie d’un caractère
char car;
car = getchar();
A chaque fois, getchar() effectue le même traitement :
– Attendre la frappe d’une touche au clavier suivi d’un retour chariot (Entrée).
– Renvoyer le caractère frappé.
Dans le 1ercas où le getchar() est tout seul, ce caractère n’est simplement pas récupéré.
do ... while, traduisez par faire ... tant que permet de réaliser une suite d’instructions tant
qu’une condition ou un ensemble de conditions est rempli.
Exemple :
1˚ lisez le programme suivant
2˚ tapez, essayez ce programme en tapant par exemple A puis la touche entrée, puis en appuyant uni-
quement sur la touche entrée plusieurs fois...
3˚ lisez les explications qui figurent en dessous du programme
4˚ comprenez ce programme... programme
#include <stdio.h>
int main () {
char car;
int sortie;
do {
printf ("Tapez S pour sortir !\n");
/* On saisit un caractère */
car = getchar ();
return 0;
}
Remarque 1 : Cette utilisation n’est pas très belle, le retour chariot utilisé pour la saisie du caractère
étant renvoyé et interprété nous donne un affichage double. Malgré celà, au niveau de ce cours, nous nous
en contenterons.
6.5. EXERCICE 2 41
Rappels :
– Un nombre entier vaut la valeur logique vraie si celui-ci est différent de 0.
– Un nombre entier vaut la valeur logique faux si celui-ci est égal à 0.
– || signifie un ou logique (or), donc au niveau de la ligne sortie=((car==’s’)||(car==’S’));
lorsque car vaut ’s’ ou ’S’, dans ce cas sortie vaudra 1. Dans tous les autres cas, sortie vaudra
0.
Dès lors, les deux portions de programme suivantes sont rigoureusement équivalentes :
Remarque 2 : pour stopper un programme qui boucle, il suffit de presser simultanément sur la touche
Ctrl et la touche C.
Remarque 3 : finalement, on comprend que getchar est assez subtile. Elle ne renvoie qu’un seul
caractère mais elle ne pourra renvoyer ce caractère que s’il a été validé par la touche entrée. Donc 2 cas de
figure :
– vous appuyez seulement sur la touche entrée. Dans ce cas getchar renverra à chaque fois la touche
entrée, autrement dit : \n.
– vous appuyez par exemple sur A puis entrée. A l’écran apparaîtra d’abord le A ce qui est le fonctionne-
ment normal de getchar ; ensuite, comme nous sommes dans une boucle, au 2ème coup, getchar
vous renverra \n.
6.5 Exercice 2
Transformez l’exemple précédent afin que l’on sorte de la boucle uniquement lorsque l’utilisateur a tapé le
nombre 10.
Attention : La saisie d’un nombre ne se fait pas par getchar mais par scanf.
6.6 Exercice 3
Pour obtenir le reste de la division entière de deux nombres, on peut utiliser le %, on appelle cela le "mo-
dulo". Ainsi 10 modulo 2 vaut 0 car la division de 10 par 2 vaut 5, en revanche, le reste de cette division
vaut 0 :
42 CHAPITRE 6. MISE AU POINT
int z;
z=10%2;
printf("10 modulo 2=%d",z);
z=10%3;
printf("10 modulo 3=%d",z);
va nous afficher :
10 modulo 2=0
10 modulo 3=1
Voici un petit exemple de programme qui permet d’obtenir des nombres aléatoires entre 0 et 99.
#include <stdio.h>
#include <stdlib.h> // sert pour les fonctions
//srand et rand
#include <time.h>
int main() {
int nb_alea; /* Nombre aléatoire */
getchar ();
return 0;
}
srand (time (NULL)) permet d’initialiser le système aléatoire. On reviendra dessus par la suite...
rand () permet d’obtenir un nombre entre 0 et RAND_MAX (RAND_MAX est assez astronomique ! ! !
de l’ordre de quelques milliards...).
rand () % 100 va donc nous renvoyer le reste de la division d’un nombre aléatoire (éventuellement
très grand) par 100, ce qui va nous faire un nombre compris entre 0 et 99...
En vous aidant de ce petit programme et de ce qui a été fait précédemment, réalisez un petit jeu qui :
2. Tente de faire deviner ce nombre à l’utilisateur en lui indiquant s’il est plus petit ou plus grand.
Exemple de dialogue avec l’ordinateur :
6.6. EXERCICE 3 43
... ...
Gagné !!!
44 CHAPITRE 6. MISE AU POINT
#include <stdio.h>
int main () {
int nb_choisi = 33;
int nb_saisi = 0;
/* Attente */
getchar ();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main () {
int valeur;
do {
printf ("Votre nombre : ");
scanf ("%d",&valeur);
}
while (valeur != 10);
return 0;
}
6.8. UN EXERCICE POUR FINIR 45
#include <stdio.h>
#include <stdlib.h> /* pour les valeurs aléatoires */
#include <time.h>
int main () {
int nb_hasard = 0;
int votre_nb = 0;
do {
printf("Votre nombre : ");
scanf("%d",&votre_nb);
printf ("Trouvé\n");
getchar ();
return 0;
}
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
46 CHAPITRE 6. MISE AU POINT
Chapitre 7
Note : "Les Shadoks" est une série télévisée d’animation française en 208 épisodes de 2 à 3 minutes, créée
par Jacques Rouxel, produite par la société aaa (Animation Art-graphique Audiovisuel) et diffusée entre le
29 avril 1968 et 1973 (trois premières saisons) et à partir de janvier 2000 (quatrième saison) sur Canal+
et rediffusée sur Cartoon Network...
7.1 While
Exemple :
char car = ’ ’;
while ((car != ’s’) && (car != ’S’)) {
car = getchar ();
}
Attention : while (condition); signifie que tant que condition est vraie, on revient à la même
ligne. Si condition est toujours vraie, on tombe alors dans un puits. Si condition est fausse, on passe alors
immédiatement à la ligne/commande suivante.
47
48 CHAPITRE 7. ET LES SHADOCKS POMPAIENT
Faire
Saisir une touche
Tant Que (touche != S) et (touche != s)
Exercice 2 Traduisez en langage C, complétez avec les variables nécessaires, compilez, exécutez, com-
prenez :
Faire
Saisir un nombre
Tant Que (nombre != 10)
Attention : La saisie d’un nombre ne se fait pas par la fonction getchar() mais par la fonction scanf
(reportez vous au chapitre correspondant pour plus de précisions).
int main () {
char car;
char car_min;
car_min = ’a’
car = toupper (car_min);
printf ("%c",car);
return 0;
}
Dans les exercices qui suivent, la notion de compteur intervient. Un compteur est une variable numérique
que l’on décrémente (-1) ou incrémente (+1) suivant nos besoins.
Exemple :
int i;
i=1;
i=i+1;
En effet, quand on fait i=1, l’ordinateur prend la case mémoire où est stocké i et il lui affecte la valeur 1.
Quand on fait i=i+1, on commence par prendre connaissance de la valeur de i en mémoire (ici 1) ; on
lui ajoute 1 et on stocke le tout dans la case mémoire associée à i. Donc finalement, i vaudra 2.
Pour gagner du temps, le Langage C vous permet de remplacer une expression i=i+1 par l’expression
suivante : i++ qui fera exactement la même chose :
Exemples :
int i;
Dans les exemples précédents, le nombre de caractères entrés peut donc être comptabilisé en ajoutant 1 à
une variable à chaque fois qu’une touche est frappée.
Exercice 6
Ecrire le programme :
Tant que je n’ai pas saisi 10 caractères, je recommence la saisie d’une
touche.
De petites difficultés peuvent surgir... vous aurez donc le droit de vous pencher plus rapidement sur la
solution.
Tant que je n’ai pas saisi 10 voyelles, je recommence la saisie d’une touche
On prendra soin d’indiquer à l’utilisateur combien de voyelles il lui reste à entrer.
int n;
n=5;
int x;
x=n++;
Pour ce programme :
int n;
n=5;
int x;
x=++n;
#include <stdio.h>
int main () {
char car = 0;
printf ("Tapez ’s’ ou ’S’ pour arrêter ...\n");
do {
car = getchar ();
printf("j’ai lu car=(%c)\n",car);
}
while ((car != ’s’) && (car != ’S’));
return 0;
}
Exercice 2
#include <stdio.h>
int main () {
int nbre = 0;
printf ("Tapez 10 pour arrêter ...\n");
do {
scanf ("%d", &nbre);
}
while (nbre != 10);
return 0;
}
Exercice 3
#include <stdio.h>
int main () {
int nbre = 0;
return 0;
}
ou bien :
52 CHAPITRE 7. ET LES SHADOCKS POMPAIENT
#include <stdio.h>
int main () {
int nbre = 0;
return 0;
}
Exercice 4
#include <stdio.h>
#include <ctype.h>
int main () {
char car = ’ ’;
Exercice 5
7.8. CORRIGÉS DES EXERCICES DU CHAPITRE 53
#include <stdio.h>
int main () {
int nbre = 0;
int nb_nbre = 0;
do {
scanf ("%d",&nbre);
nb_nbre ++;
}
while (nb_nbre != 10);
return 0;
}
Exercice 6
#include <stdio.h>
int main () {
char car = ’ ’;
int nbre = 0;
do {
car = getchar ();
printf("j’ai lu car=(%c)\n",car);
nbre ++;
printf("nbre=%d\n",nbre);
}
while (nbre != 10);
return 0;
}
Tapez 10 caractères pour arrêter ...123456789 j’ai lu (1) nbre=1 j’ai lu (2) nbre=2 j’ai lu (3) nbre=3 j’ai lu
(4) nbre=4 j’ai lu (5) nbre=5 j’ai lu (6) nbre=6 j’ai lu (7) nbre=7 j’ai lu (8) nbre=8 j’ai lu (9) nbre=9 j’ai lu
(
) nbre=10
l’utilisateur a donc tapé 123456789 suivi de la touche entrée, il a donc bien tapé 10 caractères. Ce que
montre l’affichage de la dernière ligne avec la parenthèse ouvrante sur une ligne et la parenthèse fermante
sur la ligne suivante.
54 CHAPITRE 7. ET LES SHADOCKS POMPAIENT
Exercice 7
#include <stdio.h>
#include <ctype.h>
int main () {
char car;
int nb_nbre = 10;
car=toupper(car);
if (car==’A’ || car==’E’ || car==’I’ || car==’O’ || car==’U’)
{
nb_nbre--;
printf ("Tapez encore %d voyelles pour arrêter...\n",nb_nbre);
}
}
while (nb_nbre != 0);
return 0;
}
Exercice 8
#include <stdio.h>
#include <ctype.h>
int main () {
int nb_nbre = 10;
int nbre;
return 0;
}
7.9. UN EXERCICE POUR FINIR 55
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
7.10 A retenir
Voici un exemple de programme qui résume ce qui a été vu dans ce chapitre. Ce programme doit afficher à
l’écran tous les nombres pairs (inférieurs à 100) :
#include <stdio.h>
int main () {
int i = 0;
while (i<=100) {
if ((i%2)==0) /* reste de la division de i par 2 */
printf("%d",i);
/* pas de else ici, c’est inutile...*/
i++;
}
return 0;
}
#include <stdio.h>
int main () {
int i = 0;
while (i<=100) {
printf("%d",i);
i=i+2;
}
return 0;
}
Enfin, à retenir :
Les boucles
Pour faire effectuer un certain nombre de fois une tâche, on utilise l’instruction for de la façon suivante
(avec i, une variable de type entier (int par exemple)).
i=point de départ;
i=i+pas;
}
signifie que l’on va exécuter instr pour i variant de 0 à 14 (<15) c’est à dire 15 fois.
57
58 CHAPITRE 8. LES BOUCLES
Exemple :
#include <stdio.h>
int main () {
int i;
for (i=0; i<15; i++) {
printf ("Je me répète pour i valant %d\n",i);
}
printf ("Je me suis répétée... 15 fois\n");
return 0;
}
8.2 Syntaxe
De la même façon que le if, le for ne nécessite pas d’accolades si le nombre d’instructions à répéter est
de 1.
par
Il est possible de remplacer les instructions par une boucle afin de réaliser une double boucle. On obtient
donc :
8.4. EXERCICE 2 : ET LES SHADOKS FÊTÈRENT NOËL... 59
Exemple :
#include <stdio.h>
int main () {
int i;
int j;
for (i=0; i<5; i++) {
printf ("\nJe suis dans la boucle i, i vaut %d\n",i);
return 0;
}
Exercice 1 En utilisant la double boucle, écrire un programme qui écrit une étoile, passe à la ligne, écrit
deux étoiles, passe à la ligne, écrit trois étoiles... jusqu’à cinq étoiles afin d’obtenir ceci :
*
**
***
****
*****
Exercice 2.a À l’aide d’une double boucle, réalisez un cône pour dessiner le haut du sapin sur 10 lignes.
Vous devriez obtenir ceci :
60 CHAPITRE 8. LES BOUCLES
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
Aide :
– Sur la ligne no 1, on affiche 9 espaces puis 1 étoile ;
– Sur la ligne no 2, on affiche 8 espaces puis 3 étoiles ;
– Sur la ligne no 3, on affiche 7 espaces puis 5 étoiles ;
– Sur la ligne no i, on affiche 10-i espaces puis 2*i-1 étoiles.
On obtient donc par exemple pour l’affichage des étoiles sur la ligne i :
Exercice 2.b Pour poursuivre le sapin, il nous faut maintenant dessiner le tronc. Ecrire la suite du
programme en dessinant le tronc à l’aide du caractère @. Vous devriez obtenir ceci (juste pour le tronc) :
@@@
@@@
@@@
Les codes ASCII (c’est-à-dire les nombres qui représentent les caractères en informatique) vont de 0 à 255.
Ecrire un programme qui fait afficher sur des lignes successives les codes ASCII avec les caractères qui
leur correspondent (On pourra commencer l’affichage à partir du code 32 et s’arrêter au caractère 127).
Pour faire afficher le caractère associé à un code ASCII, on écrit (le %3d signifie que le nombre va être
affiché en utilisant un emplacement qui fait 3 caractères de long) :
printf ("%3d : %c", code_ascii, code_ascii);
Exemple :
8.5. EXERCICE 3 : TABLE ASCII 61
int i = 65;
printf ("%3d : %c", i, i); // affichera 65 : A
Exercice 3.b
Même chose, mais de façon propre, par exemple sous la forme d’un tableau sur 8 colonnes...
62 CHAPITRE 8. LES BOUCLES
Exercice 1
#include <stdio.h>
#include <stdlib.h>
int main () {
int i;
int j;
for (i=1; i<=5; i++) {
for (j=1; j<=i; j++) {
printf ("*");
}
printf ("\n");
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main () {
int i;
int j;
for (i=1; i<=5; i++) {
for (j=1; j<=i; j++) // pas d’accolades nécessaires...
printf ("*");
printf ("\n");
}
return 0;
}
Exercice 2.a
8.6. CORRIGÉS DES EXERCICES DU CHAPITRE 63
#include <stdio.h>
#include <stdlib.h>
int main () {
int i;
int j;
printf ("\n");
}
return 0;
}
Exercice 2.b
...
for (i=1; i<=3; i++)
printf (" @@@\n");
}
Exercice 3
Exercice 3.a
#include <stdio.h>
#include <stdlib.h>
int main () {
int i;
int j;
return 0;
}
64 CHAPITRE 8. LES BOUCLES
Exercice 3.b
#include <stdio.h>
#include <stdlib.h>
int main () {
int i;
int j;
return 0;
}
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
8.8 A retenir
i=1;
while (i<100) {
printf("i vaut:%d",i);
i=i+2;
}
Chapitre 9
Pointeurs et Fonctions
Peu d’exercices dans ce chapitre dans lequel l’important est de comprendre les nombreux exemples cités.
9.1.1 Binaire
Vous le savez peut-être, à la base, l’ordinateur n’arrive à comprendre que des 0 et des 1. En gros 0 : pas de
courant, 1 : du courant.
Le problème, c’est qu’on est assez vite limité, rien que pour compter jusqu’à 10, on a du mal ! ...
La solution va être de regrouper plusieurs 0 et 1, ce qui nous permettra d’aller plus loin... :
Pour la machine, le nombre 7 des êtres humains serait donc représenté par trois "1" qui se suivent. Chacun
de ces "1" s’appelle un bit. Autre exemple : le nombre 1010001 est composé de 7 bits.
65
66 CHAPITRE 9. POINTEURS ET FONCTIONS
9.1.2 Octet
Si on considère 8 bits à la suite (chacun pouvant donc prendre la valeur 0 ou 1), on appelle cela un octet.
Voici un exemple d’octet : 11110000, en voici un autre : 10101010.
Depuis tout petit, vous comptez en base dix, pour ce faire vous utilisez dix chiffres de 0 à 9. Pour compter
en binaire (base 2), on utilise deux chiffres (0 et 1). Si on voulait compter en base 3, on utiliserait 3
chiffres : 0, 1 et 2.
Il s’agit d’une base très utilisée en informatique. Problème : comment compter en base 16 ? Il nous faudrait
16 chiffres, or nous n’en avons que dix ? !
Solution :
Base 16 Base 10
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
A 10
B 11
C 12
D 13
E 14
F 15
Une variable (par exemple la variable car déclarée par char car;) est en fait une petite zone mémoire
ici de 1 octet que l’on s’alloue et où l’on va ranger les informations. Chaque type de variable utilise au
moins 1 octet. Le type int varie selon les compilateurs, pour nous il utilise 4 octets c’est à dire 4 cases
mémoire. Nous avons vu précédemment qu’il était possible de voir le contenu d’une variable avec la fonc-
tion printf et qu’il était possible de mettre une valeur dans une variable avec l’opérateur = ou la fonction
scanf.
Exemple :
Représentons-nous la mémoire comme une Rue remplie de maisons. Chaque case mémoire est représentée
par une maison. Chaque maison porte un numéro, son adresse postale. Pour une case mémoire, on parlera
d’adresse mémoire. L’adresse est unique pour chaque maison. Cependant, rien ne vous empêche de vous
tromper intentionnellement ou non de maison. De la même façon, une adresse mémoire d’une case mémoire
est unique mais rien ne vous empêche de vous tromper intentionnellement ou non de case mémoire.
Exemple en C :
char car;
En langage C, l’adresse mémoire est accessible en faisant précéder la variable de l’opérateur &.
Exemple :
68 CHAPITRE 9. POINTEURS ET FONCTIONS
Le "%p" permet d’indiquer que nous avons affaire à un pointeur et qu’il faut afficher l’adresse mémoire où
il est stocké. Cet affichage se fera en hexadécimal.
9.2.2 Pointeurs
Pour utiliser les adresses mémoire des variables à la place des variables elles-mêmes, on utilise les poin-
teurs. Les pointeurs sont définis par un type et se déclarent de la façon suivante :
type* variable;
ou
type *variable;
ou
type * variable;
Le signe * indique l’utilisation d’un pointeur c’est-à-dire l’utilisation d’une adresse mémoire. Le type per-
met simplement de savoir comment le compilateur C doit interpréter l’adresse mémoire lors de sa lecture.
Nous en verrons des exemples lors de l’utilisation des chaînes de caractères.
Pour enregistrer une valeur dans une adresse mémoire on écrit :
*variable = valeur
Lire le bout de programme suivant ainsi que les explications en dessous du programme. C’est un point
délicat de ce cours, au besoin relisez 2 à 3 fois les explications :
9.2. VARIABLES : POINTEURS ET VALEURS 69
#include <stdio.h>
int main () {
char car=’C’;
char * ptr_car; /* ptr_car est une variable */
/* cette variable est particulière */
/* dans le sens où sa valeur vaudra */
/* l’adresse d’une autre variable */
return 0;
}
ptr_car = &car; Signifie que l’on m’a donné l’adresse postale de Monsieur car.
*ptr_car = ’E’; Signifie que je rentre chez Monsieur car et que j’y dépose le caractère E.
printf ("%c",car); Signifie que l’on va lire le contenu de car (en mémoire) pour l’afficher à l’écran.
Voici un dessin qui explicite tout ceci. On supposera que la variable car est stockée à l’adresse 100 ;
la variable ptr_car sera stockée à l’adresse 110. Après l’exécution des deux premières lignes du pro-
gramme :
#include <stdio.h>
int main () {
char car=’C’;
char* ptr_car;
}
Nous mettons ? pour ptr_car car cette variable n’a pas encore été initialisée...
On voit bien que ptr_car n’est qu’une "vulgaire" variable, elle contient la valeur 100 (l’adresse de la
variable car).
Finalement, la ligne :
Peut-être qu’il serait rentable de relire, avant les exercices, juste tout ce paragraphe ;-)
Exercice 2 Réaliser un programme équivalent qui change une valeur numérique (int) de 10 à 35.
Dans la pratique, lorsque l’on déclare un pointeur, il est plus prudent de l’initialiser :
9.2. VARIABLES : POINTEURS ET VALEURS 71
#include <stdio.h>
int main () {
char car=’C’;
char * ptr_car; /* ptr_car est une variable */
/* cette variable est particulière */
/* dans le sens où sa valeur vaudra */
/* l’adresse d’une autre variable */
return 0;
}
NULL vaut 0 en fait. Du coup, c’est l’adresse 0. Dans la pratique l’adresse 0 désigne donc la toute première
case de la mémoire. Cette case est utilisée par Linux ou Windows (suivant le système sous lequel vous
travaillez). En particulier, vu que cette case est utilisée par le système d’exploitation, vous n’aurez donc
pas le droit de modifier cette case. Dans la pratique, écrire prt_car=NULL sert à initialiser le pointeur et
dire que l’adresse mémoire est pour l’instant invalide. Dans la pratique aussi, c’est plus parlant/lisible que
prt_car=0.
On peut se demander à quoi servent les pointeurs ? ! En effet, ça a l’air bien compliqué alors que l’on
pourrait faire bien plus simple ; ainsi tout le travail effectué par le programme précédent se résume au code
suivant :
#include <stdio.h>
int main () {
char car=’C’;
return 0;
}
En fait, on verra par la suite que dans certains cas, on ne peut tout bonnement pas se passer des pointeurs,
notamment pour certaines manipulations au sein des fonctions...
72 CHAPITRE 9. POINTEURS ET FONCTIONS
Une fonction est un petit bloc de programme qui à l’image d’une industrie va créer, faire ou modifier
quelque chose.
Un bloc de programme est mis sous la forme d’une fonction si celui-ci est utilisé plusieurs fois dans un
code (un programme) ou simplement pour une question de clarté (Imaginez un livre sans paragraphes !).
A l’identique de notre fonction principale main(), une fonction s’écrit de la façon suivante :
Corps de la fonction
Retour
}
Souvenez vous de cette remarque que nous avions déjà faite : il ne faut pas mettre les < et > ! ! !, on met
ceci pour rendre les programmes plus lisibles... Vous ne mettrez ces < et > que pour des cas du type
#include <stdio.h>.
Dans la pratique, quand vous tapez ./programme pour lancer votre programme, l’ordinateur va lancer la
fonction main qui se trouve à la ligne /* ligne 13 */. L’ordinateur demande donc ensuite de saisir
2 valeurs pour i1 et i2. Supposons que l’utilisateur ait tapé les valeurs 111 et 222 au clavier.
Ensuite, à la /* ligne 19 */, l’ordinateur commence à afficher "max des 2 valeurs :" puis
il tombe ensuite sur le "%d" qu’il va chercher à remplacer par quelque chose... Ce quelque chose sera en
fait l’appel maximum(i1,i2). L’ordinateur saute alors à la /* ligne 03 */. A ce niveau, on peut
comprendre intuitivement que la variable valeur1 prendra la valeur de i1 (c’est-à-dire 111) et la valeur
valeur2 prendra la valeur de i2 (c’est-à-dire 222), justement à cause de l’appel de maximum(i1,i2).
Ensuite, la fonction maximum va se dérouler normalement ; après avoir passé les lignes 4 à 9, la valeur de
max sera de 222.
A la /* ligne 10 */ on va donc sortir de la fonction maximum pour revenir à l’appel fait par le
printf de la /* ligne 19 */. Dès lors, le fameux "%d" du printf sera remplacé par la valeur
issue du return max, c’est à dire 222...
Remarque de vocabulaire : on dit que les variables i1 et i2 sont passées en paramètre de la fonction
maximum lors de l’appel : maximum(i1,i2).
74 CHAPITRE 9. POINTEURS ET FONCTIONS
9.3.2 void
Exemple :
#include <stdio.h>
int main () {
char c;
int i;
for (i=0;i<100;i++)
affiche_car(c);
return 0;
}
Les variables déclarées dans les fonctions sont dites locales. Il existe aussi les variables dites globales qui
sont déclarées en dehors de toute fonction y compris le main ().
Les variables globales sont modifiables et accessibles par toutes les fonctions sans avoir besoin de les passer
en tant que paramètres. Il est de ce fait extrêmement dangereux d’utiliser des variables globales.
Les variables locales ne sont modifiables et accessibles que dans la fonction où elles sont déclarées. Pour
les modifier ou les utiliser par une autre fonction, il est nécessaire de les passer en tant que paramètres.
#include <stdio.h>
int main () {
int val_retour = 0; /* Déclaration variable locale */
return 0;
}
val_retour est dans les deux cas une variable locale. Bien qu’elles aient le même nom dans les fonctions
main et carre, les deux variables n’ont rien à voir entre elles.
#include <stdio.h>
int val = 0;
int val_retour = 0;
void carre () {
val_retour = val * val;
}
int main () {
val = 2;
carre ();
printf ("Le carré de 2 est %d\n", val_retour);
return 0;
}
Exemple :
#include <stdio.h>
return 0;
}
Ces paramètres peuvent être modifiés à condition qu’ils soient passés par adresse c’est à dire que la fonc-
tion reçoive un pointeur. Voir le programme ci-dessous et les explications/déroulements du programme
ci-après :
Exemple :
#include <stdio.h>
int main () {
int x=0;
avance_Position (&x);
return 0;
}
x pointeur_int
------------------------------
| 0 | |10|
------------------------------
10 20
En fait, nous sommes obligés d’utiliser des pointeurs pour pouvoir modifier certaines variables. Voir
l’exemple du programme ci-dessous où l’on aurait bien aimé qu’après l’appel de la fonction calcule_double
la variable i soit doublée :
Exemple :
main () {
int i =2;
calcule_double(i);
printf("i vaut à présent :%d",i); /* il vaut toujours 2 !!! */
}
Généralement, on dit en résumé qu’une fonction n’est pas capable de modifier ses arguments. L’usage
des pointeurs devient dans ce cas nécessaire...
9.3.5 Exercice 3 :
Modifier le programme précédent afin d’avoir à dispoisition une fonction qui prend en paramètre un entier
et le modifie pour le doubler...
Une fois que vous aurez votre solution, comparez avec celle qui se situe à la fin...
78 CHAPITRE 9. POINTEURS ET FONCTIONS
9.3.6 Piège !
int* x;
*x++;
augmentera l’adresse de x (on va chez le voisin) et non la valeur, pour cela il faut écrire :
(*x)++;
9.3.7 Exercice 4 :
Reprendre le programme du sapin de Noël du chapitre précédent et réaliser 2 fonctions : cone (int n)
et tronc (int n) qui dessinent respectivement le cône du sapin sur n lignes et le tronc en position n
(n blancs avant le tronc).
Ecrire un programme qui, utilisant ces deux fonctions, dessine un sapin sur n lignes.
Dans la pratique quand on veut écrire un programme qui contient des fonctions, on fait les choses propre-
ment. Ainsi, plutôt que cette version :
9.3. LES FONCTIONS 79
#include <stdio.h>
int main () {
int i1,i2;
i1=123;
i2=1267;
printf("%d\n",max(i1,i2));
return 0;
}
on lui préfère la suivante où l’on ajoute la ligne "int max (int x, int y);" que l’on appelle un
prototype.
#include <stdio.h>
/* PROTOTYPE : */
int max (int x, int y);
int main () {
int i1,i2;
i1=123;
i2=1267;
printf("%d\n",max(i1,i2));
return 0;
}
Pour l’instant, prenez cela pour argent comptant ;-) on verra dans la suite quelques intérêts à procéder
ainsi...
80 CHAPITRE 9. POINTEURS ET FONCTIONS
Le programme suivant ne présente pas de difficultés, son but est d’échanger 2 variables i1 et i2 :
#include <stdio.h>
int main () {
int i1,i2;
i1=123;
i2=1267;
int variable_auxiliaire;
printf("i1=%d i2=%d Avant l’échange\n",i1,i2);
variable_auxiliaire=i1;
i1=i2;
i2=variable_auxiliaire;
return 0;
}
9.3.10 Exercice 5 :
Exercice 1
#include <stdio.h>
int main () {
char car = ’C’;
char * ptr_car;
return 0;
}
Exercice 2
#include <stdio.h>
int main () {
int val = 10;
int *ptr_val;
printf ("Avant le nombre est : %d\n",val);
ptr_val = &val;
*ptr_val = 35;
printf ("Après le nombre est : %d\n",val);
return 0;
}
Exercice 3
82 CHAPITRE 9. POINTEURS ET FONCTIONS
#include <stdio.h>
int main () {
int i=10;
printf ("i=%d\n",i);
calcule_double (&i);
printf ("i=%d\n",i);
return 0;
}
9.4. CORRIGÉS DES EXERCICES DU CHAPITRE 83
Exercice 4
Exercice 5
84 CHAPITRE 9. POINTEURS ET FONCTIONS
#include <stdio.h>
/* PROTOTYPE : */
int echange (int *x, int *y);
variable_auxiliaire=*x;
(*x)=(*y);
(*y)=variable_auxiliaire;
}
int main () {
int i1,i2;
i1=123;
i2=1267;
return 0;
}
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
9.6 A retenir
– compter en base 2 (on dit aussi en binaire) revient à n’utiliser que des 0 et des 1
– un octet est formé de 8 bits ; exemple 11110101
– base dix : la base classique
– base 16 : les lettres utilisées : 0, 1, ..., 9, A, B, C, D, E, F.
– un pointeur n’est rien d’autre qu’une variable. Cette variable est juste un peu particulière dans le sens où
elle peut contenir l’adresse d’une autre variable.
#include <stdio.h>
int main () {
int i;
i=100;
int * pointeur_sur_i;
pointeur_sur_i=&i; // ce pointeur pointe à présent sur i
*pointeur_sur_i=200;
printf ("i vaut à présent %d\n",i); // i vaudra 200...
return 0;
}
#include <stdio.h>
int main () {
int i=0;
int x=0;
return 0;
}
Nous venons de terminer sans doute le chapitre le plus difficile de ce cours, le plus dûr est derrière nous...
86 CHAPITRE 9. POINTEURS ET FONCTIONS
Chapitre 10
10.1 Tableaux
10.1.1 Définition
Un tableau est un ensemble d’éléments consécutifs. Celui-ci peut être constitué de plusieurs lignes et
colonnes. Nous n’utiliserons dans un premier temps que les tableaux à une seule ligne.
Numéro de case 0 1 2 3 4 5 6 7
Contenu A B C D E F G H
10.1.2 Déclaration
Exemples :
87
88 CHAPITRE 10. TABLEAUX & CHAÎNES DE CARACTÈRES
10.1.3 Utilisation
On accède à une case du tableau en mettant le nom du tableau, suivi d’un crochet "[" puis un numéro de
case et un crochet fermant : "]" :
Exemple :
/* déclarations: */
int tab_int[10]; /* tableau de 10 cases (0 à 9) d’entiers */
char tab_char[10]; /* tableau de 10 cases (0 à 9) de caractères */
/* utilisations: */
tab_char [3] = ’C’; /* Accès à la case 3 (la quatrième) de tab_char */
tab_int [6] = 10; /* Accès à la case 6 (la septième) de tab_int */
Attention : Le compilateur ne vérifie pas que vous utilisez le tableau dans ses limites. Il vous est donc
possible d’écrire à l’extérieur de votre tableau donc chez le voisin. C’est l’un des bugs le plus courant de
l’informatique.
Les chaînes de caractères sont des tableaux de caractères suivis du 0 (zéro ; ne pas confondre avec le
caractère O (de Ollalah par exemple...)) qui est considéré lui aussi comme un caractère. Une chaîne s’écrit
donc : chaîne + 0.
Une chaîne de caractères se déclare sous la forme d’un tableau de caractères de longueur fixe. Attention,
comme signalé auparavant, si vous dépassez la longueur de tableau, vous écrivez chez le copain.
Exemple :
char m_chaine [20];
permettra d’enregistrer des chaînes de 19 caractères maximum (20-1 pour le 0 de fin de chaîne).
D’autre part, il est possible de déclarer une chaîne de caractères sans en spécifier la longueur de départ de
la façon suivante :
char chaine [] = "Eric";
10.2. CHAÎNES DE CARACTÈRES 89
Exemple :
printf ("%s",chaine);
La longueur d’une chaîne de caractères s’obtient par la fonction strlen. Le 0 de fin de chaîne n’est pas
compté dans cette longueur.
Exemple :
#include <stdio.h>
#include <string.h> //nécessaire pour disposer
// de la fonction strlen
int main () {
char ch [] = "toto" ;
printf ("La longueur de %s est : %d", ch, (int) (strlen (ch))) ;
return 0;
}
Le programme suivant :
char tab[10];
printf("adresse où commence tab=%p",&tab[0]);
On voit donc que tab est la même chose que &tab[0]. Dès lors, le programme suivant est incorrect :
char tab[10];
tab="coucou";
En effet, tab désigne l’adresse où débute le début du stockage de ce tableau en mémoire. Ainsi, on voit
bien que dans l’affectation tab="coucou"; le membre de gauche désigne une adresse alors que le
membre de droite désigne une chaîne de caractères ; les deux n’étant pas du même type, le Langage C se
plaindrait...
90 CHAPITRE 10. TABLEAUX & CHAÎNES DE CARACTÈRES
Pour initialiser une chaîne de caractères, il faut en fait utiliser la fonction strcpy. Cette fonction nous
impose d’inclure la bibliothèque string.h :
#include <stdio.h>
#include <string.h>
int main() {
char line[80];
strcpy(line,"un exemple de chaine initialisee...");
return 0;
}
Il y a donc recopie (et ce, caractère par caractère) de la chaine "un exemple de chaine initialisée..."
en démarrant à l’adresse où se trouve line stockée en mémoire.
Enfin, si l’on souhaite lire une chaîne directement au clavier, on peut utiliser la fonction scanf :
#include <stdio.h>
int main() {
char line[81];
printf("veuillez entrer votre chaine:");
scanf("%s",line);
10.2.5 Exercices
Case 0 1 2 3 4 5 6 7 8 9 10
Contenu ’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ 0
– faîtes afficher la chaîne de caractères ainsi obtenue (n’oubliez pas de rajouter le 0) ;
– faîtes afficher chaque caractère du tableau sous la forme « Caractère no 0 : A ».
Rappel : Je peux écrire Tab_car [i] = code_ascii; où code_ascii est un entier représen-
tant le code A SCII du caractère désigné.
10.2. CHAÎNES DE CARACTÈRES 91
ou encore (étant donné que le caractère ’\0’ désigne la même chose que 0) par :
On préfèrera généralement cette dernière solution qui est plus explicite et montre bien que l’on travaille
avec des chaînes de caractères.
Description La fonction gets permet de saisir une chaîne de caractère validée par un retour chariot.
Attention, bien que cette chaîne nécessite d’être validée par un retour chariot, celui-ci n’est pas enregistré
dans le tableau de caractères.
Remarque : lorsque vous compilez votre programme par gcc -o essai essai.c, vous verrez qu’un
message d’erreur du type "warning : gets may be dangerous", ne vous inquiétez pas. Le com-
pilateur craint simplement qu’à l’exécution, l’utilisateur saisisse une chaine de caractère qui fasse plus de
80 caractères longs... auquel cas, des problèmes risquent de surgir si la chaine qui vient d’être entrée au
clavier dépasse les 80 caractères maximum.
Exemple d’utilisation
#include <stdio.h>
int main() {
char line[81];
/* 81 : taille arbitraire supposée suffisante */
/* Une ligne écran = 80 caractères + 1 case pour le ’\0’ de fin de chaîne */
Exemple d’exécution :
92 CHAPITRE 10. TABLEAUX & CHAÎNES DE CARACTÈRES
Notons qu’il n’y a qu’un retour chariot (celui affiché par la fonction printf).
Si on veut passer un tableau en paramètres à une fonction, en fait on ne passe que la 1ère case du tableau
et pas tout le tableau. Les 2 fonctions suivantes sont donc rigoureusement équivalentes :
int main () {
char ma_chaine [30];
ma_saisie (ma_chaine);
return 0;
}
int main () {
char ma_chaine [30];
ma_saisie (ma_chaine);
return 0;
}
Nous reviendrons longuement, par la suite sur cette équivalence pointeurs/tableaux... donc pas d’inquiétude
si ça n’est pas lumineux pour l’instant ;-)
10.3. QUELQUES FONCTIONS UTILES 93
Cette fonction ajoute une chaîne à la fin d’une autre (en algorithmique, on appelle cela une concaténation).
int main () {
char chaine1[20]="Bonjour ";
char chaine2[20]="Paul";
printf("%s",chaine1);
return 0;
}
On remarquera qu’il est important de dimensionner la chaine1 à une taille suffisante, sans quoi on pour-
rait avoir des difficultés pour faire tenir la chaîne "Bonjour Paul" dans la chaine1.
strncpy (<s>, <t>, <n>) : fonctionne comme strcpy mais copie au plus <n> caractères de
<t> vers <s>.
strncat (<s>, <t>, <n>) : ajoute au plus <n> caractères de <t> à la fin de <s>.
Nous terminerons par deux fonctions très puissantes qui peuvent faire des miracles ;-)
La fonction sprintf renvoie une valeur négative en cas d’erreur et le nombre de caractères stockés dans
s sinon.
Exemple :
char s[200];
int i=15;
int code;
code=sprintf(s,"%d",i);
Exemple :
code=sscanf(s,"%f%f%f",&a,&b,&c);
On retrouvera donc les bonnes valeurs pour a, b et c, à savoir respectivement : 12.5 12.3 et 11.6.
sscanf renvoie une valeur négative en cas d’erreur, et est égal au nombre de variables affectées sinon.
Exemple :
#include <stdio.h>
int main() {
int tab[5][10];
int i,j;
/* Pour chaque ligne ... */
for (i=0; i<5; i++) {
/* ... considérer chaque case */
for (j=0; j<10; j++)
printf("%d ", tab[i][j]);
/* Retour à la ligne */
printf("\n");
}
return 0; /* c’est pareil que return (0) */
}
Si on souhaite initialiser ce tableau avec des valeurs lues au clavier, voici comment faire :
#include <stdio.h>
int main() {
int tab[5][10];
int i,j;
/* Pour chaque ligne ... */
for (i=0; i<5; i++) {
/* ... considérer chaque case */
for (j=0; j<10; j++)
scanf("%d", &tab[i][j]);
/* Retour à la ligne */
printf("\n");
}
return 0;
}
#include <stdio.h>
#define LIGNES 5
#define COLONNES 10
int main() {
int tab[LIGNES][COLONNES];
int i,j;
/* Pour chaque ligne ... */
for (i=0; i<LIGNES; i++) {
/* ... considérer chaque case */
for (j=0; j<COLONNES; j++)
scanf("%d", &tab[i][j]);
/* Retour à la ligne */
printf("\n");
}
return 0;
}
L’usage de #define LIGNES 5 va faire que l’ordinateur va parcourir tout votre programme et faire
un chercher-remplacer : chercher la chaîne LIGNES et remplacer par 5. Il fera ensuite pareil pour
COLONNES.
On comprendra pourquoi il ne faut pas mettre des " ;" à la fin des #define sous cette forme :
#include <stdio.h>
#define LIGNES 5;
#define COLONNES 10; ...
int main() {
int tab[5;][10;]; /* compile pas !!! */
int i,j;
/* Pour chaque ligne ... */
for (i=0; i<5;; i++) { /* compile pas !!! */
/* ... considérer chaque case */
for (j=0; j<10;; j++) /* compile pas !!! */
scanf("%d", &tab[i][j]);
/* Retour à la ligne */
printf("\n");
}
return 0;
}
10.5. CORRECTION DES EXERCICES 97
En utilisant une boucle (for), remplissez un tableau de 10 caractères avec les lettres de l’alphabet en
commençant par A (code A SCII 65). Faîtes afficher la chaîne de caractères ainsi obtenue (n’oubliez pas de
rajouter le 0). Faîtes afficher chaque caractère du tableau sous la forme "Caractère no 0 : A".
#include <stdio.h>
#include <stdlib.h>
/* Affichage de la chaîne */
int main () { printf ("Tableau : %s\n",tableau);
/* 10 caractères
+ 0 de fin de chaîne */ /* Saut d’une autre ligne */
char tableau [11]; printf ("\n");
int i=0; /* compteur */
/* Affichage de chacun des caractères */
/* Remplissage du tableau for (i=0; i<10; i++)
avec les caractères */ printf ("Caractère n˚%d
for (i=0; i<10; i++) : %c\n",i,tableau [i]);
tableau [i] = ’A’ + i;
return 0;
/* Ajout du 0 de fin de chaine */
tableau [10] = 0; }
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
10.7 A retenir
Pour finir un petit programme qui reprend l’essentiel de ce qui a été vu. Il permet de lire une chaine1 au
clavier puis de recopier cette chaine dans une seconde chaine2 et de l’afficher à l’écran :
98 CHAPITRE 10. TABLEAUX & CHAÎNES DE CARACTÈRES
#include <stdio.h>
#include <string.h>
int main () {
int longueur;
char chaine1[81]; /* 80 caractères + ’\0’
char chaine2[81];
Structures et Fichiers
Il est possible grâce au déclarateur typedef de définir un type nouveau qui est un type synonyme. Ainsi
la définition suivante :
typedef int entier ;
définit un type synonyme appelé entier ayant les mêmes caractéristiques que le type prédéfini int.
Une fois cette définition réalisée, nous pouvons utiliser ce nouveau type pour définir des variables et nous
pouvons mélanger les variables de ce type avec des variables entières pour réaliser des expressions.
11.2 Structures
Une structure est un objet composé de plusieurs champs qui sert à représenter un objet réel ou un concept.
Par exemple une voiture peut être représentée par les renseignements suivants : la marque, la couleur,
l’année, etc.
11.2.1 Déclaration
Solution 1 :
99
100 CHAPITRE 11. STRUCTURES ET FICHIERS
Solution 2 :
typedef struct {
/* Définition de la structure */
} nom de la structure;
#define LONGUEUR 40
struct personne{
char nom [LONGUEUR];
char prenom [LONGUEUR];
int age;
};
struct personne p;
#define LONGUEUR 40
typedef struct {
char nom [LONGUEUR];
char prenom [LONGUEUR];
int age;
} personne;
personne p;
L’accès aux éléments d’une structure, que nous appelons aussi champs, se fait selon la syntaxe :
nom_de_variable.nom_du_champ
#include <stdio.h>
typedef struct {
char nom [40];
char prenom [20];
int age;
} personne;
int main () {
personne p;
printf("Veuillez entrer le nom de la personne:");
scanf("%s",p.nom);
return 0;
}
Un fichier représente tout ce qui est enregistré sur votre disque dur ou presque, on va dire tout ce qui porte
un nom. Il est possible de créer, de lire ou d’écrire dans des fichiers. A noter, que certains fichiers par contre
peuvent être protégés en lecture, en écriture ou les deux.
– ligne /*03*/ : on crée une variable P_FICHIER qui va pointer sur un type FILE. Sans entrer dans les
détails, le type FILE est du type structure (vu au paragraphe précédent).
– ligne /*08*/ : l’utilisateur va saisir une chaîne au clavier. Cette dernière sera stockée dans la variable
NOM_FICHIER. Supposons pour fixer les idées que l’utilisateur tape au clavier essai.txt. Le fi-
chier qui sera donc crée sur le disque dur portera ce nom.
– ligne /*11*/ : fopen va créer une sorte de lien entre le fichier du disque dur qui s’intitule essai.txt et
la variable P_FICHIER. Ainsi dans la suite, vous allez faire des opérations sur la variable P_FICHIER
et toutes ces opérations seront répercutées par le langage C au niveau du disque dur, sur le fichier
essai.txt. Dans ce cas précis, on peut faire les 3 opérations suivantes :
– P_FICHIER=fopen(NOM_FICHIER, "w"); : si le fichier essai.txt existait déjà sur le
11.3. BASES SUR LES FICHIERS 103
disque dur, il est purement et simplement écrasé puis réinitialisé à vide. Si il n’existait pas encore,
le fichier est crée sur le disque dur, pour l’instant il est vide.
– P_FICHIER=fopen(NOM_FICHIER, "r"); : si le fichier essai.txt existait déjà sur le
disque dur, il est simplement ouvert en lecture. On se positionne sur le premier caractère du fichier. Si
le fichier n’existe pas (typiquement, on s’est trompé de nom), la fonction fopen renvoie alors NULL.
– P_FICHIER=fopen(NOM_FICHIER, "a"); : si le fichier essai.txt existait déjà sur le
disque dur, il est simplement ouvert. Ensuite, on se positionne sur la fin de ce fichier, prêt à ajou-
ter quelque chose après la dernière ligne. On comprend mieux le "a" : append. Si le fichier n’existe
pas (typiquement, on s’est trompé de nom), le fichier est crée sur le disque dur, pour l’instant il est vide.
– ligne /*12*/ : il est toujours prudent de faire ce test. Le pointeur sera nul s’il y a eu un problème lors de
l’accès au fichier (nom incorrect...).
– ligne /*14*/ : sortie catastrophe, le programme s’arrête immédiatement. La valeur -1 est renvoyée au
système d’exploitation (UNIX par exemple). Il est à noter que l’usage de la fonction exit impose d’in-
clure #include <stdlib.h>.
– ligne /*23*/ : en fait, un fprintf n’est pas très différent d’un printf. La seule différence est qu’au
lieu d’être écrite sur l’écran, la chaîne NOM_PERS sera écrite dans le fichier essai.txt.
– ligne /*25*/ : on indique au programme C que l’on a fini de travailler sur le fichier essai.txt pour
l’instant. Il faut toujours penser à faire cette opération.
– ligne /*28*/ : on commence par ré-ouvrir le fichier, en lecture, cette fois-ci. Si le fopen se passe bien
(ce que l’on peut supposer !), on se positionne alors au début de la 1ère ligne du fichier.
– ligne /*33*/ : feof désigne l’abréviation de file end of file. Donc cette ligne se traduit par tant que l’on
atteint pas la fin du fichier P_FICHIER...
Enfin, voici une autre fonction qui peut se montrer très utile :
La fonction fgets lit à partir du fichier au maximum maxligne-1 caractères et les stocke dans la chaîne
de caractères ligne. La lecture s’arrête sur \n qui est alors inclus dans la chaîne. La chaîne est complétée
par \0. La fonction renvoie NULL si la fin de fichier est atteinte.
Voici un exemple de programme qui va simplement afficher le contenu du fichier essai.txt à l’écran
(lisez le puis étudiez la remarque qui le suit) :
104 CHAPITRE 11. STRUCTURES ET FICHIERS
#include <stdio.h>
char ligne[100];
FILE *p_fichier;
int main() {
p_fichier=fopen("essai.txt","r");
while (! feof(p_fichier)) {
fgets(ligne,maxligne,p_fichier);
if (! feof(p_fichier))
printf("J’ai lu:%s\n",ligne);
}
fclose(p_fichier);
}
if (! feof(p_fichier))
printf("J’ai lu:%s\n",ligne);
En fait, il est nécessaire du fait que le feof (p_fichier) sera positionné à vrai dès qu’il y aura eu une
tentative infructueuse de la fonction fgets. Ainsi, lorsque le fgets lit la dernière ligne du fichier, un
appel, dans la foulée au test feof (p_fichier) renverrait faux. Ce n’est que si l’on refait un fgets
(qui sera donc infructueux) que là, le test feof (p_fichier) renverrait vrai. Donc finalement, on voit
bien le problème, pour la toute dernière ligne, le fgets va échouer et l’instruction printf("J’ai
lu:%s\n",ligne); risque bien de renvoyer n’importe quoi !
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char nom [40];
char prenom [20];
int age;
} personne;
int main() {
FILE *P_FICHIER; /* pointeur sur FILE */
/* Créer et remplir le fichier */
P_FICHIER = fopen("essai.txt","w");
if (P_FICHIER == NULL) {
printf("\aImpossible de créer le fichier \n");
exit(-1); // Abandonner le programme
}
personne p;
printf("Veuillez entrer le nom de la personne:");
scanf("%s",p.nom);
printf("Veuillez entrer le prénom de la personne:");
scanf("%s",p.prenom);
printf("Veuillez entrer l’age de la personne:");
scanf("%d",&p.age); /* ne pas oublier le ’&’ !!! */
fprintf(P_FICHIER, "%s\n",p.nom);
fprintf(P_FICHIER, "%s\n",p.prenom);
fprintf(P_FICHIER, "%d\n",p.age);
fclose(P_FICHIER);
return 0;
}
Exercice :
A l’aide de votre éditeur préféré, créez un fichier intitulé nombres.txt. Vous y stockerez les nombres
suivants :
1
2
3
4
50
Ecrire un programme qui va lire ce fichier puis créer un second fichier intitulé doublage.txt et dont le
contenu sera le suivant :
106 CHAPITRE 11. STRUCTURES ET FICHIERS
1
1
2
2
3
3
4
4
50
50
11.4. CORRECTION DE L’EXERCICE 107
Remarque : ici, nous utilisons une variante afin de détecter la fin du fichier. En effet, il faut savoir que
fscanf n’est rien d’autre qu’une fonction qui présente la particularité de renvoyer le nombre de valeurs
correctement lues.
...
int valeur_lue;
while (fscanf(p_source,"%d",&valeur_lue)==1) { // tant que l’on
//réussit à lire une valeur
fprintf(p_destination,"%d\n", valeur_lue) ;
fprintf(p_destination,"%d\n", valeur_lue) ;
}
...
#include <stdio.h>
#include <stdlib.h>
}
fclose(p_source);
fclose(p_destination);
}
int main () {
creer_fichier_double ("nombres.txt","doublage.txt") ;
return 0 ;
}
Voir sur le campus numérique l’exercice final de ce chapitre (exercice dont la solution n’est pas fournie).
Chapitre 12
Debugger un programme
L’objectif de ce chapitre est de vous permettre de vous débrouiller tout seul lorsque vous rencontrez un
bug...
Tout d’abord on fera la différence entre une erreur de compilation et une erreur à l’exécution (appelée
bug).
- erreur de compilation : vous tapez gcc -o essai essai.c pour le programme suivant :
#include <stdio.h>
int main () {
floatt f ;
return 0 ;
}
109
110 CHAPITRE 12. DEBUGGER UN PROGRAMME
#include <stdio.h>
int main () {
float f=1/0;
return 0 ;
}
une fois compilé, vous le lancez par ./essai et l’ordinateur affiche un message du type : Floating point
exception. En effet, il n’aime pas trop les divisions par zéro ;-)
#include <stdio.h>
int main () {
printf ("Je suis ici") ;
while (1)
;
return 0 ;
}
Rien ne s’affiche ? ! En fait cela provient du fait que les entrées sorties sont optimisées en Langage C.
Ainsi, lorsque vous faites un printf vous pouvez penser que ce dernier apparaîtra directement à l’écran.
Il n’en est rien ! Dans un souci d’optimisation, ce qui doit être affiché à l’écran atterrit temporairement dans
une zone mémoire de l’ordinateur. Cette zone mémoire est affichée à l’écran soit lorsqu’elle est pleine soit
lorsque l’on place un " \n " dans le printf :
Essayez :
#include <stdio.h>
int main () {
printf ("Je suis ici\n") ;
while (1)
;
return 0 ;
}
Essayez ensuite :
12.1. LA CHASSE AUX BUGS 111
#include <stdio.h>
int main () {
while (1) {
printf ("*");
usleep(1000); /* pause 1000 micro secondes...*/
}
return 0 ;
}
Ici, quelque chose s’affiche car la zone mémoire est pleine au bout d’un instant...
Nous laissons de côté l’usage des debuggers " élaborés " tels que ddd par exemple (dans l’immédiat). La
démarche pour corriger un bug est toujours la même :
1˚) localiser où est le bug. Par exemple, dans le programme suivant qui n’affiche rien pour la raison précé-
demment évoquée (le problème de l’" \n ") :
#include <stdio.h>
int main () {
int i ;
for (i=0 ; i<100 ; i++)
printf ("i=%d",i) ;
while (1)
;
return 0 ;
}
Le rajout des " mouchards " (un mouchard est simplement un printf) dans le programme ci-dessous :
#include <stdio.h>
int main () {
int i ;
printf ("1˚) Je suis ici\n") ; /* 1 er mouchard */
while (1)
;
return 0 ;
}
2˚) Il se peut que la phase 1˚ précédente vous ait aidé à localiser l’endroit où " ça coince " mais que vous
112 CHAPITRE 12. DEBUGGER UN PROGRAMME
#include <stdio.h>
int main () {
int i,j;
i=0;
j=0;
printf ("1˚) Je suis ici\n") ;
if ((i==0) && (i=j)) {
printf ("2˚) Je suis ici\n");
...
}
return 0 ;
}
Vous êtes persuadé que l’ordinateur devrait afficher 2˚) Je suis ici or il n’en est rien ? ! Vous en êtes à
invoquer un bug dans le compilateur ? ! La démarche alors est toujours la même : l’ordinateur ne fait pas ce
que vous voulez et ce à un endroit précis que vous avez localisé. Vous allez placer des mouchards jusqu’à
voir précisément le contenu de quelle(s) variable(s) pose problème :
#include <stdio.h>
int main () {
int i,j;
i=0;
j=0;
printf ("1˚) Je suis ici\n");
printf("i=%d j=%d\n",i,j); // AJOUT D’UN MOUCHARD ICI
if (i==0) && (i=j) { /* (i=j) => i vaudra 0, et 0=faux !!! */
printf ("2˚) Je suis ici\n");
...
}
return 0 ;
}
Avant d’attaquer la suite, lisez juste les deux paragraphes Conversions de type et Un usage très utile des
conversions de type à la page 119, revenez ensuite à la suite de ce chapitre...
Remarque : comme toujours, les solutions des exercices qui vont suivre se trouvent à la fin du chapitre...
cependant, cherchez un peu avant de vous y reporter.
Copiez le programme suivant tel quel, puis débuggez le. Ce programme est censé afficher ceci à l’écran :
i=0
i=1
...
12.1. LA CHASSE AUX BUGS 113
i=9
La moyenne vaut : 4.50000
#include <stdio.h>
int main () {
int i, somme;
for (i=0 ; i<10 ; i++);
printf ("i=%d\n",i) ;
somme = somme + i;
printf("La moyenne vaut:%d",somme/i) ;
return 0 ;
}
Vous rencontrez ce type d’erreur lorsque votre programme accède à une zone de la mémoire qui lui est
interdite : vous êtes sur un segment de la mémoire sur lequel vous n’avez pas le droit de travailler. Le
programme suivant peut provoquer de telles erreurs :
#include <stdio.h>
int main () {
int i=1;
scanf("%d",i);
return 0 ;
}
Soit vous voyez tout de suite l’erreur... soit vous ajoutez des mouchards :
#include <stdio.h>
int main () {
int i=1;
printf ("1˚) Je suis ici\n") ;
scanf("%d",i);
printf ("2˚) Je suis ici\n");
return 0 ;
}
qui vous permettront rapidement de voir que le problème provient de la ligne scanf("%d",i) car seul
le message "1˚) Je suis ici" s’affiche et pas le message "2˚) Je suis ici" 1
1. P.S : n’oubliez pas les \n dans vos printf pour la raison évoquée plus haut... ! ! ! ! ! ! ! ! !
114 CHAPITRE 12. DEBUGGER UN PROGRAMME
Le problème vient donc de i qui vaut 1... le scanf va tenter de stocker ce que vous venez d’entrer au
clavier à l’adresse mémoire 1 ! Cette dernière est traditionnellement réservée au système d’exploitation,
d’où l’erreur...
Il en va de même du programme ci-dessous qui pourrait poser des problèmes car on sort des bornes du
tableau :
! ! ! : On ne vous demande pas de tester ce programme... juste de vous rappeler qu’on doit accéder aux
bornes d’un tableau entre les indices 0 et TAILLE-1...
#include <stdio.h>
#define TAILLE 10
. . .
. . .
int main () {
. . .
int i;
int tab[TAILLE];
. . .
. . .
return 0 ;
}
Ce debugger est très efficace pour trouver les erreurs de segmentation. Copiez le programme suivant :
#include <stdio.h>
#include <string.h>
int main () {
int * p;
p=NULL;
*p=123;
printf("\n je suis ici...\n");
}
A présent, faites :
gcc -o essai essai.c -g
ddd essai
puis fermez les petites fenêtres " parasites " qui apparaissent au lancement de ddd puis cliquez sur le
bouton run (il faut parfois chercher un peu dans les menus)...
Maintenant, comme expliquer cette erreur ? Lorsque vous faites p=NULL;, vous placez donc la valeur
0 dans cette variable. Ce qui revient à dire que p pointe sur la première case de la mémoire. Si dans la
foulée vous faites *p=123; celà revient à vouloir mettre la valeur 123 dans la case 0 de la mémoire.
Le problème, c’est que cette case est utilisée par le système d’exploitation, d’où l’erreur car le système
d’exploitation va vous interdire de modifier cette case.
Soit le programme suivant qui doit afficher la chaîne de caractères chaîne à l’envers, et ce caractère par
caractère :
#include <stdio.h>
#include <string.h>
int main () {
int i;
char chaine[]="! euggubed tse emmargorp el";
A vous de :
– 1˚) retaper ce dernier,
– 2˚) débugguer ce dernier...
#include <stdio.h>
int main () {
int i;
int i1,i2 ;
char c1,c2;
On voit donc que l’\n subsiste du premier caractère et pollue la variable c2. en effet, quand vous faites
un :
scanf ("%c",&c1)
vous demandez à l’ordinateur de lire un unique caractère. Du coup l’\n restera de côté (pour l’instant).
Une solution pour remédier à ce travers consiste à utiliser systématiquement la fonction gets(chaine)
à la place de chaque scanf.
donc, en cas de problème avec des \n - remplacer tous les scanf ("%d", ....) ... scanf("%c",....) par du
gets
Cette dernière solution est normalement celle qui vous fera gagner le plus de temps.
/* ancienne version: */
int i;
char c;
printf("Entrez un caractère:");
scanf ("%c",&c);
printf("Entrez un chiffre:");
scanf("%d",&i);
Rappel : la fonction sscanf que nous utilisons ci-dessous a été vue à la page 94.
/* nouvelle version: */
int i;
char c;
char chaine[100];
printf("Entrez un caractère:");
gets(chaine);
c=chaine[0];
printf("Entrez un chiffre:");
gets(chaine);
sscanf(chaine,"%d",&i) ;
12.1.9 Solutions
Bonne chasse...
#include <stdio.h>
int main () {
int i, somme=0;
for (i=0 ; i<10 ; i++) {
printf ("i=%d\n",i) ;
somme = somme + i;
}
printf("La moyenne vaut:%f",(float) somme/i) ;
return 0 ;
}
#include <stdio.h>
#include <string.h>
int main () {
int i;
char chaine[]="! euggubed tse emmargorp el";
12.1.10 A retenir
On retiendra que pour trouver un bug, ça n’est pas si compliqué que cela :
1. on s’aperçoit que le programme n’a pas le comportement que l’on veut,
2. on cherche à isoler le plus précisément possible l’endroit où le choix incorrect a pu se produire. Par
exemple, si le programme affiche 1 alors qu’il devrait afficher 111 on fait afficher la/les variables
qui sont impliquées dans cet affichage,
3. ! ! ! ne pas oublier les \n dans les printf :
4. poursuivre le rajout de mouchards jusqu’à comprendre où et pourquoi le programme ne fait pas ce
qu’il devrait faire.
Enfin, si vous avez une erreur de segmentation, tentez d’utiliser le debugger ddd (ne pas oublier de com-
piler avant avec l’option -g) :
gcc -o essai essai.c -g
puis tapez :
ddd essai
Chapitre 13
Complements
Pour convertir un type en un autre, on fait ce que l’on appelle un "cast". Imaginons que l’on souhaite
convertir un nombre du type float en un entier du type int. Voici comment procéder à l’aide de l’usage
des parenthèses "(" et ")" :
int main () {
float f;
int i;
f=3.1415;
i=(int) f; /* résultat dans i: 3 */
/* donc la partie décimale */
/* est perdue... */
return 0 ;
}
Le programme suivant :
119
120 CHAPITRE 13. COMPLEMENTS
int main () {
printf("Résultat : %f",3/4);
return 0 ;
}
afficherait Résultat : 0 ! ! ! En effet, l’opérateur "/" réaliserait ici une division entière de l’entier 3 par
l’entier 4 qui vaudrait donc 0.
On écrit 3.0 à la place de 3 ce qui va forcer le programme à faire une division en flottants :
int main () {
printf("Résultat : %f",3.0/4);
return 0 ;
}
int main () {
printf("Résultat : %f",(float)3/4);
return 0 ;
}
int main () {
printf("Résultat : %f",(float)(3/4));
return 0 ;
}
car on ferait tout d’abord la division de 3 par 4 avec pour résultat l’entier 0, puis on convertit ce dernier en
un flottant pour obtenir à l’affichage :
Résultat : 0.0
13.3. LA FONCTION PUTCHAR 121
Le programme suivant :
int main () {
char c;
c=’A’;
putchar(c);
return 0 ;
}
est équivalent à :
int main () {
char c;
c=’A’;
printf("%c",c);
return 0 ;
}
donc on peut voir que la fonction putchar affiche un unique caractère à l’écran. Elle est donc moins
puissante que printf et pourrait donc être oubliée (en première lecture), en revanche elle est très utilisée
par la communauté...
La fonction malloc de la bibliothèque stdlib.h nous aide à réserver un bloc mémoire au cours de
l’exécution du programme.
malloc(N)
Exemple : supposons que nous ayons besoin d’un bloc en mémoire pour un texte de 4000 caractères.
Nous disposons d’un pointeur tab sur char, c’est-à-dire : char *tab. Alors l’instruction : tab =
122 CHAPITRE 13. COMPLEMENTS
malloc(4000); fournit l’adresse d’un bloc de 4000 octets libres et l’affecte à tab.
S’il n’y a plus assez de mémoire, tab obtient la valeur zéro (rappel : c’est la même chose que NULL). Si
nous voulons réserver de la mémoire pour des données d’un type dont la grandeur varie d’une machine
à l’autre, nous avons besoin de la grandeur effective d’une donnée de ce type. L’opérateur sizeof nous
aide alors à préserver la portabilité du programme.
Après la déclaration,
short tab1[10];
char tab2[5][10];
sizeof(tab1) s’évalue à 20
sizeof(tab2) s’évalue à 50
sizeof(double) s’évalue à 8 (généralement... !)
sizeof("bonjour") s’évalue à 8 (pensez à l’\0)
sizeof(float) s’évalue à 4 (généralement... !)
Nous voulons réserver de la mémoire pour x valeurs du type int la valeur de x est lue au clavier :
int x;
int *pNum;
printf("Introduire le nombre de valeurs :");
scanf("%d", &x);
pNum = malloc(x*sizeof(int));
En fait, étant donné que malloc renvoie un pointeur sur une adresse, on s’assure simplement (par la
conversion de type, ou "cast") que cette adresse est bien un pointeur sur un entier, d’où l’usage de (int
*).
Autre exemples :
13.4. ALLOCATION DYNAMIQUE DE MÉMOIRE 123
char * pointeur_sur_chaine;
float * pointeur_sur_float;
Exemple : le programme suivant lit 10 phrases au clavier, recherche des blocs de mémoire libres assez
grands pour la mémorisation et passe les adresses aux composantes du tableau texte[]. S’il n’y a pas
assez de mémoire pour une chaîne, le programme affiche un message d’erreur et interrompt le programme
avec le code d’erreur -1. Nous devons utiliser une variable d’aide intro comme zone intermédiaire (non
dynamique). Pour cette raison, la longueur maximale d’une phrase est fixée à 500 caractères :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
/* Déclarations */
char intro[500];
char *texte[10];
int i;
/* Traitement */
for (i=0; i<10; i++) {
gets(intro);
/* Réservation de la mémoire */
texte[i] = malloc(strlen(intro)+1);
/* S’il y a assez de mémoire, ... */
if (texte[i]!=NULL)
/* copier la phrase à l’adresse */
/* fournie par malloc, ... */
strcpy(texte[i],intro);
else {
/* sinon quitter le programme */
/* après un message d’erreur. */
printf("ERREUR: Pas assez de mémoire \n");
exit(-1);
}
}
return 0;
}
Si nous n’avons plus besoin d’un bloc de mémoire que nous avons réservé à l’aide de malloc, alors nous
pouvons le libérer à l’aide de la fonction free de la bibliothèque <stdlib.h>.
free( <Pointeur> ) : libère le bloc de mémoire désigné par le <Pointeur>; n’a pas d’effet si le
pointeur a la valeur zéro.
Attention !
124 CHAPITRE 13. COMPLEMENTS
* La fonction free peut aboutir à un désastre si on essaie de libérer de la mémoire qui n’a pas été allouée
par malloc.
* La fonction free ne change pas le contenu du pointeur ; il est conseillé d’affecter la valeur NULL au
pointeur immédiatement après avoir libéré le bloc de mémoire qui y était attaché.
* Si nous ne libérons pas explicitement la mémoire à l’aide free, alors elle est libérée automatiquement à
la fin du programme.
Exemple :
char * pointeur_sur_chaine;
#include <stdio.h>
int main() {
int i; /* Ligne 0 */
printf ("%d\n",i); /* Ligne 1 */
int * p; /* Ligne 2 */
* p = 12; /* Ligne 3 */
return 0;
}
A la ligne 1, le Langage C se fera un plaisir de vous afficher n’importe quoi car la variable i n’a pas été
initialisée.
La ligne 3 a de fortes chances de planter le programme ! La raison en est que p n’a pas été initialisé non
plus. En particulier si on faisait un printf("%p",p), on pourrait obtenir n’importe quoi ! En effet, p,
n’est qu’un pointeur, c’est-à-dire une variable contenant une valeur. Bien entendu, dans la pratique, cette
valeur n’est pas quelconque : elle désigne bien souvent l’adresse d’une variable.
Donc le plantage sera dû au fait que p pointe n’importe où et que vous essayez d’initialiser ce n’importe
où.
13.6. UN EXEMPLE POUR SE RAFRAÎCHIR LA MÉMOIRE SUR L’UTILITÉ DES POINTEURS125
Vous savez qu’une fonction n’est pas capable de modifier ses arguments :
Exemple :
main () {
int i =2;
calcule_double(i);
printf("i vaut à présent :%d",i); /* i vaut toujours 2 !!! */
}
main () {
int i =2;
calcule_double(&i);
printf("i vaut à présent :%d",i); /* i vaut bien 4 !!! */
}
i pointeur_int
------------------------------
| 2 | |10 |
------------------------------
10 20
#include <stdio.h>
void saisie (int *pointeur);
int main() {
int n;
saisie(&n);
printf("n vaut:%d",n);
return 0;
}
Imaginons que pointeur se trouve en mémoire logé à l’adresse 100 et n à l’adresse 1000 :
13.8. UN MOT SUR LES WARNINGS 127
pointeur n
------------------------------
| | | |
------------------------------
100 1000
1. int n; déclaration de n : n vaut n’importe quoi vu qu’il n’a pas été initialisé !
4. printf("Entrez un nombre:");
5. scanf (); : la fonction scanf va stocker ce que l’utilisateur tape au clavier à partir de l’adresse
mémoire 1000.
Lorsque vous compilez vous pouvez obtenir des erreurs ou des warnings ou parfois les deux. Les erreurs,
vous savez ce que c’est par exemple lorsque vous écrivez floattt au lieu de float par exemple. Dans
ce cas le compilateur ne génèrera pas de fichier exécutable (par exemple, après avoir tapé gcc -o essai
essai.c, le fichier essai ne sera pas crée sur le disque dur). Pour les warnings, le fichier essai sera
crée, mais le compilateur a souhaité vous alerter sur une erreur possible.
Il est important de comprendre qu’en langage C, un warning équivaut à une "erreur larvée". Ainsi, vous
pouvez effectivement exécuter votre programme et ce malgré les warnings mais le problème c’est que vous
risquez de "payer" cela par la suite. Un exemple :
128 CHAPITRE 13. COMPLEMENTS
#include <stdio.h>
#include <stdlib.h>
/****************************************/
void Affiche_matrice(int matrice[9][9]); / *prototype */
/****************************************/
...
int main() {
int i;
int matrice[9][9];
...
Affiche_matrice(matrice[9][9]); /* ligne A */
}
...
/****************************************/
void Affiche_matrice(int matrice[9][9]) {
/****************************************/
int i,j;
La ligne A vous renverra un warning... Pourquoi ? Prenons le cas où la ligne A serait remplacée par :
Affiche_matrice(matrice[1][1]); /* ligne B */
Dès lors, on voit bien le problème, la ligne B ne passe pas tout le tableau matrice à la fonction
Affiche_matrice, mais uniquement la case [1][1]. La solution consisterait donc à écrire :
Affiche_matrice(matrice); /* ligne C */
Conclusion : considérez tous les warnings comme des erreurs et éliminez les (cela ne concerne cependant
pas les warnings provenant de l’usage de gets).
Chapitre 14
Code Ascii
... ...
32: 33: ! 34: " 35: # 36: $ 37: % 38: & 39: ’
40: ( 41: ) 42: * 43: + 44: , 45: - 46: . 47: /
48: 0 49: 1 50: 2 51: 3 52: 4 53: 5 54: 6 55: 7
56: 8 57: 9 58: : 59: ; 60: < 61: = 62: > 63: ?
64: @ 65: A 66: B 67: C 68: D 69: E 70: F 71: G
72: H 73: I 74: J 75: K 76: L 77: M 78: N 79: O
80: P 81: Q 82: R 83: S 84: T 85: U 86: V 87: W
88: X 89: Y 90: Z 91: [ 92: \ 93: ] 94: ^ 95: _
96: ‘ 97: a 98: b 99: c 100: d 101: e 102: f 103: g
104: h 105: i 106: j 107: k 108: l 109: m 110: n 111: o
... ...
129
130 CHAPITRE 14. CODE ASCII
Chapitre 15
Annexes utiles
Format Conversion en
%d int
%f float
%f double
%c char
%s char*
Format Conversion en
%d int
%f float
%lf double
%c char
%s char*
131
132 CHAPITRE 15. ANNEXES UTILES
Le but de ce chapitre est de vous montrer quelques problèmes et leur solution. Ainsi en lisant des pro-
grammes finis, on peut aussi apprendre pas mal de choses ;-)
#include <stdio.h>
int main () {
float francs;
francs=0;
while (francs<=10) {
printf("%.1f francs = %.2f euros\n",francs,francs/6.559);
francs=francs+0.5;
}
return 0;
}
133
134 CHAPITRE 16. QUELQUES EXEMPLES DE PROGRAMMES
Voici un programme dont le but est de générer 1000 nombres aléatoires et de compter combien son pairs.
en théorie, on devrait se rapprocher de 50 %
16.3. INVERSER LES ÉLÉMENTS D’UN TABLEAU 135
#include <stdio.h>
#include <stdlib.h> /* pour les valeurs aléatoires */
#include <time.h>
int main () {
int nb_hasard = 0;
int i;
i=1;
do {
nb_hasard = rand ();
if (nb_hasard % 2==0) /* c’est un nombre pair */
nb_pairs=nb_pairs+1;
else
nb_impairs=nb_impairs+1;
i++;
}
while (i<=1000);
return 0;
}
#include <stdio.h>
#define TAILLE 10
int main () {
int i,j;
return 0;
}
136 CHAPITRE 16. QUELQUES EXEMPLES DE PROGRAMMES
#include <stdio.h>
#define TAILLE 10
int main () {
int i,j;
i=0;
j=TAILLE-1;
while (i<j) {
i++;
j--;
printf("i=%d j=%d\n",i,j);
}
return 0;
}
Voici un programme qui remplit un tableau de 10 cases par des valeurs saisies au clavier. Dans un se-
cond temps, le programme inverse tout les éléments du tableau. Ainsi, si au départ on avait les 10 valeurs
suivantes dans le tableau : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, à l’arrivée, nous aurons le tableau
suivant : 10, 9, 8, 7, 6, 5, 4, 3, 2, 1. Idée : échanger les éléments du tableau à l’aide de
deux indices qui parcourent le tableau en commençant respectivement au début et à la fin du tableau et qui
se rencontrent en son milieu. Tandis que l’indice i va démarrer au début du tableau, l’indice j va démarrer
à la fin.
16.4. AFFICHAGE D’UNE TABLE DE MULTIPLICATION 137
#include <stdio.h>
#define TAILLE 10
int main() {
/* Déclarations */
int tab[TAILLE]; /* tableau donné */
int i,j; /* indices courants */
int aide; /* pour l’échange */
/* Inverser le tableau */
for (i=0, j=TAILLE-1 ; i<j ; i++,j--) {
/* Echange de tab[i] et tab[j] */
aide = tab[i];
tab[i] = tab[j];
tab[j] = aide;
}
/* Affichage du tableau inversé */
printf("Tableau résultat :\n");
for (i=0; i<TAILLE; i++)
printf("%d ", tab[i]);
printf("\n");
return 0;
}
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
#include <stdio.h>
int main () {
int ligne, colonne;
for (ligne=1;ligne<=10;ligne++) {
for (colonne=1;colonne<=10;colonne++)
printf("%4d",ligne*colonne); /* affichage sur 4 caractères */
printf("\n");
return 0;
}
Voici un exemple de fonction qui renvoie le maximum d’un tableau qui lui est passé en paramètre. Voir le
commentaire en dessous.
16.6. EFFECTUER UN TRI 139
#include <stdio.h>
int Max_Tableau (int Tab[], int taille);
int main() {
int T1[] = {1,10,4,5,-7}, T2[] = {2,1,14,3} ;
printf("Maximum de T1 : %d\n", Max_Tableau(T1,5) );
printf("Maximum de T2 : %d\n", Max_Tableau(T2,4) );
return 0;
}
Rappel : en fait, il n’y a que l’adresse de la 1ère case du tableau qui est passée en paramètre...
Supposons que l’on dispose d’un tableau tab qui contient 10 valeurs. On souhaite trier ce tableau. Une
solution toute simple consite à faire un passage sur le tableau et comparer la case d’indice n avec celle
d’indice n+1. Si celle se trouvant à l’indice n contient une valeur plus grande que celle de l’indice n+1,
alors on inverse les 2 valeurs, et ainsi de suite. Voici un exemple sur un tableau de 10 cases :
Tableau initial :
Indice de la case : 0 1 2 3 4 5 6 7 8 9
Valeur stockée : 12 10 4 5 6 7 8 9 10 1
0 1 2 3 4 5 6 7 8 9
10 12 4 5 6 7 8 9 10 1
0 1 2 3 4 5 6 7 8 9
10 4 12 5 6 7 8 9 10 1
...
0 1 2 3 4 5 6 7 8 9
10 4 5 6 7 8 9 10 1 12
On peut constater que le tableau est mieux trié, mais ça n’est pas encore parfait. Dans la pratique, pour
un tableau de n cases, si on fait n passages comme celui-ci, on peut montrer que le tableau sera trié à
l’arrivée...
#include <stdio.h>
#define TAILLE 10
int main () {
int tab[TAILLE];
int i,j;
/* tri du tableau */
int temp;
for (i=0; i<TAILLE; i++)
for (j=0; j<TAILLE-1; j++)
if (tab[j]>tab[j+1]) { /* échange de valeurs */
temp=tab[j];
tab[j]=tab[j+1];
tab[j+1]=temp;
}
return 0;
}
Enfin, voici le même programme mais cette fois-ci avec des fonctions. Le principal intérêt des fonctions
est de rendre le programme plus clair et d’alléger le main. Enfin, les fonctions sont plus faciles à être
réutilisées par la suite :
16.6. EFFECTUER UN TRI 141
#include <stdio.h>
#define TAILLE 10
/*****************************/
void init_tableau (int tab[]);
void tri_tableau (int tab[]);
void affiche_tableau(int tab[]);
/*****************************/
int main () {
int tab[TAILLE];
int i,j;
int temp;
init_tableau(tab);
tri_tableau(tab);
affiche_tableau(tab);
return 0;
}
/* tri du tableau */
for (i=0; i<TAILLE; i++)
for (j=0; j<TAILLE-1; j++)
if (tab[j]>tab[j+1]) { /* échange de valeurs */
temp=tab[j];
tab[j]=tab[j+1];
tab[j+1]=temp;
}
}
}
}
16.7.1 Historique
John Conway était un mathématicien de l’Université de Cambridge. Très prolifique en matière de jeux
mathématiques, il décrivit en 1970 le jeu de la vie, visant à modéliser d’une façon simple l’évolution
d’organismes vivants.
Le jeu de la vie évolue normalement sur un damier infini. Chaque case est occupée par une cellule qui peut
être vivante ou morte. À chaque génération, chaque cellule peut naître, mourir, ou rester dans son état. Les
règles qui permettent de passer d’une génération à l’autre sont précises et ont été choisies avec soin pour
que l’évolution des organismes soit intéressante et imprévisible. En premier lieu, notons que sur un damier
infini, chaque case a exactement 8 voisins. Les règles données par J. Conway sont les suivantes :
– Une cellule ayant exactement 2 ou 3 voisins vivants survit à la génération suivante.
– Une cellule ayant au moins 4 cellules voisines vivantes meurt d’étouffement à la génération suivante.
– Une cellule ayant au plus une cellule voisine vivante meurt d’isolement à la génération suivante.
– Sur une case vide ayant exactement 3 voisins vivants, une cellule naîtra à la génération suivante.
Notons que c’est l’ensemble de la génération actuelle qui doit être pris en compte pour l’établissement de
l’état des cellules à la génération suivante.
Voici un exemple de figure sur un petit damier. Les cellules qui devront mourir à la génération suivante
sont grisées :
Générations : 1 2 3 4 5
De notre côté, afin de simplifier, nous n’allons considérer que 4 voisins pour une cellule : la cellule de
droite, celle de gauche, celle du dessous en enfin celle du dessus.
Par ailleurs, nous allons considérer que toutes les cellules sont stockées dans une matrice m de taille n ∗ n.
Pour une case m[i][j], on définit quatre voisins :
m[i-1][j], m[i+1][j], m[i][j-1] et m[i][j+1].
Enfin, on notera que le problème peut être simplifié en prenant une matrice (n + 2) ∗ (n + 2) dont les
contours contiennent des cellules mortes : en effet, comme les cellules mortes n’interviennent pas dans la
décision de modification de l’état d’une case, cette bordure rajoutée artificiellement permettra de ne pas
faire de traitement particulier pour les cases de bordure ou pour les cases d’angle de la matrice n ∗ n, qui
sans cela ne posséderaient que deux ou trois voisins chacune.
On se propose donc de découper le programme comme ceci (lisez ce programme, qui n’est pas encore
complet, ainsi que les explications ci-dessous) :
144 CHAPITRE 16. QUELQUES EXEMPLES DE PROGRAMMES
/***************************************/
/* JEU DE LA VIE */
/***************************************/
#include <stdio.h>
#include <stdlib.h>
#define TAILLE_SOUS_MATRICE 7
#define TAILLE_SUR_MATRICE 9
/* Taille de la matrice contenant */
/* les cellules + 2 pour la bordure */
/****************************************/
/******* P R O T O T Y P E S ********/
/****************************************/
int main() {
int i;
int nbr_cycles;
int matrice[TAILLE_SUR_MATRICE] [TAILLE_SUR_MATRICE ];
nous laissons de côté, pour l’instant le fait que nous n’avons pas écrit à la place :
on pourrait se dire que la fonction reçoit la matrice T AILLE_SU R_M AT RICE∗T AILLE_SU R_M AT RICE
en entrée et qu’à la sortie elle ne sera pas modifiée. Dès lors, dans la fonction Init, l’instruction matrice[i][j]
= ... serait vaine. En fait ce qui est passé à la fonction Init n’est autre que l’adresse mémoire de la
matrice, en gros &matrice[0][0]. Dès lors la fonction ne pourra effectivement pas modifier l’adresse
de matrice[0][0] en revanche, elle pourra modifier matrice[0][0], matrice[1][0]...
Avant de voir la correction complète de cet exercice, on notera que lors d’un cycle de vie, on ne doit pas
modifier la matrice de vie courante au fur et à mesure, elle doit être modifiée d’un coup. Il est donc néces-
saire de passer par 2 étapes :
1˚) construire une matrice intermédiaire qui contient le nombre de voisins pour chaque cellule.
2˚) parcourir cette matrice en une passe et modifier la matrice contenant les cellules vivantes.
/***************************************
/* JEU DE LA VIE
/***************************************/
#include <stdio.h>
#include <stdlib.h>
#define TAILLE_SOUS_MATRICE 7
/* On peut avoir 7 * 7 cellules vivantes */
#define TAILLE_SUR_MATRICE 9
/* On place une bordure autour qui facilite la " vie " */
/* au programmeur... */
int main( ) {
int i;
int nbr_cycles;
int matrice[TAILLE_SUR_MATRICE] [TAILLE_SUR_MATRICE ];
char s[2];
init(matrice);
printf("La population au départ : \n");
affiche_matrice(matrice);
printf("Pressez sur ENTER pour continuer...\n");
gets(s);
/****************************************/
/* Initialisation de la matrice */
void init(int matrice [][TAILLE_SUR_MATRICE ]) {
/****************************************/
int i,j;
/****************************************/
/* Calcul du nombre de voisins vivants */
int nombre_voisins (int matrice[][TAILLE_SUR_MATRICE ],
int ligne, int colonne) {
/****************************************/
return matrice[ligne-1][colonne] +
matrice[ligne+1][colonne]+
matrice[ligne][colonne-1] +
matrice[ligne][colonne+1];
}
16.7. LE JEU DE LA VIE 147
/****************************************/
/* Correspond à l’étape n+1 */
void mise_a_jour(int matrice[ ][TAILLE_SUR_MATRICE ]) {
/****************************************/
int i,j;
int nbr_voisins;
int matrice_densite[TAILLE_SOUS_MATRICE][ TAILLE_SOUS_MATRICE];
/* matrice qui comptabilise le nombre de voisins */
/* et cela, case par case */
/****************************************/
/* Tracé d’une ligne */
void ligne(int largeur) {
/****************************************/
int i;
for(i=0; i<largeur; i++)
printf("+-");
printf("+\n");
}
148 CHAPITRE 16. QUELQUES EXEMPLES DE PROGRAMMES
Chapitre 17
En 2ème lecture...
149
150 CHAPITRE 17. EN 2ÈME LECTURE...
#include <stdio.h>
typedef struct {
char nom [40];
char prenom [20];
int age;
} personne;
int main () {
personne p;
personne * pointeur_sur_une_personne;
printf("Veuillez entrer le nom de la personne:");
scanf("%s",p.nom);
pointeur_sur_une_personne=&p;
printf("Voici les caractéristiques de cette personne:\n");
printf("nom=%s\n",(*pointeur_sur_une_personne).nom);
return 0;
}
On notera que cette seconde écriture (plus pratique à l’usage) repose sur une flèche qui est construite avec
le signe moins (-) et le signe supérieur (>).
En première lecture, on pourrait dire que ce type de warning est " normal ". Prenons un exemple :
char chaine[10];
gets(chaine) ;
Supposons que votre programme soit utilisé sur internet et qu’un utilisateur malveillant entre une chaîne
de caractères plus grande que 10 caractères, par exemple : "ABCDEFGHIJKLMONPQR". Dans ce cas,
à l’exécution, votre programme va recopier à partir de l’adresse de la variable chaine les caractères A,
B,.... Dès lors, les zones mémoires qui suivent la variable chaine seront écrasées. Ceci explique que le
compilateur vous indique un warning...
char buffer[128];
fgets(buffer, 128, stdin);
Remarque : fgets présente un petit défaut, elle vous place un \n à la fin de la chaîne qui a été lue.
17.4 Stdout
Il faut savoir que stdout désigne la même chose que l’écran : standardoutput.
int i=123;
printf("%d",i);
int i=123;
fprintf(stdout,"%d",i);
Rappelez-vous qu’il est plus prudent de placer tous les prototypes de fonctions que vous appelez juste
en dessous de la suite des #include. Un prototype représente l’entête d’une fonction ; exemple (ligne
ci-dessous) :
#include <stdio.h>
int main () {
int j;
printf("Entrez une valeur:");
scanf("%d",&j);
printf("Résultat de la fonction mystere:%f\n",mystere(j)); // (ligne B)
}
#include <stdio.h>
int main () {
int j;
printf("Entrez une valeur:");
scanf("%d",&j);
printf("Résultat de la fonction mystere:%f\n",mystere(j)); // (ligne B)
}
au moment où le programme rencontre l’appel à la fonction mystere (ligne B), le compilateur rencontre
pour la première fois cette fonction. Or il ne sait pas ce que fait cette fonction et a fortiori il ignore le type
du résultat qui sera renvoyé. Dans ce cas, le compilateur suppose à tort que ce résultat sera du type int
(qui est le type par défaut) ce qui risque de poser des problèmes dans la suite quand il s’apercevra qu’en
fait, la fonction renvoyait un float.
Ce type par défaut explique pourquoi on peut se permettre d’écrire un main comme ceci :
main () {
...
}
int main () {
...
}
Le langage C possède une paire d’opérateurs un peu exotiques qui peut être utilisée comme alternative
à if - else et qui a l’avantage de pouvoir être intégrée dans une expression. L’expression suivante :
<expr1> ? <expr2> : <expr3> est traduite comme ceci :
* Si <expr1> fournit une valeur différente de zéro, alors la valeur de <expr2> est fournie comme résul-
tat.
17.7. CHOIX MULTIPLE 153
* Si <expr1> fournit la valeur zéro, alors la valeur de <expr3> est fournie comme résultat.
if (a>b)
maximum=a;
else
maximum=b;
maximum = (a > b) ? a : b;
Employé de façon irréfléchi, l’opérateur ternaire peut nuire à la lisibilité d’un programme, mais si on
l’utilise avec précaution, il fournit des solutions très élégantes.
Exemple
La déclaration char JOUR[7][9] réserve l’espace mémoire pour 7 mots contenant 9 caractères (dont
8 caractères significatifs, c’est à dire sans \0), voir figure.
154 CHAPITRE 17. EN 2ÈME LECTURE...
Par ailleurs, lors de la déclaration, il est possible d’initialiser toutes les cases du tableau par des chaînes de
caractères constantes :
char JOUR[7][9]={"lundi","mardi","mercredi","jeudi",
"vendredi","samedi","dimanche"};
Voir figure :
17.8. TABLEAUX DE CHAÎNES DE CARACTÈRES 155
Il est possible d’accéder aux différentes chaînes de caractères d’un tableau, en indiquant simplement la
ligne correspondante.
affichera la phrase : Aujourd’hui, c’est mercredi ! Des expressions comme JOUR[i] repré-
sentent l’adresse du premier élément d’une chaîne de caractères.
Cette dernière remarque est très pratique pour résoudre l’exercice suivant :
Exercice : on va écrire un programme qui lit un verbe régulier en "er" au clavier et qui affiche la conjugaison
au présent de l’indicatif de ce verbe. Contrôlez s’il s’agit bien d’un verbe en "er" avant de conjuguer.
Exemple :
Verbe : fêter
je fête
tu fêtes
il fête
nous fêtons
vous fêtez
ils fêtent
Vous utilisez deux tableaux de chaînes de caractères : SUJ pour les sujets et TERM pour les terminaisons.
Solution possible :
156 CHAPITRE 17. EN 2ÈME LECTURE...
#include <stdio.h>
#include <string.h>
int main() {
/* Déclarations */
/* Sujets et terminaisons */
char SUJ[6][5] = {"je","tu","il","nous","vous","ils"};
char TERM[6][5] = {"e","es","e","ons","ez","ent"};
char verbe[20]; /* chaîne contenant le verbe */
int longueur; /* longueur de la chaîne */
int i; /* indice courant */
Nous allons à présent examiner les liens très étroits qu’il y a entre pointeurs et tableaux. En fait, on va
voir qu’à chaque fois que vous manipulez des tableaux, comme par exemple à la ligne 1 du programme
ci-dessous :
int tab[10]={1,2,3,4,5,6,7,8,9,10};
int i;
for (i=0;i<10;i++)
printf("%d ",tab[i]); /* ligne 1 */
le langage C, va transformer vote tab[i] sous forme de pointeurs... Comme nous l’avons déjà constaté
précédemment, le nom d’un tableau représente l’adresse de son premier élément. En d’autre termes :
&tab[0] et tab sont une seule et même adresse.
17.9. POINTEURS ET TABLEAUX 157
En simplifiant, nous pouvons retenir que le nom d’un tableau est un pointeur constant sur le premier élément
du tableau. Exemple : en déclarant un tableau tab de type int et un pointeur p sur int,
int tab[10];
int *p;
Si p pointe sur une case quelconque d’un tableau, alors p+1 pointe sur la case suivante.
Au premier coup d’oeil, il est bien surprenant que p+i n’adresse pas le i-ième octet derrière p, mais la
i-ième case derrière p ...
Ceci s’explique par la stratégie de programmation ’défensive’ des créateurs du langage C : si on travaille
avec des pointeurs, les erreurs les plus perfides sont causées par des pointeurs mal placés et des adresses
mal calculées. En C, le compilateur peut calculer automatiquement l’adresse de l’élément p+i en ajoutant
à p la taille d’une case multipliée par i. Ceci est possible, parce que :
- chaque pointeur est limité à un seul type de données, et
- le compilateur connaît le nombre d’octets des différents types.
Voici un récapitulatif de tout ceci. Soit un tableau tab d’un type quelconque et i un indice entier alors :
Si p = tab, alors :
Exemple : les deux programmes suivants copient les éléments positifs d’un tableau tab dans un deuxième
tableau positifs.
Formalisme tableau :
int main() {
int tab[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9};
int positifs[10];
int i,j; /* indices courants dans tab et positifs */
for (i=0,j=0 ; i<10 ; i++)
if (tab[i]>0){
positifs[j] = tab[i];
j++;
}
return 0;
}
Nous pouvons remplacer systématiquement la notation tableau[i] par *(tableau+i), ce qui conduit
à ce programme :
Formalisme pointeur
int main() {
int tab[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9};
int positifs[10];
int i,j; /* indices courants dans tab et positifs */
for (i=0,j=0 ; i<10 ; i++)
if (*(tab+i)>0){
*(positifs+j) = *(tab+i);
j++;
}
return 0;
}
Voici un exemple de programme qui range les éléments d’un tableau tab dans l’ordre inverse. Le pro-
gramme utilise des pointeurs p1 et p2 et une variable numérique aide pour la permutation des éléments :
17.10. TABLEAUX DE POINTEURS 159
#include <stdio.h>
int main() {
/* Déclarations */
int tab[50]; /* tableau donné */
int N; /* dimension du tableau * /
int aide; /* pour la permutation */
int *p1, *p2; /* pointeurs d’aide */
int i;
/* Saisie des données */
printf("Dimension du tableau (max.50) : ");
scanf("%d", &N );
i=1;
for (p1=tab; p1<tab+N; p1++) {
printf("Elément %d : ", i++);
scanf("%d", p1);
}
/* Affichage du tableau */
for (p1=tab; p1<tab+N; p1++)
printf("%d ", *p1);
printf("\n");
/* Inverser la tableau */
for (p1=tab,p2=tab+(N-1); p1<p2; p1++,p2--) {
aide = *p1;
*p1 = *p2;
*p2 = aide;
}
/* Edition du résultat */
for (p1=tab; p1<tab+N; p1++)
printf("%d ", *p1);
printf("\n");
return 0;
}
Un peu de théorie...
Si nous avons besoin d’un ensemble de pointeurs du même type, nous pouvons les réunir dans un tableau
de pointeurs. Déclaration d’un tableau de pointeurs :
<Type> *<NomTableau>[<N>]
déclare un tableau <NomTableau> de <N> pointeurs sur des données du type <Type>.
Exemple :
double * a[10];
160 CHAPITRE 17. EN 2ÈME LECTURE...
Un peu de pratique...
déclare un tableau JOUR[] de 7 pointeurs sur char. Chacun des pointeurs est initialisé avec l’adresse de
l’une des 7 chaînes de caractères (voir figure) :
On peut afficher les 7 chaînes de caractères en fournissant les adresses contenues dans le tableau JOUR à
printf :
int i;
for (i=0; i<7; i++)
printf("%s\n", JOUR[i]);
17.11. SWITCH 161
17.11 switch
int i;
printf ("Entrer une valeur:");
scanf("%d",&i);
if (i==0)
printf ("Nombre nul\n");
else
if (i==1)
printf ("Nombre non égal à un\n");
else
printf ("Autre type de nombre\n");
int i;
printf ("Entrer une valeur:");
scanf("%d",&i);
switch (i) {
case 0: printf ("Nombre nul\n");
break;
Remarquez l’usage du break, en effet, si vous ne faites pas de break, le programme (après être entré
dans un case continuera sur le case suivant et ainsi de suite). Exemple : vous testez le programme suivant
en entrant la valeur 0 :
162 CHAPITRE 17. EN 2ÈME LECTURE...
int i;
printf ("Entrer une valeur:");
scanf("%d",&i);
switch (i) {
case 0: printf ("Nombre nul\n"); /* pas de break */
Nombre nul
Nombre non égal à un
il y a différentes étapes qui sont réalisées par le compilateur sans que vous vous en rendiez compte. Ainsi
en théorie, il faudrait tout d’abord compiler votre programme comme suit :
gcc -c essai.c
Cette étape a pour effet de générer un fichier essai.o qui ne contient pas encore toutes les fonctions
d’entrées sorties telles que printf, scanf, feof... On appelle ce fichier, le fichier objet.
à présent, les fonctions printf, scanf, feof... cette fois sont présentes dans le fichier. Cela fait
partie du travail de l’éditeur de liens d’aller chercher, dans les différentes bibliothèques système, les fonc-
tions nécessaires à l’exécution de l’application, et de les incoporer au fichier exécutable.
Cela présente toutefois un intérêt ainsi que nous allons le voir juste après le paragraphe suivant...
17.13. LA COMPILATION CONDITIONNELLE 163
La notion de compilation conditionnelle va nous permettre d’inclure ou non des lignes de code en fonction
d’une condition. Ces commandes sont utilisées de la manière suivante :
#if expression_1
groupe_de_lignes_1
#else
groupe_de_lignes_2
#endif
On notera que expression_1 doit être une constante arithmétique. Si cette dernière n’est pas nulle, alors le
groupe de ligne 1 est analysé et le groupe 2 est ignoré. Dans le cas contraire où la valeur de l’expression
est nulle, c’est le groupe 1 qui est ignoré et le groupe 2 qui est analysé.
Les commandes #if def et #if ndef peuvent être utilisées pour tester si un nom est défini ou non comme
macro du préprocesseur. En particulier, on utilise la commande #if ndef dans les fichiers d’entête de la
manière suivante :
/* fichier "entete.h" */
#ifndef ENTETE_H
#define ENTETE_H 1
int fonction1 ();
int fonction2 ();
#define TAILLE_MAX 100
...
#endif
Cette astuce permet de ne pas inclure deux fois les mêmes déclarations. En effet, si l’on inclut pour la
première fois le fichier ”entete.h”, la macro ENTETE_H n’étant pas définie, les lignes seront analysées
et f onction1, f onction2 et TAILLE_MAX seront déclarées. Par le jeu des inclusions imbriquées, si l’on
inclut une deuxième fois cet même fichier ”entete.h”, la macro étant cette fois-ci définie, les lignes seront
ignorées.
Cette astuce doit être utilisée systématiquement pour éviter deux fois l’inclusion d’un même fichier. Ceci
provient du fait qu’il n’est pas possible de faire deux fois la même déclaration.
Nous allons pratiquer sur un exemple tout simple mais la philosophie est là : créer des bibliothèques de
fonctions.
Nous allons créer une toute petite bibliothèque qui permet de calculer le minimum de 2 nombres. Créez les
3 fichiers suivants :
164 CHAPITRE 17. EN 2ÈME LECTURE...
/* fichier "minimum.c" */
#include "minimum.h"
int minimum (int a, int b) {
if (a > b)
return (b);
else
return (a);
}
/* fichier "minimum.h" */
#ifndef MINIMUM_H
#define MINIMUM_H 1
#endif
/* fichier "essai.c" */
#include <stdio.h>
#include "minimum.h"
int main () {
int a=13;
int b=15;
int c=minimum(a,b);
printf ("Minimum entre %d et %d : %d",a,b,c);
return 0;
}
gcc -c minimum.c
gcc -c essai.c
gcc -o essai minimum.o essai.o
./essai
Chapitre 18
Cette partie est optionnelle, elle ne vise qu’à occuper ;-) ceux qui auraient déjà fini au bout d’un temps bien
inférieur au temps alloué pour cette remise à niveau...
Cette partie devra donc être réalisée une fois que vous aurez fait la partie UNIX...
On souhaite réaliser un programme qui permettrait de faire rebondir une balle à l’écran. La balle devrait
pouvoir rebondir sur un écran de la forme suivante (figure de gauche) :
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 * * * * * * * * * *
* *
* *
* *
O * O *
* *
* *
* *
* *
* * * * * * * * * *
165
166 CHAPITRE 18. PARTIE OPTIONNELLE : EXERCICES SUPPLÉMENTAIRES...
Afin de faciliter la programmation, on rajoute artificiellement une couronne d’étoiles qui permettra de
simplifier les tests lorsque la balle arrivera sur un bord (figure de droite).
La balle, qui est symbolisée par le caractère ’O’ démarre en [4; 4]. Elle part suivant le vecteur d’abscisse +1
et d’ordonnée +1. Lorsqu’elle arrive sur un bord en X, il suffit d’inverser le sens de déplacement suivant
cet axe, idem pour l’axe des ordonnées Y . Astuce pour tester si on arrive sur un bord, faire :
if (grille[nouvelle_position_en_y][ nouvelle_position_en_x]==’*’)
Il vous reste donc à compléter le programme suivant que vous pouvez récupérer sur le campus numérique
(htpp://campus.eseo.fr) ... Demandez comment faire si vous n’y parvenez pas...
#include <stdio.h>
int main () {
init_grille (grille,pos_balle_x,pos_balle_y) ;
while (1) {
/* system("clear"); */ /* <- enlever ce commentaire une fois que le
programme fonctionnera... */
affiche_grille(grille);
calcule_position_balle (grille,pos_balle_x,pos_balle_y,
deplacement_x,deplacement_y,&new_pos_balle_x,&new_pos_balle_y) ;
modifie_grille (grille,
pos_balle_x, pos_balle_y,new_pos_balle_x, new_pos_balle_y);
deplacement_x=new_pos_balle_x-pos_balle_x ;
deplacement_y=new_pos_balle_y-pos_balle_y ;
pos_balle_x= new_pos_balle_x ;
pos_balle_y= new_pos_balle_y ;
18.2 Le morpion
Il s’agit du morpion classique que l’on pourrait tracer comme ceci dans l’immédiat :
X O .
X O X
O . O
#include <stdio.h>
#include <string.h>
/* test en ligne */
...
main() {
int joueur=1; /* vaut 1 ou 2 suivant le joueur */
/* qui est entrain de jouer */
char damier[3][3];
system("clear"); /* clear sous UNIX: efface l’écran */
/* sytem appelle donc UNIX pour */
/* lancer la commande "clear" */
initialiser_damier(damier);
jouer(damier);
}
Libre à vous d’ajouter ce que vous voulez. Voici quelques suggestions, au choix :
18.3 Le pendu
" Vous jouez contre l’ordinateur. " Ce dernier vous propose un mot à deviner en affichant la première et
la dernière lettre du mot à deviner (exemple pour le mot "bonjour", il afficherait : b - - - - - r). " Vous
proposez une lettre, si cette dernière se trouve dans le mot, l’ordinateur affiche le mot avec l’emplacement
où se trouve votre lettre. Exemple : vous proposez la lettre ’o’, l’ordinateur affiche alors : b o - - o - r. Si
vous proposez ensuite la lettre ’u’, l’ordinateur affiche : b o - - o u r ... " Si la lettre ne figure pas dans le
mot, l’ordinateur vous donne un point d’erreur. Au bout de 7 erreurs, vous avez perdu.
Libre à vous d’ajouter ce que vous voulez. Voici quelques suggestions, au choix :