04.01.2014 Views

cours entier

cours entier

cours entier

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Programmation Procédurale<br />

en langage C<br />

Cours de 1 re Année<br />

Dernière révision : octobre 2012<br />

Guillaume Rivière<br />

L A TEX


Avant-propos 2<br />

Le monde (informatique) se divise en deux catégories. . .


Avant-propos 3<br />

Pourquoi devez-vous apprendre à programmer ?<br />

1 Pour comprendre le monde de l’informatique<br />

2 Pour écrire de petits programmes (10 - 100 lignes)<br />

3 Pour écrire de gros programmes (1000 - 10.000 lignes)<br />

4 Pour savoir concevoir, rédiger, passer commande de projets<br />

(impossible sans connaître la réalité de la programmation)<br />

Principaux champs d’application qui vous concernent :<br />

Électronique numérique<br />

Robotique / Automatique<br />

Commande numérique<br />

Système d’information de l’Entreprise<br />

Microcontrôleur PIC1655A<br />

(Environnement MikroC)


Schéma des prérequis<br />

Avant-propos 4


Avant-propos 5<br />

Programmation de robots LEGO MINDSTORMS NXT<br />

1 micro-ordinateur intelligent<br />

4 ports d’entrée (lecture)<br />

4 ports de sortie (écriture)<br />

Différents langages<br />

possibles :<br />

C, C sharp, Ada, Java,<br />

MATLAB, .Net, . . .<br />

Capteur<br />

d’ultrasons<br />

Capteur<br />

tactile<br />

Capteur<br />

photosensible<br />

Capteur<br />

sonore<br />

Servo-moteur


Avant-propos 6<br />

Programmation de robots LEGO MINDSTORMS NXT<br />

Faire coopérer 2 robots<br />

1 robot explorateur<br />

1 robot ramasseur<br />

Photos projet de 2 élèves ESTIA


Avant-propos 7<br />

Programmation du robot NAO<br />

25 degrés de liberté<br />

58 cm de hauteur<br />

Vision par ordinateur<br />

Reconnaissance vocale<br />

Synthèse vocale<br />

Mains préhensiles<br />

Ordinateur embarqué<br />

Système Linux<br />

Différents langages possibles :<br />

C++, Python, Java, MATLAB,<br />

.Net, . . .<br />

SDK sur http://www.<br />

aldebaran-robotics.com


Avant-propos 8<br />

Programmation du robot KUKA<br />

KR6-ARC<br />

6 axes de rotation<br />

Différents langages possibles :<br />

KRL (C et C++ à venir)


Avant-propos 9<br />

Analyse Systémique de l’Entreprise<br />

Mots-clés du Système d’Information<br />

Intranet, Site web, Formulaires<br />

Réseaux, Serveurs, . . .<br />

Bases de données, ERP (PGI)<br />

GPAO, SCM, GRC, SGDT, . . .


Avant-propos 10<br />

Programmation web<br />

PHP, Javascript, Applets Java, ASP, JSP, Ajax, . . .


Avant-propos 11<br />

Où trouve-t-on des programmes ?<br />

↩→ Dans les ordinateurs : oui. . . mais pas seulement !<br />

Systèmes d’exploitation, Bureautique, Navigateur web, . . .<br />

Bases de données, Sites web, eMails, S.I. des entreprises, . . .<br />

Téléphones, GPS, Avions, Voitures, Fusées, électroménager, . . .<br />

Opérateurs mobiles, Banques, Assurances, . . .<br />

Jeux vidéos, Films animés (Pixar), Météo, Géophysique, . . .<br />

CAO (Catia, Pro-eng), Bras robotisés, Chaînes de montage, . . .<br />

Reconnaissance vocale, MP3, . . .<br />

. . .


Avant-propos 12<br />

Un langage machine est l’ensemble des mots machines<br />

compréhensibles par un processeur (jeu d’instructions).<br />

Un programme écrit en assembleur décrit (quasiment) unes à<br />

unes les opérations que doit faire le processeur (JMP, CALL, ADD,<br />

SUB, MUL, AND, OR, NOT, CMP, MOV, PUSH, POP, JS, JI,<br />

JE, ...). L’écriture d’un programme en assembleur reste un travail<br />

laborieux et chronophage (aussi les compétences se raréfient).<br />

Un langage de programmation permet de décrire un programme<br />

à un plus haut niveau de logique, en s’astreignant (d’une grande part)<br />

des contraintes machines. La traduction, dans un langage machine,<br />

du code ainsi écrit, est assurée par un compilateur.


Différents paradigmes de programmation existent :<br />

Avant-propos 13<br />

Encore d’autres caractéristiques : Multi-paradigme : Python, Javascript. . .<br />

Compilé / Interprété / Semi-interprété Asynchrones, Temps réel. . .


Cinq générations de langages :<br />

Avant-propos 14


Avant-propos 15<br />

à la fin de ce <strong>cours</strong>, vous saurez :<br />

Conduire les itérations nécessaires pour<br />

écrire un programme qui fonctionne<br />

utiliser un compilateur et un IDE<br />

corriger les erreurs<br />

tester exécution ⇒ aboutir programme<br />

qui réponde au problème posé<br />

Connaître le langage C (norme ANSI 89)<br />

très grande efficacité pour tout ce qui<br />

concerne le développement système<br />

reste un des langages les plus utilisés en informatique industrielle<br />

Construire rapidement une interface graphique et la connecter pour<br />

appeler des fonctions écrites précédemment.<br />

Vous serez aussi capable de comprendre et de coder en Pascal,<br />

MatLab, Mupad, Basic, Fortran, Perl, . . . et bien sûr en C, langage<br />

d’application choisi car c’est un prérequis pour l’EEA


Plan 16<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


Plan 17<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


1 INTRODUCTION Généralités 18<br />

Langage très répandu et utilisé, principalement parce que :<br />

Initialement lié à Unix<br />

Date des années 1970 (savoir commun. . .)<br />

Le code machine généré est rapide et compact<br />

Principales caractéristiques du langage C :<br />

Langage de programmation impérative (procédurale)<br />

où (presque) tout est fonction :<br />

a = b = 0 ; /* équivalent à a = (b = 0) ; */<br />

RMQ : le ” ;” signifie la fin d’une instruction<br />

RMQ : les commentaires s’écrivent entre /* et */<br />

Programmation structurale<br />

Typage faible<br />

Typage statique<br />

Langage ”bas niveau” (Manipulation mémoire, E/S, . . .)


1 INTRODUCTION Généralités 19<br />

Principaux avantages du langage C :<br />

Typage faible<br />

Travail ”bas niveau”<br />

Très très répandu<br />

Compatible avec nombreux langages et librairies<br />

Principaux inconvénients du langage C :<br />

Typage faible<br />

Pas de ramasse miettes<br />

Pas de système de gestion d’erreur<br />

Le langage C possède cependant un GROS défaut : il offre aux<br />

programmeurs un grand degré de liberté<br />

Philosophie du C : le programmeur sait ce qu’il fait. . .<br />

Donc on trouve beaucoup de bugs dans les programmes<br />

↩→ Conclusion : il faut de la discipline (cf. Génie Logiciel p254)


1 INTRODUCTION Généralités 20<br />

Normes :<br />

Livres :<br />

1972 : C ”K&R”<br />

1989 : ANSI C (American National Standards Institute)<br />

1999 : C99<br />

”Le langage C”, B. Kernighan & D. Ritchie (Dunod) : à lire absolument !<br />

”Méthodologie de la programmation en langage C”, A. Braquelaire<br />

(Dunod) : à lire en fin de semestre<br />

”Programmer en langage C”, C. Delannoy (Eyrolles)<br />

”Le langage C”, H. Garreta. Disponible en ligne :<br />

http://c.developpez.com/<strong>cours</strong>/poly-c/<br />

RMQ : Le langage C fut créé en 1972 par Dennies Ritchie qui travaillait<br />

alors au sein des laboratoires AT&T Bell Labs aux USA


1 INTRODUCTION Champs d’application possibles 21<br />

Le langage C est notamment très utilisé pour :<br />

Programmation ”bas niveau”<br />

Systèmes embarqués<br />

Programmation système (ex : Linux en C à 96%, Windows, MacOS)<br />

Programmation réseaux<br />

Traitement d’images (ex : Vision par ordinateur)<br />

Cartes microprogrammées<br />

Logiciels (ex : Oracle, MySQL, Apache, Firefox, Word, Excel,<br />

Photoshop, Gimp, . . .et pour ne citer qu’eux, sont écrits en C/C++)<br />

Exemples de bibliothèques logicielles écrites en C :<br />

OpenGL<br />

SDL<br />

GTK<br />

OpenCV<br />

. . .


1 INTRODUCTION Cursus ESTIA 22<br />

Langages inspirés du C (extensions) :<br />

C++ (Bjarne Stroustrup, 1979)<br />

Objective C (1983, Très utilisé par Apple)<br />

C# (Microsoft, 2001)<br />

Principaux langages reprenant la syntaxe du C :<br />

C++<br />

PHP (Rasmus Lerdorf, 1994)<br />

Java (Sun Microsystems, 1996 / Oracle, 2009)<br />

Javascript (Brendan Eich, Netscape/Mozilla, 1995)<br />

C#<br />

. . .


1 INTRODUCTION 1 re Année ESTIA 23<br />

→ Vous êtes issus de par<strong>cours</strong> différents<br />

1 Jamais programmé ?<br />

2 Déjà programmé, mais dans d’autres langages ?<br />

(Pascal, Fortran, Ada, OCaml, Python, VB, VBA, . . .)<br />

3 Déjà programmé en C ?<br />

4 Déjà programmé en C++, Java, C# ou PHP ? ? ?<br />

↩→ La programmation reste d’une manière générale un<br />

apprentissage long et difficile (de l’ordre de plusieurs années. . .)<br />

↩→ Comment apprendre ?<br />

Commencer par de petits programmes basiques pour évoluer<br />

plus tard vers des plus gros (Interfaces Graphiques, Scènes 3D,<br />

Échanges réseaux, Temps réel, Son, Image, Vidéo, Webcam, Tactile,<br />

Smartphones, GPS, SMS, emails, DB, Sécurité, Cryptage, . . .).<br />

⇒ Cet apprentissage commence dès maintenant


1 INTRODUCTION Programmation en 1 re Année ESTIA 24<br />

1 ER SEMESTRE<br />

Programmation Procédurale<br />

8h Cours<br />

8h TD<br />

10h TP + 2h Travail Personnel<br />

Contrôle continu par QCM<br />

TD :<br />

⋆ G1, G3 : N.Takouachet<br />

⋆ G2, G4 : G.Rivière<br />

TP :<br />

⋆ G1, G3 : N.Takouachet, J.M.Cieutat<br />

⋆ G2, G4 : G.Rivière, N.Ghouaiel<br />

Développement Rapide d’Interface<br />

12h TP<br />

Évaluation<br />

2 E SEMESTRE<br />

Projet Génie Informatique<br />

12h TP + 8h Travail Personnel


1 INTRODUCTION Écrire un programme 25<br />

Structure générale d’un programme C :<br />

1 Inclusion de fichiers (déclarations, prototypes, . . .)<br />

2 Définition de macros<br />

3 Définition de types et de prototypes de fonctions<br />

4 Déclaration de variables GLOBALES<br />

5 Code des fonctions


1 INTRODUCTION Premier programme 26<br />

Exemple 1 : l’incontournable<br />

helloworld.c<br />

1 #include <br />

2<br />

3 /* Fonction principale */<br />

4 int main () {<br />

5<br />

6 printf ("Hello World!!!\n") ;<br />

7<br />

8 return 0 ;<br />

9 }<br />

10 /* Fin du programme */<br />

Un programme doit TOUJOURS comporter une fonction ”main()”<br />

↩→ Cette fonction ”principale” du programme est particulière<br />

⇒ C’est le ”point de départ” du programme !<br />

RMQ : ’\n’ est le caractère ”retour chariot”, ”retour à la ligne”<br />

RMQ : Tout programme retourne une valeur représentant un code<br />

d’erreur (d’exécution). Par convention, 0 signifie ”exécution normale”<br />

(pas d’erreur)


1 INTRODUCTION Deuxième programme 27<br />

Exemple 2 : chaînes de caractères<br />

votrenom.c<br />

1 #include /* Declaration de printf() et scanf() */<br />

2 #include /* Declaration de strlen() */<br />

3<br />

4 int main () {<br />

5 char name[256] ; /* Declaration d’un tableau de 256 caracteres */<br />

6<br />

7 printf ("Donnez votre nom : ") ;<br />

8 scanf ("%s", name) ;<br />

9<br />

10 printf ("%s, votre nom comporte %d lettres.\n", name, strlen(name)) ;<br />

11<br />

12 return 0 ;<br />

13 }<br />

Exemples de 2 exécutions :


1 INTRODUCTION Troisième programme 28<br />

Exemple 3 : conditionnelle<br />

parite.c<br />

1 #include /* Declaration de printf() et scanf() */<br />

2<br />

3 int main () {<br />

4 int n ;<br />

5<br />

6 printf ("Donnez un nombre <strong>entier</strong> : ") ;<br />

7 scanf ("%d", &n) ;<br />

8<br />

9 if ((n % 2) != 0) /* Variante : if (n & 1) */<br />

10 printf ("Le nombre %d est impair.\n", n) ;<br />

11 else<br />

12 printf ("Le nombre %d est pair.\n", n) ;<br />

13<br />

14 return 0 ;<br />

15 }<br />

10 mod 2 = 0<br />

9 mod 2 = 1<br />

pair={2n|n ∈ Z}<br />

impair={2n + 1|n ∈ Z}<br />

n pair<br />

⇔ ∃i tq n = 2i<br />

n impair<br />

⇔ ∃i tq n = 2i + 1<br />

Exemples de 4 exécutions :


Page support 29<br />

http://www.guillaumeriviere.name/estia/C/<br />

Supports de <strong>cours</strong><br />

Pour écran<br />

Pour imprimante<br />

Par chapitre<br />

Exemples du <strong>cours</strong><br />

Fichiers sources .c<br />

Fichiers exe pour Windows<br />

Archive zip<br />

Exercices<br />

Énoncés de TP (en ligne)<br />

Énoncés de TD (pdf)<br />

17 exercices pour s’entraîner<br />

Ressources web<br />

Cours, Tutoriels, . . .<br />

Manuels<br />

Outils (GCC, Notepad++, . . .)


1 INTRODUCTION Quatrième programme 30<br />

Exemple 4 : paramètres en ligne de commande<br />

addition.c<br />

1 #include /* Declaration de printf() et fprintf() */<br />

2 #include /* Declaration de exit() */<br />

3<br />

4 int main (int argc, char *argv[]) {<br />

5 int a, b, c ;<br />

6<br />

7 /* Verification du nombre d’arguments */<br />

8 if (argc != 3) {<br />

9 fprintf (stderr, "Usage: %s \n", argv[0]) ;<br />

10 exit (1) ; /* Arret sur erreur */<br />

11 }<br />

12<br />

13 /* Recuperation des arguments */<br />

14 a = atoi (argv[1]) ;<br />

15 b = atoi (argv[2]) ;<br />

16<br />

17 /* Calcul du resultat */<br />

18 c = a + b ;<br />

19<br />

20 /* Affichage du resultat */<br />

21 printf ("La somme de %d et %d est %d\n", a, b, c) ;<br />

22<br />

23 return 0 ; /* Sortie normale */<br />

24 }<br />

RMQ : Commenter correctement le code est important


1 INTRODUCTION Quatrième programme 31<br />

Exemples de 3 exécutions :<br />

→ Le passage des arguments se fait au moment de l’appel du<br />

programme<br />

→ Dans notre programme, nous exigeons que le nombre<br />

d’arguments soit exactement égal à 3<br />

RMQ : Le premier argument (argv[0]) est le nom du programme<br />

RMQ : Les arguments sont des chaînes de caractère<br />

↩→ La variable argv[] est un tableau de chaînes de caractères<br />

⇒ La fonction atoi() calcule la valeur entière à partir d’une chaîne<br />

de caractères


1 INTRODUCTION Cinquième programme 32<br />

Exemple 5 : une calculatrice à deux opérandes<br />

calculatrice.c<br />

1 #include <br />

2 #include <br />

3<br />

4 void usage (char *program) {<br />

5 fprintf (stderr, "Usage: %s \n", program) ;<br />

6 fprintf (stderr, " with operator in {+,-,x,/}\n") ;<br />

7 }<br />

8<br />

9 int main (int argc, char *argv[]) {<br />

10 double v1, v2, res ;<br />

11 char op ;<br />

12<br />

13 /* Verification du nombre d’arguments */<br />

14 if (argc != 4) {<br />

15 usage (argv[0]) ;<br />

16 exit (1) ; /* Arret sur erreur */<br />

17 }<br />

18<br />

19 /* Recuperation des arguments */<br />

20 v1 = atof (argv[1]) ;<br />

21 v2 = atof (argv[3]) ;<br />

22 op = argv[2][0] ;<br />

23<br />

24 /* Calcul du resultat */<br />

25 switch (op) {<br />

26 case ’+’:<br />

27 res = v1 + v2 ;<br />

28 break ;<br />

29 case ’-’:


1 INTRODUCTION Cinquième programme 33<br />

calculatrice.c (suite et fin)<br />

30 res = v1 - v2 ;<br />

31 break ;<br />

32 case ’x’:<br />

33 res = v1 * v2 ;<br />

34 break ;<br />

35 case ’/’:<br />

36 res = v1 / v2 ;<br />

37 break ;<br />

38 default:<br />

39 usage (argv[0]) ;<br />

40 exit (1) ; /* Arret sur erreur */<br />

41 }<br />

42<br />

43 /* Affichage du resultat */<br />

44 printf ("%.1f %c %.1f = %.1f\n", v1, op, v2, res) ;<br />

45<br />

46 return 0 ; /* Sortie normale */<br />

47 }<br />

Exemples de 4 exécutions :


1 INTRODUCTION Sixième programme 34<br />

Exemple 6 : tableaux, répétitions, E/S<br />

somme.c<br />

1 #include <br />

2 #define TAILLE_MAX_TAB 20<br />

3<br />

4 int tab[TAILLE_MAX_TAB] ; /* Declaration d’un tableau global d’<strong>entier</strong>s */<br />

5 int somme_tab (int n) ; /* Declaration d’une fonction */<br />

6<br />

7 /**<br />

8 * Fonction principale<br />

9 */<br />

10 int main () {<br />

11 int i, n ; /* Declaration de variables locales */<br />

12<br />

13 /* Boucle d’entree : lecture taille du tableau tab */<br />

14 do {<br />

15 printf ("Donnez un <strong>entier</strong> entre 2 et %d : ", TAILLE_MAX_TAB-1) ;<br />

16 scanf ("%d", &n) ;<br />

17 } while (n=TAILLE_MAX_TAB) ;<br />

18<br />

19 /* Boucle : lecture des n <strong>entier</strong>s */<br />

20 for (i=0 ; i


1 INTRODUCTION Sixième programme 35<br />

somme.c (suite et fin)<br />

30<br />

31 /**<br />

32 * Fonction : somme des n premiers termes de tab<br />

33 */<br />

34 int somme_tab (int n) {<br />

35 int res = 0 ; /* Declaration d’une variable initialisee avec 0 */<br />

36<br />

37 while (n-- > 0) /* Ajout des n <strong>entier</strong>s */<br />

38 res += tab[n] ;<br />

39<br />

40 return res ; /* Retour du resultat */<br />

41 }<br />

Exemple d’exécution :<br />

Exercice :<br />

Modifier le programme pour calculer<br />

la moyenne (en plus de la somme)<br />

Pour ce faire, vous créerez une<br />

nouvelle fonction :<br />

double moyenne tab (int n) ;


1 INTRODUCTION Septième programme 36<br />

Exemple 7 : types structurés, déclaration de type, E/S<br />

complexes.c<br />

1 #include <br />

2 #define SQRT2 1.414213562<br />

3<br />

4 /** Declaration d’une nouvelle structure de donnees */<br />

5 struct complexe {<br />

6 double real ; /* Partie reelle */<br />

7 double img ; /* Partie imaginaire */<br />

8 } ;<br />

9<br />

10 /** Declaration d’un nouveau type : les complexes */<br />

11 typedef struct complexe complexe ;<br />

12<br />

13 /** Fonction : ajout de deux nombres complexes */<br />

14 complexe complexe_ajouter (complexe c1, complexe c2) {<br />

15 complexe resultat = c1 ;<br />

16<br />

17 resultat.real += c2.real ;<br />

18 resultat.img += c2.img ;<br />

19<br />

20 return resultat ;<br />

21 }<br />

22<br />

23 /** Fonction principale */<br />

24 int main () {<br />

25 complexe a, b, c ;<br />

26<br />

27 /* Initialisation du complexe a */<br />

28 a.real = SQRT2 ;<br />

29 a.img = SQRT2 ;


1 INTRODUCTION Septième programme 37<br />

complexes.c (suite et fin)<br />

30<br />

31 /* Initialisation du complexe b : saisie de 2 valeurs au clavier */<br />

32 printf ("Partie reelle : ") ;<br />

33 scanf ("%lf", &b.real) ;<br />

34 printf ("Partie imaginaire : ") ;<br />

35 scanf ("%lf", &b.img) ;<br />

36<br />

37 /* Calcul et affichage de c = a + b */<br />

38 c = complexe_ajouter (a, b) ;<br />

39 printf ("La somme est (%f, %f)\n", c.real, c.img) ;<br />

40<br />

41 return 0 ;<br />

42 }<br />

Exemple d’exécution :<br />

Exercice :<br />

Modifier le programme pour que<br />

(comme fait pour b) le complexe a<br />

soit lui aussi initialisé par des valeurs<br />

entrées au clavier par l’utilisateur<br />

(plutôt qu’il soit initialisé à ( √ 2; √ 2))


1 INTRODUCTION Huitième programme 38<br />

Exemple 8 : accès fichiers<br />

copier.c<br />

1 #include /* fprintf(), fopen(), getc(), putc(), fclose() */<br />

2 #include /* exit() */<br />

3 #include /* strcmp() */<br />

4<br />

5 /* Programme equivalent a la commande copy (sans les arguments optionnels).<br />

6 * NB : copy permet la copie d’un fichier dans un autre. */<br />

7 int main (int argc, char *argv[]) {<br />

8 FILE *fsrc, *fdst ;<br />

9 char c ;<br />

10<br />

11 /* Validation des arguments en ligne de commande */<br />

12 if (argc != 3 || strcmp(argv[1], argv[2]) == 0) {<br />

13 fprintf (stderr, "Usage: %s \n", argv[0]) ;<br />

14 exit (1) ;<br />

15 }<br />

16<br />

17 /* Ouverture des 2 fichiers (source et destination) */<br />

18 fsrc = fopen (argv[1], "r") ;<br />

19 if (fsrc == NULL) {<br />

20 fprintf (stderr, "Error: cannot open %s in read mode\n", argv[1]) ;<br />

21 exit (-1) ;<br />

22 }<br />

23<br />

24 fdst = fopen (argv[2], "w") ;<br />

25 if (fsrc == NULL) {<br />

26 fprintf (stderr, "Error: cannot open %s in write mode\n", argv[2]) ;<br />

27 fclose (fsrc) ;<br />

28 exit (-2) ;<br />

29 }


1 INTRODUCTION Huitième programme 39<br />

copier.c (suite et fin)<br />

30<br />

31 /* Copie caractere par caractere */<br />

32 while ((c = getc (fsrc)) != EOF) /* EOF = fin de fichier */<br />

33 putc (c, fdst) ; /* fprintf (fdst, "%c", c) ; */<br />

34<br />

35 /* Fermeture des deux fichiers */<br />

36 fclose (fsrc) ;<br />

37 fclose (fdst) ;<br />

38<br />

39 printf ("Done.\n") ;<br />

40 return 0 ;<br />

41 }<br />

Exemple d’exécution :<br />

Exercice :<br />

Modifier le programme pour compter<br />

au passage le nombre de caractères<br />

recopiés et en informer l’utilisateur.<br />

Les espaces seront ignorés dans ce<br />

comptage :<br />

int cpt = 0 ;<br />

/* ... */<br />

if (c != ’ ’)<br />

cpt = cpt + 1 ;<br />

/* ... */<br />

printf ("Caracteres : %d\n", cpt) ;


1 INTRODUCTION Mots réservés 40<br />

RMQ : De nombreux symboles structurent le langage C<br />

; , {} () [] -> . ! ∼ ++ −− = ∗ / % + − < = > == ! =<br />

> & ∧ | && || ? : += −= ∗= /= %= &= ∧= |= =<br />

RMQ : Certains mots sont réservés (”mots clés”)<br />

Ces mots particuliers font parti du langage C<br />

Leur emploi est précis et régit par des règles<br />

Liste des mots clés réservés en C :<br />

return sizeof extern static const<br />

char int float double void<br />

short long signed unsigned typedef<br />

struct enum if else switch<br />

case default break continue goto<br />

for while do #include #define<br />

#undef #ifdef #ifndef #else #endif<br />

(Non exhaustif, mais assez complet)


1 INTRODUCTION Programmer 41<br />

3 grandes étapes : coder, compiler, exécuter<br />

TRÈS nombreuses<br />

itérations. . . . . .<br />

1 CODER<br />

Écriture en langage C par un programmeur<br />

Le code source définit le comportement du programme<br />

⇒ Fichiers sources lisibles par un humain (ASCII)<br />

2 COMPILER<br />

Lecture des fichiers sources par un compilateur<br />

Analyse du code C, traduction en code machine<br />

Détection d’erreur (langage, bibliothèques, . . .)<br />

⇒ Un fichier ”exécutable” est généré (binaire)<br />

3 EXÉCUTER<br />

Exécution des instructions sur le processeur processus<br />

Tests : le programme répond-il au besoin, au cahier des charges ?<br />

Tests : le programme fonctionne-t-il correctement ? Fiabilité ?<br />

↩→ corriger le code source, debbugage, . . .<br />

4 UTILISER / EXPLOITER / DIFFUSER (?)


1 INTRODUCTION Programmer (Itération 1) 42<br />

Exemple : Écrire un programme qui permet d’évaluer la fonction<br />

F(x) = pour des valeurs de x données par l’utilisateur.<br />

√ x<br />

x 2 −2x−3<br />

0 RÉFLÉCHIR ASD<br />

1 CODER<br />

evaluer.c<br />

2 COMPILER gcc -Wall -ansi evaluer.c<br />

1 int main () {<br />

2 double x ;<br />

3<br />

4 /* Demander la valeur de x */<br />

5 printf ("Donnez x : ") ;<br />

6 scanf ("%lf", &x) ;<br />

7<br />

8 /* Calculer F(x) */<br />

9 F = sqrt (x) / (x*x - 2*x - 3) ;<br />

10<br />

11 /* Afficher le resultat */<br />

12 printf ("F(%f)=%d\n", x, F) ;<br />

13<br />

14 return 0 ;<br />

15 }<br />

Warnings ⇒ Les fonctions printf(),<br />

scanf() et sqrt() n’ont pas été déclarées<br />

Error ⇒ La variable F n’a pas été déclarée


1 INTRODUCTION Programmer (Itération 2) 43<br />

1 CODER<br />

→ Inclusion du fichier d’entête stdio.h où les fonctions printf() et<br />

scanf() sont déclarées<br />

→ Inclusion du fichier d’entête math.h où la fonction sqrt() est déclarée<br />

→ Enfin, déclaration de la variable F<br />

evaluer.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 double x ;<br />

6 double F ;<br />

7<br />

8 /* Demander la valeur de x */<br />

9 printf ("Donnez x : ") ;<br />

10 scanf ("%lf", &x) ;<br />

11<br />

12 /* Calculer F(x) */<br />

13 F = sqrt (x) / (x*x - 2*x - 3) ;<br />

14<br />

15 /* Afficher le resultat */<br />

16 printf ("F(%f)=%d\n", x, F) ;<br />

17<br />

18 return 0 ;<br />

19 }<br />

2 COMPILER<br />

Warning ⇒ Ligne 16 : le type attendu pour<br />

le 3 e argument de printf() est int mais F<br />

est de type double


1 INTRODUCTION Programmer (Itération 3) 44<br />

1 CODER<br />

→ Changement du %d en %f<br />

dans la chaîne de formatage<br />

de printf()<br />

evaluer.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 double x ;<br />

6 double F ;<br />

7<br />

8 /* Demander la valeur de x */<br />

9 printf ("Donnez x : ") ;<br />

10 scanf ("%lf", &x) ;<br />

11<br />

12 /* Calculer F(x) */<br />

13 F = sqrt (x) / (x*x - 2*x - 3) ;<br />

14<br />

15 /* Afficher le resultat */<br />

16 printf ("F(%f)=%f\n", x, F) ;<br />

17<br />

18 return 0 ;<br />

19 }<br />

2 COMPILER<br />

Échec lors de l’édition des liens ⇒ Le<br />

code machine de la fonction sqrt() est<br />

introuvable<br />

↩→ Inclure ne suffit pas<br />

2 (RE)COMPILER<br />

→ Ajout de l’option de compilation -lm pour<br />

lier la librairie mathématique qui fournit,<br />

parmi d’autres, le code machine de la<br />

fonction sqrt()<br />

↩→ gcc -Wall -ansi -lm evaluer.c


1 INTRODUCTION Programmer (Itération 4) 45<br />

3 EXÉCUTER<br />

→ Le nom par défaut de l’exécutable généré par GCC est a.exe<br />

↩→ Soit renommer le fichier, soit ajouter l’option -o à la commande de<br />

compilation : gcc -Wall -ansi -lm evaluer.c -o evaluer.exe<br />

Problème lorsque l’utilisateur entre x = 3<br />

↩→ En effet, 3 est une racine du polynôme<br />

x 2 − 2x − 3 ce qui entraîne une division par 0<br />

⇒ Solution 1 :<br />

Vérifier au préalable que x n’est pas une<br />

racine du polynôme<br />

(programmation défensive)<br />

⇒ Solution 2 :<br />

Évaluer le dénominateur et vérifier qu’il est<br />

non nul avant de procéder à la division


1 INTRODUCTION Programmer (Itération 5) 46<br />

1 CODER<br />

evaluer.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 double x ;<br />

6 double F, N, D ;<br />

7<br />

8 /* Demander la valeur de x */<br />

9 printf ("Donnez x : ") ;<br />

10 scanf ("%lf", &x) ;<br />

11<br />

12 /* Calculer le denominateur */<br />

13 D = (x*x - 2*x - 3) ;<br />

14<br />

15 if (D != 0.) {<br />

16 N = sqrt (x) ;<br />

17 F = N / D ;<br />

18<br />

19 /* Afficher le resultat */<br />

20 printf ("F(%f)=%f\n", x, F) ;<br />

21 }<br />

22 else {<br />

23 /* Afficher un message */<br />

24 printf ("Calcul impossible : "<br />

25 "division par zero\n")<br />

;<br />

26 }<br />

27 return 0 ;<br />

28 }<br />

2 COMPILER<br />

3 EXÉCUTER<br />

fin


1 INTRODUCTION La compilation 47<br />

La compilation C comporte<br />

3 phases principales<br />

Pour chaque fichier source<br />

(1a) Traitement du fichier<br />

source par le<br />

préprocesseur C<br />

(1b) Compilation et<br />

génération du code cible<br />

(2) Édition des liens<br />

Réunit les fichiers cibles,<br />

puis ajoute les fonctions<br />

issues des bibliothèques,<br />

pour créer un seul fichier<br />

”exécutable” autonome


1 INTRODUCTION Les outils nécessaires 48<br />

Pour développer<br />

1 éditeur de texte (BlocNote, NodePad++, Emacs, . . .)<br />

1 compilateur C (MinGW-GCC, . . .)<br />

1 invite de commandes<br />

Pour compiler depuis l’invite de commande :<br />

gcc -Wall -ansi helloworld.c -o helloworld.exe<br />

Paramètres :<br />

Autres paramètres utiles :<br />

-Wall = warning all (OBLIGATOIRE)<br />

-ansi = respecter la norme ANSI C<br />

-o = nom du fichier produit<br />

-L : répertoires où se trouvent des nouvelles bibliothèques (.lib)<br />

-l : nom des bibliothèques à utiliser lors de l’édition des liens<br />

(par exemple : -lm -lopengl32 -lpthread -lGid32)<br />

-I : répertoires où se trouvent des fichiers d’entête (.h)


1 INTRODUCTION Compilation séparée 49<br />

Il est possible d’écrire les fonctions d’un programme dans différents<br />

fichiers (plutôt que dans un seul)<br />

Permet de répartir le code dans de multiples fichiers plus petits<br />

(plutôt qu’un seul gros fichier de 10 6 lignes)<br />

→ lisibilité et maintenabilité<br />

Permet de réutiliser ces fichiers dans d’autres programmes pour<br />

réutiliser les fonctions déjà écrites (principe de modularité)<br />

→ gain de temps et coûts diminués<br />

Permet de ne recompiler que partiellement le code en cas de<br />

modification (Sur un gros programme : ne pas perdre 1h de<br />

compilation juste pour une fonction modifiée)<br />

→ temps<br />

Le mécanisme de la compilation séparée permet cela<br />

gcc -c -Wall -ansi main.c<br />

gcc -c -Wall -ansi droites.c<br />

gcc main.o droites.o -lm -o mesures.exe


1 INTRODUCTION Makefile 50<br />

Un fichier Makefile décrit les dépendances entre les différents<br />

fichiers sources d’un projet<br />

Les commandes de compilation seront invoquées séparément<br />

Les dates systèmes des fichiers indiqueront les fichiers à recompiler<br />

Un ou des fichiers seront recompilés selon les dépendances (en<br />

cascade)<br />

1 default: fic1.o fic2.o main.o<br />

2 gcc -o prog.exe fic1.o fic2.o<br />

main.o<br />

3<br />

4 main.o: fic1.h fic2.h main.c<br />

5 gcc -c -Wall -ansi main.c<br />

6<br />

7 fic1.o: fic1.h fic1.c<br />

8 gcc -c -Wall -ansi fic1.c<br />

9<br />

10 fic2.o: fic2.h fic2.c<br />

11 gcc -c -Wall -ansi fic2.c<br />

1 En invoquant la commande make (ou<br />

make default), on consulte la tâche<br />

default<br />

2 Elle implique que les tâches fic1.o<br />

fic2.o et main.o soient à jour<br />

3 Si ce n’est pas le cas, alors ces tâches<br />

sont effectuées<br />

4 Effectuer une tâche consiste à exécuter la (ou les) ligne(s) qui suivent et<br />

qui commençent par un saut de tabulation


1 INTRODUCTION Makefile 51<br />

Exemple de Makefile simple<br />

et générique<br />

1 Invoquer la commande make<br />

provoque la compilation du projet<br />

Makefile<br />

1 # Makefile (simple) generique<br />

2<br />

3 # Variables a modifier (projet en <strong>cours</strong>)<br />

4 EXEC = mesures.exe<br />

5 SRC = main.c droites.c<br />

6 LIB = -L. -lm<br />

7 INC = -I.<br />

8<br />

9 # Variables immuables (ne pas modifier)<br />

10 CC = gcc<br />

11 CFLAG = -O -c -Wall -ansi $(INC)<br />

12 OFLAG = -O -o $(EXEC)<br />

13 OBJ = $(SRC:.c=.o)<br />

14<br />

15 default: $(OBJ)<br />

16 $(CC) $(OFLAG) $(LIB) $(OBJ)<br />

17<br />

18 .c.o:<br />

19 $(CC) $(CFLAG) $<<br />

20<br />

21 clean:<br />

22 del *.o *˜ core #*#<br />

23<br />

24 # Fin du Makefile<br />

2 Invoquée de nouveau, la<br />

commande make ne déclenche rien<br />

3 Si main.c a changé, alors il est<br />

recompilé (seul)<br />

4 Invoquer make clean nettoie les<br />

fichiers de compilation (.o)


1 INTRODUCTION Environnements de développement 52<br />

Code, Compilation, Exécution : tout dans la même fenêtre


1 INTRODUCTION Environnements de développement 53<br />

Principaux IDE gratuits permettant de développer en C :<br />

(Integrated Development Environment)<br />

Code::Blocks<br />

Visual C++ 2010<br />

wxDev-c++<br />

Xcode<br />

Eclipse<br />

← (version ”Express” gratuite)<br />

← ESTIA<br />

En savoir plus : http://c.developpez.com/compilateurs/


1 INTRODUCTION Environnements de développement 54<br />

ECLIPSE<br />

Initié par IBM (Initialement dédié à Java)<br />

Passage en logiciel libre en 2001 (Eclipse Foundation)<br />

IDE extensible, universel et polyvalent<br />

Multi-plateforme (Windows, Linux, Mac, . . .)<br />

Langages : Java, C/C++, C#, Ada, Python, Pascal, Cobol. . .<br />

Version Projets Lignes Date<br />

1.0 nov. 2001<br />

3.0 juin 2004<br />

3.3 Europa 21 17.10 6 juin 2007<br />

3.5 Galileo 33 24.10 6 juin 2009<br />

3.6 Helios 77 juin 2010<br />

3.7 Indigo juin 2011<br />

La version actuelle d’Eclipse propose 12 paquetages :<br />

CDT (C/C++ Development Tools)<br />

JDT (Java Development Tools)<br />

ADT (Ada Development Tools)<br />

. . . http://www.eclipse.org


1 INTRODUCTION Environnements de développement 55<br />

ECLIPSE


1 INTRODUCTION Recommandations 56<br />

→ Ce n’est pas en regardant un forgeron que l’on apprend à forger<br />

(canfere un proverbe bien connu)<br />

→ Il en va de même en programmation :<br />

Ce n’est pas parce que vous aurez compris le code d’un<br />

programme (exemples, corrections, . . .) que vous saurez<br />

programmer<br />

Ce n’est pas non plus en recopiant des programmes qu’on<br />

apprend à programmer (même si c’est un bon début)<br />

Vous saurez programmer lorsque vous serez capables d’écrire<br />

un programme ”from scratch”, c.à.d. en partant d’un fichier vide<br />

Commencer par de petits programmes est le mieux pour<br />

apprendre (Par exemple réécrire helloworld.c depuis zéro)<br />

Essayer de modifier et/ou compléter le comportement des<br />

programmes donnés en exemple est aussi un bon réflexe pour<br />

apprendre


Page support 57<br />

http://www.guillaumeriviere.name/estia/C/<br />

Supports de <strong>cours</strong><br />

Pour écran<br />

Pour imprimante<br />

Par chapitre<br />

Exemples du <strong>cours</strong><br />

Fichiers sources .c<br />

Fichiers exe pour Windows<br />

Archive zip<br />

Exercices<br />

Énoncés de TP (en ligne)<br />

Énoncés de TD (pdf)<br />

17 exercices pour s’entraîner<br />

Ressources web<br />

Cours, Tutoriels, . . .<br />

Manuels<br />

Outils (GCC, Notepad++, . . .)


Plan 58<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


2 TYPES DE BASE Introduction 59<br />

Un modèle de données comporte 2 aspects indissociables :<br />

1 Les valeurs pouvant être attribuées aux objets<br />

2 Les opérations sur les données<br />

En C, il existe peu de modèles de données. On trouve :<br />

les types de base (nombres <strong>entier</strong>s et réels)<br />

les tableaux<br />

les enregistrements<br />

les pointeurs<br />

Dans ce chapitre on ne traite que des types de base et des tableaux.<br />

Les pointeurs et les enregistrements (ou structures) font l’objet de<br />

deux chapitres dédiés


2 TYPES DE BASE Variables 60<br />

21<br />

Variables<br />

Une variable = un nom, un type, une valeur et un espace mémoire<br />

Le nom d’une variable commence soit par une lettre, soit par un<br />

underscore. Les autres caractères peuvent aussi comporter des<br />

chiffres.<br />

Exemples de noms valides : i, Maximum,<br />

3 X, n1, n2, N, TAILLE<br />

Il y a deux principales classes de types :<br />

1 Les nombres <strong>entier</strong>s<br />

char (8 bits)<br />

int (16 ou 32 bits selon l’architecture sizeof())<br />

2 Les nombres réels<br />

float (32 bits) → IEEE754 simple précision<br />

double (64 bits) → IEEE754 double précision


2 TYPES DE BASE Nombres <strong>entier</strong>s 61<br />

22<br />

Variables entières<br />

→ Représentation binaire des <strong>entier</strong>s (base 2) :<br />

Exemples :<br />

5 (10) = 1×2 2 + 0×2 1 + 1×2 0 = 101 (2)<br />

16 (10) = 1×2 4 + 0×2 3 + 0×2 2 + 0×2 1 + 0×2 0 = 10000 (2)<br />

43 (10) = 32 + 8 + 2 + 1 = 1×2 5 + 1×2 3 + 1×2 1 + 1×2 0 = 101011 (2)<br />

L’espace en mémoire est limité (fini) ⇒ discrétisation de N et Z<br />

Sur une zone mémoire de n bits, on peut coder ces intervalles :<br />

Entiers naturels : [ 0 ; 2 n − 1 ]<br />

Entiers relatifs : [ −2 n−1 ; 2 n−1 − 1 ]<br />

Les règles mathématiques : pas toujours valides en informatique<br />

Ainsi, la règle ∀n ∈ N ∃p ∈ N tq p = n + 1 est fausse<br />

2 unsigned char i, j ;<br />

3<br />

4 i = 255 ;<br />

5 j = i + 1 ;<br />

6<br />

7 /* j vaut 0 */<br />

7 6 5 4 3 2 1 0<br />

1 1 1 1 1 1 1 1 i 255<br />

+ 0 0 0 0 0 0 0 1 + 1<br />

1 0 0 0 0 0 0 0 0 j 0


2 TYPES DE BASE Nombres <strong>entier</strong>s 62<br />

RMQ : Différents qualificatifs existent pour les types :<br />

signed, unsigned (réservé aux <strong>entier</strong>s)<br />

short (réservé aux <strong>entier</strong>s) et long (pour int et double)<br />

Table des types <strong>entier</strong>s possibles :<br />

Type Abrégé Octets Bits Intervalle<br />

signed char char 1 8 [−128; 127]<br />

signed short int short 2 16 [−32.768; 32.767]<br />

signed int int 2 ou 4<br />

signed long int long 4 32 [−2.147.483.648; 2.147.483.647]<br />

signed long long int long long 8 64 [−2 63 ; 2 63 − 1]<br />

unsigned char 1 8 [0; 255]<br />

unsigned short int unsigned short 2 16 [0; 65.535]<br />

unsigned int 2 ou 4<br />

unsigned long int unsigned long 4 32 [0; 4.294.967.295]<br />

unsigned long long int unsigned long long 8 64 [0; 2 64 − 1]<br />

La plupart des architectures codent les<br />

types signed int et unsigned int<br />

sur 4 octets.<br />

2 63 = 9, 223372 × 10 18<br />

2 64 = 1, 84467441 × 10 19


2 TYPES DE BASE Nombres réels 63<br />

23<br />

Variables réelles (float et double)<br />

En C les nombres réels sont représentés selon la norme IEEE754<br />

simple précision (type float) et double précision (type double) :<br />

(−1) S × 1,m × 2 E−d<br />

d = 2 e−1 − 1<br />

float double long double<br />

avec : 32 bits 64 bits 80 bits<br />

S Signe 1 bit 1 bit 1 bit<br />

m Mantisse 23 bits 52 bits 64 bits<br />

E Exposant e = 8 bits e = 11 bits e = 15 bits<br />

d Décalage d = 127 d = 1023 d = 16.383<br />

Exemple :<br />

S = 0<br />

E = 10000001 = 129<br />

m = 01000000000000000000000<br />

1,m = 1,01000000000000000000000 = 1 × 2 0 + 0 × 2 −1 + 1 × 2 −2 = 1 + 0 + 0, 25 = 1, 25<br />

(−1) 0 × 1,m × 2 129−127 = 1 × 1, 25 × 2 2 = 5, 0


2 TYPES DE BASE Nombres réels 64<br />

Exemple :<br />

S = 0<br />

E = 10000001 = 129<br />

m = 01100000000000000000000<br />

1,m = 1,01100000000000000000000 = 1 × 2 0 + 0 × 2 −1 + 1 × 2 −2 + 1 × 2 −3<br />

= 1 + 0 + 0, 25 + 0, 125<br />

= 1, 375<br />

(−1) 0 × 1,m × 2 129−127 = 1 × 1, 375 × 2 2 = 5, 5<br />

Valeurs spéciales :<br />

+0 et -0 : il existe deux zéros !<br />

Quand E = 0 et m = 0<br />

+Inf et -Inf : les deux infinis +∞ et −∞<br />

Qd E = 2 e − 1 et m = 0<br />

NaN (Not a Number) : du à une erreur de calcul (division par 0, . . .)<br />

Qd E = 2 e − 1 et m ≠ 0


2 TYPES DE BASE Nombres réels 65<br />

Pour les mêmes raisons qui font que les ensembles N ou Z sont des<br />

ensembles finis en informatique, l’ensemble R est lui aussi fini :<br />

1 Ainsi, il existe un nombre réel maximum et un nombre réel minimum<br />

(ensemble borné) pour chacun des types float et double<br />

2 Et la règle mathématique ∀ x, y ∈ R ∃ z ∈ R tq x < z < y<br />

n’est pas vérifiée<br />

Par exemple :<br />

Plus grand nombre<br />

float : 3, 40282346 × 10 38<br />

double : 1, 7976931348623157 × 10 308 long double : 3, 4 × 10 4932<br />

Nombre juste après 1.0<br />

float : 1.000000119209289550781250000 ≈ 1.00000012<br />

double : 1.0000000000000002220446049250313080847263336181640625000<br />

≈ 1.000000000000000222<br />

Nombre juste avant 1.0<br />

float : 0.999999940395355224609375000 ≈ 0.99999994<br />

double : 0.9999999999999998889776975374843459576368331909179687500<br />

≈ 0.999999999999999889


2 TYPES DE BASE Nombres réels 66<br />

Ainsi, sur machine, l’ensemble des réels n’est pas dense.<br />

Exemple :<br />

4 float x ;<br />

5<br />

6 x = 38.0 ;<br />

7 printf ("x vaut %.15lf\n", x) ;<br />

8<br />

9 x = x + 0.05 ;<br />

10 printf ("x vaut %.15lf\n", x) ;<br />

↩→ Toujours préférer les double car plus précis que float !<br />

Mais occupent 64 bits contre 32 bits. . .<br />

⇒ trouver compromis entre précision et occupation mémoire<br />

Par exemple OpenGL utilise triplet de float pour coordonnées 3D<br />

RMQ : Le test d’égalité se révèle donc dangeureux !<br />

Par exemple while(x != 95.005) peut ne jamais s’arrêter<br />

Il sera préférable de tester :<br />

while (fabs (x - 95.005) < epsilon)


2 TYPES DE BASE Nombres réels 67<br />

Proposition :<br />

La représentation (en virgule flottante) des nombres réels (sur un<br />

espace discret) selon la norme ieee754 est :<br />

plus dense dans l’intervale [0 ; 1] (densité très forte)<br />

que dans l’intervalle [1 ; 2] (densité moyenne)<br />

et que dans l’intervalle [2 ; +∞] (densité faible).<br />

Lemme 1 :<br />

∀ m : 1 ≤ 1,m < 2<br />

Lemme 2 :<br />

E < 127 ⇔ 2 E−127 < 1<br />

Donc si E < 127 alors ∀ m : 1,m × 2 E−127 < 1,m<br />

Lemme 3 :<br />

E > 127 ⇔ 2 E−127 > 1<br />

Donc si E > 127 alors ∀ m : 1,m × 2 E−127 > 1,m


2 TYPES DE BASE Nombres réels 68<br />

Démonstration :<br />

Considérons la fonction 2 X :<br />

Bien entendu, cette courbe n’est pas à l’échelle !<br />

2 64 = 1, 84467441 × 10 19 = 18.446, 7441 millions de milliards<br />

2 127 = 1, 70141183 × 10 38


2 TYPES DE BASE Nombres réels 69<br />

Après changement de repère avec E = X + 127 :


2 TYPES DE BASE Nombres réels 70<br />

Après multiplication par 1,m :<br />

Si E < 127 alors ∀ m : 1,m × 2 E−127 < 1,m (lemme 2)<br />

Si E > 127 alors ∀ m : 1,m × 2 E−127 > 1,m (lemme 3)


2 TYPES DE BASE Nombres réels 71<br />

Or ∀ m : 1 ≤ 1,m < 2 (lemme 1)


2 TYPES DE BASE Nombres réels 72<br />

mantisse sur 23 bits : 2 23 = 8.388.608 ≈ 8, 4 × 10 6 (8,4 millions)<br />

mantisse sur 52 bits : 2 52 ≈ 4, 5 × 10 15 ≈ 4.503.599 × 10 9 (4,5 millions de milliards)


2 TYPES DE BASE Les tableaux 73<br />

24<br />

Les tableaux<br />

Tableau = une collection de variables, du même type enregistrées,<br />

consécutivement en mémoire.<br />

Un tableau est identifié par son nom (par exemple A)<br />

Un tableau est caractérisé par le type des éléments qu’il stocke<br />

(par exemple des int)<br />

Un tableau est caractérisé par sa taille (A est de taille N)<br />

⇒ En mémoire N cases consécutives seront réservées<br />

⇒ Chaque case permet de stocker un int<br />

⇒ La longueur de l’espace mémoire est N*sizeof(int) octets<br />

L’opérateur [] permet d’accéder aux éléments du tableaux<br />

⇒ A[i] est la i-ème case du tableau<br />

Les tableaux sont toujours indicés à partir de 0<br />

⇒ La première case du tableau est donc A[0]<br />

⇒ La dernière case du tableau est donc A[N-1]


2 TYPES DE BASE Les tableaux 74<br />

Exemple 1 : Déclaration d’un tableau de 3 int :<br />

4 int T[3] ;<br />

5<br />

6 T[0] = 10 ;<br />

7 T[1] = 20 ;<br />

8 T[2] = 30 ;<br />

sizeof(int) → 4 octets<br />

3 * sizeof(int) → 12 octets (96 bits)<br />

T occupera 12 octets (consécutifs) en mémoire<br />

Exemple 2 : Déclaration d’un tableau de 6 octets<br />

4 int i ;<br />

5 char T[6] ; /* 6 octets */<br />

6<br />

7 T[0] = 1 ;<br />

8 for (i=1 ; i


2 TYPES DE BASE Les tableaux 75<br />

RMQ : Les chaînes de caractères sont représentées par des<br />

tableaux de char<br />

La taille du tableau doit être suffisante pour accueillir tous les caractères<br />

(du mot, de la phrase, du texte, . . .)<br />

La fin d’une chaîne est marquée par le caractère ’\0’<br />

↩→ La taille minimum du tableau est donc TOUJOURS :<br />

chaine.c<br />

1 #include <br />

2<br />

3 int main () {<br />

4 char nom[10] ;<br />

5<br />

6 nom[0] = ’R’ ;<br />

7 nom[1] = ’i’ ;<br />

8 nom[2] = ’t’ ;<br />

9 nom[3] = ’c’ ;<br />

10 nom[4] = ’h’ ;<br />

11 nom[5] = ’i’ ;<br />

12 nom[6] = ’e’ ;<br />

13 nom[7] = ’\0’ ;<br />

14<br />

15 printf ("Nom : %s\n", nom)<br />

;<br />

16 return 0 ;<br />

17 }<br />

(nombre de caractères à stocker) + 1<br />

Espace alloué en mémoire :<br />

↩→ Ici, taille max de la chaîne = 9


2 TYPES DE BASE Caractères 76<br />

25<br />

Les caractères<br />

Attention, car on pense souvent que type char ⇔ caractère<br />

Mais un char c’est un nombre <strong>entier</strong> ! (sur 1 octet)<br />

En fait, chaque caractère est représenté par un nombre . . .<br />

Code ASCII = American Standard Code for Information Interchange<br />

De 0 à 31 : caractères non imprimables (caractères de contrôle)<br />

De 48 à 57 : les chiffres (caractères numériques)<br />

De 65 à 90 : les caractères alphabétiques en majuscule<br />

De 97 à 122 : les caractères alphabétiques en minuscule<br />

Autres : caractères de ponctuation (33-47, 58-64, 91-96, 123-126)<br />

Table ASCII 128 (1961 - normalisation ANSI en 1986)<br />

de 0 à 127<br />

langue anglaise, pas de caractères accentués<br />

Table ASCII 256 “étendue”(ex : Latin-1 en 1987 pour l’Europe occidentale)<br />

de 0 à 255 (la partie 128-255 peut varier selon la plateforme)


2 TYPES DE BASE Extrait de la table ASCII 77<br />

CODE caractère CODE caractère CODE caractère CODE caractère CODE caractère<br />

8 \b 48 0 68 D 88 X 108 l<br />

9 \t 49 1 69 E 89 Y 109 m<br />

10 \n 50 2 70 F 90 Z 110 n<br />

13 \r 51 3 71 G 91 [ 111 o<br />

32 espace 52 4 72 H 92 \ 112 p<br />

33 ! 53 5 73 I 93 ] 113 q<br />

34 ” 54 6 74 J 94 ∧ 114 r<br />

35 # 55 7 75 K 95 115 s<br />

36 $ 56 8 76 L 96 ‘ 116 t<br />

37 % 57 9 77 M 97 a 117 u<br />

38 & 58 : 78 N 98 b 118 v<br />

39<br />

′<br />

59 ; 79 O 99 c 119 w<br />

40 ( 60 < 80 P 100 d 120 x<br />

41 ) 61 = 81 Q 101 e 121 y<br />

42 ∗ 62 > 82 R 102 f 122 z<br />

43 + 63 ? 83 S 103 g 123 {<br />

44 , 64 @ 84 T 104 h 124 |<br />

45 − 65 A 85 U 105 i 125 }<br />

46 . 66 B 86 V 106 j 126 ∼<br />

47 / 67 C 87 W 107 k 255 EOF


2 TYPES DE BASE Constantes 78<br />

→ Il existe trois sortes de constantes : les variables constantes (!),<br />

les constantes classiques et les constantes énumérées.<br />

26<br />

Les variables constantes<br />

Le mot clé const = spécification du type d’une variable, qui devient<br />

consultable mais non modifiable.<br />

2 const double e = 2.71828182845905 ;<br />

3 const char msg[] = "tableau de caracteres" ;<br />

4 int strlen (const char s[]) ;<br />

La vérification que la valeur de telles constantes ne change pas<br />

est réalisée par le compilateur. Il vérifie qu’aucune ligne de code<br />

n’essaie explicitement de changer cette valeur.<br />

(Il peut donc arriver que les constantes soient altérées, par exemple via des<br />

pointeurs ou des accès hors limite de tableaux ⇒ bugs possibles. . .)


2 TYPES DE BASE Constantes 79<br />

Exemple :<br />

La compilation avec gcc du morceaux de<br />

programme suivant produit ce message :<br />

line 2: error: assignment of read-only<br />

location<br />

const.c<br />

1 void reset (const char s[]) {<br />

2 s[0] = ’\0’ ;<br />

3 }<br />

4<br />

5 int main () {<br />

6 char T[10] ;<br />

7<br />

8 reset (T) ;<br />

9<br />

10 return 0 ;<br />

11 }<br />

27<br />

Constantes classiques<br />

Il s’agit des valeurs directement écrites (”en dur”) dans le code<br />

source, que ce soit des <strong>entier</strong>s, des réels, mais aussi des chaînes de<br />

caractères et des caractères.<br />

3 int i = 12 ;<br />

4 double r = .5 ;<br />

5 char c = ’a’ ;<br />

6 printf ("Hey" " world\n") ;<br />

12 → [chiffres] + → nombre <strong>entier</strong> 12<br />

.5 → [chiffres] ∗ .[chiffres] ∗ → nombre réel 0.5<br />

’a’ → simples quotes → code ASCII de la lettre a<br />

"Hey" → doubles quotes → chaîne de caractères


2 TYPES DE BASE Constantes 80<br />

Ces valeurs sont traitées et traduites par le compilateur. Aussi<br />

écrire x=6.2830; est moins judicieux que d’écrire x=2*3.1415;.<br />

L’efficacité est la même mais la lisibilité (et donc la maintenabilité) est<br />

bien meilleure dans le deuxième cas. De même, écrire<br />

ns=60*60*24; est plus judicieux que ns=86400;.<br />

Ce traitement s’applique aussi aux chaînes de caractères, qui sont<br />

automatiquement concaténées par le compilateur lorsqu’elles sont<br />

séparées juste par quelques espaces. Ceci permet parfois<br />

d’amméliorer grandement la lisibilité. . . (très longues chaînes)<br />

28<br />

Constantes énumérées<br />

Elles définissent un nouveau type comme étant un ensemble<br />

d’étiquettes possibles. Les valeurs des étiquettes sont des <strong>entier</strong>s qui<br />

se suivent (chaque étiquette prend la valeur de la précédente + 1) en<br />

partant de 0. (On peut cependant spécifier certaines valeurs).


2 TYPES DE BASE Constantes 81<br />

2 enum logique { FAUX, VRAI } ; /* 0 et 1 */<br />

3 enum rien { UN=1, DEUX, TROIS, CINQ=5, SIX } ;<br />

4 enum direction { LEFT, RIGHT, DOWN, UP } ;<br />

Le mécanisme de renumérotation est très utile pour spécifier des<br />

sous-ensembles d’<strong>entier</strong>s. Le compilateur vérifiera la cohérence des<br />

valeurs, dans la mesure du possible.<br />

Exemple :<br />

Définir une position et coder les<br />

déplacements possibles.<br />

RMQ : D’autres valeurs pourraient<br />

être définies (selon les besoins) :<br />

LEFT DOWN → x -= 10 ; y -= 10 ;<br />

RIGHT DOWN → x += 10 ; y -= 10 ;<br />

LEFT UP → x -= 10 ; y += 10 ;<br />

RIGHT UP → x += 10 ; y += 10 ;<br />

7 int x, y ;<br />

8 enum direction dir ;<br />

9<br />

10 /* ... */<br />

11<br />

12 switch (dir) {<br />

13 case LEFT:<br />

14 x -= 10 ;<br />

15 break ;<br />

16 case RIGHT:<br />

17 x += 10 ;<br />

18 break ;<br />

19 case DOWN:<br />

20 y -= 10 ;<br />

21 break ;<br />

22 case UP:<br />

23 y += 10 ;<br />

24 break ;<br />

25 default:<br />

26 printf ("Unknown direction\n") ;<br />

27 }


2 TYPES DE BASE Conversions 82<br />

29<br />

Conversions de types<br />

La conversion d’une variable d’un type T vers un type T’ est<br />

importante : elle permet d’exprimer une valeur dans le type T vers<br />

une valeur dans le type T’.<br />

Exemple :<br />

récupérer la valeur entière d’une opération en nombre réels,<br />

opération avec des variables de types différents.<br />

Par définition, il n’est pas assuré que la valeur dans T’ soit la même<br />

que celle dans T. (Exemple : l’<strong>entier</strong> signé -1 vers le type unsigned int.<br />

Ou encore int i = cos (sqrt ((2))) ;)<br />

Le programmeur doit expliciter les conversions pour montrer sa prise<br />

de conscience des riques et erreurs possibles.


2 TYPES DE BASE Conversions 83<br />

Deux façons pour effectuer une conversion :<br />

1 La bonne : explicitement, via l’opérateur de conversion ”cast”, i.e.<br />

les parenthèses () encadrant le type destinataire T’.<br />

2 int i = 4 ;<br />

3 float r ;<br />

4<br />

5 r = (float)i ;<br />

6 i = (int)r ;<br />

2 La moins bonne : implicitement, e.g. : un opérateur reçoit des<br />

opérandes de types différents ⇒ conversion dans un même type.<br />

5 int i = 5 ;<br />

6 char c ;<br />

7<br />

8 c = ’0’ + i ;


2 TYPES DE BASE Conversions 84<br />

Les conversions implicites sont régies par des règles précises. Si<br />

aucun opérande n’est du type unsigned, alors :<br />

Si un des opérandes est long double, l’autre le devient.<br />

Sinon, si un des opérandes est double, l’autre le devient.<br />

Sinon, si un des opérandes est float, l’autre le devient.<br />

Sinon les opérandes de type char et short sont converties en<br />

int (même si les deux opérandes sont de type char)<br />

Puis, si un des opérandes est long, l’autre le devient.<br />

RMQ : Dans du code dont la sûreté est cruciale (applications<br />

critiques : vies humaines, banques, spatial, . . .), on évitera toute<br />

conversion. Il est préférable de créer ses propres fonctions de<br />

conversions traitant les erreurs, qui seront utilisées lorsque cela est<br />

inévitable.


2 TYPES DE BASE Erreurs 85<br />

210<br />

Exemples d’erreurs classiques<br />

En oubliant qu’il est impossible d’effectuer des opérations sur des<br />

variables de types différents, on obtient souvent des bugs. . .<br />

Pourtant, le compilateur ne dit rien, toujours à cause du principe<br />

”la syntaxe est bonne, le reste concerne le programmeur. . .”<br />

Ainsi, le compilateur effectue des conversions à l’insu du<br />

programmeur. . . introduisant ainsi des erreurs.<br />

5 unsigned short s ;<br />

6 unsigned long ul ;<br />

7 float r ;<br />

8<br />

9 r = 10e30 ; /* 10eXX signifie 10 puissance XX */<br />

10 s = r ; /* on obtient s = 0 !! */<br />

11<br />

12 ul = -1 ; /* le plus grand <strong>entier</strong> non signe */<br />

13 r = ul ; /* on obtient r = 4.294.967.296<br />

14 alors que ul vaut 4.294.967.295 */


2 TYPES DE BASE Erreurs 86<br />

Ces erreurs sont rares et peu courantes : elles passent<br />

inaperçues.<br />

(Souvent, elles n’apparaissent que lors de phases de tests pointus)<br />

↩→ Débuggage laborieux pour identifier l’origine de l’erreur.<br />

Ces erreurs sont bien plus dangereuses quand on manipule des<br />

pointeurs (adresses mémoire de variables ou de fonctions), car il<br />

s’agit alors de comment le compilateur joue avec la mémoire.<br />

Exemple : écrire sur un char les 32 bits d’un int . . . dans ce cas on<br />

écrit sur de la mémoire qui est utilisée pour d’autres choses (en<br />

l’occurence 24 bits).<br />

↩→ Le débuggage de ce genre de ”bugs sévères” peut rapidement<br />

devenir extrêmement laborieux.


Plan 87<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


3 OPÉRATEURS Affectation d’une variable 88<br />

Les opérateurs, unaires (un opérande) ou binaires (deux opérandes),<br />

permettent de manipuler les variables, les nombres et les bits.<br />

31<br />

L’opérateur d’affectation<br />

L’affectation est l’opération qui permet d’attribuer (de donner) une<br />

valeur à une variable. Cette valeur peut être une constante classique<br />

(écrite directement dans le code source), la valeur d’une autre<br />

variable, la valeur résultant de l’évaluation d’une expression ou<br />

encore le résultat de l’appel d’une fonction.<br />

Exemple :<br />

5 int i, j ;<br />

6 double r, a, b ;<br />

7<br />

8 /* ... */<br />

9<br />

10 i = 2 ;<br />

11 j = (2 * i * i) % 6 - i - 1 ;<br />

12 r = fabs (sin (a) + exp (b - a)) ;


3 OPÉRATEURS Affectation d’une variable 89<br />

À chaque variable est associé un espace mémoire (d’une taille<br />

déterminée par le type → sizeof()). Les affectations permettent<br />

tout simplement d’aller écrire dans cet espace mémoire.<br />

Pseudo-code Code C En mémoire<br />

(1) a : <strong>entier</strong> int a ;<br />

(2) b : <strong>entier</strong> int b ;<br />

(3) b ← 4 b = 4 ;<br />

(4) a ← b a = b ;<br />

(5) a ← a + 2 a = a + 2 ;


3 OPÉRATEURS Échange de deux variables 90<br />

Pseudo-code Code C En mémoire<br />

(1) a, b, tmp : <strong>entier</strong> int a, b, tmp ;<br />

(2) a ← 1 a = 1 ;<br />

(3) b ← 6 b = 6 ;<br />

(4) a ↔ b /* Unknown ? */<br />

(5) tmp ← a tmp = a ;<br />

(6) a ← b a = b ;<br />

(7) b ← tmp b = tmp ;


3 OPÉRATEURS + − ∗ / % 91<br />

32<br />

Les opérateurs arithmétiques<br />

Les opérateurs arithmétiques sont +, -, *, / et l’opérateur modulo %<br />

La division entière tronque l’éventuelle partie fractionnaire<br />

(ex : (int)4/3 donne 1)<br />

Attention aux erreurs : opérations stables dans ensembles finis<br />

5 unsigned short a, b ;<br />

6<br />

7 /* ... */<br />

8<br />

9 b = a - 1 ; /* quid si a vaut 0 ? */<br />

10 a = 256 * 257 ; /* apres l’operation a vaut 256 !! */<br />

unsigned int<br />

∈ [0; 2 16 − 1] = [0; 65537]<br />

65537 = (256 × 256) − 1<br />

Attention aux conversions implicites :<br />

unsigned char c = ’9’ * ’2’ ; /* c vaudra ’"’ */<br />

Ici, ’9’ et ’2’ sont convertis en int ; l’opérateur ∗ est alors appliqué ; le<br />

résultat est converti en unsigned char (soit 50∗57 = 2850, 2850 modulo<br />

256 = 34, soit ’"’ en ASCII).


3 OPÉRATEURS + − ∗ / % 92<br />

L’opérateur modulo s’écrit % et ne s’applique qu’aux <strong>entier</strong>s. Ainsi,<br />

12%5 donnera 2, alors que 12.2%2 provoque une erreur de<br />

compilation.<br />

→ L’opération a % b calcule le reste de la division de la division<br />

entière de a par b.<br />

Exemple : 10/3 = 3 et 10%3 = 1<br />

→ Ainsi, pour savoir si a est divisible par b,<br />

on peut tester simplement si a%b vaut 0.<br />

→ Le modulo est également utile pour faire boucler un indice dans un<br />

intervalle donné :<br />

3 int incrementer (int i) {<br />

4 return (i+1) % 10 ;<br />

5 }<br />

incrementer(0) renvoie 1<br />

incrementer(1) renvoie 2<br />

. . .<br />

incrementer(8) renvoie 9<br />

incrementer(9) renvoie 0


3 OPÉRATEURS + − ∗ / % 93<br />

→ Pour calculer le modulo sur des nombre réels, utiliser les fonctions<br />

de math.h :<br />

float fmodf (float x, float y) ;<br />

double fmod (double x, double y) ;<br />

long double fmodl (long double x, long double y);<br />

Les opérateurs + − ∗ / s’appliquent aussi aux réels (float,<br />

double et long double)<br />

IMPORTANT : la priorité des opérateurs (table donnée en fin de<br />

chapitre).<br />

↩→ Utiliser des parenthèses pour contourner le problème<br />

Exemple trivial :<br />

2+5*4 → 22<br />

(2+5)*4 → 28


3 OPÉRATEURS ++ −− 94<br />

33<br />

Incrémentation et décrémentation<br />

Les deux opérateurs ++ et −−, respectivement incrémente (ajoute<br />

1) ou décrémente (ôte 1) le contenu d’une variable de type <strong>entier</strong>.<br />

Attention : comme tout opérateur, ils retournent une valeur résultat :<br />

S’il s’agit de l’opérateur de pré-incrémentation (++variable), la<br />

valeur retournée est la nouvelle valeur incrémentée.<br />

S’il s’agit de l’opérateur de post-incrémentation (variable++), la<br />

valeur retournée est l’ancienne valeur avant incrémentation.<br />

De même pour la pré- et post-décrémentation.<br />

Exemples :<br />

RMQ : On utilise ces<br />

opérateurs plutôt que la forme<br />

traditionnelle i=i+1; pour deux<br />

raisons : efficacité et lisibilité<br />

4 int i, j, k ;<br />

5<br />

6 i = 0 ;<br />

7 j = i++ ; /* j = 0 et i = 1 */<br />

8 j = ++i ; /* j = 2 et i = 2 */<br />

9 k = j+++i ; /* a voter avis ? k = 4, j = 3, i = 2 */<br />

10<br />

11 /* ... */<br />

12<br />

13 for (i=0 ; i


3 OPÉRATEURS Logiques 95<br />

34<br />

Les opérateurs logiques<br />

Les 4 opérateurs de comparaison sont les suivants :<br />

> >= <


3 OPÉRATEURS Logiques 96<br />

L’opérateur de négation est simplement l’exclamation :<br />

Rappel : tables de vérité<br />

!<br />

¬V F ¬F V<br />

ET V F OU V F OUEX V F<br />

V V F V V V V F V<br />

F F F F V F F V F<br />

RMQ : Les opérateurs && et || sont paresseux et s’évaluent de<br />

gauche à droite, ce qui permet d’écrire :<br />

5 int x, y, z ;<br />

6<br />

7 /* ... */<br />

8<br />

9 while (y!=0 && z>x/y)<br />

10 y-- ;<br />

y!=0 sera évalué en premier :<br />

↩→ Si FAUX, alors pas besoin d’évaluer le<br />

deuxième opérande de &&, car sera FAUX<br />

de toute façon (voir table de vérité).<br />

↩→ Si VRAI, alors z>x/y est évalué.


3 OPÉRATEURS Logiques 97<br />

RMQ : une expression logique vaut 1 si elle est vrai, 0 sinon (pas<br />

de type booléen).<br />

Ce fait est crucial. Exemple :<br />

5 int i, j, x ;<br />

6<br />

7 /* ... */<br />

8<br />

9 x = (i == 3) + (j == 2) ; /* x vaut 0 si i!=3 ET j!=2,<br />

10 1 si i==3 OUEX j==2<br />

11 2 si i==3 ET j==2 */<br />

12<br />

13 /* boucle infinie qui ne fait rien... */<br />

14 while (1)<br />

15 ;<br />

16<br />

17 /* boucle infinie, mais due a une erreur courante */<br />

18 for (i=1 ; continuer=1 ; i++) {<br />

19 ... /* Traitement quelconque */<br />

20 if (...)<br />

21 continuer = 1 ;<br />

22 else<br />

23 continuer = 0 ;<br />

24 }


3 OPÉRATEURS Les bits 98<br />

35<br />

Traitement des bits<br />

Il existe 6 opérateurs spécifiques de traitement bit à bit, 5 sont<br />

binaires et 1 est unaire. Ils sont très puissants, mais souvent<br />

ignorés. . .<br />

& ET > décalage à droite<br />

∧ OU exclusif (OUEX) ∼ complément à 1 (unaire)<br />

ET bits à bits : c i = a i ET b i<br />

19 char a, b, c ;<br />

20<br />

21 a = 111 ;<br />

22 b = 119 ;<br />

23 c = a & b ;<br />

24 /* c vaut 103 */<br />

7 6 5 4 3 2 1 0<br />

0 1 1 0 1 1 1 1 a 111<br />

& 0 1 1 1 0 1 1 1 b & 119<br />

0 1 1 0 0 1 1 1 c 103


3 OPÉRATEURS Les bits 99<br />

OU bits à bits : c i = a i OU b i<br />

32 char a, b, c ;<br />

33<br />

34 a = 111 ;<br />

35 b = 119 ;<br />

36 c = a | b ;<br />

37 /* c vaut 127 */<br />

7 6 5 4 3 2 1 0<br />

0 1 1 0 1 1 1 1 a 111<br />

| 0 1 1 1 0 1 1 1 b | 119<br />

0 1 1 1 1 1 1 1 c 127<br />

OUEX bits à bits : c i = a i OUEX b i<br />

46 char a, b, c ;<br />

47<br />

48 a = 111 ;<br />

49 b = 119 ;<br />

50 c = a ˆ b ;<br />

51 /* c vaut 24 */<br />

7 6 5 4 3 2 1 0<br />

0 1 1 0 1 1 1 1 a 111<br />

∧ 0 1 1 1 0 1 1 1 b ∧ 119<br />

0 0 0 1 1 0 0 0 c 24<br />

Décalage à gauche : b i = a i−1 (b=a


3 OPÉRATEURS Les bits 100<br />

Décalage à droite : b i = a i+1 (b=a>>n; ⇔ b = a/2 n )<br />

73 char a, b, c ;<br />

74<br />

75 a = 11 ;<br />

76 b = a >> 1 ;<br />

77 c = a >> 3 ;<br />

Complément à 1 b i =!a i<br />

86 char a, b, c ;<br />

87<br />

88 a = 11 ;<br />

89 b = ˜a ;<br />

90 c = ˜0 ;<br />

Exemples :<br />

7 6 5 4 3 2 1 0<br />

0 0 0 0 1 0 1 1 a a=11<br />

0 0 0 0 0 1 0 1 b b=a/2=5<br />

0 0 0 0 0 0 0 1 c c=a/2 3 =11/8=1<br />

7 6 5 4 3 2 1 0<br />

0 0 0 0 1 0 1 1 a a=11<br />

1 1 1 1 0 1 0 0 b b=∼a=224<br />

1 1 1 1 1 1 1 1 c c=∼0=255<br />

19 short i, j, k ; /* 16 bits chacun */<br />

20<br />

21 i = 0xFFFF ; /* met les 16 bits a 1 (0x -> notation hexa) i 1111111111111111 */<br />

22 i = i & 255 ; /* met a 0 les 8 bits de poids faible i 0000000011111111 */<br />

23 i = i | 0xD000 ; /* met a 1 les 2 bits de poid fort (0x -> hexa) i 1101000011111111 */<br />

24 i = i ˆ 017 ; /* inverse les 4 bits faibles (0 -> octal) i 1101000011110000 */<br />

25<br />

26 j = 1 ; /* j 0000000000000001 */<br />

27 j = j >= 1 ; /* divise par 2 (j=8) j 0000000000001000 */<br />

29<br />

30 k = 12 ; /* k 0000000000001100 */<br />

31 k = 1 + (˜k) ; /* Calcul de l’oppose (k=-12) k 1111111111110100 */


3 OPÉRATEURS Les bits 101<br />

Rappel : Table des nombres sur 8 bits (1 octet)<br />

Hexa Binaire Octal Décimal Hexa Binaire Octal Décimal<br />

base 16 base 2 base 8 base 10 base 16 base 2 base 8 base 10<br />

0 0 0 0 10 10000 20 16<br />

1 1 1 1 11 10001 21 17<br />

2 10 2 2 12 10010 22 18<br />

3 11 3 3 13 10011 23 19<br />

4 100 4 4 · · · · · · · · · · · ·<br />

5 101 5 5 1F 11111 37 31<br />

6 110 6 6 20 100000 40 32<br />

7 111 7 7 · · · · · · · · · · · ·<br />

8 1000 10 8 3F 111111 77 63<br />

9 1001 11 9 40 1000000 100 64<br />

A 1010 12 10 · · · · · · · · · · · ·<br />

B 1011 13 11 7F 1111111 177 127<br />

C 1100 14 12 80 10000000 200 128<br />

D 1101 15 13 · · · · · · · · · · · ·<br />

E 1110 16 14 FE 11111110 376 254<br />

F 1111 17 15 FF 11111111 377 255


3 OPÉRATEURS Affectations (suite) 102<br />

36<br />

Opérateurs et expressions d’affectations<br />

L’instruction i=i+2; peut s’écrire de façon plus compacte i+=2;<br />

L’avantage est que c’est optimisé (liaison avec assembleur). De<br />

même, pour les opérateurs arithmétiques et de traitements bits à bits,<br />

il existe les opérateurs d’affectation suivants :<br />

+= −= ∗= /= %= &= ∧= |= =<br />

Exemple :<br />

Fonction de comptage des bits à 1 dans un <strong>entier</strong><br />

3 int compter_bits_version_longue (unsigned x) {<br />

4 int b ;<br />

5<br />

6 for (b=0 ; x!=0 ; x=x>>1)<br />

7 if ((x & 1) == 1) /* le dernier bit de x est a 1 ? */<br />

8 b = b + 1 ;<br />

9 return b ;<br />

10 }


3 OPÉRATEURS Affectations (suite) 103<br />

12 int compter_bits (unsigned x) {<br />

13 int b ;<br />

14<br />

15 for (b=0 ; x!=0 ; x>>=1)<br />

16 b += x & 1 ;<br />

17 return b ;<br />

18 }<br />

19<br />

20 int compter_bits_rec (unsigned x) {<br />

21 return !x ? 0 : (x & 1) + compter_bits_rec (x >> 1) ;<br />

22 }<br />

RMQ : Ces opérateurs ont toujours une valeur<br />

28 int i = 3, j ;<br />

29 j = (i *= 10) ; /* syntaxiquement bon, lisible ? i=30 et j=30 */<br />

37<br />

Priorité et associativité des opérateurs<br />

Très importante, car implique ou non l’usage des parenthèses dans<br />

les expressions.


3 OPÉRATEURS Priorité 104<br />

Le tableau donne, du haut vers le bas, les opérateurs de priorité plus forte<br />

vers ceux de priorité plus faible.<br />

PRIORITÉ OPÉRATEURS ASSOCIATIVITÉ<br />

Forte () [] -> . →<br />

! ∼ ++ −− + - ∗ & (cast) sizeof ←<br />

∗ / % →<br />

+ − →<br />

> →<br />

< = > →<br />

== ! = →<br />

&<br />

→<br />

∧<br />

→<br />

| →<br />

&&<br />

→<br />

|| →<br />

? : ←<br />

= += −= ∗= /= %= ←<br />

&= ∧= |= =<br />

Faible , →


Plan 105<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


4 ENTRÉES/SORTIES Entrées/Sorties 106<br />

Les entrées/sorties (Input/Ouput, I/O) sont les échanges<br />

d’informations entre un processus et les ressources système<br />

(matérielles et logicielles)<br />

Les entrées sont les données envoyées par une ressource à<br />

destination d’un processus<br />

Les sorties sont les données envoyées par un processus à destination<br />

d’une ressource<br />

Processus = un programme en <strong>cours</strong> d’exécution (définition approximative)


4 ENTRÉES/SORTIES Entrées/Sorties 107<br />

Une entrée est un flux de données comme :<br />

La saisie au clavier<br />

Les mouvements de la souris<br />

La lecture d’un fichier sur le disque dur<br />

La réception d’un message sur le réseau<br />

La réception d’un signal d’un port d’une carte électronique<br />

. . .<br />

Une sortie est un flux de données comme :<br />

L’affichage à l’écran<br />

L’écriture d’un fichier sur le disque dur<br />

L’émission d’un message sur le réseau<br />

L’émission d’un signal sur un port d’une carte électronique<br />

L’écriture de données sur l’imprimante<br />

. . .


4 ENTRÉES/SORTIES Notion de processus 108


4 ENTRÉES/SORTIES Notion de processus 109


4 ENTRÉES/SORTIES Écriture à l’écran 111<br />

Le prototype de la fonction printf() est :<br />

int printf (const char *format, ...) ;<br />

RMQ : printf() retourne le nombre de caractères écrits (ou une<br />

valeur négative le cas échéant, par exemple si une erreur de sortie<br />

s’est produite.)<br />

RMQ : ”. . .” ⇒ nombre variable d’arguments (cf. stdarg.h).<br />

42<br />

Écriture de valeurs<br />

Pour produire l’affichage du contenu d’une variable :<br />

1 Marquer la position où la valeur doit apparaître avec un<br />

caractère %<br />

2 Puis indiquer par une lettre le format de conversion qui devra<br />

être utilisé (%d, %f, %s, etc.)<br />

3 Enfin, ajouter le nom de la variable aux arguments de printf()


4 ENTRÉES/SORTIES Écriture à l’écran 112<br />

Exemple 1 :<br />

5 int i = 5 ;<br />

6 float r = sqrt (2) ;<br />

7<br />

8 printf ("i vaut %d et r vaut %f\n", i, r) ;<br />

Produit l’affichage suivant :<br />

RMQ : Dans le cas de plusieurs variables, ces dernières sont<br />

données en argument de printf() dans leur ordre d’apparition<br />

Exemple 2 :<br />

4 int i = 4053 ;<br />

5 printf ("i vaut %X\n", i) ;


4 ENTRÉES/SORTIES Écriture à l’écran 113<br />

Exemple 3 :<br />

5 int i = 5 ;<br />

6<br />

7 printf ("Le carre de %d vaut %d\n", i, i*i) ;<br />

8 printf ("La racine de %d vaut %lf\n", i, sqrt(i)) ;<br />

Produit l’affichage suivant :<br />

RMQ : L’affichage de valeurs dans printf() n’est pas limité aux<br />

variables. En effet, ce peut être le résultat de l’évaluation d’une<br />

expression ou encore la valeur renvoyée par l’appel d’une fonction.<br />

RMQ : Rappel : ’\n’ est le caractère de ”retour à la ligne”.


4 ENTRÉES/SORTIES Écriture à l’écran 114<br />

43<br />

Nombre minimum de caractères produits<br />

→ Certains formats de conversion sont paramétrables.<br />

Par exemple, il est possible de spécifier le nombre minimum de<br />

caractères à imprimer en le spécifiant entre le % et le caractère de<br />

formatage :<br />

4 printf ("%5d\n", 45) ;<br />

5 printf ("%5d\n", 129) ;<br />

6 printf ("%5d\n", 1032) ;<br />

7 printf ("%5d\n", 100034) ;<br />

45<br />

129<br />

1032<br />

100034<br />

RMQ : Ne pas oublier que c’est le nombre minimum de caractères<br />

à imprimer qui est ainsi contrôlé. Si le nombre comporte plus de<br />

chiffres, alors il y aura dépassement (p.ex. ligne 7).


4 ENTRÉES/SORTIES Écriture à l’écran 115<br />

→ Concernant les nombres réels, le nombre de minimum de<br />

caractères à imprimer peut également être spécifié, ainsi que le<br />

nombre de décimales à imprimer après la virgule.<br />

4 printf ("%f\n", .0) ;<br />

5 printf ("%f\n", 10.08) ;<br />

6 printf ("%.1f\n", 100.08) ;<br />

7 printf ("%10.3f\n", 5.454) ;<br />

8 printf ("%10.3f\n", 5.454e4) ;<br />

9 printf ("%10.3f\n", 5.454e5) ;<br />

10 printf ("%10.3f\n", 5.454e6) ;<br />

0.000000<br />

10.080000<br />

100.1<br />

5.454<br />

54540.000<br />

545400.000<br />

5454000.000<br />

↩→ La valeur indiquée APRES le point est le nombre imposé de<br />

décimales à imprimer. La valeur indiquée AVANT le point est le<br />

nombre minimum de caractères à imprimer au total (y compris les<br />

décimales après la virgule ET la virgule elle-même).<br />

RMQ : Si rien n’est spécifié le format par défaut est %.6f (voir<br />

lignes 4 et 5). L’arrondi est réalisé automatiquement (voir ligne 6).


4 ENTRÉES/SORTIES Écriture à l’écran 116<br />

44<br />

Table des formats possibles pour printf()<br />

Caractère Argument Impression<br />

d, i int décimal<br />

o int octal<br />

x, X int hexadécimal<br />

u unsigned int <strong>entier</strong> non signé<br />

s char *<br />

chaîne de caractères (doit se<br />

terminer par ’\0’)<br />

c char caractère<br />

f double [-]m.dddddd<br />

e, E double [-]m.ddddd{e|E}±xx (⇔ ×10 xx )<br />

g, G double<br />

utilise e ou E si xx < −4 ou > 6,<br />

f sinon<br />

p void * pointeur<br />

% aucun imprime un %


4 ENTRÉES/SORTIES Écriture à l’écran 117<br />

→ Seuls les types de base sont acceptés par printf(). Il est<br />

impossible d’afficher une variable d’un type nouveau (créé par le<br />

programmeur).<br />

RMQ : Les char servent aussi bien, à représenter de petits<br />

nombres <strong>entier</strong>s [−128; +127], qu’à représenter des caractères.<br />

Cette situation est normal, du fait que les caractères sont<br />

effectivement codés par leur nombre ascii.


4 ENTRÉES/SORTIES Écriture à l’écran 118<br />

Exemple du cas char :<br />

4 char A = 121 ;<br />

5 char B = ’z’ ;<br />

6<br />

7 printf ("A = %d\n", A) ;<br />

8 printf ("A = %c\n", A) ;<br />

9 printf ("B = %c\n", B) ;<br />

10 printf ("B = %d\n", B) ;<br />

RMQ : Un modificateur de longueur doit être ajouté pour les types<br />

”longs” : un l pour les <strong>entier</strong>s et un L pour les réels. Exemples<br />

d’utilisation (non exhaustif) :<br />

Type<br />

long int<br />

long long int<br />

unsigned long int<br />

unsigned long long int<br />

long double<br />

Format<br />

ld, li<br />

lld, lli<br />

lu, lu<br />

llu, llu<br />

Lf, Le, Lg


4 ENTRÉES/SORTIES La sortie d’erreur 119<br />

45<br />

Un flux de sortie distinct pour les erreurs<br />

En fait, par défaut, un deuxième flux de sortie est toujours créé pour<br />

chaque processus.<br />

→ Ce ”canal” est destiné à recevoir les messages d’erreur, ou<br />

d’avertissement, pour les distinguer des messages dits ”normaux”<br />

→ Par défaut, le flux d’erreur est dirigé vers la console<br />

→ Mais des mécanismes existent (nous en verrons un juste après) pour<br />

modifier la destination de ces flux et pouvoir les dissocier


4 ENTRÉES/SORTIES La sortie d’erreur 120<br />

Pour ce faire, la fonction fprintf() permet de choisir le flux<br />

d’écriture. Son prototype est :<br />

int fprintf (FILE *stream, const char *format, ...) ;<br />

→ Ainsi, un appel fprintf(stderr, ...); produira une écriture<br />

sur la sortie d’erreur (dont le descripteur est stderr)<br />

Exemple :<br />

addition.c<br />

7 /* Verification du nombre d’arguments */<br />

8 if (argc != 3) {<br />

9 fprintf (stderr, "Usage: %s \n", argv[0]) ;<br />

10 exit (1) ; /* Arret sur erreur */<br />

11 }<br />

RMQ : Noter que ces deux appels sont équivalents :<br />

printf(...);<br />

fprintf(stdout, ...);


4 ENTRÉES/SORTIES Redirection 121<br />

46<br />

Les redirections<br />

Au moment de l’appel d’un programme, la destination habituelle des<br />

sorties standards (stdout et stderr) peut être changée<br />

1 Récupérer la sortie standard en la redirigeant dans un fichier<br />

Exemple : monprogramme.exe > sortie.txt


4 ENTRÉES/SORTIES Redirection 122<br />

2 Récupérer la sortie d’erreur en la redirigeant dans un fichier<br />

Exemple : monprogramme.exe 2> sortie.txt<br />

3 Ignorer une des sorties en la redirigeant vers NUL<br />

Exemple : monprogramme.exe > sortie.txt 2> NUL


4 ENTRÉES/SORTIES Lecture au clavier 123<br />

La fonction scanf() lit des caractères en provenance de l’entrée<br />

standard (stdin). Sauf cas particuliers, l’entrée standard est le<br />

clavier de l’ordinateur.<br />

47<br />

Comportement (asynchrone) de scanf()<br />

Lorsque les touches du clavier sont frappées, le Système<br />

d’Exploitation (OS) stocke les caractères émis dans une zone<br />

mémoire tampon (buffer). Le rôle de la fonction scanf() est d’aller<br />

”manger” les caractères présents dans ce buffer.


4 ENTRÉES/SORTIES Lecture au clavier 124<br />

Les appels à la fonction scanf() sont bloquants :<br />

si le buffer est vide, l’exécution du programme est mise en attente<br />

jusqu’à ce qu’il y ait des caractères à ”manger”.<br />

l’exécution du programme est réactivée lors d’une frappe sur la<br />

touche Entrée (Enter),<br />

(sinon) si le buffer n’est pas vide, alors la fonction scanf()<br />

”mange” les caractères autant que faire se peut.<br />

scanf() interprète les suites de caractères lues pour former<br />

des valeurs qui sont ensuite affectées à des variables.<br />

48<br />

Utilisation de scanf()<br />

Le prototype de la fonction scanf() est :<br />

int scanf (const char *format, ...) ;<br />

→ Le premier paramètre format est une chaîne de caractères qui<br />

informe scanf() sur le format de conversion à utiliser pour<br />

interpréter le flux de caractères ACSII en provenance du clavier


4 ENTRÉES/SORTIES Lecture au clavier 125<br />

→ Genre de conversions effectuées par scanf() :<br />

ASCII → nombre <strong>entier</strong> : scanf("%d", ...);<br />

ASCII → nombre réel : scanf("%f", ...);<br />

ASCII → ASCII : scanf("%s", ...); ou scanf("%c", ...);<br />

→ Les autres paramètres sont les adresses des emplacements<br />

mémoire où scanf() devra aller écrire les valeurs lues :<br />

scanf("%d", &n);<br />

scanf("%f", &x);<br />

scanf("%s", mot);<br />

scanf("%c", &lettre);<br />

(avec int n;)<br />

(avec float x;)<br />

(avec char mot[MAX];)<br />

(avec char lettre;)<br />

RMQ : Précéder le nom d’une variable par l’opérateur & permet<br />

d’obtenir son adresse mémoire.<br />

↩→ IMPORTANT : ne pas le faire pour les chaînes de caractères,<br />

car en fait c’est déjà une adresse mémoire (vers un tableau) !


4 ENTRÉES/SORTIES Lecture au clavier 126<br />

→ la valeur retournée par scanf() est le nombre d’éléments<br />

correctement mis en correspondance et assignés. Ce nombre peut<br />

être plus petit que le nombre d’éléments attendus, voire être nul.<br />

Exemple :<br />

Récupérer un nombre <strong>entier</strong> entré au clavier puis afficher sa valeur<br />

Cas nominal :<br />

lecture.c<br />

1 #include <br />

2<br />

3 int main () {<br />

4 int i ;<br />

5<br />

6 printf ("Donnez un <strong>entier</strong> : ") ;<br />

7 if (scanf ("%d", &i) == 1)<br />

8 printf ("lu %d\n", i) ;<br />

9 else<br />

10 printf ("erreur de lecture\n") ;<br />

11<br />

12 return 0 ;<br />

13 }<br />

Cas non nominal :


4 ENTRÉES/SORTIES Lecture au clavier 127<br />

49<br />

Table des formats possibles pour scanf()<br />

Caractère Entrée Argument<br />

d, i un <strong>entier</strong> en décimal int *<br />

o un <strong>entier</strong> en octal int *<br />

x un <strong>entier</strong> en hexadécimal int *<br />

u un <strong>entier</strong> non signé unsigned int *<br />

s<br />

une chaîne de caractères<br />

(lira jusqu’au au premier char *<br />

espace ou ’\n’ rencontré)<br />

c<br />

un caractère (les espaces et<br />

les ’\n’ seront ignorés)<br />

char *<br />

hhd un <strong>entier</strong> ∈ [−128; +127] char *<br />

e, f, g un réel float *<br />

le, lf, lg un réel double *<br />

% attend un %


4 ENTRÉES/SORTIES Lecture au clavier 128<br />

Résultats :<br />

(type argument) : <br />

. -> lu <br />

RMQ : Pour pouvoir ne ”manger” qu’un seul caractère à la fois<br />

avec %c, comme dans l’exemple ci-dessus, un caractère résiduel ’\n’<br />

présent dans le buffer doit éventuellement être ignoré. Pour ce faire,<br />

une astuce consiste à placer un espace avant le %c dans le format :<br />

scanf (" %c", &var) ;


4 ENTRÉES/SORTIES Lecture au clavier 129<br />

→ Modificateurs pour les types ”courts” (h) et ”longs” (l, L) :<br />

Caractère Entrée Argument<br />

hhd <strong>entier</strong> ∈ [−128; 127] char *<br />

hd <strong>entier</strong> ∈ [−32768; 32767] short int *<br />

ld <strong>entier</strong> long int *<br />

lld<br />

<strong>entier</strong> ∈ [−2 63 ; 2 63 − 1]<br />

(2 63 = 9, 223372 × 10 18 )<br />

long long int *<br />

hhu <strong>entier</strong> ∈ [0; 255] unsigned char *<br />

hu <strong>entier</strong> ∈ [0; 65535] unsigned short int *<br />

lu <strong>entier</strong> long unsigned int *<br />

llu<br />

<strong>entier</strong> ∈ [0; 2 64 − 1]<br />

= [0; 1, 84467441 × 10 19 ]<br />

long long unsigned *<br />

Le, Lf, Lg réel (plus grand = ) long double *<br />

RMQ : Pour %ld et %lu l’intervalle dépend de l’architecture<br />

machine (en général 4 octets, mais se référer à sizeof()).


4 ENTRÉES/SORTIES Accès fichiers 130<br />

Pour lire et écrire dans des fichiers stockés sur le disque dur, il faut<br />

demander au Système d’Exploitation d’ouvrir d’autres descripteurs<br />

→ Fichier de configuration<br />

→ Fichier de sauvegarde<br />

→ Scores, Traitements, CSV, . . .


4 ENTRÉES/SORTIES Accès fichiers 131<br />

410<br />

Demander l’ouverture d’un descripteur de fichier<br />

Afin d’établir un ”canal d’accès” vers un fichier sur le disque dur,<br />

il faut créer un nouveau descripteur à cet effet<br />

Ce descripteur pourra ensuite être utilisé pour accéder à la<br />

ressource (le fichier) en lecture ou en écriture<br />

De même que pour les entrées standards, un tampon (buffer)<br />

temporise les accès (→ Noyau du SE)<br />

La fonction fopen() permet d’ouvrir des descripteurs de fichiers :<br />

FILE *fopen (char *path, char *mode) ;<br />

path : nom du fichier (chemin d’accès absolu ou relatif)<br />

mode : genre du descripteur à créer, d’entrée ou de sortie<br />

La valeur renvoyée est l’adresse du nouveau descripteur (une structure<br />

de type FILE)


4 ENTRÉES/SORTIES Accès fichiers 132<br />

→ Le paramètre path est une chaîne de caractères décrivant le nom<br />

du fichier à ouvrir (précédé du chemin d’accès sur le disque)<br />

Exemple :<br />

Si répertoire de travail =<br />

"M:\Example\debug\"<br />

Chemins relatifs :<br />

"config.txt"<br />

"..\data\save.txt"<br />

"users\dennis.txt"<br />

Chemins absolus :<br />

"M:\Mesures\valeurs.csv"<br />

"M:\Example\data\save.txt"<br />

RMQ : Le répertoire de travail sera le répertoire depuis lequel<br />

l’exécution du programme sera lancée<br />

RMQ : Le caractère de séparation est / sous Unix, Linux et Mac


4 ENTRÉES/SORTIES Accès fichiers 133<br />

→ Le paramètre mode est une chaîne de caractères parmi :<br />

"r" = read : accès en lecture simple<br />

"w" = write : accès en écriture (écrasement si le fichier existe, création<br />

le cas échéant)<br />

"a" = append : accès en écriture (positionne le ”curseur” à la fin si le<br />

fichier existe, création le cas échéant)<br />

Un appel à la fonction fopen() peut échouer pour raisons diverses,<br />

parmi lesquelles :<br />

Tentative d’accès en lecture à un fichier qui n’existe pas<br />

Tentative d’accès en écriture à un fichier qui est déjà ouvert<br />

Les droits d’accès au fichier sont restreints<br />

↩→ En cas d’échec, la valeur renvoyée par fopen() vaut alors NULL<br />

RMQ : NULL est une valeur particulière définie comme<br />

(void *)0 et déclarée dans stddef.h qui signifie ”pointeur nul”


4 ENTRÉES/SORTIES Accès fichiers 134<br />

RMQ : La fonction fopen() produit un appel système et en cas d’échec<br />

il est possible de connaître la raison de cet échec en consultant la variable<br />

d’erreur errno (qui a été positionnée par le système) via un appel à<br />

perror()<br />

IMPORTANT : une fois que les traitements sur le fichier sont terminés<br />

et qu’on en n’a plus besoin, il faut toujours penser à appeler la<br />

fonction fclose() :<br />

int fclose (FILE *fp) ;<br />

Aussi, lorsqu’un fichier est ouvert en écriture, le fermer (après<br />

utilisation) est important car cela provoquera la synchronisation du<br />

buffer d’écriture avec le disque dur


4 ENTRÉES/SORTIES Accès fichiers 136<br />

Exemple d’utilisation :<br />

5 FILE *fp ;<br />

6<br />

7 fp = fopen ("scores.txt", "w") ;<br />

8<br />

9 if (fp == NULL) {<br />

10 perror("Failed to open scores.txt in write mode");<br />

11 exit (-1) ;<br />

12 }<br />

13<br />

14 /*************************<br />

15 * ECRIRE dans le fichier<br />

16 *************************/<br />

17<br />

18 fclose (fp) ;


4 ENTRÉES/SORTIES Accès fichiers 137<br />

411<br />

Écrire dans un fichier avec fprintf()<br />

Comme précédemment vu, la fonction fprintf() permet d’écrire<br />

des caractères ASCII sur le flux de sortie qui lui est spécifiée<br />

→ Pour écrire dans un fichier, il suffit donc de lui fournir le descripteur<br />

du fichier dans lequel on souhaite écrire<br />

↩→ un descripteur ouvert en écriture obtenu par appel à fopen()<br />

Rappel :<br />

int fprintf (FILE *stream, const char *format, ...) ;<br />

RMQ : Le caractère de fin de ligne diffère selon le Système<br />

d’Exploitation utilisé<br />

Pour marquer une fin de ligne dans un fichier : sous Windows : \r\n<br />

sous MacOS : \n\r<br />

sous Unix/Linux : \n


4 ENTRÉES/SORTIES Accès fichiers 138<br />

Exemple :<br />

ecrire-table-carre.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 int n ;<br />

6 FILE *outbuf ;<br />

7<br />

8 /* Ouverture d’un descripteur en mode ECRITURE */<br />

9 if ((outbuf = fopen ("table.txt", "w")) == NULL) {<br />

10 perror("Failed to open table.txt in write mode");<br />

11 exit (-1) ;<br />

12 }<br />

13<br />

14 /* Ecriture dans le fichier (via le descripteur...) */<br />

15 fprintf (outbuf, "TABLE DES CARRES\r\n") ;<br />

16<br />

17 for (n=0 ; n


4 ENTRÉES/SORTIES Accès fichiers 139<br />

412<br />

Lire dans un fichier avec fscanf()<br />

La fonction fscanf() permet de lire des caractères ASCII provenant<br />

du flux d’entrée qui lui est spécifiée<br />

→ Pour lire dans un fichier, il suffit donc de lui fournir le descripteur<br />

du fichier dans lequel on souhaite lire<br />

↩→ un descripteur ouvert en lecture obtenu par appel à fopen()<br />

int fscanf (FILE *stream, const char *format, ...) ;<br />

RMQ : De même que scanf(), cette fonction devra prendre pour<br />

paramètre des adresses de variables


4 ENTRÉES/SORTIES Accès fichiers 140<br />

Exemple :<br />

lire-annuaire.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 char name[512] ;<br />

6 int year, lu ;<br />

7 FILE *inbuf ;<br />

8<br />

9 /* Ouverture d’un descripteur en mode LECTURE */<br />

10 if ((inbuf = fopen ("annuaire.txt", "r")) == NULL) {<br />

11 perror("Failed to open annuaire.txt in read mode");<br />

12 exit (-1) ;<br />

13 }<br />

14<br />

15 /* Lecture du fichier ligne par ligne */<br />

16 while (!feof (inbuf)) {<br />

17 lu = fscanf (inbuf, "%s %d", name, &year) ;<br />

18<br />

19 if (lu == 2)<br />

20 printf ("Nom=%s \t Date=%u\n", name, year) ;<br />

21 }<br />

22<br />

23 /* Fermeture du descripteur de fichier */<br />

24 fclose (inbuf) ;<br />

25<br />

26 return 0 ;<br />

27 }<br />

annuaire.txt<br />

1 Ritchie 1941<br />

2 Kernighan 1942<br />

3 Thompson 1943<br />

4 Stallman 1953<br />

5 Gates 1955<br />

6 Jobs 1955<br />

7 Torvalds 1969<br />

8 _


4 ENTRÉES/SORTIES Accès fichiers 141<br />

413<br />

Accès fichiers caractère par caractère<br />

Lecture d’un caractère<br />

Écriture d’un caractère<br />

int fgetc (FILE *stream) ;<br />

int fputc (int c, FILE *stream) ;<br />

Ces deux fonctions retournent le code ASCII du caractère lu/écrit<br />

(unsigned char vers int), ou la valeur EOF en cas d’erreur (End<br />

Of File, vaut -1)<br />

Exemple : copie caractère par caractère<br />

↩→ voir le ”Huitième programme” au début du <strong>cours</strong> (copier.c)


4 ENTRÉES/SORTIES Accès fichiers 142<br />

Exemple de lecture caractère par caractère :<br />

compterT.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main (int argc, char *argv[]) {<br />

5 FILE *fp ;<br />

6 char c ;<br />

7 int cpt = 0 ;<br />

8<br />

9 /* Ouverture du fichier en lecture */<br />

10 if ((fp = fopen ("annuaire.txt", "r")) == NULL) {<br />

11 perror ("Failed to open annuaire.txt in read mode");<br />

12 exit (-1) ;<br />

13 }<br />

14<br />

15 /* Lecture caractere par caractere */<br />

16 while ((c = fgetc (fp)) != EOF)<br />

17 if (c == ’T’)<br />

18 cpt++ ;<br />

19<br />

20 /* Fermeture du fichier */<br />

21 fclose (fp) ;<br />

22<br />

23 printf ("Nombre de T = %d\n", cpt) ;<br />

24<br />

25 return 0 ;<br />

26 }<br />

annuaire.txt<br />

1 Ritchie 1941<br />

2 Kernighan 1942<br />

3 Thompson 1943<br />

4 Stallman 1953<br />

5 Gates 1955<br />

6 Jobs 1955<br />

7 Torvalds 1969<br />

8 _<br />

Ce programme compte les<br />

occurrences de la lettre T<br />

dans le fichier<br />

annuaire.txt


Plan 143<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


5 CONTRÔLE & BOUCLES Introduction 144<br />

En programmation impérative, les instructions<br />

sont exécutées pas à pas, les unes après les<br />

autres, dans l’ordre indiqué par le programmeur<br />

6 scanf ("%d", &a) ;<br />

7 scanf ("%d", &b) ;<br />

8 c = a + b ;<br />

Une structure de contrôle est une commande qui permet de<br />

jouer sur l’ordre dans lequel les instructions seront exécutées<br />

Certaines, appelées conditionnelles, permettent de<br />

décider l’exécution, ou non, des (blocs d’) instructions<br />

ou encore, de choisir les (blocs d’) instructions à<br />

exécuter<br />

10 if (a > b)<br />

11 max = a ;<br />

12 else<br />

13 max = b ;<br />

D’autres sont itératives (appelées boucles) et permettent de<br />

répéter plusieurs fois les mêmes (blocs d’) instructions<br />

15 for (i=1 ; i


5 CONTRÔLE & BOUCLES Les conditionnelles 145<br />

Il existe deux structures de contrôle de prise de décision<br />

conditionnelle :<br />

if (...) ... [ else ...]<br />

switch (...) case ... default ...<br />

et un opérateur :<br />

(...) ? ...: ...<br />

51<br />

La structure conditionnelle classique if<br />

if (expression)<br />

instruction ;<br />

L’instruction sera exécutée si et seulement<br />

si l’expression booléenne vaut VRAI (valeur<br />

entière != 0). Ainsi, si l’expression vaut FAUX<br />

(valeur entière == 0), l’instruction ne sera pas<br />

exécutée


5 CONTRÔLE & BOUCLES Les conditionnelles 146<br />

if (expression) {<br />

instruction1 ;<br />

instruction2 ;<br />

...<br />

instructionN ;<br />

}<br />

if (expression) {<br />

/* blocA */<br />

}<br />

else {<br />

/* blocB */<br />

}<br />

Délimiter un bloc d’instructions entre des<br />

accolades { ... } permet de soumettre<br />

plusieurs instructions à la conditionnelle<br />

(Valable également dans le cas d’une<br />

seule instruction)<br />

L’ajout du else permet de définir un<br />

comportement dans le cas où la<br />

condition n’est pas vérifiée. Ainsi, si la<br />

condition vaut VRAI alors le bloc A sera<br />

exécuté. Sinon (cas où la condition vaut<br />

FAUX) c’est le bloc B qui sera exécuté.<br />

RMQ : La condition peut-être une variable entière, une expression<br />

bouléenne, un appel de fonction, . . .


5 CONTRÔLE & BOUCLES Les conditionnelles 147<br />

Trois exemples de if simples :<br />

3 int x ;<br />

4 double y ;<br />

5<br />

6 /* ... */<br />

7<br />

8 if (x != 0)<br />

9 y = 1 / x ;<br />

3 int x, max ;<br />

4<br />

5 /* ... */<br />

6<br />

7 if (x > max) {<br />

8 max = x ;<br />

9 }<br />

3 int x ;<br />

4<br />

5 /* ... */<br />

6<br />

7 if (x < 0) {<br />

8 x = -x ;<br />

9 }<br />

Deux exemples de if else :<br />

5 if (a > b) {<br />

6 max = a ;<br />

7 min = b ;<br />

8 }<br />

9 else {<br />

10 max = b ;<br />

11 min = a ;<br />

12 }<br />

7 int year, ndays ;<br />

8<br />

9 /* ... */<br />

10<br />

11 if (is_bissextile (year))<br />

12 ndays = 366 ;<br />

13 else<br />

14 ndays = 365 ;


5 CONTRÔLE & BOUCLES Les conditionnelles 148<br />

Encore deux exemples divers :<br />

4 int i, N=10 ;<br />

5 int a[N], val ;<br />

6<br />

7 /* ... */<br />

8<br />

9 if (i >= 0 && i < N)<br />

10 a[i] = val ;<br />

11 else<br />

12 printf ("index out of range\n");<br />

4 char found = 0 ;<br />

5<br />

6<br />

7 /* ... */<br />

8<br />

9<br />

10 if (!found) {<br />

11 printf ("search failed\n") ;<br />

12 }<br />

RMQ : Puisque FAUX équivaut à zéro, on peut raccourcir certaines<br />

écritures. Ainsi, cette écriture :<br />

if (c != 0) ...<br />

(lisible)<br />

peut être efficacement remplacée par :<br />

if (c) ...<br />

(optimisé)<br />

↩→ Sera, de toute façon, optimisé par le compilateur


5 CONTRÔLE & BOUCLES Les conditionnelles 149<br />

→ Imbrication de plusieurs if :<br />

confusion du else.<br />

Exemple d’erreur :<br />

Le contrôle par else n’est pas associée<br />

par le compilateur au if (n > 0),<br />

mais au if (a[i] > 0).<br />

La bonne version est alors :<br />

Moralité : toujours mettre entre<br />

accolades les instructions, mêmes<br />

atomiques, afin d’éviter les confusions<br />

en cas de else<br />

Recherche d’1 valeur strict. positive<br />

dans un tableau a de n éléments.<br />

9 if (n > 0)<br />

10 for (i=0 ; i 0) {<br />

12 printf ("Trouve!!\n") ;<br />

13 pos = i ;<br />

14 }<br />

15 else /* Mauvais niveau */<br />

16 printf ("Tableau vide\n") ;<br />

9 if (n > 0) {<br />

10 for (i=0 ; i 0) {<br />

12 printf ("Trouve!!\n") ;<br />

13 pos = i ;<br />

14 }<br />

15 }<br />

16 else { /* Bon niveau */<br />

17 printf ("Tableau vide\n") ;<br />

18 }


5 CONTRÔLE & BOUCLES Les conditionnelles 150<br />

RMQ : Ne pas confondre les opérateurs == et =<br />

if (c == 0) ... /* ok */<br />

if (c = 0) ... /* argh!! */<br />

La dernière condition est syntaxiquement correcte, elle pourrait<br />

même être fait exprès, mais reste absolument proscrite pour éviter<br />

les ambiguïté de relecture !<br />

Par contre, les programmeurs utilisent ce mécanisme dans<br />

certains cas (affection suivie d’une comparaison), pour compacter<br />

l’écriture et optimiser l’exécution :<br />

5 char filename[256] ;<br />

6 FILE *fp ;<br />

7<br />

8 /* ... */<br />

9<br />

10 if ((fp = fopen (filename, "r")) == NULL) {<br />

11 perror("Failed to open in read mode");<br />

12 exit (-1) ;<br />

13 }<br />

5 char filename[256] ;<br />

6 FILE *fp ;<br />

7<br />

8 /* ... */<br />

9<br />

10 fp = fopen (filename, "r") ;<br />

11<br />

12 if (fp == NULL) {<br />

13 perror("Failed to open in read mode");<br />

14 exit (-1) ;<br />

15 }


5 CONTRÔLE & BOUCLES Les conditionnelles 151<br />

52<br />

Le contrôle par else if<br />

Écrire des conditions avec un choix entre plusieurs possibilités.<br />

if (expressionA) {<br />

/* blocA */<br />

}<br />

else if (expressionB) {<br />

/* blocB */<br />

}<br />

else if (expressionC) {<br />

/* blocC */<br />

}<br />

else {<br />

/* blocZ */<br />

}<br />

Si l’expression A est vrai alors<br />

(uniquement) le bloc A est<br />

exécuté. Sinon, si l’expression B<br />

est vrai alors c’est le bloc B<br />

(uniquement) qui est exécuté. Et<br />

ainsi de suite pour tous les<br />

else if. Si aucune des<br />

conditions n’a été vérifiée (et donc<br />

qu’aucun des blocs précédents<br />

n’a été exécuté), alors le bloc Z<br />

est exécuté (uniquement).


5 CONTRÔLE & BOUCLES Les conditionnelles 152<br />

Deux exemples :<br />

6 if (x > y) {<br />

7 printf ("x est superieur a y\n") ;<br />

8 }<br />

9 else if (x < y) {<br />

10 printf ("x est inferieur a y\n") ;<br />

11 }<br />

12 else {<br />

13 printf ("x et y sont egaux\n") ;<br />

14 }<br />

6 if (i < 0) {<br />

7 i = 0 ;<br />

8 }<br />

9 else if (i > 10) {<br />

10 i = 10 ;<br />

11 }<br />

RMQ : Les conditions sont évaluées dans l’ordre croissant et<br />

l’évaluation des expressions s’arrête à la première condition<br />

rencontrée qui est vérifiée<br />

RMQ : Il est possible d’enchaîner autant de else if que<br />

nécessaire<br />

RMQ : Le else final est facultatif


5 CONTRÔLE & BOUCLES Les conditionnelles 153<br />

Exemple :<br />

Recherche dichotomique d’une valeur x dans un tableau a trié par ordre<br />

croissant de taille N<br />

7 int dicho (int x, int a[], int N) {<br />

8 int debut, fin, milieu ;<br />

9 fin = N-1 ;<br />

10 debut = 0 ;<br />

11 while (debut a[milieu])<br />

16 debut = milieu + 1 ;<br />

17 else /* trouve ! */<br />

18 return milieu ;<br />

19 }<br />

20 return -1 ; /* pas trouve */<br />

21 }


5 CONTRÔLE & BOUCLES Les conditionnelles 154<br />

Attention, ces deux écritures ne sont pas équivalentes :<br />

if (expressionA) {<br />

/* blocA */<br />

}<br />

else if (expressionB) {<br />

/* blocB */<br />

}<br />

else if (expressionC) {<br />

/* blocC */<br />

}<br />

if (expressionA) {<br />

/* blocA */<br />

}<br />

if (expressionB) {<br />

/* blocB */<br />

}<br />

if (expressionC) {<br />

/* blocC */<br />

}<br />

Par exemple, si les 3 expressions A, B et C sont vérifiées :<br />

Seulement le bloc A<br />

sera exécuté<br />

(exclusivement)<br />

Les 3 blocs A, B et C<br />

seront exécutés


5 CONTRÔLE & BOUCLES Opérateur conditionnelle 155<br />

53 L’opérateur de conditionnelle ? :<br />

Il est parfois pratique de remplacer l’instruction conditionnelle :<br />

if (expression) instruction1 ; else instruction2 ;<br />

par :<br />

expression ? instruction1 : instruction2 ;<br />

Avantages :<br />

efficacité<br />

lisibilité<br />

retourne une valeur ! (car opérateurs . . .)<br />

Exemple :<br />

4 int a, b, max ;<br />

5<br />

6 /* ... */<br />

7<br />

8 max = (a > b) ? a : b ;<br />

4 int a, b, max ;<br />

5<br />

6 /* ... */<br />

7<br />

8 if (a > b)<br />

9 max = a ;<br />

10 else<br />

11 max = b ;


5 CONTRÔLE & BOUCLES Opérateur conditionnelle 156<br />

Autres exemples :<br />

8 char reussie ;<br />

9<br />

10 /* ... */<br />

11<br />

12 printf ("La reponse est %s\n", (reussie) ? "vrai" : "fausse") ;<br />

Attention : cet opérateur ne peut pas toujours se substituer à la<br />

structure de contrôle if classique<br />

↩→ Lorsque les instructions à exécuter sont complexes (blocs. . .)<br />

↩→ Dans les cas où la valeur de retour n’est pas utilisée


5 CONTRÔLE & BOUCLES Choix multiples 157<br />

54<br />

La structure de contrôle switch<br />

Cette structure de contrôle permet une prise de décision à choix<br />

multiples, effectuée par comparaisons successives d’une expression<br />

avec des expressions constantes entières.<br />

La syntaxe est la suivante :<br />

switch (expression) {<br />

case expression-constante1 : instructionS<br />

[ break ; ]<br />

case expression-constante2 : instructionS<br />

[ break ; ]<br />

...<br />

default: instructionS<br />

}


5 CONTRÔLE & BOUCLES Choix multiples 158<br />

Principe de fonctionnement :<br />

1 La valeur entière de l’expression (du switch) sera comparée<br />

avec celles des expressions constantes (des case)<br />

2 Les instructions seront exécutées à partir de la première égalité<br />

rencontrée<br />

3 Alors, TOUT le code qui suit sera exécuté, sans tenir compte des<br />

autres case, jusqu’à rencontrer une instruction break<br />

Exemple :<br />

le code suivant, qui devrait associer<br />

un nombre à un jour de la semaine,<br />

est erroné<br />

Lorsque jour vaut 4 l’affichage est :<br />

4 int jour ;<br />

5<br />

6 /* ... */<br />

7<br />

8 jour = 4 ;<br />

9<br />

10 switch (jour) {<br />

11 case 1 : printf ("lundi\n") ;<br />

12 case 2 : printf ("mardi\n") ;<br />

13 case 3 : printf ("mercredi\n") ;<br />

14 case 4 : printf ("jeudi\n") ;<br />

15 case 5 : printf ("vendredi\n") ;<br />

16 case 6 : printf ("samedi\n") ;<br />

17 case 7 : printf ("dimanche\n") ;<br />

18 default : printf ("unknown value\n") ;<br />

19 }


5 CONTRÔLE & BOUCLES Choix multiples 159<br />

↩→ Correction de l’exemple précédent :<br />

⇒ Lorsqu’il est nécessaire d’interrompre<br />

l’enchaînement des instructions, il faut alors<br />

utiliser l’instruction break<br />

Lorsque jour vaut 4 l’affichage est :<br />

Lorsque jour vaut 8 l’affichage est :<br />

4 int jour ;<br />

5<br />

6 /* ... */<br />

7<br />

8 switch (jour) {<br />

9 case 1 :<br />

10 printf ("lundi\n") ;<br />

11 break ;<br />

12 case 2 :<br />

13 printf ("mardi\n") ;<br />

14 break ;<br />

15 case 3 :<br />

16 printf ("mercredi\n") ;<br />

17 break ;<br />

18 case 4 :<br />

19 printf ("jeudi\n") ;<br />

20 break ;<br />

21 case 5 :<br />

22 printf ("vendredi\n") ;<br />

23 break ;<br />

24 case 6 :<br />

25 printf ("samedi\n") ;<br />

26 break ;<br />

27 case 7 :<br />

28 printf ("dimanche\n") ;<br />

29 break ;<br />

30 default :<br />

31 printf ("unknown value\n") ;<br />

32 }


5 CONTRÔLE & BOUCLES Choix multiples 160<br />

RMQ : La ligne contenant le cas par défaut (default : ...) est<br />

optionnelle<br />

↩→ Elle est exécutée lorsqu’aucun des cas listés (case) ne<br />

correspond à la valeur de l’expression de sélection (switch)<br />

↩→ De façon générale, le cas par défaut doit toujours être traité, ne<br />

serait-ce que pour afficher une erreur lorsque, contre toute attente, il<br />

a été atteint lors d’une exécution<br />

Exemple :<br />

Une fonction qui calcule<br />

le nombre de jours dans<br />

un mois<br />

3 int days_count (int month, char bissextile) {<br />

4 int res = -1 ;<br />

5 switch (month) {<br />

6 case 1: case 3: case 5: case 7: case 8: case 10: case 12:<br />

7 res = 31 ; break ;<br />

8 case 4: case 6: case 9: case 11:<br />

9 res = 30 ; break ;<br />

10 case 2:<br />

11 res = (bissextile) ? 29 : 28 ; break;<br />

12 default:<br />

13 fprintf(stderr, "Unknow value\n") ;<br />

14 }<br />

15 return res ;<br />

16 }


5 CONTRÔLE & BOUCLES Boucles 161<br />

Les boucles permettent d’exécuter un bloc d’instructions données<br />

tant qu’une condition donnée n’est pas remplie<br />

Il existe deux genres de boucles :<br />

→ avec exécution d’instruction(s) AVANT le test de continuation<br />

(while et for)<br />

→ avec exécution d’instruction(s) APRÈS le test de continuation<br />

(do...while)<br />

Applications :<br />

Demander à l’utilisateur un nombre compris dans un certain intervalle<br />

Demander N nombres à l’utilisateur<br />

Faire varier un indice pour parcourir les cases d’un tableau de taille N<br />

Faire varier deux indices pour parcourir une matrice de taille N×M<br />

Lire un fichier ligne par ligne / caractère par caractère<br />

Recommencer un programme plusieurs fois<br />

· · ·


5 CONTRÔLE & BOUCLES while 162<br />

55<br />

La boucle while<br />

Syntaxe :<br />

while (expression)<br />

instruction ;<br />

while (expression) {<br />

/* bloc */<br />

}<br />

Tant que l’expression est vrai (i.e. non nulle), alors l’instruction est<br />

exécutée (éventuellement un bloc d’instructions)<br />

Exemple :<br />

Ce programme<br />

demande des<br />

nombres jusqu’à ce<br />

que leur somme<br />

atteigne 100<br />

somme-max100.c<br />

1 #include <br />

2<br />

3 int main () {<br />

4 int n, sum=0 ;<br />

5 printf ("Somme limite = 100 !!!\n") ;<br />

6 while (sum < 100) {<br />

7 printf ("Donnez un nombre : ") ;<br />

8 scanf ("%d", &n) ;<br />

9 sum += n ;<br />

10 }<br />

11 printf ("La somme limite a ete atteinte ou depassee\n") ;<br />

12 return 0 ;<br />

13 }


5 CONTRÔLE & BOUCLES while 163<br />

Autre exemple :<br />

somme-positifs.c<br />

1 #include <br />

2<br />

3 int main () {<br />

4 char continuer=1 ;<br />

5 int n, sum=0 ;<br />

6<br />

7 while (continuer) {<br />

8 printf ("Donnez un nombre : ") ;<br />

9 scanf ("%d", &n) ;<br />

10 if (n == 0)<br />

11 continuer = 0 ;<br />

12 else if (n > 0)<br />

13 sum += n ;<br />

14 }<br />

15 printf ("La somme des positifs est %d\n", sum) ;<br />

16 return 0 ;<br />

17 }<br />

Ce programme calcule la<br />

somme des nombres positifs<br />

jusqu’à ce qu’un nombre nul soit<br />

entré


5 CONTRÔLE & BOUCLES for 164<br />

56<br />

La boucle for<br />

Syntaxe :<br />

for (initialisation ; expression ; action)<br />

instruction ;<br />

for (initialisation ; expression ; action) {<br />

/* bloc */<br />

}<br />

⇒ La boucle for équivaut à : initialisation ;<br />

while (expression) {<br />

/* bloc */<br />

action ;<br />

}


5 CONTRÔLE & BOUCLES for 165<br />

Exemple :<br />

Ces deux boucles<br />

sont équivalentes<br />

4 int i ;<br />

5<br />

6 for (i=0 ; i


5 CONTRÔLE & BOUCLES for 166<br />

Par<strong>cours</strong> de tableaux<br />

4 int tab[5], i ;<br />

5<br />

6 /* ... */<br />

7<br />

8 printf ("Tableau = (") ;<br />

9 for (i=0 ; i


5 CONTRÔLE & BOUCLES for 167<br />

Longueur d’une chaîne de caractères<br />

→ version for<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 char nom[16] ;<br />

6 int i ;<br />

7<br />

8 strcpy (nom, "ESTIA") ;<br />

9<br />

10 for (i=0 ; nom[i] != ’\0’ ; i++)<br />

11 ;<br />

12<br />

13 printf ("Longeur : %d\n", i) ;<br />

14 return 0 ;<br />

15 }<br />

→ version while<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 char nom[16] ;<br />

6 int i ;<br />

7<br />

8 strcpy (nom, "ESTIA") ;<br />

9<br />

10 i = 0 ;<br />

11 while (nom[i] != ’\0’)<br />

12 i++ ;<br />

13<br />

14 printf ("Longeur : %d\n", i) ;<br />

15 return 0 ;<br />

16 }


5 CONTRÔLE & BOUCLES do...while 168<br />

57<br />

La boucle do...while<br />

Syntaxe :<br />

do<br />

instruction ;<br />

while (expression) ;<br />

do {<br />

/* bloc */<br />

} while (expression) ;<br />

Cette troisième et dernière boucle effectue le test après l’exécution<br />

du bloc d’instructions (ou de l’instruction)<br />

↩→ Ceci implique que l’instruction (ou le bloc) sera toujours exécuté<br />

au moins une fois, contrairement aux cas du for et du while<br />

5 int n, min=1, max=15 ;<br />

6<br />

7 do {<br />

8 printf ("Donnez un nombre <strong>entier</strong> entre %d et %d : ", min, max) ;<br />

9 scanf ("%d", &n) ;<br />

10 } while (n < min || n > max) ;<br />

11<br />

12 printf ("Votre nombre est %d\n", n) ;


Page support 169<br />

http://www.guillaumeriviere.name/estia/C/<br />

Supports de <strong>cours</strong><br />

Pour écran<br />

Pour imprimante<br />

Par chapitre<br />

Exemples du <strong>cours</strong><br />

Fichiers sources .c<br />

Fichiers exe pour Windows<br />

Archive zip<br />

Exercices<br />

Énoncés de TP (en ligne)<br />

Énoncés de TD (pdf)<br />

17 exercices pour s’entraîner<br />

Ressources web<br />

Cours, Tutoriels, . . .<br />

Manuels<br />

Outils (GCC, Notepad++, . . .)


Plan 170<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


6 FONCTIONS Introduction 171<br />

→ En C toute instruction doit être dans une fonction. . .<br />

↩→ Il en faut donc au moins une dans tout programme, la fonction<br />

principale (main())<br />

⇒ De plus, plutôt que d’avoir une unique fonction regroupant<br />

l’ensemble des traitements d’un programme, il est préférable<br />

”découper” en plus petits morceaux<br />

Quatre principaux avantages à cela :<br />

1 La lisibilité : la clarté du code augmente, puisqu’une sémantique est<br />

donnée à un ensemble d’instructions<br />

2 La facilité de développement, puisque le nombre de bugs diminue<br />

inévitablement une fois le découpage effectué ! (tests unitaires)<br />

3 Le développement coopératif : plusieurs personnes peuvent travailler<br />

sur un même programme sans savoir comment les autres morceaux<br />

fonctionnent en interne. On parle de modularité ou de paquetages<br />

4 La maintenabilité : il est plus facile d’intervenir sur un petit morceau<br />

de code que sur un gros !


6 FONCTIONS Introduction 172<br />

Exemple :<br />

version-1fct.c<br />

1 #include <br />

2<br />

3 int main () {<br />

4 int a, b, c, x, y, w ;<br />

5<br />

6 printf ("Donnez un nombre : ") ;<br />

7 scanf ("%d", &a) ;<br />

8<br />

9 printf ("Donnez un nombre : ") ;<br />

10 scanf ("%d", &b) ;<br />

11<br />

12 printf ("Donnez un nombre : ") ;<br />

13 scanf ("%d", &c) ;<br />

14<br />

15 x = (a < b) ? a : b ;<br />

16 x = (c < x) ? c : x ;<br />

17<br />

18 y = (a > b) ? a : b ;<br />

19 y = (c > y) ? c : y ;<br />

20<br />

21 w = y - x ;<br />

22<br />

23 /* ... */<br />

24<br />

25 return 0 ;<br />

26 }<br />

version-4fct.c<br />

1 #include <br />

2<br />

3 int lire_nombre () {<br />

4 int n ;<br />

5 printf ("Donnez un nombre : ") ;<br />

6 scanf ("%d", &n) ;<br />

7 return n ;<br />

8 }<br />

9<br />

10 int max (int n1, int n2, int n3) {<br />

11 int m ;<br />

12 return ((m = (n1 > n2 ? n1 : n2)) < n3) ? n3 : m ;<br />

13 }<br />

14<br />

15 int min (int n1, int n2, int n3) {<br />

16 int m ;<br />

17 return ((m = (n1 < n2 ? n1 : n2)) > n3) ? n3 : m ;<br />

18 }<br />

19<br />

20 int main () {<br />

21 int a, b, c, w ;<br />

22<br />

23 a = lire_nombre () ;<br />

24 b = lire_nombre () ;<br />

25 c = lire_nombre () ;<br />

26 w = max(a,b,c) - min(a,b,c) ;<br />

27<br />

28 /* ... */<br />

29<br />

30 return 0 ;<br />

31 }


6 FONCTIONS Introduction 173<br />

Principales conséquences (corollaires) :<br />

1 La factorisation du code : évite la duplication de certaines portions de<br />

code, en regroupant au sein d’une fonction les traitements identiques,<br />

et évite ainsi la redondance (dangereuse)<br />

2 La réutilisabilité du code : les fonctions écrites peuvent être utilisées<br />

à différents endroits dans le programme et réutilisées dans d’autres<br />

programmes<br />

61<br />

La fonction principale<br />

Le point d’entrée d’un programme est la fonction main :<br />

3 int main () {<br />

4<br />

5 /* bloc */<br />

6<br />

7 return 0 ;<br />

8 }<br />

3 int main (int argc, char *argv[]) {<br />

4<br />

5 printf ("Bienvenue dans %s\n", argv[0]) ;<br />

6 printf ("Il y a %d argument%s\n",<br />

7 argc-1,<br />

8 argc>2 ? "s" : "") ;<br />

9<br />

10 return 0 ;<br />

11 }


6 FONCTIONS Définition 174<br />

62<br />

Définition d’une fonction<br />

Une fonction est définie par :<br />

1 son nom<br />

2 son type de retour<br />

3 ses arguments<br />

4 et son bloc d’instructions<br />

La syntaxe pour définir une fonction est la suivante :<br />

SYNONYMES<br />

Routine : fonctions<br />

bas-niveau, micro<br />

électronique, temps réel<br />

(appelée régulièrement par<br />

une horloge)<br />

Procédure : fonction qui ne<br />

retourne pas de résultat<br />

(PASCAL, ADA, . . . )<br />

type_de_retour nom_de_fonction (arguments) {<br />

/* DECLARATIONS */<br />

/* INSTRUCTIONS */<br />

return expression ;<br />

}


6 FONCTIONS Définition 175<br />

→ La partie arguments est soit :<br />

1 rien du tout (fonction sans paramètre), i.e. void,<br />

2 soit une liste de paramètres, séparés par des virgules<br />

où un paramètre est composé d’un type et d’un nom de variable<br />

→ Le type de retour est soit :<br />

1 rien du tout (une procédure), i.e. void,<br />

2 soit un type de base ou un type structuré<br />

Exemples :<br />

11 void trait (void) {<br />

12 printf ("-=-=-=-=-=-=-=-=-=-\n") ;<br />

13 }<br />

14<br />

15 void afficher_nombre (int n) {<br />

16 printf ("Votre nombre : %d\n", n) ;<br />

17 }<br />

18<br />

19 int saisir_nombre () {<br />

20 int n ;<br />

21 printf ("Donnez nombre : ") ;<br />

22 scanf ("%d", &n) ;<br />

23 return n ;<br />

24 }<br />

26 void nl () {<br />

27 printf ("\n") ;<br />

28 }<br />

29<br />

30 int carre (int n) {<br />

31 return n * n ;<br />

32 }<br />

33<br />

34 char pair (int n) {<br />

35 return (n % 2) == 0 ;<br />

36 }<br />

37<br />

38 int max (int n1, int n2) {<br />

39 return n1 > n2 ? n1 : n2 ;<br />

40 }


6 FONCTIONS Prototype 176<br />

Le nom, le type de retour et les types des arguments spécifient<br />

comment la fonction doit être appelée<br />

↩→ on parle alors du PROTOTYPE de la fonction<br />

Exemples :<br />

3 void trait (void) ;<br />

4 void afficher_nombre (int n) ;<br />

5 int saisir_nombre () ;<br />

6 void nl () ;<br />

7 int carre (int n) ;<br />

8 char pair (int n) ;<br />

9 int max (int n1, int n2) ;<br />

Pour qu’une fonction puisse être appelée, il faut que le compilateur<br />

connaisse son prototype au préalable<br />

↩→ Le prototype d’une fonction est connu par le compilateur soit :<br />

1 parce-que la fonction a été définie avant l’appel,<br />

2 soit parce-que son prototype a été déclaré avant l’appel<br />

RMQ : Pas nécessaire de connaître le code de la fonction avant l’appel


6 FONCTIONS Prototype 177<br />

Un exemple :<br />

version1-lineaire.c<br />

1 #include <br />

2<br />

3 /** Codes des fonctions */<br />

4<br />

5 int saisir_nombre () {<br />

6 int n ;<br />

7 printf ("Donnez nombre : ") ;<br />

8 scanf ("%d", &n) ;<br />

9 return n ;<br />

10 }<br />

11<br />

12 int carre (int n) {<br />

13 return n * n ;<br />

14 }<br />

15<br />

16 int max (int n1, int n2) {<br />

17 return n1 > n2 ? n1 : n2 ;<br />

18 }<br />

19<br />

20 /** Fonction principale */<br />

21 int main () {<br />

22 int a, b, c ;<br />

23 a = saisir_nombre() ;<br />

24 b = saisir_nombre() ;<br />

25 c = carre (max (a, b)) ;<br />

26 /* ... */<br />

27 return 0 ;<br />

28 }<br />

version2-prototype.c<br />

1 #include <br />

2<br />

3 /** Declarations des fonctions */<br />

4 int saisir_nombre () ;<br />

5 int carre (int n) ;<br />

6 int max (int n1, int n2) ;<br />

7<br />

8 /** Fonction principale */<br />

9 int main () {<br />

10 int a, b, c ;<br />

11 a = saisir_nombre() ;<br />

12 b = saisir_nombre() ;<br />

13 c = carre (max (a, b)) ;<br />

14 /* ... */<br />

15 return 0 ;<br />

16 }<br />

17<br />

18 /** Codes des fonctions */<br />

19<br />

20 int saisir_nombre () {<br />

21 int n ;<br />

22 printf ("Donnez nombre : ") ;<br />

23 scanf ("%d", &n) ;<br />

24 return n ;<br />

25 }<br />

26<br />

27 int carre (int n) {<br />

28 return n * n ;<br />

29 }<br />

30<br />

31 int max (int n1, int n2) {<br />

32 return n1 > n2 ? n1 : n2 ;<br />

33 }


6 FONCTIONS Appel 178<br />

Doit être bien clair (dans votre esprit) ! Ne pas confondre :<br />

1 le moment où l’on déclare/défini une fonction<br />

2 et le moment où l’on appelle une fonction<br />

→ Le code déclaré dans une fonction ne déclenche aucune<br />

d’exécution<br />

→ Le code déclaré dans une fonction ne fait que définir une suite<br />

d’actions à exécuter dans le cas où la fonction serait appelée<br />

→ Le code d’une fonction ne s’exécutera que lorsque la fonction aura<br />

été appelée par la fonction main() ou (indirectement) par une autre<br />

fonction qui aura été appelée par la fonction main()<br />

⇒ Une fonction peut être appelée plusieurs fois, mais n’est définie<br />

qu’une seule fois


6 FONCTIONS Appel 179<br />

Exemple : le programme suivant. . .ne fait rien !<br />

nothing.c<br />

1 #include <br />

2<br />

3 int saisir_nombre () {<br />

4 int n ;<br />

5 printf ("Donnez nombre : ") ;<br />

6 scanf ("%d", &n) ;<br />

7 return n ;<br />

8 }<br />

9<br />

10 int max (int n1, int n2) {<br />

11 return n1 > n2 ? n1 : n2 ;<br />

12 }<br />

13<br />

14 int demander () {<br />

15 int a, b, c ;<br />

16 a = saisir_nombre () ;<br />

17 b = saisir_nombre () ;<br />

18 c = max (a, b) ;<br />

19 printf ("Le plus grand est %d\n", c) ;<br />

20 return c ;<br />

21 }<br />

22<br />

23 int main () {<br />

24<br />

25 return 0 ;<br />

26 }


6 FONCTIONS Appel 180<br />

Exemple : appels multiples de fonctions<br />

calls.c<br />

1 #include <br />

2<br />

3 int saisir_nombre () {<br />

4 int n ;<br />

5 printf ("Donnez nombre : ") ;<br />

6 scanf ("%d", &n) ;<br />

7 return n ;<br />

8 }<br />

9<br />

10 int carre (int n) { return n * n ; }<br />

11<br />

12 char pair (int n) { return (n % 2) == 0 ; }<br />

13<br />

14 void afficher (int n) {<br />

15 printf ("%d est %s et son carre vaut %d\n", n, pair(n) ? "pair" : "impair", carre(n)) ;<br />

16 }<br />

17<br />

18 int main () {<br />

19 int a, b, c ;<br />

20 a = saisir_nombre () ;<br />

21 b = saisir_nombre () ;<br />

22 afficher (a) ;<br />

23 afficher (b) ;<br />

24 afficher (4) ;<br />

25 c = carre (24) ;<br />

26 printf ("%d + %d + %d = %d\n", a, b, c, a+b+c) ;<br />

27 return 0 ;<br />

28 }


6 FONCTIONS Passage des paramètres 181<br />

RMQ : Que ce soit au moment de la déclaration ou au moment de<br />

l’appel, le nom d’une fonction doit TOUJOURS être suivit d’une paire<br />

de parenthèses, même dans le cas sans arguments (void)<br />

63<br />

Passage des paramètres par valeur<br />

Les arguments d’une fonction sont des copies locales<br />

Autrement dit, le mode de passage des paramètres est<br />

(uniquement) par valeur<br />

⇒ Ce point est crucial et source de bugs !<br />

RMQ : Lié au fonctionnement des ordinateurs<br />

↩→ Chaque paramètre d’une fct se voit empiler<br />

par valeur dans la pile d’appel des fonctions<br />

↩→ Le code de la fonction va ensuite utiliser<br />

des variables locales<br />

↩→ Changement zone d’adressage mémoire. . .


6 FONCTIONS Passage des paramètres 182<br />

Exemple :<br />

echange-incorrect.c<br />

1 #include <br />

2<br />

3 /* NB: ne pas copier */<br />

4 void echange (int a, int b) {<br />

5 int tmp = a ; /* (3) */<br />

6 a = b ; /* (4) */<br />

7 b = tmp ; /* (5) */<br />

8 }<br />

9<br />

10 int main () {<br />

11 int i=10, j=5 ; /* (1) */<br />

12 echange (i, j) ; /* (2) */<br />

13 printf ("i=%d j=%d\n", i, j) ; /* (6) */<br />

14 return 0 ; /* (7) */<br />

15 }<br />

Lors de l’appel de la fonction echange(), à<br />

la ligne 12, c’est le contenu des variables i<br />

et j qui est copié respectivement dans les<br />

variables locales a et b


6 FONCTIONS Variables 183<br />

RMQ : Que penser des appels suivants ? Sont-ils corrects ?<br />

echange (3, 5) ;<br />

echange (i, 5) ;<br />

echange (3, j) ;<br />

64<br />

Retour sur les variables<br />

→ Les variables ont des durées de vie et des visibilités différentes,<br />

selon comment et où elles sont déclarées<br />

Elles peuvent être classées en 3 catégories :<br />

Visibilité/Portée Durée de vie<br />

Les variables locales Bloc Bloc<br />

Les variables globales Programme Programme<br />

Les variables statiques Bloc Programme


6 FONCTIONS Variables 184<br />

65<br />

Variable locale<br />

Une variable dont la déclaration est réalisée dans le corps d’une<br />

fonction, ou dans un bloc d’instructions (instructions délimitées par<br />

des accolades {}), est allouée dynamiquement dans la pile<br />

Il s’agit d’une variable locale (au comportement similaire à un<br />

paramètre d’une fonction) et dont la visibilité (portée) est limitée au<br />

bloc où elle est déclarée<br />

Sa durée de vie est limitée au bloc ou à la fonction où elle est<br />

déclarée<br />

3 int compteur (void) {<br />

4 int i = 0 ; /* variable locale */<br />

5 return ++i ;<br />

6 }<br />

7<br />

8 int main () {<br />

9 printf ("cpt=%d\n", compteur()) ;<br />

10 printf ("cpt=%d\n", compteur()) ;<br />

11 printf ("cpt=%d\n", compteur()) ;<br />

12 return 0 ;<br />

13 }<br />

→ Ici, la variable i n’existe pas en dehors<br />

de la fonction compteur()<br />

↩→ La valeur retournée sera toujours 1


6 FONCTIONS Variables 185<br />

66<br />

Variable globale<br />

À l’opposé des variables locales existent les variables globales<br />

Ce sont celles allouées dans le programme et qui seront créées<br />

par le compilateur (donc allocation statique)<br />

Leur visibilité (portée) est totale (i.e. de partout) et leur durée de<br />

vie celle du programme<br />

3 int i = 0 ; /* variable globale */<br />

4<br />

5 int compteur (void) {<br />

6 return ++i ;<br />

7 }<br />

8<br />

9 int main () {<br />

10 printf ("cpt=%d\n", compteur()) ;<br />

11 printf ("cpt=%d\n", compteur()) ;<br />

12 printf ("cpt=%d\n", compteur()) ;<br />

13 printf ("i vaut %d\n", i) ;<br />

14 return 0 ;<br />

→ La valeur retournée par la fonction<br />

sera 1, puis 2, 3 . . .<br />

↩→ Cependant ATTENTION, car i est modifiable de partout. . .<br />

⇒ À manipuler avec précaution (dangereux)


6 FONCTIONS Variables 186<br />

RMQ : Une variable globale peut également être visible dans des<br />

fonctions écrites dans d’autres fichiers sources du programme<br />

↩→ Pour cela, il faut redéclarer son prototype dans l’autre fichier en<br />

utilisant le mot clé extern<br />

↩→ La variable ne sera pas dupliquée, mais sera trouvée lors de<br />

l’édition des liens<br />

main.c<br />

1 #include <br />

2<br />

3 /* Variables globales */<br />

4 extern int i ;<br />

5<br />

6 /* Declarations de fonctions */<br />

7 int compteur () ;<br />

8 int megacompteur () ;<br />

9<br />

10 int main () {<br />

11 printf ("cpt=%d\n", compteur()) ;<br />

12 printf ("cpt=%d\n", compteur()) ;<br />

13 printf ("cpt=%d\n", megacompteur()) ;<br />

14 printf ("i vaut %d\n", i) ;<br />

15 return 0 ;<br />

16 }<br />

compteurs.c<br />

1 /* Variables globales */<br />

2 int i = 0 ;<br />

3<br />

4 /* Code des fonctions */<br />

5<br />

6 int compteur (void) {<br />

7 return ++i ;<br />

8 }<br />

9<br />

10 int megacompteur (void) {<br />

11 return i += 2 ;<br />

12 }<br />

Gestion compliquée. . .<br />

INTERDIT ! Sauf cas spéciaux


6 FONCTIONS Variables 187<br />

67<br />

Variable statique<br />

Une variable statique est une variable dont la déclaration est<br />

précédée du mot clé static<br />

Elle ressemble à une variable globale, mais avec une visibilité<br />

(portée) réduite<br />

Sa visibilité est restreinte au fichier ou au bloc la contenant<br />

Sa durée de vie est celle du programme<br />

3 int compteur (void) {<br />

4 static int i = 0 ; /* variable<br />

statique */<br />

5 return ++i ;<br />

6 }<br />

7<br />

8 int main () {<br />

9 printf ("cpt=%d\n", compteur()) ;<br />

10 printf ("cpt=%d\n", compteur()) ;<br />

11 printf ("cpt=%d\n", compteur()) ;<br />

12 return 0 ;<br />

13 }<br />

→ La valeur retournée par la fonction<br />

sera 1, puis 2, 3 . . .


6 FONCTIONS Variables 188<br />

68<br />

→ Un nom de<br />

variable peut<br />

servir plusieurs<br />

fois<br />

Ceci est<br />

évident pour les<br />

variables locales<br />

et/ou statiques,<br />

déclarées dans<br />

des blocs disjoints<br />

Mais ceci est<br />

également vrai<br />

pour des bloc<br />

inclus. . .<br />

Surcharge des noms de variables<br />

portee-variables.c<br />

3 int i = 0 ; /* i variable globale */<br />

4<br />

5 int compteur1 (void) {<br />

6 return ++i ;<br />

7 }<br />

8<br />

9 int compteur2 (void) {<br />

10 static int i = 0 ; /* i variable locale statique */<br />

11 return ++i ;<br />

12 }<br />

13<br />

14 int main () {<br />

15 int i ; /* i variable locale, masque i globale */<br />

16<br />

17 for (i=0 ; i


6 FONCTIONS Variables 189<br />

↩→ La portée d’une variable (de<br />

visibilité plus large) s’arrête<br />

lorsqu’une variable (de visibilité<br />

plus faible) portant le même nom<br />

est déclarée dans un sous-bloc


6 FONCTIONS Variables 190<br />

69<br />

Cas des tableaux<br />

Un tableau peut être argument d’une fonction, mais un tableau<br />

déclaré de manière statique (comme ci-dessous) ne peut pas être<br />

retourné par une fonction<br />

2 /* NB: ne compile pas */<br />

3 int *reverse (int a[], int N) {<br />

4 int i, b[N] ;<br />

5<br />

6 for (i=0 ; i


6 FONCTIONS Récursivité 191<br />

610<br />

Récursivité<br />

En C, toute fonction peut être<br />

récursive, i.e. s’appeler elle-même<br />

Exemple : Calculer la factorielle<br />

Récursion : N! = N.(N − 1)!<br />

Cas d’arrêt : 0! = 1<br />

factorielle-rec.c<br />

1 #include <br />

2<br />

3 int factorielle (int n) {<br />

4 if (n == 0)<br />

5 return 1 ;<br />

6 else<br />

7 return n * factorielle (n-1) ;<br />

8 }<br />

9<br />

10 int main () {<br />

11 int a ;<br />

12 a = factorielle (3) ;<br />

13 printf ("3! = %d\n", a) ;<br />

14 return 0 ;<br />

15 }


6 FONCTIONS Récursivité 192


6 FONCTIONS Récursivité 193<br />

Ainsi, l’appel récursif consiste à empiler des valeurs dans la pile<br />

d’appel de fonction (qui pourraient être des variables locales dans un<br />

contexte itératif)<br />

Le mécanisme récursif engendre et utilise des variables<br />

temporaires masquées, ralentissant l’exécution par rapport à ce que<br />

l’on pourrait croire<br />

Néanmoins, la facilité d’utilisation de ce processus le rend utile et<br />

fort appréciable pour des fonctions complexes, d’autant que le coût<br />

des appels d’une fonction devient négligeable si son code est<br />

conséquent (le gain en temps de développement étant d’autant plus<br />

important)<br />

L’intérêt des fonctions récursives est dans la clarté du code qui<br />

peut en découler<br />

RMQ : Clairement, toute fonction récursive peut aussi s’écrire de<br />

manière itérative, et vice versa (bijectif)


6 FONCTIONS Récursivité 194<br />

Exemple : Quicksort<br />

4 void quicksort (int a[], int debut, int fin) {<br />

5 if (debut < fin) {<br />

6 int tmp ;<br />

7 int i=debut, j=fin, pivot=a[(debut+fin)/2] ;<br />

8<br />

9 /* Partitionnement */<br />

10 do {<br />

11 while (a[i] < pivot)<br />

12 i++ ;<br />

13<br />

14 while (a[j] > pivot)<br />

15 j-- ;<br />

16<br />

17 if (i < j) {<br />

18 tmp = a[i] ;<br />

19 a[i] = a[j] ;<br />

20 a[j] = tmp ;<br />

21 }<br />

22 i++;<br />

23 j--;<br />

24 } while (i < j) ;<br />

25<br />

26 /* INVARIANT : a[debut,i] = pivot */<br />

28<br />

29 quicksort (a, debut, j) ;<br />

30 quicksort (a, i, fin) ;<br />

31 }<br />

32 }<br />

L’appel récursif est évident<br />

pour écrire Quicksort, puisque<br />

quicksort() s’appelle<br />

elle-même sur les portions non<br />

encore partitionnées du tableau<br />

La récursion s’arrête quand<br />

tableau vide (debut >= fin)<br />

RMQ : Fonctionne car a[] est<br />

un tableau (donc une adresse<br />

mémoire vers une zone de cases<br />

consécutives)<br />

↩→ ainsi, les sous-appels de<br />

fonctions travaillent effectivement<br />

tous sur le même tableau !


Plan 195<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


7 POINTEURS Introduction 196<br />

Les pointeurs occupent une place prépondérante en C pour deux<br />

raisons principales :<br />

Le besoin en terme d’algorithmique (passage d’arguments par<br />

adresse, liste chaînée, arbres, . . .)<br />

L’efficacité du code<br />

Un pointeur = une variable qui contient une adresse mémoire<br />

(Ex : l’adresse d’une autre variable ou des cases d’un tableau)<br />

Le problème des pointeurs pour le débutant tient plus dans la<br />

difficulté à comprendre le fonctionnement à cause d’une syntaxe<br />

concise et déroutante, plutôt que dans la notion qui est derrière. . .<br />

RMQ : Déjà vu implicitement dans les tableaux<br />

5 int a[5] ;<br />

6 *a = 2 ;<br />

7 *(a+1) = 4 ;<br />

8 *(a+2) = 5 ;<br />

9 *(a+3) = 1 ;<br />

10 *(a+4) = 3 ;


7 POINTEURS Généralités 197<br />

71<br />

Généralités<br />

Comme toute variable, un pointeur =<br />

un nom<br />

un type : le type de variable pointée<br />

une valeur : l’adresse de la variable pointée<br />

un espace mémoire<br />

Un pointeur se déclare comme suit :<br />

type_pointé *nom_pointeur ;<br />

Exemples :<br />

4 int *pt ;<br />

5 char *s ;<br />

6 double *x ;<br />

RMQ : La déclaration d’un pointeur reste somme<br />

toute quelque-chose d’assez imprécis<br />

↩→ Selon l’utilisation que le programmeur en fera, ces<br />

pointeurs pourront ensuite désigner aussi bien<br />

l’adresse d’une variable que celle d’un tableau


7 POINTEURS Généralités 198<br />

L’opérateur & précédant le nom d’une variable permet de<br />

récupérer l’adresse de celle-ci<br />

RMQ : Déjà vu pour appeler scanf()<br />

L’opérateur * précédant le nom d’un pointeur permet d’accéder<br />

au contenu de la variable pointée<br />

↩→ On parle alors de déréférencer le pointeur<br />

RMQ : Ne pas confondre avec l’étoile utilisée lors de la déclaration<br />

Exemple :<br />

4 int *ptr, i, j ; /* (1) */<br />

5<br />

6 ptr = &i ; /* (2) */<br />

7 *ptr = 10 ; /* (3) */<br />

8 printf ("i=%d ptr=%x\n", i, ptr) ;<br />

9<br />

10 ptr = &j ; /* (4) */<br />

11 *ptr = i + 1 ; /* (5) */<br />

12 printf ("j=%d ptr=%x\n", j, ptr) ;


7 POINTEURS Généralités 199<br />

Ce qui nuit à la compréhension des pointeurs, c’est qu’une<br />

variable pointeur a un espace mémoire et donc une adresse mémoire<br />

comme toute variable normale. . . il est donc possible de définir un<br />

pointeur de pointeur, etc. . . . ce qui conduit à des notations lourdes<br />

Exemple :<br />

4 int i, *p, **pp ;<br />

5<br />

6 pp = &p ;<br />

7 *pp = &i ; /* p = &i */<br />

8 **pp = 10 ; /* i = 10 */<br />

9<br />

10 printf ("i = %d\n", i) ;<br />

11 printf ("*p = %d\n", *p) ;<br />

12 printf ("**pp = %d\n", **pp)<br />

;<br />

↩→ Ce petit bout de code, a priori farfelu, est en fait assez simple :<br />

l6 : pp est un pointeur sur la variable p<br />

l7 : la variable pointée par pp (c.à.d p) reçoit l’adresse de la variable i<br />

l8 : on met 10 dans la variable i par une double indirection. . .


7 POINTEURS Passage des paramètres 200<br />

72<br />

Passage des paramètres par adresse<br />

Exemple :<br />

→ Reprenons la fonction echange()<br />

↩→ Elle ne fonctionnait pas à cause du fait que<br />

le passage des paramètres des fonctions se fait<br />

par valeur (passage par copie)<br />

3 /* NB: ne pas copier */<br />

4 void echange (int a, int b) {<br />

5 int tmp = a ;<br />

6 a = b ;<br />

7 b = tmp ;<br />

8 }<br />

Solution :<br />

→ Une solution correcte consiste à passer en<br />

paramètre, non pas les valeurs à échanger,<br />

mais les adresses des variables à permuter<br />

⇒ Manipulation de pointeurs<br />

3 void echange (int *a, int *b) {<br />

4 int tmp = *a ;<br />

5 *a = *b ;<br />

6 *b = tmp ;<br />

7 }


7 POINTEURS Passage des paramètres 201<br />

echange-pointeurs.c<br />

1 #include <br />

2<br />

3 void echange (int *a, int *b) {<br />

4 int tmp = *a ; /* (3) */<br />

5 *a = *b ; /* (4) */<br />

6 *b = tmp ; /* (5) */<br />

7 }<br />

8<br />

9 int main () {<br />

10 int i=10, j=5 ; /* (1) */<br />

11 echange (&i, &j) ; /* (2) */<br />

12 printf ("i=%d j=%d\n", i, j) ; /* (6) */<br />

13 return 0 ; /* (7) */<br />

14 }<br />

Dans cette nouvelle version, la fonction<br />

echange() prend maintenant en paramètres<br />

deux pointeurs d’<strong>entier</strong>s (de type int *)<br />

contenant les adresses des variables dont le<br />

contenu doit être échangé


7 POINTEURS Mémoire dynamique 202<br />

73<br />

Mémoire dynamique<br />

La bibliothèque stdlib.h définit les fonctions de gestion mémoire<br />

suivantes (entre nombreuses autres) :<br />

void *malloc (size t size) ;<br />

void free (void *) ;<br />

Elles permettent respectivement de réserver (allouer) puis libérer<br />

(désallouer) des espaces mémoire dynamiquement (i.e. durant<br />

l’exécution du programme)


7 POINTEURS Mémoire dynamique 203<br />

Exemple : allocation dynamique d’un tableau de 5 <strong>entier</strong>s<br />

allocation-tableau.c<br />

1 #include <br />

2 #include <br />

3<br />

4 int main () {<br />

5 int *tab, i ;<br />

6<br />

7 /* Demander d’allouer de la memoire (5x4 = 20 octets) */<br />

8 tab = malloc (5 * sizeof(int)) ;<br />

9<br />

10 if (tab == NULL) {<br />

11 fprintf (stderr, "Error: memory allocation failed\n") ;<br />

12 exit (-1) ;<br />

13 }<br />

14<br />

15 /* Remplir le tableau */<br />

16 for (i=0 ; i


7 POINTEURS Mémoire dynamique 204<br />

Ligne 5 : Après la<br />

déclaration des<br />

variables<br />

Ligne 8 : Après appel<br />

à malloc()<br />

Lignes 16-17 : Après<br />

affectation des cases<br />

Ligne 25 : Après appel<br />

à free()


7 POINTEURS Opérateurs 205<br />

74<br />

Opérateurs<br />

Les opérations suivantes s’appliquent aux pointeurs :<br />

Addition : Pointeur × Entier ↦−→ Pointeur<br />

Soustraction : Pointeur × Entier ↦−→ Pointeur<br />

Soustraction : Pointeur × Pointeur ↦−→ Pointeur<br />

Comparaison : Pointeur × Pointeur ↦−→ Booléen<br />

1 Addition Pointeur × Entier ↦−→ Pointeur (+ , ++ et +=)<br />

4 int *p, i, tab[10] ;<br />

5<br />

6 /* Initialiser tab[0...9] */<br />

7 for (i=0, p=tab ; i


7 POINTEURS Opérateurs 206<br />

2 Soustraction Pointeur × Entier ↦−→ Pointeur (− , −− et −=)<br />

17 for (i=9, p=tab+9 ; i>=0 ; i--)<br />

18 *(p-i) = i ;<br />

3 Soustraction Pointeur × Pointeur ↦−→ Pointeur (−)<br />

27 for (i=0, p=tab+10 ; p-tab ; i++)<br />

28 *--p = i ;<br />

37 for (p=tab+9 ; (p+1)-tab ; p--)<br />

38 *p = p-tab ;<br />

4 Comparaison Pointeur × Pointeur ↦→ Booléen<br />

(== ! = < = >)<br />

47 for (p=tab+9 ; p+1>tab ; p--)<br />

48 *p = p-tab ;<br />

RMQ : Uniquement entre pointeurs (opérateurs sur le même type)


Plan 207<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


8 STRUCTURES Principes 208<br />

81<br />

Principes généraux<br />

En C, une structure (enregistrement, record) est un regroupement<br />

de une ou plusieurs variables, de types quelconques et<br />

éventuellement différents<br />

L’intérêt réside dans une manipulation plus facile de données liées<br />

(sémantiquement), surtout dans les programmes de tailles<br />

importantes<br />

Cela permet surtout de représenter la notion d’entité (ou d’objet),<br />

justement en explicitant la liaison existant au niveau sémantique<br />

entre différentes variables<br />

Exemples :<br />

3 struct complexe {<br />

4 double real ;<br />

5 double imag ;<br />

6 } ;<br />

8 struct point {<br />

9 double x ;<br />

10 double y ;<br />

11 } ;<br />

13 struct droite {<br />

14 double a ;<br />

15 double b ;<br />

16 } ;


8 STRUCTURES Principes 209<br />

Pour travailler avec des<br />

variables structurées,<br />

l’opérateur ”.” permet d’accéder<br />

aux éléments de la structure<br />

Ce code affiche :<br />

Une structure peut<br />

regrouper des variables de<br />

types différents<br />

RMQ : On dit les champs de la<br />

structure<br />

(Ex : la structure rectangle<br />

possède 3 champs)<br />

3 struct complexe {<br />

4 double real ;<br />

5 double imag ;<br />

6 } ;<br />

7<br />

8 int main () {<br />

9 struct complexe c, b ;<br />

10<br />

11 c.real = 2. ;<br />

12 c.imag = 0. ;<br />

13<br />

14 printf ("c = (%.2f;%.2f)\n", c.real, c.imag) ;<br />

15<br />

16 return 0 ;<br />

17 }<br />

3 struct point {<br />

4 double x, y ;<br />

5 } ;<br />

6<br />

7 struct rectangle {<br />

8 struct point center ;<br />

9 double width, height ;<br />

10 } ;<br />

11<br />

12 int main () {<br />

13 struct rectangle Rect ;<br />

14<br />

15 /* Initialiser le rectangle */<br />

16 Rect.center.x = 0. ;<br />

17 Rect.center.y = 0. ;<br />

18 Rect.width = 5. ;<br />

19 Rect.height = 2. ;<br />

20


8 STRUCTURES Principes 210<br />

Autre exemple :<br />

1 #include <br />

2<br />

3 enum GENDER { FEMALE, MALE } ;<br />

4<br />

5 struct date {<br />

6 unsigned char d ;<br />

7 unsigned char m ;<br />

8 unsigned int y ;<br />

9 } ;<br />

10<br />

11 struct user {<br />

12 char *name ;<br />

13 char *password ;<br />

14 struct date birthday ;<br />

15 enum GENDER gender ;<br />

16 } ;<br />

17<br />

18 int main () {<br />

19 struct user playerOne, playerTwo ;<br />

20<br />

21 playerOne.name = strdup("Me");<br />

22 playerOne.birthday.d = 26 ;<br />

23 playerOne.birthday.m = 7 ;<br />

24 playerOne.birthday.y = 1982 ;<br />

25 playerOne.gender = MALE ;<br />

26 playerOne.name = strdup("You");<br />

27 /*...*/<br />

1 #include <br />

2<br />

3 typedef enum GENDER { FEMALE, MALE } Gender ;<br />

4<br />

5 typedef struct date {<br />

6 unsigned char d ;<br />

7 unsigned char m ;<br />

8 unsigned int y ;<br />

9 } Date ;<br />

10<br />

11 typedef struct user {<br />

12 char *name ;<br />

13 Date birthday ;<br />

14 Gender gender ;<br />

15 } User ;<br />

16<br />

17 int main () {<br />

18 User playerOne, playerTwo ;<br />

19<br />

20 playerOne.name = strdup("Me");<br />

21 playerOne.birthday.d = 26 ;<br />

22 playerOne.birthday.m = 7 ;<br />

23 playerOne.birthday.y = 1982 ;<br />

24 playerOne.gender = MALE ;<br />

25 playerOne.name = strdup("You");<br />

26 /*...*/<br />

RMQ : Utiliser typedef pour définir un nouveau type et éviter d’avoir à<br />

écrire struct partout


8 STRUCTURES Opérations 211<br />

Initialisation<br />

3 struct point {<br />

4 double x, y ;<br />

5 } ;<br />

6<br />

7 struct rectangle {<br />

8 struct point center ;<br />

9 double width, height ;<br />

10 } ;<br />

11<br />

12 int main () {<br />

13 struct point P = {10., 10.} ;<br />

14 struct rectangle Rect = {{0., 0.}, 5., 2.} ;<br />

15 /*...*/<br />

16 return 0 ;<br />

17 }<br />

Copie<br />

RMQ : Aucun autre des<br />

opérateurs n’est possible<br />

(+, −, , ==, ! =, . . .)<br />

↩→ Il faut définir des fonctions<br />

qui accéderont proprement<br />

aux champs<br />

3 struct complexe {<br />

4 double real ;<br />

5 double imag ;<br />

6 } ;<br />

7<br />

8 int main () {<br />

9 struct complexe a, b ;<br />

10<br />

11 a.real = 2. ;<br />

12 a.imag = 0. ;<br />

13 b = a ;<br />

14<br />

15 printf ("a = (%.2f;%.2f)\n", a.real, a.imag) ;<br />

16 printf ("b = (%.2f;%.2f)\n", b.real, b.imag) ;<br />

17 return 0 ;<br />

18 }


8 STRUCTURES Fonctions & structures 212<br />

82<br />

Fonctions et structures<br />

Les variables structurées peuvent être passées en paramètres des<br />

fonctions et aussi retournées par des fonctions<br />

Exemple :<br />

Une fonction qui calcule<br />

l’aire d’un rectangle<br />

3 struct rectangle {<br />

4 int width, height ;<br />

5 } ;<br />

6<br />

7 int rectangle_aire (struct rectangle rect) {<br />

8 return rect.width * rect.height ;<br />

9 }<br />

10<br />

11 int main () {<br />

12 struct rectangle R1 ;<br />

13 int a ;<br />

14<br />

15 R1.width = 5 ;<br />

16 R1.height = 2 ;<br />

17<br />

18 a = rectangle_aire (R1) ;<br />

19<br />

20 printf ("L’aire du rectangle est %d\n", a) ;<br />

21 return 0 ;<br />

22 }


8 STRUCTURES Fonctions & structures 213<br />

Exemple : addition et soustraction des complexes<br />

3 struct complexe {<br />

4 double real, imag ;<br />

5 } ;<br />

6<br />

7 struct complexe complexe_ajouter (struct complexe a, struct complexe b) {<br />

8 struct complexe res = a ; /* affectation : copie les champs */<br />

9 res.real += b.real ;<br />

10 res.imag += b.imag ;<br />

11 return res ; /* retourne une structure */<br />

12 }<br />

13<br />

14 struct complexe complexe_soustraire (struct complexe a, struct complexe b) {<br />

15 return (struct complexe) {a.real - b.real, a.imag - b.imag} ;<br />

16 }<br />

17<br />

18 int main () {<br />

19 struct complexe a, b, c ;<br />

20<br />

21 a.real = 1.41421 ;<br />

22 a.imag = 1.41421 ;<br />

23<br />

24 b.real = 0. ;<br />

25 b.imag = 1. ;<br />

26<br />

27 c = complexe_ajouter (a, b) ;<br />

28<br />

29 printf ("c = (%.2f;%.2f)\n", c.real, c.imag) ;<br />

30<br />

31 return 0 ;<br />

32 }


8 STRUCTURES Fonctions & structures 214<br />

Les paramètres des fonctions sont copiés dans une pile<br />

↩→ Cette copie peut être coûteuse en temps d’exécution, et parfois<br />

bien inutile<br />

⇒ On privilégie donc, pour des structures de tailles importantes, le<br />

passage par adresse. . . et donc les pointeurs<br />

3 struct rectangle {<br />

4 int width, height ;<br />

5 } ;<br />

6<br />

7 void rectangle_afficher (struct rectangle *rect) {<br />

8 printf ("Rectangle [Largeur=%d;Hauteur=%d]\n", (*rect).width, (*rect).height) ;<br />

9 }<br />

10<br />

11 int rectangle_perimetre (struct rectangle *rect) {<br />

12 return (*rect).width * 2 + (*rect).height * 2 ;<br />

13 }<br />

14<br />

15 int main () {<br />

16 struct rectangle R1 ;<br />

17 int p ;<br />

18 R1.width = 5 ;<br />

19 R1.height = 2 ;<br />

20 p = rectangle_perimetre (&R1) ; /* RMQ: on donne l’adresse de R1 */<br />

21 rectangle_afficher (&R1) ; /* RMQ: on donne l’adresse de R1 */<br />

22 printf ("Le perimetre vaut %d\n", p) ;<br />

23 return 0 ;<br />

24 }


8 STRUCTURES Fonctions & structures 215<br />

Déréférencement des champs d’une structure<br />

Lorsqu’on a un pointeur vers une structure :<br />

struct rectangle *rect ;<br />

Pourquoi les parenthèses pour déréférencer ?<br />

(*rect).width<br />

⇒ À cause de la priorité des opérateurs :<br />

le point est plus prioritaire que l’étoile<br />

Au fil des ans, cette notation a été jugée pénible, aussi une nouvelle<br />

forme d’écriture a été ajoutée au langage :<br />

rect->width<br />

⇒ Ainsi, la flèche permet de déréférencer<br />

les champs de la structure pointée


8 STRUCTURES Fonctions & structures 216<br />

L’écriture à de multiples<br />

reprises du mot clé<br />

struct également est<br />

assez pénible. . .<br />

→ Une alternative existe :<br />

définir un nouveau type, que<br />

l’on utilisera en lieu et place<br />

de la structure. . .<br />

3 struct complexe {<br />

4 double real, imag ;<br />

5 } ;<br />

6<br />

7 typedef struct complexe Complexe ;<br />

8<br />

9 /* Addition de 2 complexes */<br />

10 Complexe CAdd (Complexe a, Complexe b) {<br />

11 a.real += b.real ;<br />

12 a.imag += b.imag ;<br />

13 return a ;<br />

14 }<br />

15<br />

16 /* Equivalent du += pour complexes */<br />

17 Complexe CCAdd (Complexe *a, Complexe b) {<br />

18 a->real += b.real ;<br />

19 a->imag += b.imag ;<br />

20 return *a ;<br />

21 }<br />

22<br />

23 int main () {<br />

24 Complexe a, b, c ;<br />

25<br />

26 a.real = 1.41421 ;<br />

27 a.imag = 1.41421 ;<br />

28<br />

29 b.real = 0. ;<br />

30 b.imag = 1. ;<br />

31<br />

32 c = CAdd (a, b) ;<br />

33 printf ("c = (%.2f;%.2f)\n", c.real, c.imag) ;<br />

34<br />

35 CCAdd (&c, b) ;<br />

36 printf ("c = (%.2f;%.2f)\n", c.real, c.imag) ;<br />

37


8 STRUCTURES Tableaux & structures 217<br />

RMQ : typedef peut servir aussi à définir des types à partir d’autres<br />

choses que des structures. . . Par exemple, les <strong>entier</strong>s courts non signés,<br />

les octets. . .<br />

typedef unsigned short ushort ;<br />

typedef unsigned char octet ;<br />

83<br />

Tableaux et structures<br />

Comme pour les types de base, il est possible de créer des tableaux<br />

de structures :<br />

rectangles-tab5.c<br />

3 struct rectangle {<br />

4 int width, height ;<br />

5 } ;<br />

6<br />

7 int rectangle_aire (struct rectangle rect) {<br />

8 return rect.width * rect.height ;<br />

9 }<br />

10<br />

11 void rectangle_afficher (struct rectangle rect) {<br />

12 printf ("rectangle [%d;%d]\n", rect.width, rect.height) ;<br />

13 }


8 STRUCTURES Tableaux & structures 218<br />

rectangles-tab5.c (suite et fin)<br />

14<br />

15 int main () {<br />

16 struct rectangle tab[5] ;<br />

17 int i, aire_totale ;<br />

18<br />

19 /* Initialisation */<br />

20 for (i=0 ; i


8 STRUCTURES Structure récurrente 219<br />

84<br />

Les structures auto-référencielles<br />

Ou structures récurrente : des structures qui ont au moins un champs<br />

qui est du type même de la structure<br />

Exemples :<br />

Coder des arbres binaires<br />

22 typedef struct node *Node ;<br />

23<br />

24 struct node {<br />

25 int value ; /* Valeur du noeud */<br />

26 Node lson ; /* Fils gauche */<br />

27 Node rson ; /* Fils droit*/<br />

28 } ;<br />

29<br />

30 typedef struct tree {<br />

31 Node root ;<br />

32 } Tree ;<br />

Coder des listes<br />

Simplement chaînées<br />

3 struct list {<br />

4 int value ; /* Valeur du noeud */<br />

5 struct list *next ; /* Element suivant */<br />

6 } ;<br />

7<br />

8 typedef struct list *List ;<br />

Doublement chaînées<br />

12 struct list {<br />

13 int value ; /* Valeur du noeud */<br />

14 struct list *next ; /* Element suivant */<br />

15 struct list *last ; /* Element precedent */<br />

16 } ;<br />

17<br />

18 typedef struct list *List ;


Plan 220<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


9 LE PRÉPROCESSEUR Introduction 221<br />

Le préprocesseur C permet la<br />

définition de macros, qui vont être<br />

traitées avant la phase de<br />

compilation proprement dite, dans<br />

la phrase de préprocess<br />

Le préprocesseur C permet<br />

ainsi de faire de la compilation<br />

conditionnelle, d’inclure des<br />

fichiers d’entêtes, de définir des<br />

constantes<br />

→ La phase de préprocess est un<br />

simple traitement sur des fichiers<br />

textes (ne correspond pas à des<br />

instructions qui seraient exécutées<br />

par le futur programme)


9 LE PRÉPROCESSEUR Macro-constantes 222<br />

→ La phase de préprocess exécute les lignes commençant par un #<br />

RMQ : L’option -E de GCC permet de visualiser le résultat de la<br />

phase de préprocess<br />

gcc -Wall -ansi -E main.c -o programme.exe<br />

↩→ Attention, avec l’option -E, la compilation n’a pas lieu<br />

91<br />

Les macro-constantes<br />

Elles se définissent par le mot clé #define<br />

L’intérêt est qu’il n’y a pas de définition de nouvelle variable, les<br />

valeurs des macro-constantes étant écrites dans le code avant<br />

compilation


9 LE PRÉPROCESSEUR Macro-constantes 223<br />

Exemple<br />

→ Le code suivant :<br />

1 #define MAX 100<br />

2<br />

3 int main () {<br />

4 int T[MAX] ; /* Definition du tableau T */<br />

5 int i ; /* Indice de par<strong>cours</strong> */<br />

6<br />

7 for (i=0 ; i


9 LE PRÉPROCESSEUR Paquetages 224<br />

92<br />

Les paquetages<br />

Concept : regrouper les structures et fonctions (et éventuellement les<br />

variables globales (extern) et locales (static)) par thèmes<br />

Un paquetage regroupe 2 types de fichiers, différents par leur<br />

extension de nom :<br />

1 fichier.c : l’implantation (ou fichier source), contient le code<br />

et les variables<br />

2 fichier.h : l’interface du paquetage (ou fichier d’entête),<br />

contient les déclarations des prototypes des fonctions du<br />

paquetage, éventuellement des définitions types (structures) et<br />

des variables globales externes, et qui sera par la suite inclue<br />

dans les implantations (fichiers sources) utilisatrices


9 LE PRÉPROCESSEUR Paquetages 225<br />

Un fichier interface (ou fichier d’entête) commence toujours par les<br />

lignes :<br />

#ifndef<br />

#define<br />

XXXX H<br />

XXXX H<br />

↩→ En remplaçant les XXXX par le nom du paquetage<br />

Et termine toujours par la ligne :<br />

#endif<br />

RMQ : Ce mécanisme est fait pour palier aux possibles inclusions<br />

multiples


9 LE PRÉPROCESSEUR Paquetages 226<br />

Exemple :<br />

Un (mini-)paquetage<br />

de géométrie<br />

L’interface →<br />

boolean.h<br />

1 #ifndef __BOOLEAN_H<br />

2 #define __BOOLEAN_H<br />

3<br />

4 typedef enum BOOLEAN { FALSE, TRUE} BOOLEAN ;<br />

5<br />

6 #endif /* __BOOLEAN_H */<br />

geometrie.h<br />

1 #ifndef __GEOMETRIE_H<br />

2 #define __GEOMETRIE_H<br />

3<br />

4 #include "boolean.h"<br />

5<br />

6 typedef struct point {<br />

7 int x, y ;<br />

8 } point ;<br />

9<br />

10 typedef struct droite {<br />

11 double a, b ;<br />

12 BOOLEAN verticale ;<br />

13 int x ;<br />

14 } droite ;<br />

15<br />

16 point point_lire () ;<br />

17 double point_distance (point p1, point p2) ;<br />

18 droite droite_calculer (point p1, point p2) ;<br />

19<br />

20 #endif /* __GEOMETRIE_H */


9 LE PRÉPROCESSEUR Paquetages 227<br />

L’implantation→ geometrie.c<br />

1 #include /* printf() scanf() */<br />

2 #include /* sqrt() */<br />

3 #include "geometrie.h"<br />

4<br />

5 point point_lire () {<br />

6 point res ;<br />

7 printf (" Donnez l’abscisse : ") ;<br />

8 scanf ("%d", &res.x) ;<br />

9 printf (" Donnez l’ordonnee : ") ;<br />

10 scanf ("%d", &res.y) ;<br />

11 return res ;<br />

12 }<br />

13<br />

14 double point_distance (point p1, point p2) {<br />

15 return sqrt ((p1.x-p2.x) * (p1.x-p2.x) + (p1.y-p2.y) * (p1.y-p2.y)) ;<br />

16 }<br />

17<br />

18 droite droite_calculer (point p1, point p2) {<br />

19 droite res ;<br />

20 if (p1.x == p2.x) {<br />

21 res.verticale = TRUE ;<br />

22 res.x = p1.x ;<br />

23 }<br />

24 else {<br />

25 res.verticale = FALSE ;<br />

26 res.a = (double)(p1.y - p2.y) / (p1.x - p2.x) ;<br />

27 res.b = p1.y - res.a * p1.x ;<br />

28 }<br />

29 return res ;<br />

30 }


9 LE PRÉPROCESSEUR Paquetages 228<br />

Un premier programme utilisant le paquetage :<br />

main.c<br />

1 #include <br />

2 #include "geometrie.h"<br />

3<br />

4 int main () {<br />

5 point P = {9, 8} ;<br />

6 point Q = {2, 3} ;<br />

7 droite D ;<br />

8 double dist ;<br />

9<br />

10 D = droite_calculer (P, Q) ;<br />

11 dist = point_distance (P, Q) ;<br />

12<br />

13 if (D.verticale)<br />

14 printf ("Equation x = %d\n", D.x) ;<br />

15 else<br />

16 printf ("Equation y = %.2f x + %.2f\n", D.a, D.b) ;<br />

17<br />

18 printf ("Distance %.2f\n", dist) ;<br />

19<br />

20 return 0 ;<br />

21 }<br />

gcc -Wall -ansi -lm main.c geometrie.c -o calculs.exe


9 LE PRÉPROCESSEUR Paquetages 229<br />

Un autre programme utilisant aussi le paquetage :<br />

main2.c<br />

1 #include <br />

2 #include "geometrie.h"<br />

3<br />

4 int main () {<br />

5 point P, Q ;<br />

6 droite D ;<br />

7<br />

8 printf ("\n-=- Coordonnees du point P -=-\n") ;<br />

9 P = point_lire () ;<br />

10<br />

11 printf ("\n-=- Coordonnees du point Q -=-\n") ;<br />

12 Q = point_lire () ;<br />

13<br />

14 printf ("\n") ;<br />

15<br />

16 D = droite_calculer (P, Q) ;<br />

17<br />

18 if (D.verticale)<br />

19 printf ("Equation x = %d\n", D.x) ;<br />

20 else<br />

21 printf ("Equation y = %.2f x + %.2f\n", D.a, D.b) ;<br />

22<br />

23 return 0 ;<br />

24 }<br />

gcc -Wall -ansi -lm main2.c geometrie.c -o calculs2.exe


9 LE PRÉPROCESSEUR Paquetages 230<br />

Relations de dépendance entre les fichiers :<br />

Pour utiliser le (mini-)paquetage<br />

de géométrie, les fichiers<br />

sources main.c et main2.c<br />

doivent inclure l’interface<br />

geometrie.h<br />

Le fichier d’implantation<br />

geometrie.c doit inclure<br />

l’interface geometrie.h afin de<br />

connaître les déclaration des<br />

types structurés point et<br />

droite, mais également pour<br />

assurer que les prototypes des<br />

fonctions seront respectés<br />

Le fichier geometrie.h inclue<br />

le fichier d’entête boolean.h<br />

pour connaître le type énuméré<br />

BOOLEAN


Plan 231<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


10 BIBLIOTHÈQUE STANDARD Introduction 232<br />

→ Le C possède très peu de mots clés de langage<br />

→ Par contre, pour permettre son utilisation véritable, il existe une<br />

bibliothèque standard, normalisée, permettant de multiples actions :<br />

Les entrées/sorties<br />

Les mathématiques (de base)<br />

Des traitements sur les chaînes de caractères<br />

. . .<br />

La bibliothèque standard ANSI contient l’ensemble des librairies<br />

suivantes :<br />

<br />

<br />

<br />

<br />

Seulement certains de ces modules seront présentés ici (Par<br />

exemple, il est encore beaucoup trop tôt pour et<br />

)


10 BIBLIOTHÈQUE STANDARD 233<br />

101<br />

Les entrées sorties : <br />

Rappel : un flot (ou stream) est associé à une source/destination<br />

de données (clavier, écran, disque, . . .)<br />

Les flots peuvent être de deux sortes : texte ou binaire<br />

Un flot doit être ouvert avant utilisation, fermé après<br />

Ici, le flot est un pointeur sur une structure de FILE<br />

Trois flots sont ouverts par défaut : stdin, stdout et stderr<br />

Nous avons déjà exposé les fonctions suivantes (ouverture et<br />

fermeture de flots) :<br />

FILE *fopen (const char *path, const char *mode) ;<br />

int fclose (FILE *stream) ;<br />

⇒ D’autres fonctions de manipulation de fichiers existent


10 BIBLIOTHÈQUE STANDARD 234<br />

int fflush (FILE *stream) ;<br />

↩→ Provoque l’écriture des données mises en tampon (indéfini sur un<br />

flot d’entrée). Retourne 0, ou EOF si erreur<br />

int remove (const char *path) ;<br />

↩→ Destruction pure et simple d’un fichier ! Retourne 0 si erreur<br />

int rename (const char *oldpath, const char *newpath) ;<br />

↩→ Change le nom d’un fichier donné. Retourne 0 si erreur<br />

int setvbuf(FILE *stream, char *buf, int mode, size t sz);<br />

↩→ Permet de contrôler le tamponnage d’un flot, uniquement avant la<br />

1 re lecture/écriture. Le mode IOFBD provoque un tamponnage<br />

complet, IOLBF par ligne, IONBF inexistant. Si buf est différent de<br />

NULL, alors il sert de tampon, de taille size. Retourne une valeur<br />

non nulle si erreur


10 BIBLIOTHÈQUE STANDARD 235<br />

Nous avons déjà étudié les fonction d’E/S classiques (printf,<br />

scanf, fprintf, etc.)<br />

Très pratiques en général, elles sont cependant pénibles car<br />

limitées aux informations textuelles formatées<br />

D’autres fonctions permettent des lectures/écritures binaires<br />

size t fread (void *ptr, size t size, size t n, FILE *stream);<br />

↩→ Lit sur stream au plus n éléments de taille size octets et les<br />

stockent dans le tableau ptr. Cette fonction retourne le nombre<br />

d’éléments effectivement lus (peut être différent de n). En cas<br />

d’erreur par rapport à ce qui est attendu, le programmeur utilisera les<br />

fonctions de gestion d’erreur sur flots<br />

size t fwrite (void *ptr, size t size, size t n, FILE *stream);<br />

↩→ Idem à la fonction précédente mais dans l’autre sens : elle écrit<br />

les éléments sur le flot. Retourne aussi le nombre d’éléments traités


10 BIBLIOTHÈQUE STANDARD 236<br />

Positionnement dans les fichiers<br />

int fseek (FILE *stream, long offset, int origin);<br />

↩→ Déplacement à une position donnée dans un flot. Le déplacement<br />

dépend de type de fichier :<br />

binaire : La nouvelle position est à offset octets de l’origine<br />

origin, qui est soit le début du fichier (SEEK SET), soit la fin du<br />

fichier (SEEK END), soit la position courante (SEEK CUR).<br />

texte : offset faudra soit 0, soit une valeur retournée par<br />

ftell, avec origin==SEEK SET<br />

Une erreur correspond à une valeur de retour non nulle<br />

int ftell (FILE *stream);<br />

↩→ Retourne la position courante sur le flot donné, ou bien -1 en cas<br />

d’erreur


10 BIBLIOTHÈQUE STANDARD 237<br />

void rewind (FILE *stream);<br />

↩→ L’indicateur de position revient au début du fichier, et annule les<br />

erreurs éventuellement survenues sur le flot<br />

int fgetpos (FILE *stream, fpos t *ptr);<br />

↩→ Place dans *ptr la position courante. Retourne une valeur non<br />

nulle en cas d’erreur<br />

int fsetpos (FILE *stream, fpos t *ptr);<br />

↩→ Positionne le flot à la position mémorisée dans *ptr. Retourne<br />

une valeur non nulle en cas d’erreur


10 BIBLIOTHÈQUE STANDARD 238<br />

Certaines fonctions de la bibliothèque standard affectent, dans la<br />

structure FILE, un indicateur pour signaler une erreur et un autre<br />

pour signaler que la fin de fichier a été atteinte<br />

Ces indicateurs peuvent être gérés via les fonctions suivantes<br />

void clearerr (FILE *stream);<br />

↩→ Remet à zéro les erreurs et fin de fichier sur un flot donné<br />

int feof (FILE *stream);<br />

↩→ Retourne une valeur non nulle si l’indicateur de fin de fichier est à<br />

vrai<br />

int ferror (FILE *stream);<br />

↩→ Retourne une valeur non nulle si l’indicateur d’erreur est à vrai<br />

void perror (const char *msg);<br />

↩→ Imprime un message d’erreur précédé du contenu de msg, en<br />

fonction de la valeur de la variable globale errno. Cette dernière et<br />

son prototype sont définis dans


10 BIBLIOTHÈQUE STANDARD 239<br />

102<br />

Tests sur les caractères : <br />

Pour chacune des fonctions suivantes, le caractère c est un int<br />

(et non pas char, notamment parce que EOF==-1). Elles retournent<br />

une valeur != 0 si l’argument c remplit la condition indiquée, 0 sinon<br />

1 int isalnum(int c); /* == isalpha(c) || isdigit(c) */<br />

2 int isalpha(int c); /* Une lettre qqs la casse */<br />

3 int iscntrl(int c); /* Caractere de controle */<br />

4 int isdigit(int c); /* Chiffre decimal */<br />

5 int isgraph(int c); /* Caractere imprimable - espace */<br />

6 int islower(int c); /* Lettre minuscule */<br />

7 int isprint(int c); /* Caractere imprimable */<br />

8 int ispunct(int c); /* Caractere - {lettres, espaces, chiffres} */<br />

9 int isspace(int c); /* Espace au sens large (espace, saut de page, fin de ligne,<br />

10 retour chariot, tabulation, tabulation verticale) */<br />

11 int isupper(int c); /* Lettre majuscule */<br />

12 int isxdigit(int c); /* Chiffre hexadecimal */<br />

Deux fonctions supplémentaires retournent la lettre c dans la<br />

casse correspondante (ou c si c n’est pas de la casse inverse)<br />

14 int toupper(int c); /* Retourne une lettre majuscule */<br />

15 int tolower(int c); /* Retourne une lettre minuscule */


10 BIBLIOTHÈQUE STANDARD 240<br />

103<br />

Manipulation de chaînes de caractères : <br />

Cette librairie regroupe deux sortes de fonctions :<br />

Celles manipulant des chaînes de caractères, i.e. des tableaux<br />

de char contenant un ’\0’ ; Leur nom commence par str<br />

Celles manipulant des tableaux d’octets de longueurs<br />

quelconques ; Leur nom débute par mem<br />

Dans la suite, les paramètres des fonctions listées seront :<br />

s et t de type char *<br />

cs et ct de type const char *<br />

n de type size t<br />

c de type int


10 BIBLIOTHÈQUE STANDARD 241<br />

char *strcpy (s, ct)<br />

char *strncpy (s, ct, n)<br />

char *strcat (s, ct)<br />

char *strncat (s, ct, n)<br />

int strcmp (cs, ct)<br />

int strncmp (cs, ct, n)<br />

char *strchr (cs, c)<br />

char *strrchr (cs, c)<br />

char *strdup (cs)<br />

size t strlen (cs)<br />

char *strerror (n)<br />

copie ct dans s, retourne s<br />

copie n caractères de ct dans s, en<br />

complétant éventuellement par des 0<br />

et retourne s<br />

concatène la chaîne ct en fin de s<br />

et retourne s<br />

idem pour au plus n caractères de ct<br />

compare les chaînes cs et ct selon l’ordre<br />

lexicographique et retourne ct et 0 si identiques<br />

idem en comparant au plus les n premiers<br />

caractères<br />

retourne l’adresse de la première occurrence<br />

de c dans cs, ou NULL le cas échéant<br />

idem, mais pour la dernière occurrence<br />

allocation, puis duplication de cs<br />

(⇒ faire un free() après)<br />

retourne la longueur de cs<br />

retourne la chaîne associée à l’erreur n


10 BIBLIOTHÈQUE STANDARD 242<br />

Les fonctions suivantes permettent des traitements efficaces sur<br />

les tableaux d’octets (gèrent au mieux les défauts de page)<br />

RMQ : à part pour memmove, leur comportement est indéterminé en cas<br />

de chevauchement des tableaux d’octets (en mémoire) . . .<br />

Dans la suite, les paramètres des fonctions listées seront :<br />

s et t de type void *<br />

n de type size t<br />

cs et ct de type const void * c de type int<br />

void *memcpy (s, ct, n)<br />

void *memmove (s, ct, n)<br />

void *memcmp (cs, ct, n)<br />

void *memchr (cs, c, n)<br />

void *memset (s, c, n)<br />

copie de n octets de ct vers s ; retourne<br />

s<br />

”déplace” n octets de ct vers s qui peuvent<br />

se chevaucher ; retourne s<br />

compare les n premiers octets de cs et<br />

ct (comme strncmp)<br />

retourne l’adresse de la première occurrence<br />

de c dans cs, ou NULL le cas<br />

échéant<br />

remplie et retourne s avec n fois c


10 BIBLIOTHÈQUE STANDARD 243<br />

104<br />

Fonctions mathématiques : <br />

La librairie mathématiques C fournie un ensemble de fonctions<br />

dont le calcul n’est pas trivial<br />

Définies dans libm.o ou libm.lib ⇒ option -lm<br />

Dans la suite :<br />

les paramètres x et y sont de type double<br />

le paramètre n est de type int<br />

toutes les fonctions retournent un double<br />

sin (x)<br />

sinus de x<br />

cos (x)<br />

cosinus de x<br />

tan (x)<br />

tangente de x<br />

asin (x) arc sinus de x, dans [−π/2, π/2], pour x ∈ [−1, 1]<br />

acos (x) arc cosinus de x, dans [0, π], pour x ∈ [−1, 1]<br />

atan (x)<br />

arc tangente de x, dans [−π/2, π/2]<br />

atan2 (x, y) arc tangente de x/y, dans [−π, π]


10 BIBLIOTHÈQUE STANDARD 244<br />

sinh (x)<br />

sinus hyperbolique de x<br />

cosh (x)<br />

cosinus hyperbolique de x<br />

tanh (x)<br />

tangente hyperbolique de x<br />

exp (x) exponentielle de x (i.e. e x )<br />

log (x) logarithme népérien, pour x > 0<br />

log10 (x) logarithme à base 10, pour x > 0<br />

élévation de x à la puissance y (i.e. x<br />

pow (x, y)<br />

) Erreur de<br />

domaine si (x==0 && y


10 BIBLIOTHÈQUE STANDARD 245<br />

frexp (x, int *exp)<br />

modf (x, double *ip)<br />

fmod (x, y)<br />

sépare x en une fraction normalisée dans<br />

[−1/2, 1[ qui est retournée, et une puissance<br />

de 2 placée dans *exp. Si x==0,<br />

les valeurs retournées sont nulles<br />

sépare x en ses parties entières (dans<br />

*ip) et fractionnaire (retournée) de même<br />

signe que x<br />

reste de x/y de même signe que x. Comportement<br />

indéterminé si y==0<br />

Certaines erreurs peuvent survenir : les macros relatives aux réels sont<br />

écrites dans . La variable globale errno reçoit EDOM ou<br />

ERANGE en cas d’erreur de domaine ou d’intervalle ; lorsqu’un résultat est<br />

trop grand pour être représenté par un double, alors on utilise HUGE VAL<br />

avec le signe nécessaire, et errno reçoit ERANGE. Inversement, si le<br />

résultat est trop petit, 0 est retourné, et peut-être que errno reçoit ERANGE<br />

RMQ : ne pas vérifier les erreurs c’est aller au devant de bugs difficiles à<br />

détecter


10 BIBLIOTHÈQUE STANDARD 246<br />

105<br />

Fonctions utilitaires : <br />

Dans ce module, on trouve les fonctions de conversions,<br />

d’allocation mémoire et autres outils très utiles !<br />

Conversions chaînes ↦−→ nombres<br />

double atof (const char *s) ;<br />

int atoi (const char *s) ;<br />

long atol (const char *s) ;<br />

↩→ Ces trois fonctions convertissent une chaîne de caractères<br />

RMQ : Pour s="23trucs", les résultats seront 23. . .


10 BIBLIOTHÈQUE STANDARD 247<br />

On trouve aussi dans ce module la gestion de la mémoire<br />

dynamique<br />

void *calloc(size t nmemb, size t size);<br />

void *malloc(size t size);<br />

void *realloc(void *ptr, size t size);<br />

void free(void *ptr);<br />

RMQ : Rappelons que les 3 premières retournent NULL en cas d’erreur et<br />

donc qu’il importe de tester cette valeur pour gérer les erreurs<br />

On trouve aussi les fonctions permettant d’interrompre l’exécution<br />

du programme :<br />

void abort(void);<br />

void exit(int status);<br />

int atexit(void (*function)(void));<br />

RMQ : La dernière permet d’enregistrer une fonction qui sera appelée<br />

lors de la sortie normale d’un programme


10 BIBLIOTHÈQUE STANDARD 248<br />

D’autres fonctions permettent de d’interagir avec le système<br />

int system(const char *commande);<br />

char *getenv(const char *name);<br />

↩→ system() passe la chaîne s à l’environnement d’exécution pour que<br />

celui-ci l’exécute (comme une ligne de commande). Retourne -1 en cas<br />

d’erreur, le code de retour de la commande en cas de succès<br />

↩→ getenv() recherche name dans la liste des variables d’environnement,<br />

et renvoie un pointeur sur la chaîne correspondante<br />

Des fonctions de tri sont également à disposition<br />

void qsort(void *base, size t n, size t size,<br />

int(*cmp)(const void *, const void *));<br />

void *bsearch(const void *key, const void *base, size t n,<br />

size t size, int (*cmp)(const void *, const void *));<br />

→ Elles agissent sur un tableau base d’objets de taille size octets et en<br />

nombre n. Le tri et la recherche se font via une fonction cmp capable de<br />

comparer 2 éléments du tableau (e.g. strcmp())<br />

→ La recherche utilise sur un objet key, qui est l’adresse de l’élément<br />

recherché dans le tableau et retourne NULL si elle ne trouve pas


10 BIBLIOTHÈQUE STANDARD 249<br />

Les fonctions de génération de nombres pseudo-aléatoires<br />

void srand(unsigned int seed);<br />

int rand(void);<br />

La fonction srand() utilise son argument comme ”graine” pour générer<br />

une nouvelle séquence de nombres pseudo-aléatoires, qui seront ensuite<br />

fournis par rand(). Ces séquences sont reproductibles en appelant<br />

srand() avec la même valeur de graine.<br />

Il est<br />

recommandé<br />

de les utiliser<br />

d’une manière<br />

particulière<br />

19 /* AUTHOR: Jean-Marc Bourguet, March 2006, www.bourguet.org */<br />

20 int hasard_alea (int n) {<br />

21 int partSize = 1 + (n == RAND_MAX ? 0 : (RAND_MAX - n) / (n + 1));<br />

22 int draw, maxUsefull = partSize * n + (partSize-1);<br />

23 do {<br />

24 draw = rand();<br />

25 } while (draw > maxUsefull);<br />

26 return draw / partSize;<br />

27 }<br />

28<br />

29 void hasard_initialiser_graine () {<br />

30 srand (time (NULL)) ;<br />

31 }<br />

32<br />

33 int hasard_generer_nombre (int min, int max) {<br />

34 return min + hasard_alea (max-min) ;<br />

35 }


10 BIBLIOTHÈQUE STANDARD 250<br />

106<br />

La date et l’heure : <br />

Ce module permet, depuis un programme, de récupérer la date et<br />

l’heure auprès du système d’exploitation<br />

Dans la suite, clock t et time t sont des <strong>entier</strong>s représentant<br />

des instants, et la structure tm est la suivante :<br />

3 struct tm {<br />

4 int tm_sec; /* secondes [0-60] */<br />

5 int tm_min; /* minutes [0-59] */<br />

6 int tm_hour; /* heures [0-23] */<br />

7 int tm_mday; /* jour du mois [1-31] */<br />

8 int tm_mon; /* mois [0-11] */<br />

9 int tm_year; /* annee - 1900 */<br />

10 int tm_wday; /* jour de semaine [0-6] */<br />

11 int tm_yday; /* jours depuis 1er janvier [0-365] */<br />

12 int tm_isdst; /* drapeau heure d’ete [-1/0/1] */<br />

13 };<br />

Le champ tm isdst est positif en heure d’été, nul en hivers, et négatif si<br />

cette information est inconnue


10 BIBLIOTHÈQUE STANDARD 251<br />

clock t clock(void);<br />

↩→ Retourne le temps CPU, en unités d’horloge, depuis le début de<br />

l’exécution du programme, ou -1 s’il est impossible de le savoir. Pour<br />

obtenir une durée en secondes, diviser par CLOCKS PER SEC ou CLK TCK<br />

time t time(time t *tp);<br />

↩→ Retourne et initialise tp avec l’heure calendaire ou -1 si non disponible<br />

(tp peut être NULL). (nombre de secondes écoulées depuis le 1er janvier<br />

1970 sous Unix)<br />

double difftime(time t t1, time t t0);<br />

↩→ Renvoie le nombre de secondes écoulées entre les instants t1 et t0 au<br />

format double<br />

time t mktime(struct tm *tm);<br />

↩→ Convertit une heure locale en heure calendaire. Retourne -1 si<br />

problème. . .


10 BIBLIOTHÈQUE STANDARD 252<br />

char *asctime(const struct tm *tm);<br />

↩→ Convertit une heure en une chaîne de caractères. Cette dernière est un<br />

tableau statique de la fonction (donc écrasement !)<br />

struct tm *localtime(const time t *tp);<br />

↩→ Convertit l’heure calendaire *tp en heure locale<br />

char *ctime(const time t *timep);<br />

↩→ Convertit une heure calendaire en chaîne de caractères (⇒<br />

asctime(localtime(tp)))<br />

struct tm *gmtime(const time t *timep);<br />

↩→ Convertit *tp en Temps Universel. Retourne NULL si ce dernier n’est<br />

pas disponible


10 BIBLIOTHÈQUE STANDARD 253<br />

size t strftime(char *s, size t smax,<br />

const char *fmt, const struct tm *tm);<br />

↩→ Convertit un temps *tm en une chaîne s selon un format défini par la<br />

chaîne fmt. Retourne le nombre de caractères (moins le ’\0’) produits, ou<br />

0 si ce nombre aurait été supérieur à smax.<br />

→ Les caractères de la chaîne fmt sont traités à la manière des fonctions<br />

printf()et autres. Les sélecteurs sont :<br />

%a abréviation du jour de la semaine %M minutes<br />

%A nom complet du jour de la semaine %p équivalence locale de AM ou PM<br />

%b abréviation du mois %S secondes (00-60)<br />

%B nom complet du mois %U numéro de la semaine dans l’année,<br />

%c représentation locale commence au 1 er dimanche (00-53)<br />

%d jour du mois (01-31) %W numéro de la semaine dans l’année,<br />

%H heure sur 24 (0-23) commence au 1 er lundi (00-53)<br />

%I heure sur 12 (1-12) %w numéro du jour de la semaine (0-6)<br />

%j numéro du jour de l’année (001-366) %x représentation locale de la date<br />

%m numéro du jour du mois (01-12) %X idem pour l’heure<br />

%z nom du fuseau horaire %y année dans le siècle<br />

%% % %Y année


Plan 254<br />

1 Introduction<br />

2 Types de base<br />

3 Opérateurs<br />

4 Entrées/Sorties<br />

5 Structures de Contrôle & Boucles<br />

6 Fonctions<br />

7 Pointeurs<br />

8 Structures<br />

9 Le préprocesseur<br />

10 Bibliothèque standard<br />

11 Génie logiciel et C


11 GÉNIE LOGICIEL ET C Introduction 255<br />

Définition : art de spécifier, concevoir, réaliser et faire évoluer, avec<br />

des moyens et dans des délais raisonnables, des programmes, des<br />

documentations et des procédures de qualité en vue d’utiliser un<br />

ordinateur pour résoudre certains problèmes<br />

111<br />

Du génie logiciel à la programmation<br />

Objectifs du génie logiciel : structurer et ”formaliser” les différentes<br />

étapes de conception du logiciel pour obtenir des logiciels plus<br />

fiables, réutilisables et performants<br />

Avant la programmation :<br />

→ cahier des charges, spécifications globales, spécifications<br />

détaillées<br />

Après la programmation :<br />

→ intégration, tests, maintenance<br />

Pendant la programmation :<br />

→ encapsulation, généricité, modularité


11 GÉNIE LOGICIEL ET C Méthode 256<br />

Une méthode de conception :<br />

concevoir un programme comme une armée mexicaine<br />

Un minimum de soldats : ils font tout le travail !<br />

→ soldat = fonction élémentaire qui accède directement aux<br />

structures de données<br />

Un maximum de colonels : ils font travailler les soldats. . .<br />

→ colonel = fonction qui utilise exclusivement des fonctions<br />

élémentaires en les combinant<br />

Un général : il organise l’intervention des colonels<br />

→ général = fonction principale qui appelle les fonctions,<br />

élémentaires ou non


11 GÉNIE LOGICIEL ET C Caractéristiques 257<br />

112<br />

Caractéristiques d’un programme<br />

Encapsulation<br />

→ les colonels ne connaissent pas les outils des soldats<br />

→ ils organisent l’intervention des soldats indépendamment de<br />

leurs outils<br />

Généricité<br />

→ certains soldats et colonels sont remplaçables, sans que le<br />

travail du reste de l’armée ne soit modifié<br />

Modularité<br />

→ On structure l’armée mexicaine en plusieurs garnisons<br />

→ Chaque garnison distingue elle-même les soldats, les<br />

colonels, et sa propre description


11 GÉNIE LOGICIEL ET C Caractéristiques 258<br />

En pratique cela revient à :<br />

Encapsulation (”masquage” des données)<br />

→ les fonctions de haut niveau ne connaissent pas les structures<br />

de données<br />

→ leur description est indépendante de ces structures<br />

Généricité<br />

→ on peut définir des données qui en manipulent d’autres, sans<br />

que ces dernières ne soient connues<br />

Modularité<br />

→ on structure le programme en modules (paquetages)<br />

→ chaque module distingue lui-même les fonctions<br />

élémentaires, les fonctions et leur description (fichier d’interface,<br />

d’implantation, public et privé. . .)


Fin 259<br />

Ce que nous n’avons pas abordé, ou alors que faiblement, dans ce<br />

<strong>cours</strong> de 8h (non exhaustif) :<br />

Les pointeurs ; Les pointeurs de fonction ;<br />

Les champs de bits ; Les unions ;<br />

Les macro fonctions ; La compilation conditionnelle ;<br />

Les structures auto-référentielles (liste, pile, file, arbre, graphe) ;<br />

Les mots clés continue et goto (proscrits) ;<br />

Les E/S binaires (open(), read(), write(), close()) ;<br />

. . .<br />

↩→ Aller plus loin :<br />

”Le langage C” Henri Garreta<br />

http://c.developpez.com/<strong>cours</strong>/poly-c/<br />

”Le langage C” B. Kernighan & D. Ritchie (éditions Dunod)


Table des matières – I 260<br />

1 Introduction 17<br />

2 Types de base 58<br />

1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />

2 Variables entières (char et int) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />

3 Variables réelles (float et double) . . . . . . . . . . . . . . . . . . . . . . . . . . 63<br />

4 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73<br />

5 Les caractères et la table ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />

6 Les variables constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78<br />

7 Constantes classiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79<br />

8 Constantes énumérées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />

9 Conversions de types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />

10 Exemples d’erreurs classiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />

3 Opérateurs 87<br />

1 L’opérateur d’affectation = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88<br />

2 Les opérateurs arithmétiques + − ∗ / % . . . . . . . . . . . . . . . . . . . 91<br />

3 Incrémentation et décrémentation ++ et −− . . . . . . . . . . . . . . . . . . 94


Table des matières – II 261<br />

4 Les opérateurs logiques == = < > != && || . . . . . . . 95<br />

5 Traitement des bits & | > . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98<br />

6 Opérateurs et expressions d’affectations += −= /= ∗= %=<br />

102<br />

7 Priorité et associativité des opérateurs . . . . . . . . . . . . . . . . . . . . . . . 103<br />

4 Entrées/Sorties 105<br />

1 Écriture d’une chaîne simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110<br />

2 Écriture de valeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .111<br />

3 Nombre minimum de caractères produits . . . . . . . . . . . . . . . . . . . . 114<br />

4 Table des formats possibles pour printf() . . . . . . . . . . . . . . . . . 116<br />

5 Un flux de sortie distinct pour les erreurs : stderr . . . . . . . . . . .119<br />

6 Les redirections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121<br />

7 Comportement (asynchrone) de scanf() . . . . . . . . . . . . . . . . . . . 123<br />

8 Utilisation de scanf() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124<br />

9 Table des formats possibles pour scanf() . . . . . . . . . . . . . . . . . . 127<br />

10 Ouvrir un descripteur de fichier avec fopen() . . . . . . . . . . . . . . . 131<br />

11 Écrire dans un fichier avec fprintf() . . . . . . . . . . . . . . . . . . . . . . 137


Table des matières – III 262<br />

12 Lire dans un fichier avec fscanf() . . . . . . . . . . . . . . . . . . . . . . . . . 139<br />

13 Accès fichiers caractère par caractère fgetc() et fputc() 141<br />

5 Structures de Contrôle & Boucles 143<br />

1 La structure conditionnelle classique if . . . . . . . . . . . . . . . . . . . . . 145<br />

2 Le contrôle par else if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151<br />

3 L’opérateur de conditionnelle ? : . . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />

4 La structure de contrôle switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157<br />

5 La boucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162<br />

6 La boucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164<br />

7 La boucle do...while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168<br />

6 Fonctions 170<br />

1 La fonction principale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

2 Définition d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174<br />

3 Passage des paramètres par valeur . . . . . . . . . . . . . . . . . . . . . . . . . .181<br />

4 Retour sur les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .183<br />

5 Variable locale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .184


Table des matières – IV 263<br />

6 Variable globale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185<br />

7 Variable statique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .187<br />

8 Surcharge des noms de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . .188<br />

9 Cas des tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190<br />

10 Récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191<br />

7 Pointeurs 195<br />

1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197<br />

2 Passage des paramètres par adresse . . . . . . . . . . . . . . . . . . . . . . . .200<br />

3 Mémoire dynamique avec malloc() et free() . . . . . . . . . . . . .202<br />

4 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205<br />

8 Structures 207<br />

1 Principes généraux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208<br />

2 Fonctions et structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212<br />

3 Tableaux et structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217<br />

4 Les structures auto-référencielles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219<br />

9 Le préprocesseur 220


Table des matières – V 264<br />

1 Les macro-constantes avec #define . . . . . . . . . . . . . . . . . . . . . . . .222<br />

2 Les paquetages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224<br />

10 Bibliothèque standard 231<br />

1 Les entrées sorties : . . . . . . . . . . . . . . . . . . . . . . . . . . . 233<br />

2 Tests sur les caractères : . . . . . . . . . . . . . . . . . . . . . . .239<br />

3 Manipulation de chaînes de caractères : . . . . . . 240<br />

4 Fonctions mathématiques : . . . . . . . . . . . . . . . . . . . . . . 243<br />

5 Fonctions utilitaires : . . . . . . . . . . . . . . . . . . . . . . . . . . 246<br />

6 La date et l’heure : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250<br />

11 Génie logiciel et C 254<br />

1 Du génie logiciel à la programmation . . . . . . . . . . . . . . . . . . . . . . . . .255<br />

2 Caractéristiques d’un programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257


Page support 265<br />

http://www.guillaumeriviere.name/estia/C/<br />

Supports de <strong>cours</strong><br />

Pour écran<br />

Pour imprimante<br />

Par chapitre<br />

Exemples du <strong>cours</strong><br />

Fichiers sources .c<br />

Fichiers exe pour Windows<br />

Archive zip<br />

Exercices<br />

Énoncés de TP (en ligne)<br />

Énoncés de TD (pdf)<br />

17 exercices pour s’entraîner<br />

Ressources web<br />

Cours, Tutoriels, . . .<br />

Manuels<br />

Outils (GCC, Notepad++, . . .)

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!