Vous êtes sur la page 1sur 702

Abonnez-vous à DeepL Pro p

Visitez www.DeepL.com/pro
Tony 2hang
Apprenez vous-
même

C
inH2ou4
Tony Zhang

DEUXIÈME ÉDITION
201 West 103rd St., Indianapolis, Indiana, 46290 USA
Sams Teach Yourself C in 24 Hours, ÉDITEUR ASSOCIÉ
Michael Stephens
deuxième édition RÉDACTEUR EN CHEF DES ACQUISITIONS
Copyright ©2000 par Sams Publishing Carol Ackerman
Tous droits réservés. Aucune partie de ce livre ne peut être reproduite, stockée RÉDACTEUR EN CHEF DU DÉVELOPPEMENT
dans un système d'archivage ou transmise par quelque moyen que ce soit, Gus A. Miklos
électronique, mécanique, photocopie, enregistrement ou autre, sans
l'autorisation écrite de l'éditeur. Aucune responsabilité en matière de brevet RÉDACTEUR EN CHEF
n'est assumée en ce qui concerne l'utilisation des informations contenues dans Charlotte Clapp
le présent document. Bien que toutes les précautions aient été prises dans la
préparation de ce livre, l'éditeur et l'auteur n'assument aucune responsabilité en RÉDACTEUR DU PROJET
cas d'erreurs ou d'omissions. De même, aucune responsabilité n'est assumée Andy Beaster
pour les dommages résultant de l'utilisation des informations contenues dans le RÉDACTEUR EN CHEF
présent ouvrage.
Kate Givens
Numéro international normalisé du livre : 0-672-31861-x INDEXEURS
Numéro de catalogue de la bibliothèque du Congrès : 99- Christine Nelsen
Deborah Hittel
067311 Imprimé aux États-Unis d'Amérique
CORRECTEUR D'ÉPREUVES
Première impression : Février 2000 Candice Hightower
05 04 036 5 4 3 RÉDACTEUR TECHNIQUE
Bill Mitchell
Marques déposées COORDINATEUR D'ÉQUIPE
Tous les termes mentionnés dans ce livre qui sont connus pour être des marques Pamalee Nelson
commerciales ou des marques de service ont été mis en majuscules de manière
appropriée. Sams Publishing ne peut attester de l'exactitude de ces ARCHITECTE D'INTÉRIEUR
informations. L'utilisation d'un terme dans ce livre ne doit pas être considérée Gary Adair
comme affectant la validité d'une marque commerciale ou d'une marque de
CONCEPTEUR DE LA COUVERTURE
service.
Aren Howell

Avertissement et clause de non- RÉDACTEUR


Eric Borgert
responsabilité ASSISTANTE ÉDITORIALE
Tous les efforts ont été faits pour rendre ce livre aussi complet et précis que
Angela Boley
possible, mais aucune garantie ou adéquation n'est implicite. Les informations
fournies le sont "en l'état". L'auteur et l'éditeur n'assument aucune PRODUCTION
responsabilité à l'égard d'une personne ou d'une entité en ce qui concerne les Stacey DeRome
pertes ou les dommages résultant des informations contenues dans ce livre. Mark Walchle
Sommaire en un coup d'œil
Introduction 1

Partie I Les bases du langage C 9


Heure 1Faire le premier pas 11
2 Écrire votre premier programme C 27
3 Apprendre la structure d'un programme C 41
4 Comprendre les types de données et les mots-clés 55
5 Gestion des entrées et sorties standard 71

Partie II Opérateurs et déclarations de flux de contrôle 89


Heure 6Manipulation des données 91
7 Travailler avec des boucles 105
8 Utilisation des opérateurs conditionnels 121
9 Travailler avec des modificateurs de données et des fonctions mathématiques 141
10 Contrôler le déroulement du programme 155

Partie III Pointeurs et tableaux 173


Heure 11Comprendre les pointeurs 175
12 Comprendre les tableaux 189
13 Manipuler des chaînes de caractères 207
14 Comprendre la portée et les classes de stockage 223

Partie IV Fonctions et allocation dynamique de la mémoire 241


Heure Travailler avec des fonctions 243
15
16 Application des pointeurs 259
17 Attribution de la mémoire 279
18 Utilisation de types de données et de fonctions 295
spéciales
Partie V Structure, Union, E/S de fichiers, et plus encore 311
Heure 19Compréhension des structures 313
20 Comprendre les syndicats 333
21 Lire et écrire avec des fichiers 355
22 Utilisation des fonctions de fichiers spéciaux 373
23 Compilation des programmes : Le préprocesseur C 391
24 Quelle est la suite des événements ? 409

Partie VI Annexes 437


Annexe Fichiers d'en-tête de la norme AANSI 439
BAnswers to Quiz Questions and Exercises (en anglais) 441
Index 503
Table des matières
Introduction 1
Qui devrait lire ce livre ?..........................................................................................1
Caractéristiques de ce livre.......................................................................................1
Exemples de programmation....................................................................................2
Questions-réponses et atelier....................................................................................4
Conventions utilisées dans ce livre ..........................................................................4
Ce que vous apprendrez en 24 heures ......................................................................4

Partie I Les bases du langage C 9


Heure 1 Faire le premier pas 11
Qu'est-ce que C ?....................................................................................................12
La norme C de l'ANSI............................................................................................15
Hypothèses sur vous................................................................................................16
Configuration du système ......................................................................................16
Matériel .............................................................................................................16
Logiciels............................................................................................................16
Exemple de configuration de programmation en C...........................................17
Utiliser le compilateur de Microsoft .................................................................18
Utilisation du compilateur de Borland ..............................................................21
Résumé...................................................................................................................24
Q&R .......................................................................................................................25
Atelier.....................................................................................................................25
Quiz...................................................................................................................25

Heure 2 Écrire votre premier programme en C 27


Un programme C simple ........................................................................................28
Commentaires.........................................................................................................29
La directive #include .............................................................................................31
Fichiers d'en-tête ...............................................................................................32
Crochets d'angle (< >) et guillemets (" ") ......................................................32
La fonction main() .................................................................................................33
Le caractère de nouvelle ligne (\n)...................................................................33
La déclaration de retour ...................................................................................34
La fonction exit() ............................................................................................34
Compilation et liaison ............................................................................................34
Qu'est-ce qui ne va pas avec mon programme ? ....................................................36
Débogage du programme........................................................................................37
vi Sams Teach Yourself C en 24
heures

Résumé ...................................................................................................................37
Q&R .......................................................................................................................38
Atelier.....................................................................................................................38
Quiz...................................................................................................................38
Exercices ...........................................................................................................39
Heure 3 Apprendre la structure d'un programme C 41
Les bases d'un programme C ..................................................................................42
Constantes et variables......................................................................................42
Expressions........................................................................................................42
Déclarations.......................................................................................................45
Blocs de déclaration ..........................................................................................45
Anatomie d'une fonction C.....................................................................................46
Déterminer le type d'une fonction .....................................................................46
Donner un nom valide à une fonction ...............................................................47
Passer des arguments aux fonctions C ..............................................................47
Le début et la fin d'une fonction........................................................................48
Le corps de fonction ..........................................................................................48
Effectuer des appels de fonction.............................................................................49
Résumé ...................................................................................................................51
Q&R .......................................................................................................................52
Atelier.....................................................................................................................52
Quiz...................................................................................................................52
Exercices ...........................................................................................................53

Heure 4 Comprendre les types de données et les mots-clés 55


C Mots-clés.............................................................................................................56
Le type de données char .........................................................................................57
Variables de caractère .......................................................................................58
Constantes de caractères....................................................................................58
Le personnage de l'évasion (\)...........................................................................59
Caractères d'impression.....................................................................................60
Le type de données int ...........................................................................................62
Déclaration de variables entières.......................................................................62
Afficher les valeurs numériques des caractères ................................................63
Le type de données float .......................................................................................64
Déclarer des variables à virgule flottante..........................................................64
Le spécificateur de format en virgule flottante (%f)..........................................65
Le type de données double .....................................................................................67
Utiliser la notation scientifique ..............................................................................67
Nommer une variable. ............................................................................................68
Résumé ...................................................................................................................68
Q&R .......................................................................................................................68
Contenu vii

Atelier.....................................................................................................................69
Quiz...................................................................................................................69
Exercices ...........................................................................................................70

Heure 5 Manipulation des entrées et sorties standard 71


Comprendre les E/S standard .................................................................................72
Obtenir l'avis de l'utilisateur....................................................................................72
Utilisation de la fonction getc() .......................................................................72
Utilisation de la fonction getchar() .................................................................74
Impression des résultats à l'écran ...........................................................................75
Utilisation de la fonction putc() .......................................................................75
Une autre fonction pour écrire : putchar() .....................................................................77
Réexamen de la fonction printf() ........................................................................78
Conversion en nombres hexadécimaux.............................................................79
Spécification de la largeur de champ minimale ................................................81
Alignement de la production .............................................................................83
Utilisation du spécificateur de précision ...........................................................84
Résumé ...................................................................................................................85
Q&R .......................................................................................................................86
Atelier.....................................................................................................................86
Quiz...................................................................................................................87
Exercices ...........................................................................................................87

Partie II Opérateurs et déclarations de flux de contrôle 89


Heure 6 Manipulation des données 91
Opérateurs d'affectation arithmétique.....................................................................92
L'opérateur d'affectation (=) ..............................................................................92
Combinaison d'opérateurs arithmétiques avec =............................................................92
Obtenir des négations de valeurs numériques ........................................................95
Incrémenter ou décrémenter de un .........................................................................96
Plus grand que ou moins grand que ? .....................................................................98
Utilisation de l'opérateur de fonte.........................................................................101
Résumé .................................................................................................................102
Q&R .....................................................................................................................102
Atelier ...................................................................................................................103
Quiz .................................................................................................................103
Exercices .........................................................................................................103
Heure 7 Travailler avec des boucles 105
La boucle while ....................................................................................................106
La boucle "do-while .............................................................................................107
Boucle sous l'instruction for ................................................................................109
viii Sams Teach Yourself C en 24
heures

L'affirmation nulle ................................................................................................112


Utilisation d'expressions complexes dans une instruction for ........................113
Utilisation de boucles imbriquées.........................................................................116
Résumé .................................................................................................................118
Q&A .....................................................................................................................118
Atelier ...................................................................................................................119
Quiz .................................................................................................................119
Exercices .........................................................................................................120

Heure 8 Utilisation des opérateurs conditionnels 121


Mesure de la taille des données ............................................................................122
Tout est logique ....................................................................................................124
L'opérateur logique ET (&&) ...........................................................................124
L'opérateur logique OR (||)............................................................................126
L'opérateur logique NEGATION ( !) .............................................................128
Manipulation des bits............................................................................................129
Conversion du décimal en hexadécimal ou en binaire....................................129
Utilisation des opérateurs bitwise....................................................................130
Utilisation des opérateurs Shift .......................................................................133
Que signifie x?y:z ?.............................................................................................135
Résumé .................................................................................................................137
Q&R .....................................................................................................................137
Atelier ...................................................................................................................138
Quiz .................................................................................................................138
Exercices .........................................................................................................138

Heure 9 Travailler avec des modificateurs de données et des fonctions mathématiques 141
Activation ou désactivation du bit de signe ..........................................................142
Le modificateur signé......................................................................................142
Le modificateur non signé ..............................................................................143
Modification de la taille des données ...................................................................145
Le modificateur court .....................................................................................145
Le modificateur long .......................................................................................145
Ajouter h, l ou L aux spécificateurs de format printf et fprintf................147
Fonctions mathématiques en C.............................................................................148
Appel de sin(), cos() et tan() ........................................................................................149
Appeler pow() et sqrt().......................................................................................................150
Résumé .................................................................................................................152
Q&R .....................................................................................................................153
Atelier ...................................................................................................................154
Quiz .................................................................................................................154
Exercices .........................................................................................................154
Contenu ix

Heure 10 Contrôle du déroulement du programme 155


Toujours dire "si..." ...........................................................................................156
L'énoncé if-else ............................................................................................158
Déclarations "if" imbriquées .........................................................................160
L'interrupteur Déclaration ................................................................................161
La déclaration de rupture .....................................................................................164
Rompre une boucle infinie....................................................................................166
La déclaration continue .......................................................................................167
La déclaration goto ...............................................................................................168
Résumé .................................................................................................................170
Q&R .....................................................................................................................170
Atelier ...................................................................................................................171
Quiz .................................................................................................................171
Exercices .........................................................................................................172

Partie III Pointeurs et tableaux 173


Heure 11 Comprendre les pointeurs 175
Qu'est-ce qu'un pointeur ?.....................................................................................176
Adresse (valeur de gauche) par rapport au contenu (valeur de droite) ...................176
L'opérateur d'adresse (&) ................................................................................177
Déclaration des pointeurs .....................................................................................179
L'opérateur de déréférence (*).........................................................................182
Pointeurs nuls ..................................................................................................183
Mise à jour des variables via des pointeurs ..........................................................183
Pointer vers le même emplacement mémoire .......................................................184
Résumé .................................................................................................................186
Q&R .....................................................................................................................187
Atelier ...................................................................................................................188
Quiz .................................................................................................................188
Exercices .........................................................................................................188
Heure 12 Comprendre les tableaux 189
Qu'est-ce qu'un tableau ? ......................................................................................190
Déclarer des tableaux ......................................................................................190
Indexation des tableaux...................................................................................190
Initialisation des tableaux................................................................................191
La taille d'un tableau.............................................................................................192
Tableaux et pointeurs............................................................................................194
Affichage de tableaux de caractères................................................................196
Le caractère nul ("\0")....................................................................................198
Tableaux multidimensionnels...............................................................................199
x Sams Teach Yourself C en 24
heures

Tableaux non dimensionnés .................................................................................201


Résumé .................................................................................................................203
Q&R .....................................................................................................................203
Atelier ...................................................................................................................204
Quiz .................................................................................................................204
Exercices .........................................................................................................205

Heure 13 Manipuler des chaînes de caractères 207


Déclarer des chaînes de caractères........................................................................208
Qu'est-ce qu'une chaîne de caractères ? ...........................................................208
Initialisation des chaînes de caractères ............................................................208
Constantes de chaîne et constantes de caractère .............................................209
Quelle est la longueur d'une corde ?.....................................................................212
La fonction strlen() ......................................................................................212
Copier des chaînes de caractères avec strcpy() .........................................................213
Lire et écrire des chaînes de caractères .................................................................215
Les fonctions gets() et puts() ......................................................................215
Utilisation de %s avec la fonction printf() ...................................................217
La fonction scanf() ........................................................................................217
Résumé .................................................................................................................219
Q&A .....................................................................................................................220
Atelier ...................................................................................................................221
Quiz .................................................................................................................221
Exercices .........................................................................................................221

Heure 14 Comprendre la portée et les classes de stockage 223


Cacher des données...............................................................................................224
Bloc Scope ......................................................................................................224
Portée des blocs imbriqués ..............................................................................225
Champ d'application de la fonction..................................................................226
Champ d'application du programme ................................................................227
Les spécificateurs de classe de stockage...............................................................229
Le spécificateur automatique ..........................................................................229
Le spécificateur statique................................................................................230
Champ d'application des fichiers et hiérarchie des champs d'application .......232
Le spécificateur de registre ...........................................................................233
Le spécificateur extern ...................................................................................233
Les modificateurs de classe de stockage...............................................................234
Le modificateur const .....................................................................................234
Le modificateur volatil .................................................................................235
Résumé .................................................................................................................236
Q&R .....................................................................................................................237
Contenu xi

Atelier ...................................................................................................................238
Quiz .................................................................................................................238
Exercices .........................................................................................................239

Partie IV Fonctions et allocation dynamique de la mémoire 241


Heure 15 Travailler avec des fonctions 243
Déclarer des fonctions ..........................................................................................244
Déclaration versus définition ...........................................................................244
Spécification des types de retour.....................................................................244
Utilisation des prototypes................................................................................245
Effectuer des appels de fonction .....................................................................245
Fonctions de prototypage......................................................................................247
Fonctions sans arguments................................................................................248
Utilisation de time(), localtime() et asctime()......................................................249
Fonctions avec un nombre fixe d'arguments ...................................................251
Prototypage d'un nombre variable d'arguments ..............................................251
Traitement des arguments de variables ............................................................252
Apprendre la programmation structurée ...............................................................255
Résumé .................................................................................................................255
Q&R .....................................................................................................................256
Atelier ...................................................................................................................257
Quiz .................................................................................................................257
Exercices .........................................................................................................257
Heure 16 Application de pointeurs 259
Arithmétique des pointeurs ...................................................................................259
La taille scalaire des pointeurs ........................................................................260
Soustraction de pointeurs.................................................................................263
Pointeurs et tableaux.............................................................................................264
Accès aux tableaux par le biais de pointeurs...................................................264
Pointeurs et fonctions ...........................................................................................266
Transmettre des tableaux à des fonctions........................................................266
Passer des pointeurs à des fonctions ...............................................................268
Passer des tableaux multidimensionnels comme arguments...........................270
Tableaux de pointeurs...........................................................................................272
Pointer vers des fonctions.....................................................................................274
Résumé .................................................................................................................276
Q&R .....................................................................................................................276
Atelier ...................................................................................................................277
Quiz .................................................................................................................277
Exercices .........................................................................................................278
xii Sams Teach Yourself C en 24
heures

Heure 17 Allocation de la mémoire 279


Allocation de mémoire à l'exécution ....................................................................280
La fonction malloc() ............................................................................................280
Libération de la mémoire allouée avec free() ...................................................................283
La fonction calloc() ............................................................................................286
La fonction realloc() ..........................................................................................288
Résumé .................................................................................................................291
Q&R .....................................................................................................................292
Atelier ...................................................................................................................293
Quiz .................................................................................................................293
Exercices .........................................................................................................294

Heure 18 Utilisation de types de données et de fonctions spéciales 295


L'enum Type de données .......................................................................................296
Déclaration du type de données enum ..............................................................296
Attribution de valeurs aux noms des enums.......................................................296
Créer des définitions typées .................................................................................300
Pourquoi utiliser un typedef ?.............................................................................300
Fonctions récursives .............................................................................................303
Réexamen de la fonction main(). ........................................................................305
Arguments de la ligne de commande ..............................................................305
Réception des arguments de la ligne de commande........................................306
Résumé .................................................................................................................308
Q&R .....................................................................................................................308
Atelier ...................................................................................................................309
Quiz .................................................................................................................309
Exercices .........................................................................................................310

Partie V Structure, Union, E/S de fichiers, et plus encore 311


Heure 19 Comprendre les structures 313
Qu'est-ce qu'une structure ? ..................................................................................314
Déclaration des structures ...............................................................................314
Définition des variables de structure ...............................................................315
Référencement des membres d'une structure à l'aide de l'opérateur point ...........315
Initialisation des structures ...................................................................................317
Structures et appels de fonctions ..........................................................................319
Référencement de structures avec des pointeurs.............................................322
Référencement d'un membre de structure avec ->......................................................324
Tableaux de structures ..........................................................................................324
Structures imbriquées ......................................................................................327
Résumé .................................................................................................................330
Contenu xiii

Q&R .....................................................................................................................330
Atelier ...................................................................................................................331
Quiz .................................................................................................................331
Exercices .........................................................................................................332
Heure 20 Comprendre les syndicats 333
Qu'est-ce qu'un syndicat ?.....................................................................................334
Déclarer les syndicats......................................................................................334
Définition des variables de l'Union .................................................................334
Référencement d'une union avec . ou ->.......................................................................335
Syndicats et structures ..........................................................................................337
Initialisation d'une union.................................................................................337
La taille d'une union........................................................................................339
Utilisation des syndicats .......................................................................................341
Référencer différemment le même emplacement mémoire.............................341
Rendre les structures flexibles.........................................................................343
Définir des champs de bits avec struct .........................................................................347
Résumé .................................................................................................................350
Q&R .....................................................................................................................351
Atelier ...................................................................................................................352
Quiz .................................................................................................................352
Exercices .........................................................................................................353

Heure 21 Lire et écrire avec des fichiers 355


Fichiers et flux ......................................................................................................356
Qu'est-ce qu'un fichier ? ..................................................................................356
Qu'est-ce qu'un cours d'eau ?...........................................................................356
E/S tamponnées ...............................................................................................356
Les bases des entrées/sorties de fichiers sur disque ...............................................357
Pointeurs de FILE............................................................................................357
Ouverture d'un fichier......................................................................................357
Fermeture d'un fichier......................................................................................358
Lecture et écriture de fichiers sur disque..............................................................360
Un personnage à la fois ...................................................................................360
Une ligne à la fois ...........................................................................................363
Un bloc à la fois ..............................................................................................366
Résumé .................................................................................................................370
Q&R .....................................................................................................................370
Atelier ...................................................................................................................371
Quiz .................................................................................................................371
Exercices .........................................................................................................372
xiv Sams Teach Yourself C en 24
heures

Heure 22 Utilisation des fonctions de fichiers spéciaux 373


Accès aléatoire aux fichiers du disque..................................................................374
Les fonctions fseek() et ftell() ..................................................................374
La fonction rewind() ......................................................................................378
Autres exemples d'E/S de fichiers disque .............................................................378
Lecture et écriture de données binaires ...........................................................378
Les fonctions fscanf() et fprintf() .............................................................381
Redirection des flux standards avec freopen() ...........................................................384
Résumé .................................................................................................................387
Q&R .....................................................................................................................387
Atelier ...................................................................................................................388
Quiz .................................................................................................................388
Exercices .........................................................................................................389

Heure 23 Compilation des programmes : Le préprocesseur C 391


Qu'est-ce que le préprocesseur C ? .......................................................................392
Le préprocesseur C et le compilateur ...................................................................392
Les directives #define et #undef .........................................................................393
Définition de macros de type fonction avec #define ................................................394
Définitions de macros imbriquées ...................................................................396
Compiler votre code sous conditions....................................................................397
Les directives #ifdef et #endif......................................................................397
La directive #ifndef ........................................................................................397
Les directives #if, #elif et #else ..................................................................399
Compilation conditionnelle imbriquée ............................................................402
Résumé .................................................................................................................405
Q&R .....................................................................................................................405
Atelier ...................................................................................................................406
Quiz .................................................................................................................406
Exercices .........................................................................................................407

Heure 24 : Quelle est la suite des événements ? 409


Création d'une liste chaînée ..................................................................................410
Style de programmation........................................................................................418
Programmation modulaire ....................................................................................419
Débogage ..............................................................................................................420
Ce que vous avez appris .......................................................................................420
C Mots-clés .....................................................................................................420
Opérateurs .......................................................................................................421
Constantes .......................................................................................................422
Types de données ............................................................................................423
Expressions et déclarations..............................................................................426
Déclarations de flux de contrôle ......................................................................426
Contenu xv

Pointeurs..........................................................................................................430
Fonctions .........................................................................................................432
Entrées et sorties (I/O) ....................................................................................433
Le préprocesseur C ..........................................................................................434
La route à suivre... ................................................................................................434
Résumé .................................................................................................................435

Partie VI Annexes 437


Annexe A Fichiers d'en-tête standard ANSI 439

Annexe B Réponses aux questions du quiz et aux exercices 441


Heure 1, "Faire le premier pas" (en anglais).........................................................441
Quiz .................................................................................................................441
Heure 2, "Écrire votre premier programme en C" ................................................442
Quiz .................................................................................................................442
Exercices .........................................................................................................442
Heure 3, "Apprendre la structure d'un programme C" .........................................443
Quiz .................................................................................................................443
Exercices .........................................................................................................444
Heure 4, "Comprendre les types de données et les mots-clés".............................445
Quiz .................................................................................................................445
Exercices .........................................................................................................445
Heure 5, "Manipuler les entrées et sorties standard"............................................447
Quiz .................................................................................................................447
Exercices .........................................................................................................447
Heure 6, "Manipuler les données"........................................................................449
Quiz .................................................................................................................449
Exercices .........................................................................................................449
Heure 7, "Travailler avec des boucles".................................................................451
Quiz .................................................................................................................451
Exercices .........................................................................................................451
Heure 8, "Utiliser les opérateurs conditionnels"...................................................453
Quiz .................................................................................................................453
Exercices .........................................................................................................453
Heure 9, "Travailler avec des modificateurs de données et des fonctions mathématiques" 455
Quiz .................................................................................................................455
Exercices .........................................................................................................456
Heure 10, "Contrôler le déroulement du programme"..........................................458
Quiz .................................................................................................................458
Exercices .........................................................................................................458
xvi Sams Teach Yourself C en 24
heures

Heure 11, "Comprendre les pointeurs".................................................................460


Quiz .................................................................................................................460
Exercices .........................................................................................................461
Heure 12, "Comprendre les tableaux" ..................................................................462
Quiz .................................................................................................................462
Exercices .........................................................................................................463
Heure 13, "Manipuler des chaînes de caractères" .................................................465
Quiz .................................................................................................................465
Exercices .........................................................................................................466
Heure 14, "Comprendre la portée et les classes de stockage" ..............................467
Quiz .................................................................................................................467
Exercices .........................................................................................................468
Heure 15, "Travailler avec des fonctions"............................................................470
Quiz .................................................................................................................470
Exercices .........................................................................................................470
Heure 16, "Appliquer les pointeurs".....................................................................473
Quiz .................................................................................................................473
Exercices .........................................................................................................474
Heure 17, "Allocation de mémoire" .....................................................................476
Quiz .................................................................................................................476
Exercices .........................................................................................................476
Heure 18, "Utiliser des types de données et des fonctions spéciales" ..................480
Quiz .................................................................................................................480
Exercices .........................................................................................................480
Heure 19, "Comprendre les structures" ................................................................482
Quiz .................................................................................................................482
Exercices .........................................................................................................482
Heure 20, "Comprendre les syndicats".................................................................486
Quiz .................................................................................................................486
Exercices .........................................................................................................486
Heure 21, "Lire et écrire avec des fichiers"..........................................................490
Quiz .................................................................................................................490
Exercices .........................................................................................................490
Heure 22, "Utiliser les fonctions de fichiers spéciaux" ........................................494
Quiz .................................................................................................................494
Exercices .........................................................................................................494
Heure 23, "Compiler des programmes : Le préprocesseur C"..............................499
Quiz .................................................................................................................499
Exercices .........................................................................................................500
Index 503
A propos de l'auteur
TONY ZHANG A PLUS DE 15 ans d'expérience dans la programmation informatique et la
conception de systèmes d'information à l'échelle de l'entreprise. Il travaille
actuellement pour l'un des "big 5" cabinets de conseil qui se concentre sur la
conception, le développement et la mise en œuvre d'infrastructures liées au commerce
électronique.
Titulaire d'une maîtrise en physique, il a publié des dizaines d'articles de recherche sur les
lasers et la programmation informatique. Parmi ses nombreux centres d'intérêt figurent la
peinture à l'huile et la photographie, les deux choses que Tony aime le plus.
Vous pouvez contacter Tony par l'intermédiaire de Sams Publishing ou en lui envoyant un courriel à l'adresse
suivante
tyc24h@hotmail.com.

À propos de l'auteur de la contribution


JOHN SOUTHMAYD est un ingénieur concepteur de logiciels qui a acquis de l'expérience
dans des domaines allant de la programmation au niveau des systèmes et des pilotes de
périphériques au développement de Windows et aux technologies Internet. Il travaille
actuellement comme consultant chez Excell Data Corporation et vit avec sa femme à
Kirkland, dans l'État de Washington.
Dédicace
À ma femme, Ellen, et à mes parents, Zhi-ying et Bing-rong, pour leur amour et leur inspiration.

-Tony Zhang

Remerciements
Tout d'abord, j'aimerais remercier les lecteurs de la première édition de ce livre pour leurs
encouragements, leur patience, leurs commentaires et surtout leurs critiques, qui ont
permis de rendre la deuxième édition plus adaptée aux personnes qui veulent se lancer
dans le monde de la programmation en C.
J'ai le grand plaisir de travailler pour la deuxième fois avec l'éditrice Sharon Cox. Je tiens
à remercier les éditeurs Carol Ackerman et Gus Miklos, ainsi que l'auteur collaborateur
John Southmayd, pour leur excellent travail qui a rendu la deuxième édition du livre plus
accessible et largement, voire totalement, exempte d'erreurs. Je tiens également à
exprimer ma gratitude aux autres membres de l'équipe de rédaction pour leur excellent
travail. Ensemble, ils ont rendu cette deuxième édition possible.
J'apprécie énormément l'amour et le soutien de ma femme, Ellen, qui m'incite à regarder
le monde de la technologie sous des angles différents. C'est toujours une grande joie de
discuter avec elle de questions de philosophie et de littérature. Mes parents, que je ne
remercierai jamais assez, m'ont donné non seulement de l'amour et de l'affection, mais
aussi la possibilité de recevoir la meilleure éducation possible lorsque j'étais en Chine.
Dites-nous ce que vous en pensez !
En tant que lecteur de ce livre, vous êtes notre critique et commentateur le plus important.
Votre avis nous intéresse et nous voulons savoir ce que nous faisons de bien, ce que nous
pourrions améliorer, les domaines dans lesquels vous aimeriez nous voir publier, et toute
autre parole de sagesse que vous voudriez bien nous transmettre.
En tant qu'éditeur associé de Sams, vos commentaires sont les bienvenus. Vous pouvez
me faxer, m'envoyer un e-mail ou m'écrire directement pour me faire savoir ce que vous
avez aimé ou non dans ce livre, ainsi que ce que nous pouvons faire pour rendre nos
livres plus forts.
Veuillez noter que je ne peux pas vous aider à résoudre des problèmes techniques liés
au sujet de ce livre et qu'en raison du volume élevé de courrier que je reçois, il se peut
que je ne sois pas en mesure de répondre à tous les messages.
Lorsque vous écrivez, n'oubliez pas d'indiquer le titre et l'auteur de ce livre, ainsi que
votre nom et votre numéro de téléphone ou de télécopie. J'examinerai attentivement
vos commentaires et les transmettrai à l'auteur et aux éditeurs qui ont travaillé sur le
livre.
Fax : 317-581-4770
Courriel : michael.stephens@macmillanusa.com
Mail: Michael Stephens
Éditeur associé
Sams Publishing
201 West 103rd Street
Indianapolis, IN 46290 USA
Introduction
Si l'on apprend des autres mais que l'on ne pense pas, on sera
déconcerté ; si l'on pense mais que l'on n'apprend pas des autres, on
sera en péril.
-Confucius
Bienvenue dans la deuxième édition de Teach Yourself C in 24 Hours !
Sur la base du succès de la première édition et des réactions des lecteurs, nous avons
réécrit ou modifié chaque chapitre du livre afin de rendre la deuxième édition plus
adaptée aux débutants comme vous qui veulent s'initier au langage de programmation C
le plus rapidement possible.
Bien sûr, il est tout à fait normal de passer plus de 24 heures pour vraiment comprendre
les concepts et les compétences de programmation présentés dans le livre. Cependant,
la bonne nouvelle est que ce livre propose de nombreux exemples de programmes et
d'exercices avec des explications et des réponses claires, ce qui rend les concepts du
langage C plus faciles à comprendre.
En fait, Teach Yourself C in 24 Hours constitue un bon point de départ pour la pro-
grammation en C. Il couvre les sujets importants de la programmation en C et pose des
bases solides pour un débutant sérieux comme vous. Il couvre les sujets importants de la
programmation en C et pose des bases solides pour un débutant sérieux comme vous.
Après avoir lu ce livre, vous serez en mesure d'écrire des programmes C de base par
vous-même.
Vous tirerez profit de la lecture de ce livre lorsque vous commencerez à appliquer des
programmes C à des problèmes réels ou lorsque vous passerez à l'apprentissage d'autres
langages de programmation, tels que Perl, C++ et Java.

Qui devrait lire ce livre ?


Si vous apprenez le langage C pour la première fois, ce livre est fait pour vous. En fait, en
écrivant ce livre, je pars du principe que les lecteurs n'ont aucune expérience préalable de
la programmation. Bien sûr, c'est toujours un grand avantage si vous avez quelques
connaissances en informatique.

Particularités de ce livre
Ce livre contient les éléments spéciaux suivants, qui simplifient et clarifient
l'assimilation des fonctions et concepts rudimentaires du langage C au fur et à mesure
qu'ils sont présentés :
• Boîtes syntaxiques
• Notes
• Précautions
• Conseils
2 Sams Teach Yourself C en 24
heures

Les encadrés syntaxiques expliquent certaines des caractéristiques les plus complexes du
langage C, telles que les structures de contrôle. Chaque boîte syntaxique consiste en une
définition formelle de la fonctionnalité suivie d'une explication. Voici un exemple de
boîte syntaxique :
La syntaxe de la fonction malloc() est la suivante
#include <stdlib.h>
void *malloc(size_t size) ;

Ici, la taille spécifie le nombre d'octets de stockage à allouer. Le fichier d'en-tête


E
S

stdlib.h doit être inclus avant que la fonction malloc() puisse être appelée.
Comme la fonction malloc() renvoie un pointeur void, son type est automatiquement
E

converti en type de pointeur du côté gauche d'un opérateur d'affectation.


(Vous en apprendrez plus sur la fonction malloc() plus loin dans ce livre).
Les notes sont des explications sur les propriétés intéressantes d'une fonctionnalité
particulière du programme C. Examinons l'exemple de note suivant :

Dans une édition justifiée à gauche, la valeur affichée apparaît à


l'extrémité gauche du champ de valeurs. Dans une édition justifiée à
droite, la valeur affichée apparaît à l'extrémité droite du champ de
valeurs.

Les avertissements vous mettent en garde contre les pièges de la programmation que vous devez éviter.
Voici un avertissement typique :

N'utilisez jamais les mots-clés réservés en C, ni les noms des fonctions de la


bibliothèque C comme noms de variables dans votre programme.

Les astuces sont des conseils sur la manière d'améliorer l'écriture de vos programmes
C. Voici un exemple de conseil :

Si vous avez un projet de programmation complexe, divisez-le en petits


morceaux. Essayez de faire en sorte qu'une fonction ne fasse qu'une
seule chose et qu'elle la fasse très bien.

Exemples de programmation
Comme indiqué précédemment, ce livre contient de nombreux exemples de
programmation utiles accompagnés d'explications. Ces exemples ont pour but de vous
Introduction 3
montrer comment utiliser les différents types de données et les fonctions fournies par le
langage C.
4 Sams Teach Yourself C en 24
heures

Chaque exemple comporte une liste du programme C ; la sortie générée à partir de cette
liste suit. L'exemple propose également une analyse du fonctionnement du programme.
Des icônes spéciales sont utilisées pour indiquer chaque partie de l'exemple : Type,
Entrée/Sortie et Analyse.
Dans l'exemple présenté dans le Listing IN.1, il existe des conventions typographiques
particulières. Les données que vous saisissez sont affichées en caractères gras et la
sortie générée par le programme exécutable de la liste IN.1 est affichée en caractères
gras.

TYPE LISTING IN.1 Lecture d'un caractère saisi par l'utilisateur


1 : /* INL01.c : Lire l'entrée en appelant
getc() */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int ch ;
7 :
8 : printf("Please type in one character:\n")
; 9: ch = getc(stdin) ;
10 : printf("Le caractère que vous venez d'entrer est :
%c\n", ch) ; 11: return 0 ;
12 : }

La sortie suivante s'affiche après la création et l'exécution du fichier exécutable


INL01.exe. L'utilisateur saisit le caractère H et le programme affiche ce qu'il a saisi.

Veuillez saisir un seul caractère :


H
Le caractère que vous venez de saisir est : H

Dans la ligne 2 du Listing IN.1, le fichier d'en-tête stdio.h est inclus à la fois pour la fonction getc()
ANALYSE
et printf() utilisées dans le programme. Les lignes 4 à 12 indiquent le nom et le
corps de la fonction main().
À la ligne 6, une variable entière ch est déclarée, qui est affectée à la valeur de retour de
la fonction getc() plus loin à la ligne 9. La ligne 8 imprime un message demandant à
l'utilisateur d'entrer un caractère au clavier. La fonction printf() de la ligne 8 utilise la
sortie standard par défaut stdout pour afficher les messages à l'écran.
À la ligne 9, l'entrée standard stdin est transmise à la fonction getc(), ce qui indique
que le flux de fichiers provient du clavier. Après que l'utilisateur a saisi un caractère, la
fonction getc() renvoie la valeur numérique (c'est-à-dire un nombre entier) du
caractère. Notez qu'à la ligne 9, la valeur numérique est affectée à la variable entière ch.
Introduction 5

À la ligne 10, le caractère saisi est affiché à l'écran à l'aide de la fonction printf().
Notez que le spécificateur de format de caractère %c est utilisé dans la fonction printf()
à la ligne 10.

Questions et réponses et atelier


Chaque heure (c'est-à-dire chaque chapitre) se termine par une section Q&R qui contient
les réponses aux questions les plus courantes relatives à la leçon du chapitre. La section
Q&R est suivie d'un atelier qui consiste en des questions de quiz et des exercices de
programmation. Les réponses à ces questions et les exemples de solutions pour les
exercices sont présentés dans l'annexe D, "Réponses aux questions et aux exercices".
Pour vous aider à consolider votre compréhension de chaque leçon, nous vous
encourageons à essayer de répondre aux questions du quiz et à terminer les exercices
proposés dans l'atelier.

Conventions utilisées dans ce livre


Ce livre utilise des caractères spéciaux pour vous aider à faire la différence entre le code
C et l'anglais normal, et pour identifier les concepts importants.
• Le code C proprement dit est composé dans une police monospaciale spéciale.
Cette police est utilisée dans les listes, les exemples d'entrées/sorties et les extraits
de code. Dans l'explication des fonctionnalités du langage C, les commandes, les
noms de fichiers, les instructions, les variables et tout le texte que vous voyez à
l'écran sont également typographiés dans cette police.
• Les commandes et tout ce que vous êtes censé saisir apparaissent dans une
police monospace en gras. Vous verrez cela principalement dans les sections
Entrée/Sortie des exemples.
• Les espaces réservés dans les descriptions syntaxiques apparaissent en italique
et en monospace. Remplacez l'espace réservé par le nom de fichier, le paramètre
ou l'élément qu'il représente.
• L'italique met en évidence les termes techniques lorsqu'ils apparaissent pour la
première fois dans le texte et est parfois utilisé pour souligner des points
importants.

Ce que vous apprendrez en 24 heures


Teach Yourself C in 24 Hours se compose de cinq parties. Dans la première partie, "Les
bases du C", vous apprendrez les bases du langage C. Voici un résumé de ce que vous
allez apprendre :
L'heure 1, "Faire le premier pas", vous présente le langage C, la norme ANSI et les
6 Sams Teach Yourself C en 24
heures
exigences logicielles et matérielles de base pour la programmation en C.
L'heure 2, "Votre premier programme C", montre la procédure complète
d'écriture, de compilation, d'édition de liens et d'exécution d'un programme C.
Introduction 7

L'heure 3, "Apprendre la structure d'un programme C", vous enseigne plusieurs


concepts importants, tels que les constantes, les variables, les expressions et les
instructions. L'anatomie d'une fonction est également introduite dans cette heure.
L'heure 4, "Comprendre les types de données et les mots-clés", dresse la liste de
tous les mots-clés C réservés. Quatre types de données, char, int, float et double,
sont présentés en détail. Les règles pour nommer une variable sont également
expliquées.
L'heure 5, "Manipuler les entrées et sorties standard", vous apprend à recevoir
des entrées du clavier et à imprimer des sorties à l'écran à l'aide d'un ensemble de
fonctions C, telles que getc(), getchar(), putc(), putchar() et printf().

La partie II, "Opérateurs et instructions de flux de contrôle", met l'accent sur les
opérateurs et les instructions de flux de contrôle en C. Voici un résumé de ce que vous
apprendrez :
L'heure 6, "Manipulation des données", vous apprend à utiliser les opérateurs
d'affectation arithmétique, l'opérateur unaire moins, les opérateurs
d'incrémentation/décrémentation, les opérateurs relationnels et l'opérateur cast.
L'heure 7, "Travailler avec des boucles", présente le bouclage (c'est-à-dire
l'itération) à l'aide des instructions for, while ou do-while.
L'heure 8, "Utiliser les opérateurs conditionnels", vous présente d'autres
opérateurs, tels que les opérateurs logiques, les opérateurs bitwise, l'opérateur
sizeof et l'opérateur ? :, qui sont fréquemment utilisés en C.
L'heure 9, "Travailler avec les modificateurs de données et les fonctions
mathématiques", décrit comment utiliser les modificateurs de données pour activer
ou désactiver le bit de signe, ou changer la taille d'un type de données. Plusieurs
fonctions mathématiques fournies par le langage C sont également présentées.
L'heure 10, "Contrôler le flux du programme", présente toutes les instructions de
flux de contrôle utilisées en C. Il s'agit des instructions if, if-else, switch, break,
continue et goto.

Les pointeurs et les tableaux sont traités dans la partie III, "Pointeurs et tableaux".
Voici un résumé de ce que vous apprendrez :
L'heure 11, "Comprendre les pointeurs", vous apprend à référencer des variables
à l'aide de pointeurs. Des concepts tels que la valeur de gauche et la valeur de
droite sont également introduits.
L'heure 12, "Comprendre les tableaux", explique comment déclarer et
initialiser les tableaux. La relation entre le tableau et le pointeur en C est
également abordée.
L'heure 13, "Manipuler des chaînes de caractères", est consacrée à la lecture et à
l'écriture de chaînes de caractères. Plusieurs fonctions de la bibliothèque C, telles
8 Sams Teach Yourself C en 24
que strlen(), strcpy()heures
, gets(), puts() et scanf(), sont introduites pour
manipuler les chaînes de caractères.
L'heure 14, "Comprendre la portée et les classes de stockage", présente la portée
du bloc, la portée de la fonction, la portée du programme et la portée du fichier. En
outre, les spécificateurs ou modi- fiers des classes de stockage, tels que auto, static,
register, extern, const et volatile, sont expliqués.
Introduction 9

La partie IV, "Fonctions et allocation dynamique de mémoire", est consacrée aux


fonctions et à l'allocation dynamique de mémoire en C. Voici un résumé de ce que vous
apprendrez :
L'heure 15, "Travailler avec des fonctions", décrit la déclaration et la définition des
fonctions en C. Le prototypage des fonctions est expliqué, ainsi que la spécification
du type de retour des fonctions.
L'heure 16, "Application des pointeurs", vous apprend à effectuer des opérations
arithmétiques avec des pointeurs, à accéder à des éléments de tableaux avec des
pointeurs et à passer des pointeurs à des fonctions.
L'heure 17, "Allocation de la mémoire", explique le concept d'allocation dynamique
de la mémoire. Les fonctions C, telles que malloc(), calloc(), realloc() et free(),
sont introduites dans le cadre de l'allocation dynamique de la mémoire.
L'heure 18, "Utilisation de types de données et de fonctions spéciales", présente le
type de données enum et l'utilisation de typedef. La récursivité des fonctions et les
arguments de ligne de commande de la fonction main() sont également enseignés
dans l'heure 18.

La partie V, "Structure, union, E/S de fichier et autres", traite des structures, des unions et
des E/S de fichier disque en C. Voici un résumé de ce que vous apprendrez :
L'heure 19, "Comprendre les structures", présente le type de données
structure. Vous apprenez à accéder aux membres d'une structure et à transmettre
des structures à des fonctions à l'aide de pointeurs. Les structures imbriquées et les
structures à référence directe sont également abordées dans cette heure.
L'heure 20, "Comprendre les unions", décrit le type de données union et la
différence entre union et structure. Les applications des unions sont démontrées
dans plusieurs exemples.
L'heure 21, "Lire et écrire avec des fichiers", explique les concepts de fichier et de
flux en C. Les bases de l'entrée et de la sortie de fichiers sur disque sont introduites
dans cette première partie. Les fonctions C suivantes, ainsi que plusieurs exemples,
sont présentées dans cette heure : fopen(), fclose(), fgetc(), fputc(), fgets(),
fputs(), fread(), fwrite(), et feof().
L'heure 22, "Utilisation des fonctions spéciales de fichiers", est la deuxième
partie des E/S de fichiers sur disque, dans laquelle les fonctions fseek(), ftell()
et rewind() sont présentées pour montrer comment elles peuvent vous aider à
obtenir un accès aléatoire aux fichiers sur disque. En outre, les fonctions
fscanf(), fprintf() et freopen() sont enseignées et invoquées dans des
exemples de programmes.
L'heure 23, "Compiler des programmes : Le préprocesseur C", décrit le rôle joué
par le préprocesseur C. Vous pouvez apprendre les directives du préprocesseur,
telles que #define, #undef, #ifdef, #endif, #ifndef, #if, #elis, et #else à travers
1 Sams Teach Yourself C en 24
0 heures
les exemples donnés dans cette heure.
Introduction 1
1

L'heure 24, intitulée "Et maintenant ?", résume les concepts et fonctionnalités
importants présentés dans ce livre. En outre, le style de programmation, la
programmation modulaire et le débogage sont brièvement expliqués. Une liste de
livres recommandés sur le langage C est fournie pour vous permettre de poursuivre
votre lecture.

Vous êtes maintenant prêt à vous lancer dans l'apprentissage du langage C, alors que le
monde est entré dans un nouveau millénaire. Amusez-vous bien en lisant ce livre, et
profitez de la programmation en C !
Tony Zhang
Downingtown, Pennsylvanie
Janvier 2000
PARTIE I
Les bases du langage C
Heure
1 Faire le premier pas
2 Votre premier programme C
3 Apprendre la structure d'un programme C
4 Comprendre les types de données et les mots-clés
5 Gestion des entrées standard et des sorties
HEURE 1
Faire le premier pas
Un voyage de mille lieues commence par un premier pas.
-Proverbe chinois
Les pensées élevées doivent être exprimées dans un langage élevé.
-Aristophane
Bienvenue dans Teach Yourself C in 24 Hours. Dans cette première leçon,
vous apprendrez ce qui suit :
• Ce qu'est C
• Pourquoi apprendre le langage C
• La norme ANSI
• Matériel et logiciels nécessaires à l'écriture et à l'exécution de programmes C
12 Heure
1

Qu'est-ce que C ?
C est un langage de programmation. Le langage C a été développé pour la première fois
en 1972 par Dennis Ritchie aux AT&T Bell Labs. Ritchie a appelé son nouveau langage
C simplement parce qu'il existait déjà un langage de programmation B. (En fait, le
langage B a conduit au développement du langage C.) (En fait, c'est le langage B qui a
conduit au développement du langage C).
Le C est un langage de programmation de haut niveau. En fait, le C est l'un des langages
de programmation généralistes les plus populaires.
Dans le monde informatique, plus un langage de programmation est éloigné de
l'architecture de l'ordinateur, plus le niveau du langage est élevé. On peut imaginer que
les langages de bas niveau sont des langages de machine que les ordinateurs
comprennent et exécutent directement. Les langages de programmation de haut niveau,
en revanche, sont plus proches de nos langages humains (voir figure 1.1).

FIGURE 1.1
Haut
Le spec- trum
linguistique. Si la ligne est
La langue humaine pas occupé, se
(par exemple, connecter à
l'anglais) l'Internet ; sinon,
attendre...

Si (ligne ! = occupée)
connecter
Le langage de (Internet) ;
programmation de haut autre
niveau (par exemple, C)
attendre (5)...

Le langage machine (c'est- 10001111101100


Faib
à-dire le code binaire) 01100111011000
le

Les langages de programmation de haut niveau, dont le C, présentent les avantages suivants :
• Lisibilité : Les programmes sont faciles à lire.
• Facilité de maintenance : Les programmes sont faciles à maintenir.
• Portabilité : Les programmes sont faciles à porter sur différentes plates-formes informatiques.
Faire le premier pas 13

La lisibilité et la maintenabilité du langage C bénéficient directement de sa relative


proximité avec les langues humaines, en particulier l'anglais.
1
Chaque langage de haut niveau a besoin d'un compilateur ou d'un interprète pour
traduire les instructions écrites dans le langage de programmation de haut niveau en un
langage machine qu'un ordinateur peut comprendre et exécuter. Pour un même langage
de programmation, différentes machines peuvent avoir besoin de compilateurs ou
d'interprètes différents. Par exemple, j'utilise le compilateur C de Microsoft pour
compiler les programmes C de ce livre sur mon ordinateur personnel (PC). Si je dois
exécuter les programmes C sur une station de travail basée sur UNIX, je dois utiliser un
autre type de compilateur C pour compiler ces programmes. Par conséquent, la
portabilité des programmes écrits en C est réalisée en recompilant les programmes avec
différents compilateurs pour différentes machines (voir figure 1.2).

FIGURE 1.2
Le
Portage de programmes program
écrits en C sur différents me C

types d'ordinateurs.

Compilat Compilat Compilat


eur A eur eur C
B

Le cerveau de l'ordinateur
Vous savez peut-être que le cerveau d'un ordinateur est l'unité centrale de traitement (UC).
Certains ordinateurs peuvent avoir plus d'une unité centrale à l'intérieur. Une unité centrale
de traitement comporte des millions de transistors qui utilisent des interrupteurs
électroniques. Les interrupteurs électroniques n'ont que deux états : éteint et allumé.
(Symboliquement, 0 et 1 sont utilisés pour représenter les deux états.) Par conséquent, un
ordinateur ne peut comprendre que des instructions composées de séries de 0 et de 1. En
d'autres termes, les instructions lisibles par une machine doivent être au format binaire.
14 Heure
1

Cependant, un programme informatique écrit dans un langage de haut niveau, tel que C,
Java ou Perl, n'est qu'un fichier texte, composé de caractères et de mots de type anglais.
Vous devez utiliser des programmes spéciaux, appelés compilateurs ou interprètes, pour
traduire un tel programme en un code lisible par la machine. En d'autres termes, le format
texte de toutes les instructions écrites dans un langage de haut niveau doit être converti en
format binaire. Le code obtenu après la traduction est appelé code binaire. Avant la
traduction, un programme au format texte est appelé code source.
La plus petite unité du code binaire s'appelle un bit (de binary digit), qui peut avoir une
valeur de 0 ou de 1. En général, huit bits constituent un octet, et un demi-octet (quatre

En outre, le langage C présente d'autres avantages. Les programmes écrits en C peuvent


être réutilisés. Vous pouvez enregistrer des parties de vos programmes C dans un fichier
de bibliothèque et les invoquer dans votre prochain projet de programmation en incluant
simplement le fichier de bibliothèque. De nombreuses tâches de programmation
courantes et utiles sont déjà implémentées dans les bibliothèques fournies avec les
compilateurs. En outre, les bibliothèques vous permettent d'exploiter facilement la
puissance et les fonctionnalités du système d'exploitation que vous utilisez. De plus
amples détails sur l'utilisation des fonctions de la bibliothèque C sont abordés dans le
reste de cet ouvrage.
Le C est un langage de programmation relativement petit, ce qui vous facilite la vie. Vous
n'avez pas besoin de vous souvenir de nombreux mots-clés ou commandes en C avant de
commencer à écrire des programmes en C pour résoudre des problèmes dans le monde
réel.
Pour ceux qui recherchent la rapidité tout en conservant la commodité et l'élégance d'un
langage de haut niveau, le langage C est probablement le meilleur choix. En fait, le
langage C vous permet de contrôler le matériel et les périphériques de l'ordinateur. C'est
pourquoi le langage C est parfois appelé le langage de programmation de haut niveau le
plus bas.
De nombreux autres langages de haut niveau ont été développés sur la base du C. Par
exemple, Perl est un langage de programmation populaire dans la conception du World
Wide Web (WWW) sur Internet. Perl emprunte en fait de nombreuses fonctionnalités au
langage C. Si vous comprenez le langage C, apprendre Perl est un jeu d'enfant. Un autre
exemple est le langage C++, qui est simplement une version étendue du C, bien que le
C++ facilite la programmation orientée objet. De même, l'apprentissage de Java devient
beaucoup plus facile si vous connaissez déjà le C.
Faire le premier pas 15

Il existe généralement deux types de langages de programmation : les


langages compilés et les langages interprétés.
Un compilateur est nécessaire pour traduire un programme écrit dans un
langage compilé en code compréhensible par la machine (c'est-à-dire en
code binaire) avant que vous puissiez exécuter le programme sur votre
machine. Une fois la traduction effectuée, le code binaire
16 Heure
1

Le code source peut être enregistré dans un fichier d'application. Vous


pouvez continuer à exécuter le fichier d'application sans compilateur, sauf si 1
le programme (code source) est mis à jour et que vous devez le recompiler.
Le code binaire ou le fichier d'application est également appelé code
exécutable (ou fichier exécutable).
En revanche, un programme écrit dans un langage interprété peut être exécuté
immédiatement après que vous avez fini de l'écrire - ou d'ailleurs, pendant que
vous l'écrivez ! Mais un tel programme a toujours besoin d'un interprète pour
traduire les instructions de haut niveau en instructions compréhensibles par la
machine (code binaire) au moment de l'exécution. Vous ne pouvez pas
exécuter le programme sur une machine si l'interprète approprié n'est pas
disponible.
On peut considérer le langage C comme un langage compilé car la plupart des
fournisseurs de langage C ne fabriquent que des compilateurs C, par
opposition aux interprètes, pour soutenir les programmes écrits en C.
Cependant, il n'y a rien d'inhérent à un langage compilé qui empêche
quelqu'un de fournir un interprète pour ce langage ; de même, les gens
peuvent écrire et écrivent souvent des compilateurs pour les langages
interprétés. En fait, il n'est pas rare de mélanger les deux types de
langages, lorsqu'un programmeur compile le code source dans un petit

La norme ANSI C
Pendant de nombreuses années, la norme de facto pour le langage de programmation C
était le livre The C Programming Language, écrit par Brian Kernighan et Dennis Ritchie
en 1978. Ce livre est communément connu dans la communauté des programmeurs sous
le nom de K&R (en référence aux initiales des auteurs) et trouve encore aujourd'hui sa
place sur les étagères de nombreux programmeurs. Cependant, le livre a été écrit comme
un tutoriel d'introduction au langage C, et non comme une norme exhaustive ou officielle
pour le langage. Au fur et à mesure que les différents fournisseurs proposaient des
implémentations différentes du langage C, des différences entre ces implémentations ont
commencé à apparaître.
Craignant que le langage C ne perde sa portabilité, un groupe de vendeurs de
compilateurs et de développeurs de logiciels a demandé à l'American National Standards
Institute (ANSI) d'élaborer une norme pour le langage C en 1983. L'ANSI a approuvé la
demande et a formé le comité technique X3J11 pour travailler sur la norme C. Fin 1989,
le comité a approuvé la norme ANSI pour le langage de programmation C.
La norme ANSI pour le langage C améliore la norme K&R originale et définit un groupe
de fonctions C couramment utilisées, connu sous le nom de bibliothèque standard ANSI
C. Dans la plupart des cas, les compilateurs C incluent la bibliothèque standard, ainsi que
d'autres bibliothèques qui fournissent d'autres fonctions spécifiques au compilateur.
Faire le premier pas 17

Ce livre se concentre sur les fonctions C définies dans la norme ANSI, qui est prise en
charge par tous les fournisseurs de compilateurs. Tous les programmes de ce livre
peuvent être compilés par n'importe quel compilateur conforme à la norme ANSI. Si
vous êtes intéressé par un compilateur spécifique, vous pouvez apprendre les fonctions
qui lui sont propres en consultant son manuel de référence.

Hypothèses sur vous


Aucune expérience préalable en programmation n'est requise pour apprendre le langage C
à partir de ce livre, bien qu'une certaine connaissance de l'informatique soit utile. De plus,
c'est à vous de déterminer à quelle vitesse vous allez parcourir les 24 heures de ce livre :
Vous pouvez vous asseoir avec une grande cafetière et lire le livre d'une traite ou vous
pouvez prendre une heure par jour pendant 24 jours.
Lorsque vous aurez terminé ce livre, après avoir fait tous les exercices, vous devriez
maîtriser et être à l'aise avec la syntaxe et les caractéristiques du langage C. En outre,
vous aurez déjà une certaine expérience de la plupart des tâches rencontrées dans la
programmation en C. En outre, vous aurez déjà une certaine expérience de la plupart des
tâches rencontrées dans la programmation en C. Lorsque vous serez prêt à entreprendre
vos propres projets de programmation, vous serez en mesure d'utiliser le langage C
comme outil pour écrire les programmes puissants et utiles que vous souhaitez créer. Au
fur et à mesure de votre progression, vous constaterez qu'il y a toujours plus à apprendre,
non seulement sur le langage C et sur la façon d'exploiter sa puissance, mais aussi sur les
nouvelles technologies et les idées de programmation en général. Avec du travail et
beaucoup de pratique, vous pouvez rapidement développer les compétences et les
technologies que vous apprenez.

Configuration du système
En principe, tout ce dont vous avez besoin, c'est d'un ordinateur et d'un compilateur C
afin de compiler et d'exécuter vos propres programmes C ou les programmes C de ce
livre. Le matériel et les logiciels recommandés sont énumérés dans les sections suivantes.

Matériel
Tout type d'ordinateur disposant d'un compilateur C ou pouvant y accéder convient. Le
compilateur C doit être conforme à la norme ANSI C. Il est fort probable que vous
disposiez d'un PC sur votre bureau. Un PC 286 avec un disque dur de 50 Mo et 1 Mo de
mémoire (RAM) est probablement le minimum requis pour faire fonctionner un
compilateur C basé sur DOS. Pour un compilateur C basé sur Windows, votre ordinateur
doit disposer d'un disque dur plus important et de plus de mémoire. Consultez le
fournisseur de votre compilateur pour plus de détails sur les exigences matérielles.
18 Heure
1
Logiciel
Si vous utilisez une station de travail basée sur UNIX, il se peut que vous ayez déjà un
compilateur C chargé sur votre machine, ou du moins que vous puissiez accéder à un
compilateur C sur une machine serveur. Consultez votre administrateur système pour
savoir comment accéder à un compilateur ANSI C
Faire le premier pas 19

compilateur conforme à la norme C. Sur une machine UNIX, vous devez savoir comment
utiliser un éditeur de texte tel que vi ou emacs pour écrire des programmes C.
1
Si vous disposez d'un PC équipé d'un système d'exploitation Microsoft Windows (tel que
Windows 95), vous devez installer un compilateur C et un éditeur de texte sur votre PC.
Cependant, la plupart des compilateurs C sont livrés avec un éditeur intégré. Vous
pouvez également utiliser n'importe quel éditeur de texte déjà installé sur votre machine.
Turbo C de Borland International et Quick C de Microsoft étaient autrefois très
populaires sur le marché des compilateurs C. De nos jours, un compilateur C conforme à
la norme ANSI fait généralement partie de tout progiciel de développement C++
disponible dans le commerce, tel que Microsoft Visual C++. En outre, les progiciels de
développement sont livrés avec un environnement de développement intégré (IDE), que
vous pouvez utiliser pour éditer, compiler, exécuter et déboguer vos programmes C à
partir de la même fenêtre.
Vous pouvez choisir n'importe quel compilateur C pour compiler les exemples de code
donnés dans le livre, à condition que le compilateur soit compatible avec le langage C
ANSI. Vous ne devriez pas avoir de problèmes pour installer un compilateur C sur votre
ordinateur si vous lisez les manuels fournis avec le compilateur et suivez correctement
les instructions d'installation. La plupart des compilateurs C et/ou C++ fournissent un
tutoriel rapide qui vous montre comment installer le compilateur et mettre en place un
environnement de développement fonctionnel sur votre ordinateur.
De nos jours, le système d'exploitation Linux est de plus en plus populaire parmi les
utilisateurs de PC. Dans la plupart des cas, le paquetage Linux que vous recevez contient
un compilateur C. Le compilateur C peut être installé sur votre PC lors de l'installation du
système d'exploitation Linux. Le compilateur C peut être installé sur votre PC lors de
l'installation du système d'exploitation Linux, ou peut être ajouté plus tard, une fois
l'installation de Linux terminée.

Exemple de configuration de programmation en C


J'ai un PC Pentium 100MHz avec 32MB de mémoire et un disque dur de 2,5GB. (Le
disque dur avait environ 1,5 Go d'espace libre avant que je n'installe une copie de
Microsoft Visual C++ 5.0). J'ai également Windows 95 comme système d'exploitation sur
la machine.
Dans ce livre, tous les programmes C sont développés avec Microsoft Visual C++
version 5.0. Les raisons pour lesquelles j'ai choisi Visual C++ sont simples : Tous les
programmes C de ce livre sont écrits en C ANSI et peuvent être compilés dans des
applications en mode console (c'est-à-dire des programmes textuels s'exécutant dans une
fenêtre DOS) ; le paquetage Visual C++ 5.0 comprend un bon compilateur C conforme à
la norme C ANSI.
J'ai configuré mon environnement de développement de manière à ce que tous les
20 Heure
1 et transformés en applications de
programmes C de ce livre puissent être compilés
console. En outre, je teste et j'exécute les applications créées à partir des programmes C à
l'invite DOS fournie par Windows 95.
Dans les deux sections suivantes, je vous montrerai brièvement comment utiliser les
compilateurs C de Microsoft et de Boralnd.
Faire le premier pas 21

Utilisation du compilateur de Microsoft


Dans cette section, je vais vous montrer comment utiliser le compilateur C qui est
fourni avec le paquet Visual C++ de Microsoft. Si vous souhaitez obtenir plus de
détails sur l'installation de Visual C++, veuillez suivre les instructions fournies avec le
compilateur.
Je suppose que vous avez installé une copie de Visual C++ 5.0 sur votre ordinateur.
Pour lancer le compilateur, vous pouvez cliquer sur le bouton Démarrer de votre
Windows 95 (ou 98 ou NT), et choisir : Programmes, Microsoft Visual C++ 5.0,
Microsoft Visual C++ 5.0. Vous pouvez également exécuter le fichier d'application
MSDEV.EXE directement à partir du répertoire (dossier) où vous avez installé le progiciel
Visual C++. La figure 1.3 montre un exemple de l'environnement de développement
intégré (IDE) de Visual C++ 5.0.

FIGURE 1.3
Exemple de l'IDE de la
version 5.0 de Visual
C++.

Ensuite, vous pouvez ouvrir un nouveau fichier dans l'IDE en cliquant sur le bouton
Nouveau fichier à l'extrême gauche de la barre d'outils, et taper le texte suivant dans
l'espace du nouveau fichier :
#include <stdio.h>

main()
{
printf ("Howdy, neighbor ! This is my first C program.\n")
; return 0 ;
}
22 Heure
1

La figure 1.4 montre l'IDE avec le texte que vous venez de taper. Ne vous inquiétez pas
de la signification du texte. Le chapitre suivant, "Votre premier programme C", vous
l'expliquera.
1
FIGURE 1.4
Écrire du code dans
l'IDE Visual C++ 5.0.

Ensuite, vous devez enregistrer le texte dans un fichier. Appelons ce fichier


MonPremierProgramme.c. Il est également conseillé de créer un nouveau répertoire
sur votre disque dur pour y stocker vos projets de programmation et d'y enregistrer le
nouveau fichier. Tout d'abord, cliquez sur le bouton Enregistrer de la barre d'outils. Dans
la boîte de dialogue Enregistrer sous, cliquez sur le bouton Nouveau dossier et tapez un
nom pour votre dossier de programmation. Double-cliquez ensuite sur ce dossier pour
l'ouvrir, tapez Monpremierprogramme.c dans la case Nom du fichier et cliquez sur
Enregistrer. Notez que l'extension .c est utilisée pour indiquer que le fichier que vous
venez d'enregistrer est un fichier de programme C.
Vous devez maintenant cliquer sur le menu Build et choisir l'option Compile
MyFirstProgram.c. Ce faisant, vous demandez au compilateur de compiler le texte que
vous venez de taper et de sauvegarder (voir figure 1.5). À ce stade, Visual C++ peut vous
demander de créer un nouvel espace de travail ; il vous suffit de cliquer sur Oui pour que
cela se fasse automatiquement. Il ne doit pas y avoir d'erreurs ou d'avertissements dans la
fenêtre de sortie après l'exécution du compilateur.
Ensuite, cliquez à nouveau sur le menu Build et, cette fois, choisissez l'option Build
MyFirstProgram.exe qui produira finalement un fichier exécutable appelé
MyFirstProgram.exe.
La figure 1.6 montre qu'il n'y a pas d'erreurs ou d'avertissements après la construction de MyFirstProgram.exe.
Faire le premier pas 23

FIGURE 1.5
Compilation d'un
programme C à l'aide
de l'IDE.

FIGURE 1.6
Création d'un
fichier exécutable
de programme.

Vous êtes maintenant prêt à exécuter le fichier exécutable, MyFirstProgram.exe, que


vous venez de compiler. Vous pouvez l'exécuter à partir du chemin de menu : Construire,
Exécuter MonPremierProgramme.exe. Comme le fichier exécutable est une application
en mode console, une fenêtre d'invite DOS s'affiche lorsque le fichier exécutable est en
24 Heure
cours d'exécution (voir Figure 1.7). 1
Faire le premier pas 25

FIGURE 1.7
Exécution d'un programme C.
1

Vous pouvez constater que la première ligne de la fenêtre d'invite DOS illustrée dans la
figure 1.7 est la ligne exacte que vous venez de taper : "Bonjour voisin ! C'est mon
premier programme C." Il s'agit en effet de la sortie de votre premier programme C !
(Notez que la deuxième ligne affichée dans l'invite DOS est juste un rappel intégré de la
fenêtre d'invite DOS).
Je viens de montrer comment utiliser le compilateur Visual C++ pour écrire et compiler
un programme C, et comment rendre le programme exécutable. Pour plus de détails, vous
devez lire des livres tels que Teach Yourself Visual C++ 5 in 21 Days qui se concentrent
sur l'apprentissage de l'utilisation du compilateur Visual C++.

Utilisation du compilateur de Borland


Dans cette section, je vais vous montrer comment utiliser le compilateur C qui est fourni
avec le paquetage C++ de Borland. La procédure de cette section est assez similaire à
celle de la section précédente. Si vous souhaitez obtenir plus de détails sur l'installation
de Borland C++, veuillez suivre les instructions fournies avec le compilateur.
Je suppose que vous avez installé une copie de Borland C++ 5.02 sur votre ordinateur.
Pour lancer le compilateur, vous pouvez cliquer sur le bouton Démarrer de la barre des
tâches de Windows 95 (ou Windows 98 ou NT) et choisir Programmes, Borland C++
5.02, Borland C++. Vous pouvez également exécuter le fichier d'application bcw.exe
directement à partir du répertoire (dossier) où vous avez installé le progiciel Borland
C++. La figure 1.8 montre un exemple de l'environnement de développement intégré
(IDE) de Borland C++ 5.02.
Ensuite, vous pouvez ouvrir un nouveau fichier dans l'IDE, et taper le texte suivant dans
l'espace du fichier nouvellement ouvert :
#include <stdio.h>

main()
{
printf ("Howdy, neighbor ! This is my first C program.\n") ;
return 0 ;
}
26 Heure
1

FIGURE 1.8
Création d'un
programme avec l'IDE
Borland C++.

La figure 1.9 montre l'IDE avec le texte que vous venez de taper. Ne vous préoccupez pas
de la signification du texte. L'heure suivante vous l'expliquera.

FIGURE 1.9
Sauvegarde du texte
d'un programme C avec
l'IDE de Borland.
Faire le premier pas 27

Vous devez maintenant enregistrer le texte dans un fichier. Appelons ce fichier


MonPremierProgramme.c. Notez que l'extension .c est utilisée pour indiquer que le
fichier que vous venez d'enregistrer est un fichier de programme C.
1
Vous devez maintenant cliquer sur le menu Projet et choisir l'option Compiler. Ce
faisant, vous demandez au compilateur de commencer à compiler le texte que vous
venez de taper et d'enregistrer. La figure 1.10 montre qu'il n'y a ni erreur ni
avertissement après la compilation de MonPremierProgramme.c et la création de
MonPremierProgramme.exe.

FIGURE 1.10
Compilation d'un
programme C à l'aide
de l'IDE de Borland.

Vous êtes maintenant prêt à exécuter le fichier exécutable, MyFirstProgram.exe, que


vous venez de compiler. Vous pouvez l'exécuter en cliquant sur le bouton Exécuter de la
barre d'outils. Vous pouvez également exécuter MyFirstProgram.exe directement à
partir du répertoire où vous l'avez créé. Comme le fichier exécutable est en fait une
application DOS, une fenêtre d'invite DOS s'affiche lorsque le fichier exécutable est en
cours d'exécution (voir figure 1.11).

FIGURE 1.11
Exécution d'un
programme C à l'aide
de l'IDE de Borland.
28 Heure
1

La figure 1.11 affiche la sortie exactement comme vous venez de la taper : "Bonjour
voisin ! C'est mon premier programme C." Il s'agit en effet de la sortie de votre premier
programme C !
Si vous voulez en savoir plus sur l'utilisation de Borland C++, lisez un livre tel que Teach
Yourself Borland C++ 5 in 21 Days.

Voici une brève note sur le code binaire et les fichiers exécutables. Vous
trouverez plus de détails plus loin dans ce livre. Fondamentalement,
avant de pouvoir exécuter un programme C sur votre ordinateur, vous
devez utiliser un compilateur C pour traduire le programme C en code
compréhensible par la machine (c'est-à-dire en code binaire). Une fois la
traduction effectuée, le code binaire peut être enregistré dans un fichier
d'application. Le code binaire ou le fichier d'application est appelé code
exécutable, ou fichier exécutable, parce qu'il peut être exécuté

Résumé
Dans cette première leçon, vous avez appris les éléments de base suivants concernant le langage C :
• C est un langage de programmation à usage général.
• Le C est un langage de haut niveau qui présente les avantages de la lisibilité, de
la maintenabilité et de la portabilité.
• Le langage C est un langage très efficace qui permet de contrôler le matériel
informatique et les périphériques.
• Le langage C est un petit langage que vous pouvez facilement apprendre en un temps relativement
court.
• Les programmes écrits en C peuvent être réutilisés.
• Les programmes écrits en C doivent être compilés et traduits en code lisible par la
machine avant que l'ordinateur puisse les exécuter.
• De nombreux autres langages de programmation, tels que Perl, C++ et Java, ont
adopté des concepts de base et des fonctionnalités utiles du langage C. Une fois que
vous avez appris le langage C, l'apprentissage de ces autres langages est beaucoup
plus facile.
• La norme ANSI pour le langage C est la norme soutenue par les vendeurs de
compilateurs C afin de maintenir la portabilité des programmes C.
• Vous pouvez utiliser n'importe quel compilateur ANSI C pour compiler tous les
programmes C de ce livre.
Dans la prochaine leçon, vous apprendrez à écrire votre premier programme C.
Faire le premier pas 29

Q&R
Q Quel est le langage de plus bas niveau dans le monde informatique ? 1
R Le langage machine de l'ordinateur est le plus bas car le langage machine,
composé de 0 et de 1, est le seul langage que l'ordinateur peut comprendre
directement.
Q Quels sont les avantages des langages de programmation de haut niveau ?
R La lisibilité, la maintenabilité et la portabilité sont les principaux avantages des
langages de programmation de haut niveau.
Q Qu'est-ce que C ?
A Le C est un langage de programmation à usage général. C'est un langage de haut
niveau qui présente des avantages tels que la lisibilité, la maintenabilité et la
portabilité. De plus, le C permet de descendre au niveau du matériel pour
augmenter la vitesse de performance si nécessaire. Un compilateur C est nécessaire
pour traduire les programmes écrits en C en code compréhensible par la machine.
La portabilité du langage C est réalisée en recompilant les programmes C avec
différents compilateurs C destinés à différents types d'ordinateurs.
Q Puis-je apprendre le C en peu de temps ?
R Oui. Le C est un petit langage de programmation. Il n'y a pas beaucoup de mots-clés
ou de com- mandes à retenir. De plus, il est très facile de lire et d'écrire des
programmes en C, car c'est un langage de programmation de haut niveau proche
des langues humaines, en particulier de l'anglais. Vous pouvez apprendre le C en
relativement peu de temps.

Atelier
Pour vous aider à consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz fourni dans l'atelier avant de passer à la
leçon suivante. Les réponses et les conseils sont donnés dans l'annexe D, "Réponses aux
questions du quiz et aux exercices".

Quiz
1. Quels sont les langages de plus bas niveau et de plus haut niveau mentionnés dans ce livre ?
2. Un ordinateur peut-il comprendre directement un programme écrit en C ? De quoi
avez-vous besoin pour traduire un programme écrit en C en code compréhensible
par la machine (c'est-à-dire en code binaire) ?
3. Si nécessaire, un programme C peut-il être réutilisé dans un autre programme C ?
4. Pourquoi avons-nous besoin de la norme ANSI pour le langage C ?
HEURE 2
Écrire son premier
programme en C
Coupez votre propre bois et il vous réchauffera deux fois.
-Proverbe chinois
Dans l'Heure 1, "Faire le premier pas", vous avez appris que le C est un
langage de programmation de haut niveau et que vous avez besoin d'un
compilateur C pour traduire vos programmes C en code binaire que votre
ordinateur peut comprendre et exécuter. Dans cette leçon, vous écrirez
votre premier programme C et apprendrez les bases d'un programme C,
telles que

• La directive #include
• Fichiers d'en-tête
• Commentaires
• La fonction main()
• La déclaration de retour
• La fonction exit()
28 Heure
2

• Le caractère de retour à la ligne (\n)


• Traduire un programme C en un fichier exécutable
• Débogage

Un programme C simple
Jetons un coup d'œil à votre premier programme C, présenté dans le Listing 2.1. Plus
loin dans cette leçon, vous écrirez votre propre programme C pour la première fois.

LISTE 2 .1 Un programme C simple


1 : /* 02L01.c : C'est mon premier programme
C */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : printf ("Howdy, neighbor ! This is my first C
program.\n") ; 7: return 0 ;
8 : }

Il s'agit d'un programme C très simple, enregistré dans un fichier appelé 02L01.c. Notez
que le nom d'un fichier de programme C doit avoir une extension .c. Si vous avez
installé un compilateur C et mis en place l'environnement de développement approprié,
vous devriez être en mesure de compiler ce programme C et d'en faire un fichier
exécutable. Nous verrons plus loin dans ce chapitre comment créer un fichier exécutable.

Dans l'heure précédente, vous avez appris à saisir un programme dans votre éditeur de
texte et à l'enregistrer en tant que fichier de programme C. Ici, vous remarquerez que,
contrairement à l'exemple du chapitre précédent, chaque ligne est numérotée. Ici, vous
remarquerez que, contrairement à l'exemple du dernier chapitre, chaque ligne est
numérotée. Cette numérotation sert uniquement de référence lorsque j'explique ce que fait
chaque ligne d'un programme. Contrairement à d'autres langages tels que BASIC, le
langage C n'a pas du tout de numéros de ligne. En fait, si vous entrez les numéros de
ligne dans le listing, votre programme ne fonctionnera pas ! Lorsque vous entrez dans ces
programmes, n'oubliez donc pas de ne pas entrer les numéros de ligne indiqués dans le
livre.

Deux choses que vous pourriez remarquer en jetant un coup d'œil au Listing
2.1 sont les points-virgules et l'indentation sur les lignes 6 et 7.
Contrairement à d'autres langages, tels que BASIC, la fin d'une ligne n'a pas
de signification particulière en C. Il est parfaitement légal (et dans de
nombreux cas, conseillé) de diviser une déclaration en plusieurs lignes pour
plus de clarté. En général, une instruction C individuelle se termine par un
point-virgule, mais nous reviendrons sur ce point plus loin dans le livre.
Écrire son premier 29
programme en C

els d'un programme dans une sorte de format d'aperçu. La fonction


main() est, en fait, le niveau principal du programme, elle se trouve donc
à l'extrême gauche. Les lignes 6 et 7 font partie de main() et sont donc
indentées d'un niveau vers la droite. En général, vous utilisez la touche
Tab pour indenter un niveau avant de commencer à taper. Il convient de
noter que l'indentation, tout comme les numéros de ligne, n'est pas
imposée - ni même remarquée ! - par le compilateur. Le programmeur est
libre d'utiliser les sauts de ligne et l'indentation, communément appelés 2
espaces blancs, pour rendre le programme lisible. C'est généralement une
question de style, mais il est bon de suivre les conventions généralement
acceptées afin que les autres programmeurs puissent comprendre vos
programmes et vice-versa. Examinez l'utilisation des espaces dans les

J'ai configuré mon environnement de développement de manière à ce que tous les


programmes C de ce livre puissent être compilés et transformés en applications de
console. Par exemple, 02L01.exe est le nom de l'application console créée à partir de
02L01.c. Notez que .exe est l'extension du nom d'un programme d'application DOS ou
Windows (c'est-à-dire un fichier exécutable).
De plus, sur ma machine, j'enregistre tous les fichiers exécutables créés à partir des
programmes C de ce livre dans un répertoire dédié appelé C:\app. Par conséquent, si je
tape 02L01 à partir d'une invite DOS et que j'appuie sur la touche Entrée, je peux
exécuter le fichier exécutable 02L01.exe et afficher à l'écran le message Howdy,
neighbor ! C'est mon premier programme C. à l'écran. La sortie suivante est une
copie de l'écran :
Bonjour, voisin ! C'est mon premier programme en C.
SORTIE

Commentaires
Examinons maintenant de plus près le programme C de la
liste 2.1. La première ligne contient un commentaire :
/* 02L01.C : C'est mon premier programme en C */

Vous remarquez que cette ligne commence par une combinaison de barre oblique et d'astérisque, /*, et se
termine par
*/. En C, /* est appelé la marque de commentaire d'ouverture, et */ est la marque de
commentaire de fermeture. Le compilateur C ignore tout ce qui se trouve entre la
marque de commentaire d'ouverture et la marque de commentaire de fermeture. Cela
signifie que le commentaire de la première ligne du Listing 2.1, 02L01.C : This is my
first C program, est complètement ignoré par le compilateur.

Le seul but de l'inclusion de commentaires dans votre programme C est de vous aider à
documenter ce que fait le programme ou certaines sections spécifiques du programme.
28 Heure
N'oubliez pas que les commentaires sont écrits pour vous et pour les autres
2 le programme
programmeurs. Par exemple, lorsque vous lisez
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

30 Visitez www.DeepL.com/pro
Heure pour en savoir plus.
2

Les commentaires dans le code vous aident à comprendre ce que fait le code, ou du
moins ce qu'il a l'intention de faire. Au fur et à mesure que vos programmes s'étoffent et
se compliquent, vous pouvez utiliser les commentaires pour vous écrire des notes sur ce
que vous essayez de faire et pourquoi.
Vous ne devez pas vous inquiéter de la taille ou de la vitesse de performance de votre
programme C si vous y ajoutez de nombreux commentaires. L'ajout de commentaires
dans un programme C n'augmente pas la taille du code binaire du programme (c'est-à-
dire le fichier exécutable), bien que la taille du code binaire du programme (c'est-à-dire
le fichier exécutable) n'augmente pas la taille du code binaire du programme.
le programme lui-même (c'est-à-dire le code source) peut devenir plus volumineux. La
vitesse de performance du fichier exécutable créé à partir de votre programme C n'est en
aucun cas affectée par les com- ments contenus dans votre programme C.
Le langage C vous permet d'écrire un commentaire qui traverse plus d'une ligne. Par
exemple, vous pouvez écrire un commentaire en C comme suit :
/*
Ce commentaire n'augmente pas la taille
du fichier exécutable (code binaire) et
n'affecte pas la vitesse d'exécution.
*/

ce qui équivaut à ceci :


/* Ce commentaire n'augmente pas la taille de */
/* le fichier exécutable (code binaire), ni */
/* cela affecte la vitesse d'exécution. */

De nos jours, il existe une autre façon d'insérer des commentaires dans un
programme C. Le C++ a commencé à utiliser deux barres obliques (//) pour
marquer le début d'une ligne de commentaire. Le langage C++ a commencé à
utiliser deux barres obliques (//) pour marquer le début d'une ligne de
commentaire ; de nombreux compilateurs C utilisent désormais cette
convention également. Le commentaire se termine à la fin de la ligne. Par
exemple, si j'écris un programme C en Borland C++ ou en Visual C++, les
deux commentaires suivants sont identiques :
/*
Ce commentaire n'augmente pas la taille du
fichier exécutable (code binaire) et
n'affecte pas la vitesse d'exécution.
*/
// Ce commentaire n'augmente pas la taille de
// le fichier exécutable (code binaire).
// il affecte la vitesse d'exécution.

Notez que ce nouveau style d'utilisation de // comme marque de début


Écrire son premier 31
programme en C

Il convient de souligner que la norme ANSI ne prend pas en charge les commentaires
imbriqués, c'est-à-dire les commentaires à l'intérieur des commentaires. Par exemple, ce
qui suit n'est pas autorisé par la norme ANSI :
/* Ceci est la première partie du premier commentaire
/* C'est le deuxième commentaire */

2
Vous pouvez utiliser la marque de commentaire d'ouverture, /*, et la marque
de commentaire de fermeture, */, pour vous aider à tester et à corriger toute
erreur trouvée dans votre programme C. C'est ce que l'on appelle
communément commenter un bloc de code. Vous pouvez commenter une ou
plusieurs instructions C dans votre programme C avec /* et */ lorsque vous
avez besoin de vous concentrer sur d'autres instructions et d'observer leur
comportement de près. Le compilateur C ignorera les instructions que vous
commentez.
Par la suite, vous pourrez toujours restaurer les déclarations précédemment
commentées en supprimant simplement les marques de commentaire
d'ouverture et de fermeture. De cette façon, vous n'avez pas besoin d'effacer
ou de réécrire les déclarations pendant les tests et le débogage.
Cependant, comme vous ne pouvez pas imbriquer les commentaires faits
avec /* et */, si vous essayez de commenter un code qui contient déjà ces
commentaires, votre programme ne se compilera pas. L'une des raisons pour
lesquelles les commentaires de type // sont si utiles est qu'il est possible
de les imbriquer, et donc de les commenter légalement avec /* et */.
Si votre éditeur de texte prend en charge la coloration syntaxique, vous
C'est la deuxième partie du premier commentaire */

La directive #include
Passons maintenant à la ligne 2 du programme C de la liste 2.1 :
#include <stdio.h>

Vous voyez que cette ligne commence par un dièse, #, qui est suivi par include. En C,
#include constitue une directive du préprocesseur qui indique au préprocesseur C de
rechercher un fichier et de placer le contenu de ce fichier à l'endroit indiqué par la
directive #include.
Le préprocesseur est un programme qui effectue certaines préparations pour le
compilateur C avant que votre code ne soit compilé. Plus de détails sur le préprocesseur
C sont abordés dans l'Heure 23, "Compiler des programmes : Le préprocesseur C".
Dans cette ligne, vous voyez également que <stdio.h> suit #include. Vous pouvez
supposer que le fichier demandé par la directive #include est un fichier appelé stdio.h.
Vous avez tout à fait raison ! Ici, la directive #include demande effectivement au
préprocesseur C de rechercher et de placer stdio.h à l'emplacement de la directive dans
32 Heure
le programme C. 2
Écrire son premier 33
programme en C

Le nom du fichier stdio.h signifie fichier d'en-tête d'entrée-sortie standard. Le


fichier stdio.h contient de nombreux prototypes et macros permettant d'effectuer des
entrées ou des sorties (E/S) pour les programmes C. Vous verrez plus d'E/S dans l'Heure
5, "Manipuler les entrées et sorties standard". Vous verrez plus d'E/S de programme dans
l'Heure 5, "Manipuler les entrées et sorties standard".

Certains systèmes d'exploitation font la distinction entre les lettres majuscules


et minuscules, d'autres non. Par exemple, stdio.h et STDIO.H sont des
noms de fichiers identiques sur un PC, alors qu'ils sont différents sous UNIX.

Fichiers d'en-tête
Les fichiers inclus par la directive #include, comme stdio.h, sont appelés fichiers
d'en-tête parce que les directives #include sont presque toujours placées au début ou à
la tête des programmes C. En fait, le nom d'extension .h signifie "header" (en-tête) et
ces fichiers sont parfois appelés "dot-h" dans les conversations.
Outre stdio.h, il existe d'autres fichiers d'en-tête, tels que stdlib.h, string.h,
math.h, etc. L'annexe A, "Fichiers d'en-tête standard ANSI", fournit une liste de tous
les fichiers d'en-tête standard ANSI. Les fichiers d'en-tête spécifiques que vous devez
inclure dépendent des fonctions de bibliothèque spécifiques que vous avez l'intention
d'appeler. La documentation des fonctions de la bibliothèque vous indiquera quel fichier
d'en-tête est nécessaire.

Crochets d'angle (< >) et doubles guillemets (" ")


Dans la deuxième ligne du Listing 2.1, il y a deux crochets d'angle, < et >, qui sont
utilisés pour entourer stdio.h. Vous vous demandez peut-être ce que font les crochets
d'angle. En C, les crochets demandent au préprocesseur C de rechercher un fichier d'en-
tête dans un répertoire autre que le répertoire actuel.
Par exemple, le répertoire courant contenant le fichier 02L01.C s'appelle C:\code sur
mon ordinateur. Par conséquent, les crochets entourant <stdio.h> indiquent au
préprocesseur C de rechercher stdio.h dans un répertoire autre que C:\code.
Si vous voulez que le préprocesseur C cherche d'abord un fichier d'en-tête dans le
répertoire courant avant de commencer à chercher ailleurs, vous pouvez utiliser des
guillemets doubles pour entourer le nom du fichier d'en-tête. Par exemple, lorsque le
préprocesseur C voit "stdio.h", il cherche d'abord dans le répertoire courant, qui est
C:\code sur ma machine, avant de chercher ailleurs le fichier d'en-tête stdio.h.

Normalement, les fichiers d'en-tête sont enregistrés dans un sous-répertoire appelé


include. Par exemple, j'installe un compilateur Microsoft C dans le répertoire MSVC de
mon disque dur, qui est désigné comme le lecteur C. Le chemin d'accès aux
34 Heure
fichiers d'en-tête devient alors2C:\MSVC\include. Le chemin d'accès aux
fichiers d'en-tête devient alors C:\MSVC\include.
Écrire son premier 35
programme en C

Le chemin où sont conservés les fichiers d'en-tête est généralement déterminé par votre
compilateur lorsque vous l'installez. C'est ce qu'on appelle communément le répertoire
d'inclusion ou le chemin d'inclusion de votre environnement. Normalement, vous n'avez
pas besoin de vous préoccuper du répertoire include jusqu'à ce que vous créiez vos
propres fichiers d'en-tête. Pour l'instant, il vous suffit de spécifier le nom du fichier d'en-
tête que vous souhaitez inclure.

La fonction main() 2
La ligne 4 de la liste 2.1 présente cette fonction :
principal ()

Il s'agit d'une fonction très spéciale en C. Chaque programme C doit avoir une fonction
main(), et chaque programme C ne peut avoir qu'une seule fonction main(). Des
discussions plus générales sur les fonctions sont données dans l'Heure 3, "Apprendre la
structure d'un programme C".
Vous pouvez placer la fonction main() où vous le souhaitez dans votre programme C.
Cependant, l'exécution de votre programme commence toujours par la fonction main().
Si vous créez d'autres fonctions dans votre programme, la fonction main() s'exécutera
toujours en premier, même si elle se trouve au bas de votre fichier de programme.
Dans le Listing 2.1, le corps de la fonction main() commence à la ligne 4 et se termine à
la ligne 8. Comme il s'agit d'un programme très simple, la fonction main() est la seule
fonction définie dans le programme. Dans le corps de la fonction main(), une fonction de
la bibliothèque C, printf(), est appelée afin d'imprimer un message d'accueil (voir ligne
6). Plus de détails sur printf() sont abordés dans l'Heure 5.
Une autre chose importante à propos de main() est que l'exécution de tout programme
C se termine par main(). Un programme se termine lorsque toutes les instructions de
la fonction main() ont été exécutées.

Le caractère de nouvelle ligne (\n)


Dans la fonction printf(), une chose qui mérite d'être mentionnée à ce stade est le
caractère de retour à la ligne, \n. Habituellement placé à la fin d'un message, le
caractère de retour à la ligne indique à l'ordinateur de déplacer le curseur au début de la
ligne suivante, de sorte que tout ce qui est imprimé après le message commence sur la
ligne suivante de l'écran.
Dans un environnement UNIX, \n seul passe à une nouvelle ligne mais laisse le curseur
à la position qu'il occupait sur la ligne précédente. Dans ce cas, il est nécessaire
d'imprimer \r\n plutôt que simplement \n. La fonction
Le caractère \r est le caractère de retour chariot. Lorsque vous exécuterez les
exemples de programmes de ce livre, vous serez en mesure de savoir immédiatement si le
36 Heure
curseur revient au début de la nouvelle ligne2; si ce n'est pas le cas, utilisez simplement
\r\n partout où vous voyez \r\n dans les listes de programmes.
L'exercice 3 de cette leçon vous donne l'occasion d'utiliser le caractère de retour à la ligne
pour diviser un message d'une ligne en deux lignes.
Écrire son premier 37
programme en C

La déclaration de retour
Toutes les fonctions en C peuvent renvoyer des valeurs. Par exemple, lorsque vous créez
une fonction pour additionner deux nombres, vous pouvez créer une telle fonction qui
vous renvoie la valeur de l'addition.
La fonction main() renvoie elle-même une valeur entière. En C, les entiers sont des
nombres décimaux sans fraction.
Par conséquent, à la ligne 7 du Listing 2.1, il y a une déclaration, return 0 ;, qui
indique que la fonction main() renvoie 0 et que le programme se termine normalement.
Dans certains cas, vous devez mettre fin à votre programme en raison d'une condition
d'erreur. Dans ce cas, vous pouvez renvoyer des valeurs autres que 0 pour indiquer au
système d'exploitation (ou au programme qui a exécuté votre programme) qu'il y a eu
une erreur.

La fonction exit()
Il existe également une fonction de la bibliothèque C, exit(), qui peut être utilisée pour
mettre fin à un programme. La fonction exit() étant définie dans un fichier d'en-tête,
stdlib.h, vous devez inclure le fichier d'en-tête au début de votre programme pour
pouvoir utiliser la fonction. La fonction exit() elle-même ne renvoie pas de valeur à
votre programme.
Notez que return et exit() peuvent également être utilisés dans d'autres fonctions.
Vous verrez d'autres exemples du mot-clé return dans la suite du livre.

Compilation et liaison
Vous êtes peut-être déjà impatient de savoir comment un fichier exécutable est créé.
Voyons comment un programme C est compilé et traduit en un fichier exécutable.
Comme le montre la figure 2.1, trois étapes au moins sont nécessaires pour créer un
fichier exécutable.
Tout d'abord, un fichier de programme écrit en C, appelé code source, est créé. Le nom du
fichier de code source se termine par l'extension .c.
Le fichier de code source est ensuite compilé par un compilateur C, qui crée un nouveau
fichier. Ce nouveau fichier est un fichier objet. Dans le système d'exploitation UNIX, le
nom d'un fichier objet se termine par l'extension .o ; dans les systèmes d'exploitation
DOS et Windows, l'extension est .obj.
Vous ne pouvez pas exécuter le fichier objet parce qu'il manque un code de fonction.
Vous devez terminer l'étape suivante : l'édition de liens. L'édition de liens se fait en
invoquant un programme spécial appelé éditeur de liens, qui est normalement fourni avec
le compilateur.
38 Heure
Un éditeur de liens est utilisé pour lier2le fichier objet, la bibliothèque C standard ANSI
et d'autres bibliothèques créées par l'utilisateur afin de produire un fichier exécutable -
le code binaire. À ce stade, le code binaire des fonctions de la bibliothèque qui sont
appelées dans le code source est combiné
Écrire son premier 39
programme en C

avec le fichier objet ; le résultat est enregistré dans un nouveau fichier - un fichier
exécutable. Comme vous l'avez appris dans le premier chapitre de ce livre, le nom d'un
fichier exécutable se termine généralement par l'extension .exe sous DOS et Windows
(.com est une autre extension utilisée pour un nom de fichier exécutable sous DOS).
(Sous UNIX, il n'est pas nécessaire d'ajouter une telle extension au nom d'un fichier
exécutable.

FIGURE 2.1 Les fichiers d'en-têteLe programme C


Création d'un
2
fichier exécutable
stdio.h
par le compilateur stdlib.h /* 02L01.C */
et l'éditeur de liens. .....

Dossiers de la
Le compilateur bibliothèque
C &
Autres fichiers

L'éditeur
de liens

Le fichier exécutable
10011111001
0111000011
110001100

Plus tard, vous apprendrez que dans de nombreux cas, il peut y avoir plusieurs fichiers
objets qui doivent être liés ensemble afin de créer un programme exécutable.
Notez que le fichier objet et le fichier exécutable sont tous deux dépendants de la
machine. Vous ne pouvez pas simplement déplacer un fichier exécutable de la plate-
forme informatique actuelle vers une autre plate-forme fonctionnant avec un système
d'exploitation différent, bien que le code source du fichier exécutable,
vraisemblablement écrit en C ANSI, puisse être indépendant de la machine (c'est-à-
dire portable).
40 Heure
2

La portabilité est un concept important en C, car c'était l'un des objectifs


initiaux de la conception du langage. La portabilité du code C est à l'origine
de sa large diffusion et de sa popularité. À l'époque où les applications et les
systèmes d'exploitation étaient adaptés à un système informatique spécifique,
les logiciels devaient être écrits à partir de zéro à chaque fois qu'un nouvel
ordinateur apparaissait. L'avènement du langage C a permis d'abstraire le
logiciel du matériel et, dans un sens très concret, a contribué à donner
naissance à l'industrie du logiciel telle que nous la connaissons aujourd'hui.
La portabilité, par essence, se réfère au processus de portage d'un
programme sur un ordinateur et/ou un système d'exploitation différent. Le
développement de logiciels est un processus long et coûteux, et la réécriture
d'un programme existant pour qu'il fonctionne sur un nouvel ordinateur est
souvent une tâche décourageante qu'il vaut mieux éviter. En supposant que
chaque combinaison de système d'exploitation et de compilateur soit adaptée
à l'ordinateur spécifique sur lequel elle est destinée à fonctionner, un
programme C devrait être facilement portable avec seulement des
changements minimes dans le code. La portabilité est mentionnée tout au
long de ce livre, car il est important que les programmes C ne fassent pas
d'hypothèses sur le système informatique spécifique sur lequel ils s'exécutent.
Heureusement, la norme ANSI C fournit de nombreuses caractéristiques et
fonctions qui permettent à votre programme de s'adapter à son
environnement, plutôt que d'agir sur la base d'hypothèses formulées par

Qu'est-ce qui ne va pas avec mon programme ?


Lorsque vous avez fini d'écrire un programme C et que vous commencez à le compiler,
il se peut que vous obteniez des messages d'erreur ou d'avertissement. Ne paniquez pas
lorsque vous voyez des messages d'erreur. Nous sommes des êtres humains. Tout le
monde fait des erreurs. En fait, vous devriez apprécier que votre compilateur détecte
certaines erreurs pour vous avant que vous n'alliez plus loin.
En général, votre compilateur peut vous aider à vérifier la grammaire - c'est-à-dire la
syntaxe - de votre programme C et à vous assurer que vous avez suivi les règles de
programmation C correctement. Par exemple, si vous oubliez de placer l'accolade finale
sur la fonction main() à la ligne 8 du Listing 2.1, vous obtiendrez un message d'erreur
du type : syntax error : end of file found.
En outre, l'éditeur de liens émettra un message d'erreur s'il ne trouve pas le code
manquant pour une fonction nécessaire dans les bibliothèques. Par exemple, si vous
avez mal orthographié printf() en pprintf() dans le programme de la liste 2.1, vous
obtiendrez un message d'erreur : '_pprintf' : unresolved external (ou quelque
chose de similaire).
Toutes les erreurs détectées par le compilateur et l'éditeur de liens doivent être corrigées
avant qu'un fichier exécutable (code binaire) puisse être créé.
Écrire son premier 41
programme en C

Débogage du programme
Dans le monde de l'informatique, les erreurs de programme sont également appelées
"bugs". Dans de nombreux cas, votre compilateur C et votre éditeur de liens ne trouvent
aucune erreur dans votre programme, mais le résultat obtenu en exécutant le fichier
exécutable du programme n'est pas celui auquel vous vous attendiez. Pour trouver ces
erreurs "cachées" dans votre programme, vous devrez peut-être utiliser un débogueur.

Normalement, votre compilateur C comprend déjà un logiciel de débogage. Le 2


débogueur peut exécuter votre programme une ligne à la fois afin que vous puissiez
observer de près ce qui se passe avec le code dans chaque ligne, ou afin que vous
puissiez demander au débogueur d'arrêter l'exécution de votre programme sur n'importe
quelle ligne. Pour plus de détails sur votre débogueur, reportez-vous aux instructions
fournies avec votre compilateur C.
Plus loin dans ce livre, vous apprendrez que le débogage est une étape très importante
et nécessaire dans l'écriture de programmes logiciels. (Ce sujet est abordé dans l'Heure
24, "Et maintenant ?").

Résumé
Dans cette leçon, vous avez appris les concepts et déclarations suivants concernant le langage C :

• Certains fichiers d'en-tête doivent être inclus au début de votre programme C.


• Les fichiers d'en-tête, tels que stdio.h et stdlib.h, contiennent les déclarations
des fonctions utilisées dans votre programme C ; par exemple, les fonctions
printf() et exit().

• Les commentaires dans vos programmes C sont nécessaires pour vous aider à
documenter vos programmes. Vous pouvez placer des commentaires où vous le
souhaitez dans vos programmes.
• En C ANSI, un commentaire commence par la marque de commentaire
d'ouverture, /*, et se termine par la marque de commentaire de fermeture, */.
• Chaque programme C doit avoir une et une seule fonction main(). L'exe- cution
du programme commence et se termine par la fonction main().
• L'instruction return peut être utilisée pour renvoyer une valeur indiquant au
système d'exploitation si une erreur s'est produite. La fonction exit() met fin à un
programme ; l'argument de la fonction indique également l'état de l'erreur.
• La compilation et la liaison sont des étapes consécutives qui doivent être achevées
avant qu'un fichier exe- cutable ne soit produit.
• Tout le monde, y compris vous et moi, fait des erreurs de programmation. Le
débogage est une étape très importante dans la conception et le codage de votre
programme.
Dans la prochaine leçon, vous en apprendrez davantage sur les éléments essentiels des programmes C.
42 Heure
2

Q&R
Q Pourquoi est-il nécessaire d'insérer des commentaires dans vos programmes ?
Les commentaires vous aident à documenter ce que fait un programme. En
particulier lorsqu'un programme devient très complexe, vous devez écrire des
commentaires pour expliquer les différentes parties du programme.
Q Pourquoi la fonction main() est-elle nécessaire dans un programme ?
A L'exécution d'un programme C commence et se termine par la fonction main().
Sans la fonction main(), l'ordinateur ne sait pas par où commencer pour exécuter
un programme.
Q Que fait la directive #include ?
R La directive #include est utilisée pour inclure les fichiers d'en-tête qui contiennent
les déclarations des fonctions utilisées dans votre programme C. En d'autres
termes, la directive #include indique au préprocesseur C de rechercher le fichier
d'en-tête spécifié dans le chemin d'inclusion.
Q Pourquoi avez-vous besoin d'un linker ?
A Après la compilation, il se peut qu'il manque encore du code de fonction dans le
fichier objet d'un programme. Un éditeur de liens doit alors être utilisé pour lier le
fichier objet à la bibliothèque standard C ou à d'autres bibliothèques créées par
l'utilisateur et inclure le code de fonction manquant afin qu'un fichier exécutable
puisse être créé.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe D, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Un compilateur C peut-il voir les commentaires de votre programme C ?
2. Quel type de fichiers un compilateur C produit-il réellement ?
3. La fonction exit() renvoie-t-elle une valeur ? Qu'en est-il de l'instruction return ?
4. Qu'est-ce qu'un fichier d'en-tête ?
Écrire son premier 43
programme en C

Exercices
1. Est-ce que #include <stdio.h> est la même chose que #include "stdio.h" ?
2. Il est temps pour vous d'écrire un programme par vous-même. En vous référant
au programme de la liste 2.1, écrivez un programme C capable d'imprimer un
message : C'est amusant d'écrire
mon propre programme en C.
3. Mettez à jour le programme de la liste 2.1 en ajoutant un caractère de nouvelle
ligne dans le message imprimé par la fonction printf(). Vous devriez voir 2
deux lignes du message à l'écran après avoir exécuté le fichier exécutable mis à
jour :
Bonjour, voisin !
C'est mon premier programme en C.

4. Quels sont les éventuels messages d'avertissement ou d'erreur que vous


obtiendrez lors de la compilation du programme suivant ?
#include <stdlib.h>
#include <stdio.h>
main()
{
printf ("Howdy, neighbor ! This is my first C program.\n") ;
exit(0) ;
}

5. Quels sont les messages d'erreur que vous obtiendrez pour le programme suivant
lorsque vous tenterez de le compiler ?
void main()
{
printf ("Howdy, neighbor ! This is my first C program.\n") ;
return 0 ;
}
HEURE 3
Apprendre la structure
d'un programme C
Le tout est égal à la somme des parties.
-Euclide
Dans l'heure 2, "Écrire votre premier programme C", vous avez vu et écrit
quelques programmes C simples. Vous avez également appris la structure de
base d'un programme C. Vous savez qu'un programme écrit en C doit être
compilé avant de pouvoir être exécuté. Dans cette leçon, vous apprendrez
d'autres éléments essentiels d'un programme C, tels que
• Constantes et variables
• Expressions
• Déclarations
• Blocs de déclarations
• Types et noms de fonctions C
• Arguments pour les fonctions
• Le corps d'une fonction
• Appels de fonction
42 Heure
3

Les bases d'un programme C


Comme un bâtiment est fait de briques, un programme C est constitué d'éléments de base,
tels que des expressions, des instructions, des blocs d'instructions et des blocs de
fonctions. Ces éléments sont abordés dans les sections suivantes. Mais d'abord, vous
devez apprendre deux éléments plus petits mais importants, les constantes et les variables,
qui composent les expressions.

Constantes et variables
Comme son nom l'indique, une constante est une valeur qui ne change jamais. Une
variable, en revanche, peut être utilisée pour présenter différentes valeurs.
Vous pouvez considérer une constante comme un CD-ROM de musique ; la musique
sauvegardée sur le CD-ROM ne change jamais. Une variable ressemble davantage à une
cassette audio : Vous pouvez toujours mettre à jour les contenus de la cassette en
remplaçant simplement les anciens morceaux par de nouveaux.
Il existe de nombreux exemples dans lesquels les constantes et les variables se trouvent
dans la même déclaration. Prenons l'exemple suivant :
i = 1 ;

où le symbole 1 est une constante car il a toujours la même valeur (1), et le sym- bol i
se voit attribuer la constante 1. En d'autres termes, i contient la valeur 1 après
l'exécution de l'instruction. Plus tard, s'il y a une autre instruction,
i = 10 ;

après son exécution, la valeur 10 est attribuée à i. Comme i peut contenir différentes
valeurs, on l'appelle une variable dans le langage C.

Expressions
Une expression est une combinaison de constantes, de variables et d'opérateurs utilisés
pour indiquer des calculs.
Par exemple, le texte suivant :
(2 + 3) * 10

est une expression qui additionne d'abord 2 et 3, puis multiplie le résultat de l'addition par
10. (Le résultat final de l'expression est 50.)
De même, l'expression 10 * (4 + 5) donne 90. L'expression 80/4 donne 20. Voici
d'autres exemples d'expressions :
Apprendre la structure d'un 43
programme C

Expression Description
6 Expression d'une constante.
i Expression d'une variable.
6 + i Expression composée d'une constante et d'une variable.
exit(0) Expression d'un appel de fonction.

Opérateurs
Comme vous l'avez vu, une expression peut contenir des symboles tels que +, * et /.
Dans le langage C, ces symboles sont appelés opérateurs arithmétiques. Le tableau 3.1
répertorie tous les opérateurs arithmétiques et leur signification.

TABLEAU 3.1 Opérateurs arithmétiques en C 3


Symbole Signification
+ Addition
- Soustraction
* Multiplication
/ Division
% Reste (ou module)

Vous connaissez peut-être déjà tous les opérateurs arithmétiques, à l'exception de


l'opérateur de reste (%). % est utilisé pour obtenir le reste du premier opérande divisé par
le second opérande. Par exemple, l'expression
6 % 4

donne une valeur de 2 car 4 entre une fois dans 6 avec un reste de 2.
L'opérateur de reste, %, est également appelé l'opérateur de module.
Parmi les opérateurs arithmétiques, les opérateurs de multiplication, de division et de
reste ont une priorité plus élevée que les opérateurs d'addition et de soustraction. Par
exemple, l'expression
2 + 3 * 10

donne 32 et non 50, en raison de la préséance de l'opérateur de multiplication.


On calcule d'abord 3 * 10, puis on ajoute 2 au résultat de la multiplication.
44 Heure
3

Comme vous le savez peut-être, vous pouvez mettre des parenthèses autour d'une
addition (ou d'une soustraction) pour forcer l'addition (ou la soustraction) à être effectuée
avant une multiplication, une division ou un calcul modu- lus. Par exemple, l'expression
(2 + 3) * 10

effectue d'abord l'addition de 2 et 3 avant de multiplier le résultat par 10.


D'autres opérateurs, utilisés pour la syntaxe, comprennent la virgule et le point-virgule.
Le point-virgule est généralement utilisé pour indiquer la fin d'une déclaration, comme
vous le verrez plus loin. La virgule est utilisée dans certains cas, lorsqu'une instruction
est constituée d'une liste d'expressions ou de déclarations.
Vous en apprendrez davantage sur les opérateurs du langage C dans les heures 6,
"Manipulation des données", et 8, "Utilisation des opérateurs conditionnels".

Identifiants
Outre les nombres (tels que la constante 7) et les opérateurs (tels que le symbole +), les
expressions peuvent également contenir des mots appelés identificateurs. Les noms de
fonctions (comme exit) et de variables (comme i), ainsi que les mots-clés réservés,
sont tous des identificateurs en C.
Voici l'ensemble des caractères que vous pouvez utiliser pour créer un identifiant valide.
Tout caractère ou symbole qui ne respecte pas ces règles est illégal dans un identifiant.
• Caractères A à Z et a à z.
• Les caractères numériques 0 à 9 (mais ils ne peuvent pas être utilisés comme
premier caractère d'un identifiant).
• Le caractère de soulignement (_).
Par exemple, stop_sign, Loop3 et _pause sont tous des identifiants valables.
Les caractères suivants sont illégaux, c'est-à-dire qu'ils ne respectent pas les règles
d'identification énoncées ci-dessus :
• Signes arithmétiques en C (+, -, *, /).
• Point, ou caractère point (.).
• Apostrophes (') ou guillemets (").
• Tout autre symbole spécial tel que *, @, #, ?, etc.
Certains identificateurs non valides sont par exemple 4flags, sum-result, method*4, et
quelle_taille ?
Apprendre la structure d'un 45
programme C

N'utilisez jamais les mots-clés réservés ou les noms des fonctions de la


bibliothèque C standard pour les noms de variables ou de fonctions que
vous créez dans vos propres programmes C.

Vous avez déjà vu le mot-clé return dans l'heure précédente. L'heure 4, "Comprendre
les types de données et les mots-clés", énumère tous les mots-clés réservés.

Déclarations
Dans le langage C, une instruction est une instruction complète qui se termine par un
point-virgule. Dans de nombreux cas, vous pouvez transformer une expression en
instruction en ajoutant simplement un point-virgule à la fin de l'expression.
Par exemple, le texte suivant :
3
i = 1 ;

est une instruction. Vous avez peut-être déjà compris que l'instruction se compose d'une
expression i = 1 et d'un point-virgule ( ;).
Voici d'autres exemples de déclarations :
i = (2 + 3) * 10 ;
i = 2 + 3 * 10 ;
j = 6 % 4 ;
k = i + j ;

De plus, dans la première leçon de ce livre, vous avez appris des affirmations telles que
retour 0 ;
exit(0) ;
printf ("Howdy, neighbor ! This is my first C program.\n") ;

Blocs de déclarations
Un groupe d'instructions peut former un bloc d'instructions qui commence par une
accolade ouvrante ({) et se termine par une accolade fermante (}). Un bloc
d'instructions est traité comme une seule instruction par le compilateur C.
Par exemple, le texte suivant :
for(. . .) {
s3 = s1 + s2
; mul = s3 *
c ;
reste = somme % c ;
}
46 Heure
3

est un bloc d'instructions qui commence par { et se termine par }. Ici, for est un mot-
clé du langage C qui détermine le bloc d'instructions. Le mot-clé for est abordé dans
l'heure 7, "Travailler avec des boucles".
Un bloc d'instructions permet de regrouper une ou plusieurs instructions en une seule. De
nombreux mots-clés C ne peuvent contrôler qu'une seule instruction. Si vous souhaitez
placer plusieurs instructions sous le contrôle d'un mot-clé C, vous pouvez ajouter ces
instructions dans un bloc d'instructions de sorte que le bloc soit considéré comme une
seule instruction par le mot-clé C.

Anatomie d'une fonction C


Les fonctions sont les éléments constitutifs des programmes C. Outre les fonctions
standard de la bibliothèque C, vous pouvez également utiliser d'autres fonctions créées
par vous-même ou par un autre programmeur dans votre programme C. Dans l'Heure 2,
vous avez vu la fonction main(), ainsi que deux fonctions de la bibliothèque C,
printf() et exit(). Maintenant, regardons de plus près les fonctions.

Comme le montre la figure 3.1, une fonction se compose de six parties : le type de
fonction, le nom de la fonction, les arguments de la fonction, l'accolade ouvrante, le
corps de la fonction et l'accolade fermante.

FIGURE 3.1 Type de Nom de Liste des


L'anatomie d'une fonction la arguments
fonction en langage C. fonctio de la

intinteger_add (int x, int y)

{ Début de la

int result ;
result = x + y ; Fonction Corps
return result ;
} Fin de la fonction
Corps et fonction

Les six parties d'une fonction sont expliquées dans les sections suivantes.

Déterminer le type d'une fonction


Le type de fonction est utilisé pour indiquer le type de valeur qu'une fonction va renvoyer
après son exécution. Dans l'heure 2, par exemple, vous avez appris que le type de
fonction par défaut de main() est int.
En C, int est utilisé comme mot-clé pour le type de données integer. Dans l'heure qui
suit, vous en apprendrez davantage sur les types de données.
Apprendre la structure d'un 47
programme C

Outre le type int, un type de fonction peut être l'un des autres types, tels que le type
caractère (mot-clé : char), le type flottant (float), etc. Vous en apprendrez plus sur ces
types plus loin dans ce livre.
Lorsque vous appelez une fonction C qui renvoie un type de données, la valeur qu'elle
renvoie (valeur de retour) peut être utilisée dans une expression. Vous pouvez l'affecter à
une variable, telle que
int a = func() ;

ou l'utiliser dans une expression, comme ceci


a = func() + 7 ;

Donner un nom valide à une fonction


Le nom d'une fonction est normalement donné de manière à refléter ce que la fonction
peut faire. Par exemple, le nom de la fonction printf() signifie "imprimer des 3
données formatées".
Comme le nom d'une fonction est un identifiant, vous devez suivre les règles de création
d'identifiants valides lorsque vous créez vos propres fonctions.
En outre, vous ne pouvez pas utiliser les noms des fonctions C standard telles que
printf() ou exit() pour nommer vos propres fonctions. Elles sont déjà définies et il
est illégal d'utiliser le même nom de fonction pour définir plus d'une fonction.

Passer des arguments aux fonctions C


Les fonctions sont utiles parce que vous pouvez les appeler à plusieurs reprises à partir
de différents points de votre programme. Cependant, il est probable que vous appeliez
une certaine fonction pour une raison légèrement différente à chaque fois.
Par exemple, dans le Listing 2.1 de l'Heure 2, une chaîne de caractères, "Howdy,
neighbor ! Ceci est mon premier programme C.\N", est passée à la fonction
printf(), puis printf() imprime la chaîne à l'écran. Le but de printf() est
d'imprimer une chaîne de caractères à l'écran, mais à chaque fois que vous l'appelez,
vous lui passez la chaîne de caractères spécifique que vous voulez qu'elle imprime cette
fois-ci.
Les éléments d'information transmis aux fonctions sont appelés arguments. Comme vous
l'avez vu, l'argument d'une fonction est placé entre les parenthèses qui suivent
immédiatement le nom de la fonction.
Le nombre d'arguments d'une fonction est déterminé par la déclaration de la fonction, qui
est elle-même déterminée par la tâche que la fonction doit accomplir. Si une fonction a
besoin de plus d'un argument, les arguments transmis à la fonction doivent être séparés
par des virgules ; ces arguments sont considérés comme une liste d'arguments.
48 Heure
3

Si aucune information ne doit être transmise à une fonction, il suffit de laisser vide le
champ d'argument entre les parenthèses. Par exemple, la fonction main() du Listing 2.1
de l'Heure 2 n'a pas d'argument, donc le champ entre les parenthèses suivant le nom de
la fonction est vide. Voir la copie de la liste 3.1 ci-dessous :

LISTE 3 .1 - Un programme C simple.


1 : /* 02L01.c : C'est mon premier
programme C */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : printf ("Howdy, neighbor ! This is my first C
program.\n") ; 7: return 0 ;
8 : }

Le début et la fin d'une fonction


Comme vous l'avez peut-être déjà compris, les accolades sont utilisées pour marquer le
début et la fin d'une fonction. L'accolade ouvrante ({) indique le début du corps d'une
fonction, tandis que l'accolade fermante (}) marque la fin du corps de la fonction.
Comme indiqué précédemment, les accolades sont également utilisées pour marquer le
début et la fin d'un bloc d'instructions. L'utilisation des accolades avec les fonctions est
une extension naturelle, car une fonction se compose d'une ou de plusieurs instructions.

Le corps de fonction
Le corps d'une fonction est l'endroit qui contient les déclarations de variables et d'autres
instructions C. La tâche d'une fonction est accomplie en exécutant les instructions à
l'intérieur du corps de la fonction, une à la fois.
Il est important de se rappeler que toute déclaration de variable doit être placée au
début du corps de la fonction. Il est illégal de placer des déclarations de variables
ailleurs qu'au tout début d'un bloc d'instructions.

Si le corps de votre fonction contient des déclarations de variables, elles


doivent toutes être placées en premier, avant toute autre déclaration.

La liste 3.2 présente une fonction qui additionne deux entiers spécifiés par ses arguments
et renvoie le résultat de l'addition.
Apprendre la structure d'un 49
programme C

LISTE 3.2 Une fonction qui additionne deux entiers


1 : /* 03L01.c : Cette fonction additionne deux entiers et renvoie le
résultat */ 2 : int integer_add( int x, int y )
3 : {
4 : résultat int ;
5: résultat = x + y ;
6 : retour du résultat ;
7 : }

Comme vous l'avez appris dans l'Heure 2, la ligne 1 du Listing 3.1 est un
ANALYSE
commentaire qui décrit ce que la fonction peut faire.
À la ligne 2, vous voyez que le type de données int est préfixé avant le nom de la
fonction. Ici, int est utilisé comme type de fonction, ce qui signifie que la fonction
renvoie un entier. Le nom de la fonction indiqué à la ligne 2 est integer_add. La liste 3
d'arguments contient deux arguments, int x et int y, à la ligne 2, où le type de données
int spécifie que les deux arguments sont tous deux des entiers.

La ligne 3 contient l'accolade ouvrante ({) qui marque le début de la fonction.


Le corps de la fonction se trouve aux lignes 4 à 6 du Listing 3.1. La ligne 4 donne la
déclaration de la variable result, qui est spécifiée par le type de données int en tant
qu'entier. L'instruction de la ligne 5 additionne les deux entiers représentés par x et y et
affecte le résultat du calcul à la variable result. L'instruction return de la ligne 6
renvoie ensuite le résultat du calcul représenté par result.
Enfin, l'accolade fermante (}) de la ligne 7 est utilisée pour fermer la fonction.

Lorsque vous créez une fonction dans votre programme C, ne lui confiez
pas trop de travail. Si une fonction a trop de travail, elle sera très difficile à
écrire et à déboguer. Si vous avez un projet de programmation complexe,
divisez-le en plusieurs parties. Faites de votre mieux pour que chaque
fonction n'ait qu'une seule tâche à accomplir.

Appels de fonctions
Sur la base de ce que vous avez appris jusqu'à présent, vous pouvez écrire un programme
C qui appelle la fonction integer_add() pour calculer une addition et imprimer le
résultat à l'écran. Un exemple de programme de ce type est présenté dans la liste 3.3.
50 Heure
3

LISTE 3.3 Un programme C qui calcule une addition et imprime le résultat à


l'écran

1 : /* 03L02.c : Calculer une addition et imprimer le résultat


*/ 2 : #include <stdio.h>
3 : /* Cette fonction additionne deux entiers et renvoie le
résultat */ 4 : int integer_add( int x, int y )
5 : {
6 : résultat int ;
7: résultat = x + y ;
8 : retour du résultat ;
9 : }
10 :
11 : int main()
12 : {
13 : somme int ;
14 :
15 : sum = integer_add(5, 12) ;
16 : printf("L'addition de 5 et 12 est %d.\n", sum)
; 17: return 0 ;
18 : }

Le programme de la liste 3.2 est enregistré sous la forme d'un fichier source appelé
03L02.c. Une fois ce programme compilé et lié, un fichier exécutable pour 03L02.c est
créé. Sur ma machine, le fichier exécutable s'appelle 03L02.exe. Voici la sortie imprimée
à l'écran après l'exécution du fichier exécutable sur ma machine :
L'addition de 5 et de 12 est égale à 17.
SORTIE

La ligne 1 du Listing 3.2 est un commentaire sur le programme. Comme vous


ANALYSE
l'avez appris au cours de l'heure 2, la directive include de la ligne 2 inclut le
fichier d'en-tête stdio.h en raison de la directive
la fonction printf() dans le programme.
Les lignes 3 à 9 représentent la fonction integer_add() qui additionne deux entiers,
comme nous l'avons vu dans la section précédente.
La fonction main(), préfixée par le type de données int, commence à la ligne 11. Les
lignes 12 et 18 contiennent respectivement l'accolade ouvrante et l'accolade fermante de
la fonction main(). Une variable entière, sum, est déclarée à la ligne 13.
L'instruction de la ligne 15 appelle la fonction integer_add() que vous avez examinée
dans la section précédente. Notez que deux constantes entières, 5 et 12, sont passées à la
fonction integer_add(), et que la variable sum se voit attribuer le résultat de retour de
la fonction
fonction integer_add().
Apprendre la structure d'un 51
programme C

Vous avez vu pour la première fois la fonction printf() de la bibliothèque standard


C dans l'heure 2. Si vous pensiez avoir trouvé quelque chose de nouveau ajouté à la
fonction à la ligne 16, vous aviez raison. Cette fois, deux arguments sont passés à la
fonction printf(). Il s'agit de la chaîne de caractères "L'addition de 5 et 12
est %d.\n" et de la variable sum.

Notez qu'un nouveau symbole, %d, est ajouté au premier argument. Le deuxième
argument est la variable entière sum. Comme la valeur de sum va être imprimée à
l'écran, vous pourriez penser que le %d a quelque chose à voir avec la variable entière
sum. Vous avez encore raison. Le %d indique à l'ordinateur le format dans lequel la somme
doit être imprimée à l'écran.
La relation entre %d et sum est abordée dans l'heure 5, "Manipulation des entrées et
sorties standard".
Plus important encore, vous devez vous concentrer sur le programme de la liste 3.2 et
prêter attention à la manière d'appeler une fonction générée par l'utilisateur ou une 3
fonction de la bibliothèque C standard à partir de la fonction main().

Résumé
Dans cette leçon, vous avez appris les concepts et opérateurs importants suivants :
• Une constante en C est une valeur qui ne change jamais. Une variable, en
revanche, peut présenter différentes valeurs.
• Une combinaison de constantes, de variables et d'opérateurs s'appelle une
expression dans le langage C. Une expression est utilisée pour désigner différents
calculs.
• Les opérateurs arithmétiques comprennent +, -, *, / et %.
• Une instruction consiste en une expression complète suivie d'un point-virgule.
• Le compilateur C traite un bloc d'instructions comme une seule instruction, bien
que le bloc d'instructions puisse contenir plus d'une instruction.
• Le type de fonction spécifié dans la déclaration d'une fonction détermine le type
de la valeur renvoyée par la fonction.
• Vous devez respecter certaines règles pour créer un nom de fonction valide.
• Un argument contient des informations que vous souhaitez transmettre à une
fonction. Une liste d'arguments contient deux ou plusieurs arguments séparés
par des virgules.
• L'accolade ouvrante ({) et l'accolade fermante (}) sont utilisées pour marquer le
début et la fin d'une fonction C.
• Le corps d'une fonction contient des déclarations de variables et des instructions.
• En général, une fonction ne doit accomplir qu'une seule tâche.
52 Heure
Dans la prochaine leçon, vous en apprendrez3plus sur les types de données dans le langage C.
Apprendre la structure d'un 53
programme C

Q&R
Q Quelle est la différence entre une constante et une variable ?
R La principale différence est que la valeur d'une constante ne peut pas être modifiée,
alors que la valeur d'une variable peut l'être. Vous pouvez attribuer différentes
valeurs à une variable chaque fois que cela est nécessaire dans votre programme C.
Q Pourquoi avez-vous besoin d'un bloc de déclaration ?
A Plusieurs mots-clés C ne peuvent contrôler qu'une seule instruction. Un bloc
d'instructions permet de rassembler plusieurs instructions et de placer le bloc
d'instructions sous le contrôle d'un mot-clé C. Le bloc d'instructions est alors traité
comme une seule instruction. Le bloc d'instructions est alors traité comme une seule
instruction.
Q Quels sont les opérateurs arithmétiques les plus prioritaires ?
A Parmi les cinq opérateurs arithmétiques, les opérateurs de multiplication, de
division et de reste ont une priorité plus élevée que les opérateurs d'addition et de
soustraction.
Q Combien de parties une fonction comporte-t-elle normalement ?
A Une fonction comporte normalement six parties : le type de fonction, le nom de la
fonction, les arguments, l'accolade d'ouverture, le corps de la fonction et
l'accolade de fermeture.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe D, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Dans le langage C, 74 est-il une constante ? Et 571 ?
2. x = 570 + 1 est-il une expression ? Et x = 12 + y ?
3. Les noms de fonctions suivants sont-ils valables ?
2méthodes

m2_algorithme

*Taille de la

pièce

.End_Exe
_turbo_add
54 Heure
4. Est-ce que 2 + 5 * 2 est égal à3(2 + 5) * 2 ?
5. Est-ce que 7 % 2 donne le même résultat que 4 % 3 ?
Apprendre la structure d'un 55
programme C

Exercices
1. Étant donné deux énoncés, x = 3 ; et y = 5 + x ;, comment pouvez-vous
construire un bloc d'énoncés avec ces deux énoncés ?
2. Quel est le problème avec la fonction suivante ?
int 3integer_add( int x, int y, int z)
{
int sum ;
sum = x + y + z ;
return sum ;
}

3. Quel est le problème avec la fonction suivante ?


int integer_add( int x, int y, int z)
{
int sum ; 3
sum = x + y + z
return sum ;
}

4. Écrire une fonction C capable d'effectuer une multiplication de deux nombres


entiers et de renvoyer le résultat calculé.
5. Ecrivez un programme C qui appelle la fonction C que vous venez d'écrire dans
l'exercice 4 pour calculer la multiplication de 3 par 5, puis imprimez la valeur de
retour de la fonction à l'écran.
HEURE 4
Comprendre les
types de données et
les mots-clés
Qu'est-ce qu'un nom ? Ce que nous appelons
une rose par n'importe quel autre nom
sentirait bon.
-W. Shakespeare
Vous avez appris à donner un nom valide à une fonction C dans l'heure 3,
"Apprendre la structure d'un programme C". Dans cette heure, vous allez en
apprendre davantage sur la façon de nommer une variable et sur les mots-
clés C réservés par le compilateur C.
Au cours de cette heure, vous apprendrez également à connaître en détail les
quatre types de données du langage C :
• type de données char
• type de données int
• type de données float
• type de données double
56 Heure
4

C Mots-clés
Le langage C réserve certains mots qui ont une signification particulière pour le langage.
Ces mots réservés sont parfois appelés mots-clés C. Vous ne devez pas utiliser les mots-
clés C pour vos propres noms de variables, de constantes ou de fonctions dans vos
programmes. Le tableau 4.1 dresse la liste des 32 mots-clés C réservés.

TABLEAU 4.1 Mots-clés réservés en C


Mot-clé Description de l'activité
auto Spécification de la classe de stockage
pause Déclaration
dossier Déclaration
char Spécificateur de type
const Modificateur de la classe de stockage
continuer Déclaration
par défaut Étiquette
faire Déclaration
double Spécification de type
autre Déclaration
enum Spécification de type
externe Spécificateur de classe de stockage
float (flotteur) Spécification de type
pour Déclaration
obtenir Déclaration
si Déclaration
int Spécification de type
long Spécification de type
enregistrer Spécification de la classe de stockage
retour Déclaration
court Spécification de type
signé Spécification de type
taille de Opérateur
statique Spécification de la classe de stockage
struct Spécificateur de type
interrupteur Déclaration
Comprendre les types de données et les 57
mots-clés

Mot-clé Description de l'activité


typedef Déclaration
union Spécificateur de type
non signé Spécification de type
vide Spécification de type
volatile Modificateur de la classe de stockage
tout en Déclaration

Ne vous inquiétez pas si vous ne vous souvenez pas de tous les mots-clés C la première
fois. Dans la suite du livre, vous vous familiariserez avec eux et commencerez à utiliser
de nombreux mots-clés au travers d'exemples et d'exercices.
Notez que tous les mots-clés C sont écrits en minuscules. Comme je l'ai mentionné, le
langage C est sensible à la casse. Par conséquent, int, tel qu'il apparaît dans la liste, est
considéré comme un mot-clé C, mais INT ne l'est pas.

Le type de données char 4


Un objet du type de données char représente un seul caractère du jeu de caractères utilisé
par votre ordinateur. Par exemple, A est un caractère, tout comme a. Mais 7 est un
nombre.
Cependant, un ordinateur ne peut stocker que des codes numériques. Par conséquent, les
caractères tels que A, a, B, b, etc. ont tous un code numérique unique qui est utilisé
par les ordinateurs pour représenter les caractères. En général, un caractère prend 8 bits
(c'est-à-dire 1 octet) pour stocker son code numérique.
Pour de nombreux ordinateurs, les codes ASCII sont les codes standard de facto pour
représenter un jeu de caractères. (ASCII, pour information, signifie American Standard
Code for Information Interchange). Le jeu de caractères ASCII original ne comporte que
128 caractères car il utilise les 7 bits inférieurs qui peuvent représenter 27 (c'est-à-dire 128)
caractères.
Sur les PC compatibles IBM, cependant, le jeu de caractères est étendu pour contenir un
total de 256 (c'est-à-dire 28) caractères.

Une chose que j'aimerais mentionner ici est que la norme ANSI C ne spécifie
que la valeur du caractère nul, qui est toujours zéro (c'est-à-dire un octet
dont tous les bits sont à 0.) Les valeurs numériques des autres caractères
sont déterminées par les types d'ordinateurs, les systèmes d'exploitation et
les compilateurs C. Nous vous encourageons à explorer le jeu de caractères
de l'ordinateur que vous utilisez. Vous pouvez le faire avec le programme de
la liste 4.1.
58 Heure
4

Dans le monde informatique, un bit est la plus petite unité de stockage de


données, et il ne peut avoir que l'une des deux valeurs suivantes : 0 ou 1.
Ces valeurs représentent deux états des commutateurs électroniques utilisés
dans la mémoire et l'unité centrale de l'ordinateur. Un octet est une unité
plus grande qu'un bit. En fait, huit bits sont égaux à un octet.

Variables de caractère
Une variable qui peut représenter différents caractères est appelée variable de caractère.
Vous pouvez définir le type de données d'une variable comme étant char en utilisant le format de
déclaration suivant :
char variablename ;

où nomvariable est le nom que vous fournissez pour stocker les valeurs de ce type.
Si vous avez plus d'une variable à déclarer, vous pouvez utiliser le format suivant :
char variablename1
; char
variablename2 ;
char variablename3 ;

ou celui-ci :
char variablename1, variablename2, variablename3 ;

Par exemple, l'instruction suivante déclare Moncaractère et lui attribue la

valeur "A" : char Moncaractère = "A" ;

De même, les instructions suivantes déclarent x, y et z en tant que variables de


caractère, puis leur attribuent des valeurs :
char x, y, z
; x = 'A' ;
y = "f" ;
z = '7' ;

Notez que la dernière affectation, z = '7', définit z comme étant égal à la valeur
numérique représentant le caractère '7' dans le jeu de caractères - et non le nombre réel
7.

Vous en apprendrez plus sur la variable caractère et sur la façon de l'utiliser dans vos
programmes C plus loin dans ce livre.

Constantes de caractères
Un caractère entre guillemets simples (') est appelé constante de caractère. Par exemple, un caractère entre
guillemets simples (') est appelé une constante de caractère,
Comprendre les types de données et les 59
mots-clés
A", "a", "B" et "b" sont des constantes de caractères qui ont leur propre valeur numérique.
60 Heure
4

dans un jeu de caractères donné. Par exemple, vous pouvez voir les valeurs numériques
uniques du jeu de caractères ASCII.
Il est important de se rappeler que les constantes de caractère sont toujours entourées de
guillemets simples (') alors qu'une chaîne de plus d'un caractère est entourée de
guillemets doubles ("). Si cela vous semble confus, rappelez-vous simplement que les
guillemets simples vont avec les caractères simples. Vous avez vu un exemple de
guillemets doubles et de chaînes de caractères avec les appels à la fonction printf()
dans l'heure précédente.
D'après le jeu de caractères ASCII, vous constaterez que les valeurs numériques
(décimales) uniques de "A", "a", "B" et "b" sont respectivement 65, 97, 66 et 98. Par
conséquent, étant donné que x est une variable de caractère et que le jeu de caractères
ASCII est donné, par exemple, les deux instructions d'affectation suivantes sont
équivalentes :
x = 'A'
; x =
65 ;

Il en va de même pour les deux affirmations suivantes :


x = 'a'
; x =
97 ;

Plus tard dans cette heure, vous verrez un programme, présenté dans le Listing 4.2, qui
4
convertit les valeurs numériques en caractères correspondants.

Ne confondez pas x = 'a' ; avec x = a ;. La première instruction


affecte la valeur numérique du caractère a à la variable x, c'est-à-dire
que x contiendra la valeur 97 (la valeur ASCII de la lettre "a") après
l'affectation. L'instruction x = a ; affecte cependant la valeur contenue
dans la variable a à la variable x.
Vous en apprendrez plus sur cette différence plus loin dans ce livre.

Le personnage de l'évasion (\)


En fait, vous avez vu le caractère d'échappement (\N) pour la première fois dans l'Heure
2, "Votre premier programme C", lorsque vous avez appris à utiliser le caractère de retour
à la ligne (\N) pour séparer un message en deux parties.
Par conséquent, la barre oblique inverse (\) est appelée le caractère d'échappement dans
le jeu de caractères ASCII. Le caractère d'échappement est utilisé dans le langage C pour
indiquer à l'ordinateur qu'un caractère spécial suit.
Par exemple, lorsque l'ordinateur voit \ dans le caractère de retour à la ligne \n, il
sait que le caractère suivant, n, provoque une séquence de retour à la ligne et de saut
Comprendre les types de données et les 61
de ligne. mots-clés
62 Heure
4

Outre le caractère de retour à la ligne, les autres caractères spéciaux du langage C sont les
suivants :

Caractère Description du personnage


\b Le caractère de retour arrière ; déplace le curseur d'un caractère vers la gauche.
\f Le caractère de saut de page ; va au début d'une nouvelle page.
\r Le caractère de retour ; retourne au début de la ligne en cours.
\t Le caractère de tabulation ; permet de passer au taquet de tabulation suivant.

Impression de caractères
Vous savez déjà que la fonction printf(), définie dans le fichier d'en-tête C stdio.h,
peut être utilisée pour imprimer des messages à l'écran. (Dans cette section, vous allez
apprendre à utiliser le spécificateur de format de caractère, %c, qui indique à la fonction
printf() que l'argument à imprimer est un caractère. (Vous en apprendrez plus sur le
spécificateur de format dans l'Heure 5, "Manipuler les entrées et sorties standard". Ici,
vous n'avez qu'à vous familiariser avec le sujet). La chose importante à savoir pour
l'instant est que chaque spécificateur de format dans la chaîne que vous passez à
printf() correspondra à l'une des variables que vous passez à la fonction. Examinons
d'abord le programme de la liste 4.1, qui imprime des caractères à l'écran.

TYPE LISTE 4.1 Impression de caractères à l'écran


1 : /* 04L01.c : Impression de caractères
*/ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char c1 ;
7 : char c2 ;
8 :
9: c1 = 'A' ;
10: c2 = 'a' ;
11 : printf("Convert the value of c1 to character : %c.\n",
c1) ; 12 : printf("Convert the value of c2 to character :
%c.\n", c2) ; 13 : return 0 ;
14 : }

Une fois que le fichier exécutable 04L01.c de la liste 4.1 est créé, vous pouvez
l'exécuter pour voir ce qui sera imprimé à l'écran. Sur mon ordinateur, le fichier
exécutable s'appelle 04L01.exe. Voici la sortie imprimée sur l'écran de mon ordinateur
après l'exécution de l'exécutable :
Comprendre les types de données et les 63
mots-clés

Convertir la valeur de c1 en caractère


SORTIE : A. Convertir la valeur de c2 en
caractère : a.

Comme vous le savez, la ligne 2 inclut le fichier d'en-tête, stdio.h, pour la


ANALYSE
fonction printf(). Les lignes 5 à 15 constituent le corps de la fonction main().
Les lignes 6 et 7 déclarent deux variables de caractère, c1 et c2, tandis que les lignes 9 et 10 affectent c1
et c2 avec les constantes de caractères "A" et "a", respectivement.
Notez que le spécificateur de format %c est utilisé dans la fonction printf() aux lignes
11 et 12, ce qui indique à l'ordinateur que le contenu de c1 et c2 doit être imprimé sous
forme de caractères. Lorsque les deux instructions des lignes 11 et 12 sont exécutées,
deux caractères sont formatés et affichés à l'écran, en fonction des valeurs numériques
contenues dans c1 et c2, respectivement.
Examinez maintenant le programme présenté dans le Listing 4.2. Cette fois, %c est utilisé
pour reconvertir les valeurs numériques en caractères correspondants.

TYPE LISTE 4.2 Reconversion des valeurs numériques en caractères


1 : /* 04L02.c : Conversion de valeurs numériques en caractères
*/ 2 : #include <stdio.h> 4
3 :
4 : main()
5 : {
6 : char c1 ;
7 : char c2 ;
8 :
9: c1 =65 ;
10: c2 =97 ;
11 : printf("Le caractère dont la valeur numérique est 65 est : %c.\n",
c1) ; 12 : printf("Le caractère dont la valeur numérique est 97 est :
%c.\n", c2) ; 13 : return 0 ;
14 : }

Voici la sortie imprimée sur l'écran de mon ordinateur après avoir exécuté le fichier
exe- cutable, 04L02.exe. (Il se peut que votre ordinateur affiche une sortie différente ;
cela dépend de l'implémentation. Cela dépend du type d'ordinateur, du système
d'exploitation et de l'ordinateur C que vous utilisez) :
Le caractère qui a la valeur numérique de 65 est : A.
SORTIE Le caractère dont la valeur numérique est 97 est : a

Le programme du Listing 4.2 est similaire à celui du Listing 4.1 à l'exception des
ANALYSE
deux instructions des lignes 9 et 10. Notez que dans les lignes 9 et 10 de
l'énumération 4.2, le char-
Les variables c1 et c2 sont respectivement affectées à 65 et 97.
64 Heure
4

Comme vous le savez, 65 est la valeur numérique (décimale) du caractère A dans le jeu
de caractères ASCII ; 97 est la valeur numérique de a. Aux lignes 11 et 12, le
spécificateur de format %c convertit les valeurs numériques 65 et 97 en A et a,
respectivement. Les caractères A et a sont ensuite imprimés à l'écran.

Le type de données
int
Le mot-clé int est utilisé pour spécifier que le type de données d'une variable est un
entier. Les nombres entiers sont également appelés nombres entiers, qui n'ont pas de
partie fractionnaire ni de virgule décimale. Par conséquent, le résultat d'une division
entière est tronqué, simplement parce que toute partie fractionnaire est ignorée.
La longueur d'un entier varie en fonction du système d'exploitation et du compilateur C
que vous utilisez. Sur la plupart des stations de travail UNIX, par exemple, un entier a
une longueur de 32 bits, ce qui signifie que la plage d'un entier est comprise entre
2147483647 (c'est-à-dire 231-1)
à -2147483648. La plage d'un entier de 16 bits est comprise entre 32767 (c'est-à-dire
215-1) et -32768. Là encore, cela peut varier d'un système à l'autre. Vous pouvez donc
consulter les documents de référence de votre compilateur pour vous en assurer.
Certains compilateurs C, comme Visual C++ 1.5, ne fournissent que des entiers de 16
bits, tandis que d'autres compilateurs C 32 bits, comme Visual C++ 5.0, prennent en
charge les entiers de 32 bits.

Déclaration de variables entières


Vous avez également vu la déclaration d'un entier dans l'heure 3. Le tableau suivant
montre le format de base de la déclaration :
int variablename ;

Comme pour la déclaration de caractères, si vous avez plus d'une variable à déclarer,
vous pouvez utiliser le format suivant :
int variablename1
; int
variablename2 ;
int variablename3 ;

ou comme ceci :
int variablename1, variablename2, variablename3 ;

Ici, variablename1, variablename2 et variablename3 indiquent les endroits où vous


mettez les noms des variables int.
Par exemple, l'instruction suivante déclare MyInteger comme une variable entière et lui
Comprendre les types de données et les 65
mots-clés
attribue une valeur :
int MyInteger = 2314 ;
66 Heure
4

De même, l'instruction suivante déclare A, a, B et b comme des variables entières :


int A, a, B, b
;
A = 37 ;
a = -37 ;
B = -2418
; b = 12 ;

Vous en apprendrez plus sur le type de données "integer" plus loin dans le livre.

Afficher les valeurs numériques des caractères


Comme le spécificateur de format de caractère (%c) qui est utilisé pour formater un seul
caractère, %d, appelé spécificateur de format d'entier, est utilisé pour formater un entier.
Vous vous souvenez peut-être qu'à la ligne 16 du Listing 3.2, %d est utilisé dans la
fonction printf() pour formater le deuxième argument de la fonction en un entier.
Dans cette section, vous allez étudier un programme, présenté dans le Listing 4.3, qui
peut imprimer les valeurs numériques des caractères en utilisant le spécificateur de
format entier %d avec printf().

TYPE LISTE 4 .3 Affichage des valeurs numériques des caractères


1 : /* 04L03.c : Afficher les valeurs numériques des 4
caractères */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char c1 ;
7 : char c2 ;
8 :
9: c1 = 'A' ;
10: c2 = 'a' ;
11 : printf("La valeur numérique de A est :
%d.\n", c1) ; 12 : printf("La valeur numérique de
a est : %d.\n", c2) ; 13 : return 0 ;
14 : }

J'obtiens le résultat suivant sur l'écran de mon ordinateur après avoir exécuté le fichier
exécutable, 04L03.exe. (Vous pouvez obtenir un résultat différent si votre machine
n'utilise pas le jeu de caractères ASCII).
La valeur numérique de A est : 65.
SORTIE La valeur numérique de a est : 97.

Vous constaterez peut-être que le programme de la liste 4.3 est assez similaire à celui de la liste
ANALYSE
4.1. En fait, j'ai simplement copié le code source du Listing 4.1 dans le fichier
Listing 4.3 et j'ai apporté des modifications aux lignes 11 et 12. La principale
modification a consisté à remplacer le spécificateur de format de caractère (%c) par le
Comprendre les types de données et les 67
mots-clés
spécificateur de format d'entier (%d).
68 Heure
4

Les deux spécificateurs de format font essentiellement la même chose - insérer des
données dans la chaîne que vous passez à printf() - mais la différence réside dans la
manière dont printf() affiche ces données. Le spécificateur %c imprime toujours un
caractère ; le spécificateur %d imprime toujours un nombre. Même s'ils se réfèrent
exactement aux mêmes données, celles-ci seront imprimées de la manière indiquée dans
le spécificateur de format, quel que soit le type de données réel.
Les deux instructions des lignes 11 et 12 formatent les deux variables de caractères (c1 et
c2) en utilisant le spécificateur de format entier %d, puis impriment deux messages
indiquant les valeurs numériques 65 et 97 qui représentent, respectivement, les
caractères A et a dans le jeu de caractères ASCII.

Le type de données float


Le nombre à virgule flottante est un autre type de données dans le langage C.
Contrairement à un nombre entier, un nombre à virgule flottante contient un point
décimal. Par exemple, 7,01 est un nombre à virgule flottante, tout comme 5,71 et -3,14.
Un nombre à virgule flottante est également appelé nombre réel.
Un nombre à virgule flottante est spécifié par le mot-clé float dans le langage C. Les
constantes à virgule flottante peuvent être suffixées par f ou F pour spécifier float. Un
nombre à virgule flottante sans suffixe est double par défaut. Le type de données double
est présenté plus loin dans cette leçon.
Comme un nombre entier, un nombre à virgule flottante a une plage limitée. La norme
ANSI exige que cette plage soit au moins égale à plus ou moins 1,0 × 1037. Dans la plupart
des cas, un nombre à virgule flottante est représenté en prenant 32 bits. Par conséquent,
un nombre à virgule flottante en C a une précision d'au moins six chiffres. En d'autres
termes, pour un nombre flottant, il y a au moins six chiffres (ou décimales) à droite de la
virgule.
Contrairement à une division entière dont le résultat est tronqué et la fraction est dis-
cartée, une division en virgule flottante produit un autre nombre en virgule flottante. Une
division en virgule flottante est effectuée si le diviseur et le dividende, ou l'un d'entre eux,
sont des nombres en virgule flottante.
Par exemple, 571,2 / 10,0 produit un autre nombre à virgule flottante, 57,12. Il en va de même pour
571,2
/ 10 et 5712 / 10.0.

Déclaration de variables à virgule flottante


Le tableau suivant présente le format de déclaration d'une variable à virgule flottante :
float variablename ;
Comprendre les types de données et les 69
mots-clés

Comme pour la déclaration de caractères ou d'entiers, si vous avez plus d'une variable à
déclarer, vous pouvez utiliser le format suivant :
float variablename1
; float
variablename2 ;
float variablename3 ;

ou comme la suivante :
float variablename1, variablename2, variablename3 ;

Par exemple, l'instruction suivante déclare myFloat comme variable float et lui attribue une
valeur :
float myFloat = 3.14 ;

De même, l'instruction suivante déclare a, b et c comme variables flottantes :

float a, b, c ;
a = 10.38 ;
b = -32.7 ;
c = 12,0f ;

Le spécificateur de format en virgule flottante (%f)


4
Vous pouvez également utiliser le spécificateur de format en virgule flottante (%f) pour formater votre sortie.
Énumération
Le point 4.4 montre un exemple d'utilisation du spécificateur de format %f avec la fonction printf().

TYPE LISTING 4.4 Impression des résultats de la division en nombres entiers et en virgule
flottante
1 : /* 04L04.c : Divisions en nombres entiers ou en
virgule flottante */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: int int_num1, int_num2, int_num3; /* Déclarer des
variables entières */
7 : float flt_num1, flt_num2, flt_num3 ; /* Déclarer flottant
-variables de point */
8 :
9: int_num1 = 32 / 10; /* Le diviseur et le dividende
sont tous deux des nombres entiers */
10 : flt_num1 = 32 / 10 ;
11 : int_num2 = 32.0 / 10 ; /* Le diviseur est un entier
*/ 12: flt_num2 = 32.0 / 10 ;
13 : int_num3 = 32 / 10.0 ; /* Le dividende est un entier
*/ 14: flt_num3 = 32 / 10.0 ;
15 :
16 : printf("Le divis. entier de 32/10 est : %d\n", int_num1) ;
70 Heure
4 continue
Comprendre les types de données et les 71
mots-clés

LISTE 4.4 suite


17 : printf("Le divis. en virgule flottante de 32/10 est : %f\n",
flt_num1) ; 18 : printf("La division entière de 32.0/10 est :
%d\n", int_num2) ;
19 : printf("Le divis. en virgule flottante de 32.0/10 est : %f\n",
flt_num2) ; 20 : printf("La division entière de 32/10.0 est :
%d\n", int_num3) ;
21 : printf("Le divis. en virgule flottante de 32/10,0 est : %f\n",
flt_num3) ; 22: return 0 ;
23 : }

La sortie suivante est celle de l'écran de mon ordinateur après l'exécution du fichier exécutable,
04L04.exe, est exécuté sur ma machine :

J'ai reçu plusieurs messages d'avertissement concernant les conversions de


type pendant que je composais le programme de la liste 4.4, mais je les ai
tous ignorés parce que je voulais créer un fichier exécutable afin de vous
montrer les différences entre le type de données int et le type de données
float.

Le divis. entier de 32/10 est : 3


SORTIE La division en virgule flottante de 32/10 est :
3.000000 La division entière de 32.0/10 est : 3
La division en virgule flottante de 32.0/10 est :
3.200000 La division entière de 32/10.0 est : 3
La division en virgule flottante de 32/10,0 est : 3.200000

ANALYSE Dans la fonction main(), les deux instructions des lignes 6 et 7 déclarent trois
variables entières, int_num1, int_num2 et int_num3, et trois variables à virgule
flottante, int_num2 et int_num3, et trois variables à virgule flottante, int_num2
et int_num3.
ables, flt_num1, flt_num2 et flt_num3.
Les lignes 9 et 10 affectent le résultat de 32/10 à int_num1 et flt_num1,
respectivement ; 32.0/10 à int_num2 et flt_num2 dans les lignes 11 et 12, et 32/10.0
à int_num3 et flt_num3 dans les lignes 13 et 14.
Ensuite, les lignes 16 à 21 impriment les valeurs contenues dans les trois variables int et
les trois variables à virgule flottante. Notez que %d est utilisé pour les variables entières
et que le spécificateur de virgule flottante (%f) est utilisé pour formater les variables à
virgule flottante dans la fonction printf().
Comme la troncature se produit dans la division entière de 32/10, flt_num1 contient
3.000000, et non 3.200000, comme vous pouvez le voir sur la deuxième ligne de la
sortie. Cependant, flt_num2 et flt_num3 reçoivent 3.200000 parce que 32.0/10 et
32/10.0 sont considérés comme la division en virgule flottante.
72 Heure
4

Mais int_num2 et int_num3, en tant que variables entières, rejettent respectivement les
parties fractionnaires des divisions en virgule flottante de 32.0/10 et 32/10.0. Par
conséquent, vous ne voyez que l'entier 3 dans les troisième et cinquième lignes de la
sortie.

Le type de données double


Dans le langage C, un nombre à virgule flottante peut également être représenté par un
autre type de données, appelé type de données double. En d'autres termes, vous pouvez
spécifier une variable à l'aide du mot-clé double et lui affecter un nombre à virgule
flottante.
La différence entre un type de données double et un type de données flottant est que
le premier utilise deux fois plus de bits que le second. Par conséquent, un nombre flottant
double a une précision d'au moins 10 chiffres, bien que la norme ANSI ne le spécifie
pas pour le type de données double.
Dans l'heure 8, "Utilisation des opérateurs conditionnels", vous apprendrez à utiliser
l'opérateur sizeof pour obtenir la longueur en octets d'un type de données, tel que
char, int, float ou double, spécifié sur votre système informatique.

4
Utiliser la notation scientifique
Le langage C utilise la notation scientifique pour vous aider à écrire de longs nombres à virgule flottante.
En notation scientifique, un nombre peut être représenté par la combinaison de la
mantisse et de l'exposant. Le format de la notation est le suivant : la mantisse est suivie
de l'exposant, qui est préfixé par e ou E. Voici deux exemples :

mantissaeexponent et
mantissaEexponent

Veuillez noter que la mantisse et l'exposant ci-dessus sont des caractères génériques et
que vous devez les remplacer par des valeurs numériques.
Par exemple, 5000 peut être représenté par 5e3 en notation scientifique. De même, -
300 peut être représenté par -3e2, et 0,0025 par 2,5e-3.

De même, le spécificateur de format, %e ou %E, est utilisé pour formater un nombre à


virgule flottante en notation scientifique. L'utilisation de %e ou %E dans la fonction
printf() est identique à celle de %f.
Comprendre les types de données et les 73
mots-clés

Nommer une variable


Il y a quelques points à garder à l'esprit lorsque vous créez vos propres noms de variables.
N'oubliez pas que, puisque les noms de variables sont des identifiants, vous devez suivre
les règles relatives aux identi- fiants qui ont été exposées dans l'heure précédente.
Tout comme les noms de fonctions devraient idéalement refléter la tâche que la fonction
exécute, les noms de variables devraient décrire la valeur stockée dans la variable et son
utilité dans votre programme ou votre fonction. La plupart des exemples de code
présentés jusqu'à présent ont utilisé des noms de variables à une lettre, tels que i, mais
au fur et à mesure que vous écrivez des programmes plus importants et des fonctions plus
compliquées, il devient de plus en plus important de donner des noms significatifs à vos
variables.

N'utilisez jamais les mots-clés réservés au langage C ou les noms des


fonctions de la bibliothèque C standard comme noms de variables dans votre
programme C.

Résumé
Dans cette leçon, vous avez appris à connaître les mots-clés et les types de données C importants suivants :
• Les mots-clés C réservés au langage C
• Le type de données char et le spécificateur de format %c
• Le type de données int et le spécificateur de format %d
• Le type de données float et le spécificateur de format %f
• Les nombres à virgule flottante peuvent être suffixés par f ou F pour indiquer
qu'il s'agit d'un nombre flottant. Un nombre à virgule flottante sans suffixe est
double par défaut.

• Les plages possibles des types de données char, int et float


• Le type de données double
• Notation scientifique et spécificateurs de format %e et %E
• Les règles à suivre pour créer un nom de variable valide
Dans la prochaine leçon, vous en apprendrez davantage sur la fonction printf() et sur
d'autres fonctions permettant de gérer les entrées et les sorties.

Q&R
Q Pourquoi les caractères ont-ils une valeur numérique unique ?
A Les caractères sont stockés dans les ordinateurs sous forme de bits. Les
74 Heure
4 utilisées pour représenter différentes valeurs
combinaisons de bits peuvent être
numériques. Un caractère doit avoir une
Comprendre les types de données et les 75
mots-clés

Le jeu de caractères ASCII est une valeur numérique unique qui permet de se
différencier. De nombreux systèmes informatiques prennent en charge le jeu de
caractères ASCII, qui contient un ensemble de valeurs numériques uniques pour
un maximum de 256 caractères.
Veuillez noter que votre ordinateur peut utiliser un jeu de caractères différent du jeu
de caractères ASCII. Dans ce cas, tous les caractères utilisés par le langage C, à
l'exception du caractère nul, peuvent avoir des valeurs numériques différentes des
valeurs représentées par le jeu de caractères ASCII.
Q Comment déclarer deux variables de caractère ?
R Il y a deux façons de faire la déclaration. La première est
...char nom-variable1, nom-variable2 ;
Le second est
char nom-variable1 ;
char nom-variable2 ;

Q Que sont %c, %d et %f ?


A Il s'agit de spécificateurs de format. %c est utilisé pour obtenir le format des
caractères ; %d pour le format des nombres entiers ; %f pour le format des
nombres à virgule flottante. %c, %d et %f sont souvent utilisés avec des fonctions
C telles que printf().
4
Q Quelles sont les principales différences entre le type de données int (entier) et le type de données
type de données float (virgule flottante) ?
R Tout d'abord, un nombre entier ne contient pas de parties fractionnaires, mais un
nombre à virgule flottante en contient. Un nombre à virgule flottante doit avoir un
point décimal. En C, le type de données float prend plus de bits que le type de
données int. En d'autres termes, le type de données float a une plus grande
plage de valeurs numériques que le type de données int.
De plus, la division entière tronque la partie fractionnaire. Par exemple, la division
entière de 16/10 donne un résultat de 1, et non de 1,6.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe C, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Les divisions entières de 134/100 et 17/10 sont-elles égales ?
2. Le résultat de 3000 + 1,0 est-il un nombre à virgule flottante ? Et 3000/1,0 ?
76 Heure
4

3. Comment pouvez-vous représenter les nombres suivants en notation scientifique ?


• 3500
• 0.0035
• -0.0035
4. Les noms de variables suivants sont-ils valables ?
• 7e_calcul
• Méthode de Tom
• Index
• Label_1

Exercices
1. Ecrivez un programme qui imprime les valeurs numériques des caractères Z et z.
2. Étant donné deux valeurs numériques, 72 et 104, écrivez un programme pour
imprimer les deux caractères correspondants.
3. Pour une variable entière de 16 bits, pouvez-vous assigner à la variable une valeur entière de
72368 ?
4. Étant donné la déclaration double dbl_num = 123.456 ;, écrivez un
programme qui imprime la valeur de dbl_num à la fois en virgule flottante et
en notation scientifique.
5. Ecrivez un programme capable d'imprimer la valeur numérique du caractère de
retour à la ligne (\n). (Astuce : assignez '\n' à une variable de caractère).
HEURE 5
Gestion des entrées et
sorties standard
I/O, I/O, c'est parti pour le travail...
-Les sept nains (en quelque sorte)
Dans la dernière leçon, vous avez appris à imprimer à l'écran des caractères,
des nombres entiers et des nombres en virgule flottante en appelant la
fonction printf(). Dans cette leçon, vous allez en apprendre davantage sur
printf(), ainsi que sur les fonctions suivantes, qui sont nécessaires pour
recevoir les données de l'utilisateur ou pour imprimer la sortie à l'écran :
• La fonction getc()
• La fonction putc()
• La fonction getchar()
• La fonction putchar()
Avant de nous plonger dans ces nouvelles fonctions, commençons par nous
faire une idée des entrées et sorties (E/S) standard en C.
72 Heure
72

Comprendre les E/S standard


Un fichier contient des caractères et des nombres associés. Comme tous les caractères et
les nombres sont représentés en bits sur les ordinateurs et qu'un octet est une série de bits,
le langage C traite un fichier comme une série d'octets. Une série d'octets est également
appelée un flux. En fait, le langage C traite tous les flux de fichiers de la même manière,
bien que certains flux de fichiers puissent provenir d'une unité de disque ou de bande,
d'un terminal ou même d'une imprimante.
En outre, en C, trois flux de fichiers sont pré-ouverts pour vous, c'est-à-dire qu'ils sont
toujours disponibles pour être utilisés dans vos programmes :
• stdin - Entrée standard pour la lecture.
• stdout - La sortie standard pour l'écriture.
• stderr - L'erreur standard pour l'écriture des messages d'erreur.
En général, le flux de fichiers d'entrée standard (stdin) est lié à votre clavier, tandis que
les flux de fichiers de sortie standard (stdout) et d'erreur standard (stderr) sont liés à
l'écran de votre terminal. De plus, de nombreux systèmes d'exploitation permettent à
l'utilisateur de rediriger ces flux de fichiers.
En fait, vous avez déjà utilisé stdout. Lorsque vous avez appelé la fonction printf()
dans la dernière leçon, vous avez envoyé la sortie vers le flux de fichiers par défaut,
stdout, qui pointe vers votre écran.

Vous en apprendrez plus sur stdin et stdout dans les sections suivantes.

Le langage C fournit de nombreuses fonctions pour manipuler la lecture


et l'écriture de fichiers (E/S). Le fichier d'en-tête stdio.h contient les
déclarations de ces fonctions. C'est pourquoi vous devez toujours inclure le
fichier d'en-tête stdio.h dans votre programme C avant de faire quoi que ce
soit avec les E/S de fichiers.

Obtenir des informations de l'utilisateur


De nos jours, la saisie au clavier reste la méthode standard de facto pour entrer des
informations dans un ordinateur. Le langage C dispose de plusieurs fonctions permettant
d'indiquer à l'ordinateur de lire les données saisies par l'utilisateur (généralement au
moyen du clavier).

Utilisation de la fonction getc()


La fonction getc() lit le caractère suivant à partir d'un flux de fichiers et renvoie le
caractère sous la forme d'un entier.
Gestion des entrées et sorties standard 73

Entrée syntaxique
SYNTAX

La syntaxe de la fonction getc() est


#include <stdio.h>
int getc(FILE *stream) ;

Ici, FILE *stream déclare un flux de fichiers (c'est-à-dire une variable). La fonction
renvoie la valeur numérique du caractère lu. En cas de fin de fichier ou d'erreur, la
fonction renvoie
EOF.

Pour l'instant, ne vous préoccupez pas de la structure FILE. Les heures 21, "Lire et
écrire avec des fichiers", et 22, "Utiliser des fonctions de fichiers spéciales", vous
donneront plus de détails à ce sujet. Dans cette section, le flux d'entrée standard stdin
est utilisé comme flux de fichiers spécifié par FILE *stream.

Définie dans le fichier d'en-tête stdio.h, EOF est une constante. EOF
signifie "end-of- file" (fin de fichier). Habituellement, la valeur de EOF est -1.
Mais continuez à utiliser EOF, au lieu de -1, pour indiquer la fin de fichier
dans vos programmes. Ainsi, si vous utilisez ultérieurement une
compilation ou un système d'exploitation qui utilise une valeur différente,

La liste 5.1 montre un exemple qui lit un caractère tapé par l'utilisateur sur le clavier et
l'affiche ensuite à l'écran.

TYPE LISTE 5.1 Lecture d'un caractère saisi par l'utilisateur


1 : /* 05L01.c : Lecture de l'entrée en appelant 5
getc() */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int ch ;
7 :
8 : printf("Please type in one character:\n")
; 9: ch = getc( stdin ) ;
10 : printf("Le caractère que vous venez d'entrer est :
%c\n", ch) ; 11: return 0 ;
12 : }

Voici la sortie affichée sur l'écran de mon ordinateur après avoir exécuté le fichier exe-
cutable, 05L01.exe, saisi le caractère H et appuyé sur la touche Entrée :
Veuillez saisir un seul caractère :
SORTIE H
Le caractère que vous venez de saisir est : H
74 Heure
74

ANALYSE Vous voyez à la ligne 2 du Listing 5.1 que le fichier d'en-tête stdio.h est inclus
pour les fonctions getc() et printf() utilisées dans le programme. Les lignes
4 à 12 indiquent les fonctions
le nom et le corps de la fonction main().
À la ligne 6, une variable entière, ch, est déclarée ; la valeur de retour de la fonction
getc() lui est attribuée à la ligne 9. La ligne 8 imprime un message demandant à
l'utilisateur de saisir un caractère au clavier. Comme je l'ai mentionné plus tôt dans cette
leçon, l'appel à la fonction printf() à la ligne 8 utilise la sortie standard par défaut
stdout pour afficher les messages à l'écran.

À la ligne 9, le flux d'entrée standard stdin est transmis à la fonction getc(), qui
indique que le flux de fichiers provient du clavier. Après que l'utilisateur a saisi un
caractère, la fonction getc() renvoie la valeur numérique (c'est-à-dire un nombre
entier) du caractère. Vous voyez qu'à la ligne 9, la valeur numérique est affectée à la
variable entière ch.
Ensuite, à la ligne 10, le caractère saisi par l'utilisateur est affiché à l'écran à l'aide de la
fonction printf(). Notez que le spécificateur de format de caractère (%c) est utilisé
dans l'appel de la fonction printf() à la ligne 10. (L'exercice 1 de cette leçon vous
demande d'utiliser %d dans un programme pour imprimer la valeur numérique d'un
caractère saisi par l'utilisateur).

Utilisation de la fonction getchar()


Le langage C fournit une autre fonction, getchar(), qui remplit une fonction similaire
à getc(). Plus précisément, la fonction getchar() est équivalente à getc(stdin).

Entrée syntaxique
SYNTAX

La syntaxe de la fonction getchar() est


#include <stdio.h>
int getchar(void) ;

Ici, void indique qu'aucun argument n'est nécessaire pour appeler la fonction. La fonction
renvoie la valeur numérique du caractère lu. En cas de fin de fichier ou d'erreur, la
fonction renvoie EOF.
Le programme de la liste 5.2 montre comment utiliser la fonction getchar() pour lire
les données de l'utilisateur.

TYPE
LISTE 5.2 Lecture d'un caractère en appelant getchar()
1 : /* 05L02.c : Lecture de l'entrée en appelant
getchar() */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int ch1, ch2
Gestion des entrées et sorties standard 75
;
7 :
76 Heure
76

8 : printf("Please type in two characters together:\n")


; 9: ch1 = getc( stdin ) ;
10 : ch2 = getchar( ) ;
11 : printf("Le premier caractère que vous venez d'entrer est :
%c\n", ch1) ; 12 : printf("Le deuxième caractère que vous
venez d'entrer est : %c\n", ch2) ; 13: return 0 ;
14 : }

Après avoir lancé le fichier exécutable, 05L02.exe, et saisi deux caractères (H et i)


ensemble sans espace, j'appuie sur la touche Entrée et la sortie suivante s'affiche sur
l'écran de mon ordinateur :
Veuillez saisir deux caractères à la fois :
SORTIE Bonjour
Le premier caractère que vous venez de
saisir est : H Le deuxième caractère que
vous venez de saisir est : i

Le programme de l'illustration 5.2 est assez similaire à celui de l'illustration 5.1,


ANALYSE
sauf que celui-ci lit deux caractères.
L'instruction de la ligne 6 déclare deux entiers, ch1 et ch2. La ligne 8 affiche un message
demandant à l'utilisateur de saisir deux caractères ensemble.
Ensuite, les fonctions getc() et getchar() sont appelées respectivement aux lignes 9 et
10 pour lire les deux caractères saisis par l'utilisateur. Notez qu'à la ligne 10, rien n'est
transmis à la fonction getchar(). En effet, comme indiqué précédemment, getchar()
utilise le fichier d'entrée par défaut stream-stdin. Vous pouvez remplacer la fonction
getchar() de la ligne 10 par getc(stdin) car getc(stdin) est équivalent à getchar().

Les lignes 11 et 12 envoient deux caractères (respectivement conservés par ch1 et ch2) à l'écran.
5

Impression des données à l'écran


Outre getc() et getchar() pour la lecture, le langage C fournit également deux fonctions,
putc() et putchar(), pour l'écriture. Les deux sections suivantes présentent ces fonctions.

Utilisation de la fonction putc()


La fonction putc() écrit un caractère dans le flux de fichiers spécifié, qui, dans votre
cas, est la sortie standard pointant vers votre écran.
Gestion des entrées et sorties standard 77

SYNTAX Entrée syntaxique


La syntaxe de la fonction putc() est
#include <stdio.h>
int putc(int c, FILE *stream) ;

Ici, le premier argument, int c, indique que la sortie est un caractère enregistré dans une
variable entière c ; le deuxième argument, FILE *stream, spécifie un flux de fichiers. En
cas de succès,
putc() renvoie le caractère écrit, sinon il renvoie EOF.

Dans cette leçon, la sortie standard stdout est spécifiée comme flux de fichier de sortie
dans putc(). La fonction putc() est utilisée dans le Listing 5.3 pour afficher le caractère
A à l'écran.

TYPE LISTE 5.3 Mise en place d'un caractère à l'écran


1 : /* 05L03.c : Sortie d'un caractère avec putc()
*/ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int ch ;
7 :
8: ch = 65 ; /* la valeur numérique de A */
9 : printf("Le caractère dont la valeur numérique est 65
est:\n") ; 10: putc(ch, stdout) ;
11 : retour 0 ;
12 : }

La sortie suivante est celle que j'obtiens de ma machine :


Le caractère dont la valeur numérique est 65 est :
SORTIE A

Comme indiqué, le fichier d'en-tête stdio.h, qui contient la déclaration de putc(), est
ANALYSE
inclus dans la ligne 2.
La variable entière, ch, déclarée à la ligne 6, se voit attribuer la valeur numérique 65 à la
ligne 8. La valeur numérique du caractère A est 65 dans le jeu de caractères
ASCII.

La ligne 9 affiche un message pour rappeler à l'utilisateur la valeur numérique du


caractère qui va être affiché à l'écran. Ensuite, la fonction putc() de la ligne 10 affiche
le caractère A à l'écran. Notez que le premier argument de la fonction putc() est la
variable entière (ch) qui contient 65, et le deuxième argument est le flux du fichier de
sortie standard, stdout.
78 Heure
78

Une autre fonction pour écrire : putchar()


Comme putc(), putchar() peut également être utilisée pour afficher un caractère à
l'écran. La seule différence entre les deux fonctions est que putchar() n'a besoin que
d'un seul argument pour contenir le caractère. Il n'est pas nécessaire de spécifier le flux de
fichiers car la sortie standard (stdout) est définie comme flux de fichiers pour
putchar().

Entrée syntaxique
SYNTAX

La syntaxe de la fonction putchar() est


#include <stdio.h>
int putchar(int c) ;

Ici, int c est l'argument qui contient la valeur numérique d'un caractère. La fonction
renvoie EOF en cas d'erreur, sinon elle renvoie le caractère qui a été écrit.
Un exemple d'utilisation de putchar() est présenté dans le Listing 5.4.

TYPE
LISTE 5.4 Sortie de caractères avec putchar().
1 : /* 05L04.c : Sortie de caractères avec putchar()
*/ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : putchar(65) ;
7 : putchar(10) ;
8 : putchar(66) ;
9 : putchar(10) ;
10 : putchar(67) ;
11 : putchar(10) ; 5
12 : retour 0 ;
13 : }

Après avoir exécuté le fichier exécutable, 05L04.exe, j'obtiens le résultat suivant :


A
SORTIE B
La façon d'écrire le programme de la liste 5.4 est un peu différente. Aucune variable
ANALYSE
n'est déclarée dans le programme. Au lieu de cela, des entiers sont passés à
putchar()
directement, comme le montrent les lignes 6 à 11.
Comme vous avez pu le constater, 65, 66 et 67 sont respectivement les valeurs
numériques des caractères A, B et C du jeu de caractères ASCII. Dans l'exercice 5 de
l'heure 4, "Comprendre les types de données et les mots-clés", vous pouvez découvrir que
10 est la valeur numérique du caractère de retour à la ligne (\n).
Gestion des entrées et sorties standard 79

Par conséquent, les lignes 6 et 7 affichent respectivement le caractère A à l'écran et font


démarrer l'ordinateur au début de la ligne suivante. De même, la ligne 8 affiche le
caractère B à l'écran et la ligne 9 commence une nouvelle ligne. Ensuite, la ligne 10
affiche le caractère C à l'écran et la ligne 11 commence une nouvelle ligne. Ainsi, A, B
et C sont placés au début de trois lignes consécutives, comme le montre la section de
sortie.

Réexamen de la fonction printf()


La fonction printf() est la première fonction de la bibliothèque C que vous avez
utilisée dans ce livre pour imprimer des messages à l'écran. printf() est une fonction
très importante en C, cela vaut donc la peine de s'y attarder.

Entrée syntaxique
SYNTAX

La syntaxe de la fonction printf() est


#include <stdio.h>
int printf(const char *format-string, . . .) ;

Ici, const char *format-string est le premier argument qui contient le(s)
spécificateur(s) de format ; ... indique la section d'expression qui contient la (les)
expression(s) à formater selon les spécificateurs de format. Le nombre d'expressions est
déterminé par le nombre de spécificateurs de format dans le premier argument. La
fonction renvoie le nombre d'expressions formatées si elle réussit. Elle renvoie une
valeur négative en cas d'erreur.
La notion de const char * est expliquée plus loin dans ce livre. Pour l'instant,
considérez le premier argument de la fonction printf() comme une chaîne de
caractères (une série de caractères entourés de guillemets doubles) avec quelques
spécificateurs de format à l'intérieur. Par exemple, vous pouvez passer "La somme de
deux entiers %d + %d est : %d.\n" à la fonction comme premier argument.

La figure 5.1 illustre la relation entre la chaîne de format et les expressions. Notez que
les spécificateurs de format et les expressions sont comparés dans l'ordre, de gauche à
droite.

FIGURE 5.1
Format chaîne Expressions
La relation entre la
chaîne de format et
les expressions de la
rubrique
printf().

printf("Une virgule flottante : %f ; Un entier : %d.", 123.45, 12345) ;


80 Heure
80

N'oubliez pas que vous devez utiliser exactement le même nombre d'expressions que le
nombre de spécificateurs de format dans la chaîne de format.
Voici tous les spécificateurs de format qui peuvent être utilisés dans printf() :
%c Spécification du format des caractères.
%d Spécification du format des nombres entiers.
%i Spécification du format des nombres entiers (identique à %d).
%f Spécification du format de la virgule flottante.
%e Le spécificateur de format de notation scientifique (notez le e minuscule).
%E Le spécificateur de format de notation scientifique (notez le E majuscule).
%g Utilise %f ou %e, selon le résultat le plus court.
%G Utilise %f ou %E, selon le résultat le plus court.
%o Spécification du format octal non signé.
%s Spécification du format de la chaîne.
%u Spécification du format d'un entier non signé.
%x Le spécificateur de format hexadécimal non signé (notez le x minuscule).
%X Le spécificateur de format hexadécimal non signé (notez le X majuscule).
%p Affiche l'argument correspondant qui est un pointeur.
%n Enregistre le nombre de caractères écrits jusqu'à présent.
5
%% Produit un signe de pourcentage (%).

Parmi les spécificateurs de format de cette liste, %c, %d, %f, %e et %E ont été présentés
jusqu'à présent. Plusieurs autres sont expliqués plus loin dans ce livre. La section
suivante montre comment convertir des nombres décimaux en nombres hexadécimaux
en utilisant %x ou %X.

Conversion en nombres hexadécimaux


Étant donné que toutes les données d'un ordinateur sont des données binaires (une série
de zéros et de uns), toutes les données avec lesquelles nous travaillons ou que nous
imprimons ne sont en fait qu'une sorte de représentation lisible par l'homme des données
binaires. En tant que programmeur, il est souvent nécessaire de traiter directement les
données binaires, mais il est extrêmement long de déchiffrer une chaîne de zéros et de
uns et d'essayer de les convertir en données numériques ou en données de caractères
significatives.
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

80 Visitez www.DeepL.com/pro
Heure pour en savoir plus.
5

La solution à ce problème est la notation hexadécimale (ou hex), qui est une sorte de
main courte pour représenter les nombres binaires. L'hexadécimal est un compromis
entre le système de numération en base 2 (ou binaire), lisible par les ordinateurs, et le
système en base 10 (ou décimal), qui nous est plus familier. La conversion de nombres
de l'hexadécimal au décimal (ou du binaire à l'hexadécimal) et vice-versa est beaucoup
plus facile (et même plus rapide) que la conversion directe du binaire au décimal ou
vice-versa.
La différence entre un nombre décimal et un nombre hexadécimal est que
l'hexadécimal est un système de numération en base 16. Un nombre hexadécimal peut
être représenté par quatre bits. (24] est égal à 16, ce qui signifie que quatre bits peuvent
produire 16 nombres uniques).
Les nombres hexadécimaux de 0 à 9 utilisent les mêmes symboles numériques que les
nombres décimaux de 0 à 9. A, B, C, D, E et F sont utilisés pour représenter,
respectivement, les nombres 10 à 15 en majuscules. (De même, en minuscules, a, b, c, d,
e et f sont utilisés pour représenter ces nombres hexagonaux. Les majuscules et les
minuscules hexagonales sont interchangeables et ne sont qu'une question de style).
La liste 5.5 donne un exemple de conversion de nombres décimaux en nombres hexadécimaux à l'aide de la
commande
%x ou %X dans la fonction printf().

TYPE LISTE 5.5 Conversion en nombres hexadécimaux


1 : /* 05L05.c : Conversion en nombres
hexagonaux */
2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : printf("Hex(uppercase) Hex(minuscules) Decimal\n") ;
7 : printf("%X %x %d\n", 0, 0, 0) ;
8 : printf("%X %x %d\n", 1, 1, 1) ;
9 : printf("%X %x %d\n", 2, 2, 2) ;
10 printf("%X %x %d\n", 3, 3, 3) ;
:
11 printf("%X %x %d\n", 4, 4, 4) ;
:
12 printf("%X %x %d\n", 5, 5, 5) ;
:
13 printf("%X %x %d\n", 6, 6, 6) ;
:
14 printf("%X %x %d\n", 7, 7, 7) ;
:
15 printf("%X %x %d\n", 8, 8, 8) ;
:
16 printf("%X %x %d\n", 9, 9, 9) ;
:
17 printf("%X %x %d\n", 10, 10, 10) ;
:
18 printf("%X %x %d\n", 11, 11, 11) ;
Gestion des entrées et sorties standard 81

Le résultat suivant est obtenu en exécutant le fichier exécutable, 05L05.exe, sur mon
ordinateur :
Hex(majuscule) Hex(minuscules Décimal
SORTIE ) e
0 0 0
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6
7 7 7
8 8 8
9 9 9
A a 10
B b 11
C c 12
D d 13
E e 14
F f 15
Ne paniquez pas lorsque vous voyez autant d'appels à la fonction printf() dans le Listing
ANALYSE
5.5. En fait, le programme de la liste 5.5 est très simple. Il ne comporte qu'un seul
corps de fonction aux lignes 5 à 23.
La fonction printf() de la ligne 6 imprime un titre contenant trois champs :
Hex(majuscules), Hex(minuscules) et Décimal.

Ensuite, les lignes 7 à 22 impriment les nombres hexadécimaux et décimaux de 0 à 15.


Seize appels à printf() sont effectués pour réaliser ce travail. Chacun des appels
printf() comporte une chaîne de format comme premier argument, suivie de trois 5
entiers comme trois expressions. Notez que les spécificateurs de format hexagonal %X et
%x sont utilisés dans la chaîne de format de chacun des appels printf() pour convertir
les expressions correspondantes au format hexagonal (en majuscules et en minuscules).
En réalité, personne n'écrirait un programme comme celui de la liste 5.5. Au lieu de
cela, une boucle peut être utilisée pour appeler la fonction printf() de manière
répétée. Les boucles (ou itérations) sont présentées dans l'Heure 7, "Travailler avec des
boucles".

Spécification de la largeur de champ minimale


Le langage C permet d'ajouter un nombre entier entre le signe de pourcentage (%) et la
lettre dans un spécificateur de format. L'entier est appelé spécificateur de largeur de
champ minimale parce qu'il spécifie la largeur de champ minimale et garantit que la
sortie atteint la largeur minimale. Par exemple, dans %10f, 10 est un spécificateur de
largeur de champ minimale qui garantit que la sortie a une largeur d'au moins 10
espaces de caractères. Ceci est particulièrement utile lors de l'impression d'une colonne
de chiffres.
82 Heure
5

L'exemple de la liste 5.6 montre comment utiliser le spécificateur de largeur de champ minimale.

TYPE LISTE 5.6 Spécification de la largeur minimale des champs


1 : /* 05L06.c : Spécification de la largeur
minimale du champ */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int num1, num2
;
7 :
8 : num1 = 12 ;
9 : num2 = 12345 ;
10 : printf("%d\n", num1) ;
11 : printf("%d\n", num2) ;
12 : printf("%5d\n", num1) ;
13 : printf("%05d\n", num1) ;
14 : printf("%2d\n", num2) ;
15 : retour 0 ;
16 : }

Voici le résultat que j'obtiens en exécutant le fichier exécutable 05L06.exe : 12


SORTIE 12345
12
00012
12345

ANALYSE Dans le Listing 5.6, deux variables entières, num1 et num2, sont déclarées à la
ligne 6, puis affectées à 12 et 12345, respectivement, aux lignes 8 et 9.
Sans utiliser de spécification de largeur de champ minimale, les lignes 10 et 11
impriment les deux entiers en appelant la fonction printf(). Vous pouvez voir dans la
section de sortie que la sortie de l'instruction de la ligne 10 est 12, ce qui prend deux
espaces de caractères, tandis que la sortie 12345 de la ligne 11 prend cinq espaces de
caractères.
A la ligne 12, une largeur de champ minimale de 5 est spécifiée par %5d. La sortie de la
ligne 12 comporte donc cinq espaces de caractères, dont trois espaces vides et deux
espaces de caractères de 12 (voir la troisième ligne de sortie dans la section sur la
sortie).
Le %05d dans printf(), indiqué à la ligne 13, indique que la largeur minimale du champ
est de 5, et le 0 indique que des zéros sont utilisés pour remplir les espaces. Par
conséquent, le résultat de l'exécution de l'instruction de la ligne 13 est 00012.
Gestion des entrées et sorties standard 83

Le %2d de la ligne 14 définit la largeur minimale du champ à 2, mais vous voyez toujours
la sortie pleine taille de 12345 à la ligne 14. Cela signifie que lorsque la largeur minimale
du champ est inférieure à la largeur de la sortie, c'est cette dernière qui est prise en
compte, et la sortie est toujours imprimée en entier.

Alignement de la production
Comme vous l'avez peut-être remarqué dans la section précédente, tous les textes sont
justifiés à droite. En d'autres termes, par défaut, toutes les sorties sont placées sur le
bord droit du champ, tant que la largeur du champ est supérieure à la largeur de la
sortie.
Vous pouvez modifier cela et forcer la sortie à être justifiée à gauche. Pour ce faire, vous
devez faire précéder le spécificateur de champ minimal du signe moins (-). Par exemple,
%-12d spécifie que la largeur minimale du champ est de 12 et justifie l'édition à partir du
bord gauche du champ.
La liste 5.7 donne un exemple d'alignement de la sortie par une justification à gauche ou à droite.

TYPE LISTE 5.7 Sortie justifiée à gauche ou à droite


1 : /* 05L07.c : Alignement de la
sortie */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int num1, num2, num3, num4, num5
; 7 :
8 : num1 = 1 ;
9 : num2 = 12 ;
10 : num3 = 123 ;
11 :
12 :
num4 = 1234 ;
num5 = 12345 ;
5
13 : printf("%8d %-8d\n", num1, num1)
; 14 : printf("%8d %-8d\n", num2,
num2) ; 15 : printf("%8d %-8d\n",
num3, num3) ; 16 : printf("%8d %-
8d\n", num4, num4) ; 17 : printf("%8d
%-8d\n", num5, num5) ; 18 : return 0 ;
19 : }

J'obtiens la sortie suivante sur l'écran de mon ordinateur après avoir exécuté l'exe-
cutable 05L07.exe :
1 1
SORTIE 12 12
123 123
1234 1234
12345 12345
84 Heure
5

ANALYSE Dans le Listing 5.7, cinq variables entières, num1, num2, num3, num4 et num5,
sont déclarées à la ligne 6 et des valeurs leur sont attribuées aux lignes 8 à 12.
Ces valeurs représentées par les cinq variables entières sont ensuite imprimées par les
fonctions printf() aux lignes 13 à 17. Notez que tous les appels à printf() ont le
même premier argument : "%8d %-8d\n". Ici, le premier spécificateur de format, %8d,
aligne la sortie sur le bord droit du champ, et le second spécificateur, %-8d, aligne la
sortie sur le bord gauche du champ.
Après l'exécution des instructions des lignes 13 à 17, l'alignement est effectué et la
sortie est affichée à l'écran comme suit :
1 1
12 12
123 123
1234 1234
12345 12345

Utilisation du spécificateur de précision


Vous pouvez placer un point (.) et un nombre entier juste après l'indication de la largeur
minimale du champ. La combinaison du point (.) et de l'entier constitue un spécificateur
de précision. Vous pouvez utiliser le spécificateur de précision pour déterminer le
nombre de décimales pour les nombres à virgule flottante, ou pour spécifier la largeur (ou
la longueur) maximale du champ pour les entiers ou les chaînes de caractères. (Les
chaînes de caractères en C sont présentées dans l'Heure 13, "Manipulation des chaînes de
caractères").
Par exemple, avec %10.3f, la largeur minimale du champ est fixée à 10 caractères et le
nombre de décimales à 3 (rappelons que le nombre de décimales par défaut est de 6).
Pour les entiers, %3.8d indique que la largeur minimale du champ est de 3 et que la
largeur maximale est de 8.
La liste 5.8 donne un exemple d'utilisation des spécificateurs de précision.

TYPE LISTE 5.8 Utilisation des spécifications de précision


1 : /* 05L08.c : Utilisation des
spécificateurs de précision */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : int int_num ;
7 : double flt_num ;
8 :
9 : int_num = 123 ;
10 : flt_num = 123.456789 ;
11 : printf("Format d'entier par défaut : %d\n", int_num) ;
Gestion des entrées et sorties standard 85

12 : printf("Avec spécification de %2.8d\n", int_num) ;


précision :
13 : printf("Format par défaut des %f\n", flt_num) ;
flottants :
14 : printf("Avec spécification de %-10.2f\n", flt_num) ;
précision :
15 : retour 0 ;
16 :
}

Après avoir exécuté le fichier exécutable 05L08.exe sur mon ordinateur, j'obtiens le
résultat suivant à l'écran :
Format entier par défaut : 123
SORTIE Avec spécificateur de 00000123
précision :
Format par défaut des 123.456789
flottants :
Le programme du Listing 5.8 déclare une variable entière, int_num, à la ligne 6, et
ANALYSE
un nombre à virgule flottante, flt_num, à la ligne 7. Les lignes 9 et 10 affectent
123 et
123.456789 à int_num et flt_num, respectivement.

À la ligne 11, le format entier par défaut est spécifié pour la variable entière, int_num,
tandis que l'instruction de la ligne 12 spécifie le format entier avec un spécificateur de
précision qui indique que la largeur maximale du champ est de huit caractères. Par
conséquent, vous voyez que cinq zéros sont ajoutés avant l'entier 123 dans la deuxième
ligne de la sortie.
Pour la variable à virgule flottante, flt_num, la ligne 13 imprime la valeur à virgule
flottante dans le format par défaut, et la ligne 14 réduit les décimales à deux en plaçant le
spécificateur de précision .2 dans le spécificateur de format %-10.2f. Notez ici que la
justification à gauche est également spécifiée par le signe moins (-) dans le spécificateur
de format en virgule flottante.
5
Le nombre en virgule flottante 123,46 de la quatrième ligne de la sortie est produit par
l'instruction de la ligne 14 avec le spécificateur de précision pour deux décimales. Par
conséquent, 123.456789 arrondi à deux décimales devient 123.46.

Résumé
Dans cette leçon, vous avez appris les concepts, spécificateurs et fonctions importants suivants :
• Le langage C traite un fichier comme une série d'octets.
• stdin, stdout et stderr sont trois flux de fichiers pré-ouverts et toujours
disponibles.
• Les fonctions getc() et getchar() de la bibliothèque C peuvent être utilisées
pour lire un caractère à partir de l'entrée standard.
86 Heure
• Les fonctions putc() et putchar() 5 la bibliothèque C peuvent être utilisées
de
pour écrire un caractère sur la sortie standard.
Gestion des entrées et sorties standard 87

• %x ou %X peut être utilisé pour convertir des nombres décimaux en nombres hexagonaux.
• Une largeur de champ minimale peut être spécifiée et garantie en ajoutant un
nombre entier dans un spécificateur de format.
• Une sortie peut être alignée sur le bord gauche ou droit du champ de sortie.
• Un spécificateur de précision peut être utilisé pour spécifier le nombre de
décimales pour les nombres à virgule flottante, ou la largeur maximale du champ
pour les nombres entiers ou les chaînes de caractères.
Dans la prochaine leçon, vous découvrirez quelques opérateurs importants en C.

Q&R
Q Que sont stdin, stdout et stderr ?
A En C, un fichier est traité comme une série d'octets appelée flux de fichiers. stdin,
stdout et stderr sont tous des flux de fichiers pré-ouverts. stdin est l'entrée
standard pour la lecture ; stdout est la sortie standard pour l'écriture ; stderr est
l'erreur standard pour l'affichage des messages d'erreur.
Q Quelle est la valeur du nombre hexadécimal 32 ?
L'hexadécimal, ou hexagone en abrégé, est un système numérique en base 16. Par
conséquent, 32 (hex) est égal à 3*161+2*160, soit 50 en décimal.
Q Les fonctions getc(stdin) et getchar() sont-elles équivalentes ?
R. Parce que la fonction getchar() lit par défaut à partir du flux de fichiers stdin,
getc(stdin) et getchar() sont équivalents dans ce cas.
Q Dans la fonction printf("The integer %d is the same as the hex %x", 12,
12), quelle est la relation entre les spécificateurs de format et les expressions
?
A Les deux spécificateurs de format, %d et %x, précisent les formats des valeurs
numériques contenues dans la section d'expression. Ici, la première valeur
numérique de 12 sera imprimée au format entier, tandis que la deuxième valeur
de 12 (dans la section expression) sera affichée au format hexadécimal. D'une
manière générale, le nombre de spécificateurs de format dans la section format
doit correspondre au nombre d'expressions dans la section expression.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe D, "Réponses aux questions du quiz et aux exercices".
88 Heure
5

Quiz
1. Pouvez-vous aligner votre sortie sur le bord gauche, plutôt que sur le bord droit,
du champ de sortie ?
2. Quelle est la différence entre putc() et putchar() ?
3. Que retourne getchar() ?
4. Dans %10.3f, quelle partie correspond à la largeur minimale du champ et quelle
partie correspond à la précision ?

Exercices
1. Ecrivez un programme pour afficher les caractères B, y et e ensemble sur l'écran.
2. Affichez les deux nombres 123 et 123.456 et alignez-les sur le bord gauche du
champ.
3. Étant donné trois nombres entiers, 15, 150 et 1500, écrivez un programme qui
imprime les nombres entiers à l'écran au format hexadécimal.
4. Écrire un programme qui utilise getchar() et putchar() pour lire un caractère
saisi par l'utilisateur et l'écrire à l'écran.
5. Si vous compilez le programme C suivant, quels sont les messages d'avertissement
ou d'erreur que vous obtiendrez ?
main()
{
int ch ;
ch = getchar()
; putchar(ch) ; 5
return 0 ;
}
Gestion des entrées et sorties standard 89
PARTIE II
Opérateurs et
déclarations de flux de
contrôle
Heure
6 Manipulation des données
7 Travailler avec des boucles
8 Utilisation des opérateurs conditionnels
9 Travailler avec des modificateurs de
données et des fonctions
mathématiques
10 Contrôle du déroulement du programme
HEURE 6
Manipulation des données
"La question est la suivante", a déclaré Humpty Dumpty,
"qui est d'être maître, c'est tout."
-L. Carroll
Vous pouvez considérer les opérateurs comme des verbes en C qui vous
permettent de manipuler des données (qui sont comme des noms). En fait,
vous avez appris certains opérateurs, tels que + (addition),
- (soustraction), * (multiplication), / (division) et % (reste), dans l'heure 3,
"Apprendre la structure d'un programme C". Le langage C dispose d'un riche
ensemble d'opérateurs. Dans cette heure, vous découvrirez d'autres
opérateurs, tels que
• Opérateurs d'affectation arithmétique
• Opérateur unaire moins
• Opérateurs d'incrémentation et de décrémentation
• Opérateurs relationnels
• Opérateur de casting
92 Heure
6

Opérateurs d'affectation arithmétique


Avant d'aborder les opérateurs d'affectation arithmétique, examinons de plus près
l'opérateur d'affectation lui-même.

L'opérateur d'affectation (=)


Dans le langage C, l'opérateur = est appelé opérateur d'affectation, que vous avez vu et
utilisé pendant plusieurs heures.
La forme générale de l'énoncé permettant d'utiliser un opérateur d'affectation est la suivante
opérande gauche = opérande droit ;

Ici, l'instruction fait en sorte que la valeur de l'opérande de droite soit affectée (ou
écrite) à l'emplacement de mémoire de l'opérande de gauche. Ainsi, après l'affectation,
l'opérande gauche sera égal à la valeur de l'opérande droit. En outre, l'expression
d'affectation entière est évaluée à la même valeur que celle affectée à l'opérande gauche.
Par exemple, l'instruction a = 5 ; écrit la valeur de l'opérande de droite (5) dans
l'emplacement mémoire de la variable entière a (qui est l'opérande de gauche dans ce
cas).
De même, l'instruction b = a = 5 ; affecte d'abord 5 à la variable entière a, puis à la
variable entière b. Après l'exécution de l'instruction, a et b contiennent tous deux la
valeur 5.
Il est important de se rappeler que l'opérande gauche de l'opérateur d'affectation doit
être une expression dans laquelle vous pouvez légalement écrire des données. Une
expression telle que 6 = a, bien qu'elle semble correcte à première vue, est en fait
inversée et ne fonctionnera pas. L'opérateur = fonctionne toujours de droite à gauche ;
par conséquent, la valeur de gauche doit être une forme de variable qui peut recevoir les
données de l'expression de droite.

Ne confondez pas l'opérateur d'affectation (=) avec l'opérateur relationnel,


== (appelé l'opérateur égal à). L'opérateur == est présenté plus loin dans
cette heure.

Combinaison d'opérateurs arithmétiques avec =


Prenons l'exemple suivant : Étant donné deux variables entières, x et y, comment affecter
l'addi- tion de x et y à une autre variable entière, z ?
Manipulation des 93
données

En utilisant l'opérateur d'affectation (=) et l'opérateur d'addition (+), vous obtenez l'énoncé
suivant :
z = x + y ;

Comme vous le voyez, c'est assez simple. Reprenons maintenant le même exemple. Cette
fois, au lieu d'affecter le résultat à la troisième variable, z, écrivons le résultat de
l'addition dans la variable entière, x :
x = x + y ;

N'oubliez pas que l'opérateur = fonctionne toujours de la droite vers la gauche, de sorte
que le côté droit sera évalué en premier. Ici, du côté droit de l'opérateur d'affectation (=),
l'addition de x et de y est exécutée ; du côté gauche de =, la valeur précédente de x est
remplacée par le résultat de l'addition du côté droit.
Le langage C propose un nouvel opérateur, +=, qui permet d'effectuer à la fois l'addition
et l'affectation. Par conséquent, vous pouvez réécrire l'instruction x = x + y ; comme
suit
x += y ;

Les combinaisons de l'opérateur d'affectation (=) avec les opérateurs arithmétiques, +, -,


*, / et %, donnent un autre type d'opérateurs : les opérateurs d'affectation arithmétique :

Opérateur Description de l'activité


+= Opérateur d'affectation par addition
-= Opérateur d'affectation de soustraction
*= Opérateur d'affectation de la multiplication
/= Opérateur d'affectation de division
%= Opérateur d'affectation des

restes La figure suivante montre l'équivalence des


6
déclarations :
x += y ; est équivalent à x = x + y ;

x -= y ; équivaut à x = x - y ; x

*= y ; équivaut à x = x * y ; x

/= y ; équivaut à x = x / y ; x

%= y ; équivaut à x = x % y ;

Notez que la déclaration


94 Heure
6

z = z * x + y ;

n'est pas équivalent à l'affirmation


z *= x + y ;

parce que
z *= x + y

multiplie z par tout le côté droit de l'énoncé, de sorte que le résultat serait le même que
celui de
z = z * (x + y) ;

La liste 6.1 donne un exemple d'utilisation de certains opérateurs d'affectation arithmétique.

TYPE LISTE 6.1 Utilisation des opérateurs d'affectation arithmétique


1 : /* 06L01.c : Utilisation d'opérateurs d'affectation
arithmétique */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int x, y, z
;
7 :
8 : x = 1 ; /* initialise x */
9 : y = 3 ; /* initialise y */
10 : z = 10 ; /* initialise z
*/
11 : printf("Given x = %d, y = %d, and z = %d,\n", x, y,
z) ; 12 :
13: x = x + y ;
14 : printf("x = x + y assigne %d à x;\n", x)
; 15 :
16: x = 1 ; /*
réinitialisation de x */
17: x += y ;
18 : printf("x += y assigne %d à x;\n", x)
; 19 :
20: x = 1 ; /*
réinitialisation de x */
21: z = z * x + y ;
22: printf("z = z * x + y assigne %d à z;\n",
z) ; 23 :
24: z = 10 ; /* remise à
zéro de z */ 25: z = z *
(x + y) ;
26 : printf("z = z * (x + y) assigne %d à z;\n", z)
; 27 :
28: z = 10 ; /*
réinitialisation de z */
Manipulation des 95
29: z *= x + y ; données
30 : printf("z *= x + y assigne %d à z.\n", z)
; 31 :
32 : retour 0 ;
33 : }
96 Heure
6

Une fois ce programme compilé et lié, un fichier exécutable est créé. Sur ma machine, ce
fichier exécutable s'appelle 06L01.exe. Voici la sortie affichée après l'exécution de
l'exécutable :
Étant donné x = 1, y = 3 et z = 10,
SORTIE x = x + y attribue 4 à x
; x += y attribue 4 à x
;
z = z * x + y attribue 13 à z
; z = z * (x + y) attribue 40
à z ; z *= x + y attribue 40 à
z.

ANALYSE La ligne 2 du Listing 6.1 inclut le fichier d'en-tête stdio.h en utilisant l'option include
. Le fichier d'en-tête stdio.h est nécessaire pour la fonction printf() utilisée dans le programme
lignes 4-33.
Les lignes 8 à 10 initialisent trois variables entières, x, y et z, déclarées à la ligne 6. La
ligne 11 imprime ensuite les valeurs initiales attribuées à x, y et z.
L'instruction de la ligne 13 utilise un opérateur d'addition et un opérateur d'affectation
pour additionner les valeurs contenues dans x et y, puis affecte le résultat à x. La ligne
14 affiche le résultat à l'écran.
De même, les lignes 17 et 18 effectuent la même addition et affichent à nouveau le
résultat, après que la variable x a été réinitialisée à la valeur 1 à la ligne 16. Cette fois,
l'opérateur d'affectation arithmétique,
+=, est utilisé.

La valeur de x est à nouveau réinitialisée à la ligne 20. La ligne 21 effectue une


multiplication et une addition et enregistre le résultat dans la variable entière z, c'est-à-
dire z = z * x + y ;. L'appel printf() à la ligne 22 affiche le résultat, 13, à l'écran.
Une fois encore, l'instruction x = 1 ; de la ligne 20 réinitialise la variable entière x.
Les lignes 24 à 30 affichent deux résultats issus de deux calculs. Les deux résultats sont
en fait identiques (c'est-à-dire 40) car les deux calculs des lignes 25 et 29 sont
équivalents. La seule différence entre les deux instructions des lignes 25 et 29 est que
l'opérateur d'affectation arithmétique, *=, est utilisé à la ligne 29.
6
Obtenir des négations de valeurs numériques
Si vous voulez changer le signe d'un nombre, vous pouvez placer l'opérateur moins (-)
juste avant le nombre. Par exemple, pour un nombre entier de 7, vous pouvez obtenir sa
valeur négative en changeant le signe du nombre entier comme suit : -7 : -7. Ici, - est
l'opérateur moins.
Le symbole - utilisé de cette manière est appelé l'opérateur unaire moins. En effet, cet
opérateur ne prend qu'un seul opérande : l'expression située immédiatement à sa droite.
Manipulation des 97
données
Le type de données de l'opérande peut être n'importe quel nombre entier ou flottant.
98 Heure
6

Vous pouvez appliquer l'opérateur unaire moins à un entier ou à une variable à virgule
flottante. Par exemple, si x = 1,234, -x est égal à -1,234. Ou, étant donné x = -1,234,
-x est égal à
1,234 puisque la négation d'une valeur négative donne un nombre positif.

Ne confondez pas l'opérateur unaire moins avec l'opérateur de soustraction,


bien que les deux opérateurs utilisent le même symbole. Par exemple,
l'énoncé suivant :
z = x - -y ;
est en fait identique à cette déclaration :
z = x - (-y) ;
ou celui-ci :
z = x + y ;
Ici, dans les deux énoncés, le premier symbole - est utilisé comme opérateur
de soustraction, tandis que le second symbole - est l'opérateur unaire
moins.

Incrémenter ou décrémenter d'une unité


Les opérateurs d'incrémentation et de décrémentation sont très pratiques lorsque vous
souhaitez ajouter ou soustraire 1 à une variable. Le symbole de l'opérateur
d'incrémentation est ++. L'opérateur de décrémentation est --.
Par exemple, vous pouvez réécrire l'énoncé x = x + 1 ; comme ++x ; ou vous
pouvez remplacer x = x - 1 ; par --x ;.
En fait, il existe deux versions des opérateurs d'incrémentation et de décrémentation.
Dans l'instruction ++x ;, où ++ apparaît avant son opérande, l'opérateur d'incrémentation
est appelé opérateur de préincrémentation. Cela fait référence à l'ordre dans lequel les
choses se produisent : l'opérateur ajoute d'abord 1 à x, puis donne la nouvelle valeur de
x. De même, dans l'instruction --x ;, l'opérateur de pré-décrémentation soustrait d'abord
1 de x, puis donne la nouvelle valeur de x.
Si vous avez une expression comme x++, où ++ apparaît après son opérande, vous
utilisez l'opérateur de post-incrémentation. De même, dans x--, l'opérateur de
décrémentation est appelé opérateur de post-décrémentation.
Par exemple, dans l'instruction y = x++ ;, la valeur originale de x est d'abord attribuée à y, puis àx
est augmenté de 1.
L'opérateur de post-décrémentation a une histoire similaire. Dans l'instruction y = x--
; l'affectation de y à la valeur de x a d'abord lieu, puis x est décrémenté. Le
programme de la liste 6.2 illustre les différences entre les deux versions des opérateurs
d'incrémentation et de décrémentation.
Manipulation des 99
données

TYPE LISTE 6.2 Utilisation des opérateurs de pré ou post incrémentation et décrémentation
1 : /* 06L02.c : opérateurs pré- ou post-incrémentation
(décrémentation) */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int w, x, y, z, result
; 7 :
8: w = x = y = z = 1 ; /* initialisation de x et y */
9 : printf("Given w = %d, x = %d, y = %d, and z = %d,\n", w, x, y,
z) ; 10 :
11 : résultat = ++w ;
12 : printf("++w évalue à %d et w est maintenant %d\n",
result, w) ; 13: result = x++ ;
14 : printf("x++ évalue à %d et x est maintenant %d\n",
result, x) ; 15: result = --y ;
16 : printf("--y évalue à %d et y est maintenant %d\n",
result, y) ; 17: result = z-- ;
18 : printf("z-- evaluates to %d and z is now %d\n", result,
z) ; 19: return 0 ;
20 : }

Le résultat suivant est obtenu en exécutant le fichier exécutable 06L02.exe :


Étant donné w = 1, x = 1, y = 1 et z = 1,
SORTIE
++w évalue à 2 et w est
maintenant 2 x++ évalue à 1 et x
est maintenant 2
--y évalue à 0 et y est
maintenant 0 z-- évalue à 1 et z
est maintenant 0

Dans la fonction main(), la ligne 8 de l'illustration 6.2 attribue 1 à chacune des


ANALYSE
variables entières w, x, y et z. L'appel printf() de la ligne 9 affiche les valeurs
contenues dans la fonction main().
par les quatre variables entières.
Ensuite, l'instruction de la ligne 11 est exécutée et le résultat de la pré-incrémentation de
w est affecté à la variable entière result. À la ligne 12, la valeur de result, qui est 2,
est imprimée à l'écran, ainsi que la valeur de w après l'instruction de pré-incrémentation. 6
Notez que w est toujours 2 puisque l'incrémentation a eu lieu avant que la nouvelle
valeur de w ne soit affectée à result.
Les lignes 13 et 14 obtiennent la post-incrémentation de x et impriment le résultat.
Comme vous le savez, le résultat est obtenu avant que la valeur de x ne soit augmentée.
Par conséquent, vous voyez la valeur 1 (l'ancienne valeur de x) dans le résultat de x++,
alors que la nouvelle valeur de x est 2 puisqu'elle a été incrémentée après l'affectation
au résultat à la ligne 13 L'opérateur de prédécrémentation à la ligne 15 fait en sorte que
100 Heure
la valeur de y 6
soit réduite de 1 avant que la nouvelle valeur ne soit affectée à la variable
entière résultat. Par conséquent, le résultat de --y affiché à l'écran est 0, ce qui
reflète la nouvelle valeur de y, qui est également 0.
Manipulation des 101
données

A la ligne 17, cependant, l'opérateur de post-décrémentation n'a aucun effet sur


l'affectation car la valeur originale de z est donnée à la variable entière result avant
que z ne soit diminué de 1. La post-décrémentation agit comme si la ligne 17 était
simplement result = z, z étant alors diminué de 1 après l'exécution de l'instruction. La
ligne 18 imprime donc le résultat 1, qui est bien sûr la valeur originale de z, ainsi que 0,
la valeur de z après la post-décrémentation.

Plus grand ou moins grand ?


Il existe six types de relations entre deux expressions : égal à, différent de, supérieur à,
inférieur à, supérieur ou égal à et inférieur ou égal à. En conséquence, le langage C
fournit ces six opérateurs relationnels :

Opérateur Description de l'activité


== Egal à
!= N'est pas égal à
> Supérieur à
< Inférieur à
>= Supérieur ou égal à
<= Inférieur ou égal à

Tous les opérateurs relationnels ont une priorité inférieure à celle des opérateurs
arithmétiques. Par conséquent, toutes les opérations arithmétiques effectuées de part et
d'autre d'un opérateur relationnel sont exécutées avant toute comparaison. Vous devez
utiliser des parenthèses pour entourer les opérations des opérateurs qui doivent être
effectuées en premier.
Parmi les six opérateurs relationnels, les opérateurs >, <, >= et <= sont plus importants
que les opérateurs == et !=.
Par exemple, l'expression
x * y < z + 3

est interprété comme


(x * y) < (z + 3)

Un autre point important est que toutes les expressions relationnelles produisent un
résultat de 0 ou de 1. En d'autres termes, une expression relationnelle est évaluée à 1 si
la relation spécifiée existe. Dans le cas contraire, le résultat est 0.
Avec x = 3 et y = 5, par exemple, l'expression relationnelle x < y donne un résultat
de 1. La liste 6.3 présente d'autres exemples d'utilisation des opérateurs relationnels.
102 Heure
6

Lorsque plusieurs opérateurs différents apparaissent ensemble dans une


expression, les opérandes intermédiaires sont associés aux opérateurs dans
un ordre donné.
La priorité des opérateurs fait référence à l'ordre dans lequel les opérateurs
et les opérandes sont regroupés. Les opérateurs qui ont la priorité la plus
élevée dans une expression sont regroupés avec leurs opérandes en
premier,
Par exemple, dans l'expression
z + x * y - 3

L'opérateur * est plus prioritaire que les opérateurs + et -. Par conséquent,


x * y sera évalué en premier et son résultat deviendra l'opérande de droite
de l'opérateur +. Le résultat de ce dernier est ensuite donné à l'opérateur
- en tant qu'opérande de gauche.
Si vous souhaitez ignorer la priorité par défaut des opérateurs, vous pouvez
utiliser des parenthèses pour regrouper les opérandes au sein d'une
expression. Si, par exemple, vous vouliez multiplier z + x par y - 3, vous
pourriez réécrire l'expression ci-dessus comme suit
(z + x) * (y - 3)

En outre, vous pouvez toujours utiliser les parenthèses lorsque vous n'êtes
pas certain des effets de la préséance des opérateurs, ou lorsque vous

TYPE
LISTE 6 .3 Résultats produits par les expressions relationnelles
1 : /* 06L03.c : Utilisation des opérateurs
relationnels */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int x, y ;
7 : double z ;
8 :
9: x = 7
;
10: y = 25 ; 6
11: z = 24.46 ;
12 : printf("Given x = %d, y = %d, and z = %.2f,\n", x, y,
z) ; 13: printf("x >= y produit : %d\n", x >= y) ;
14 : printf("x == y produit : %d\n", x == y)
; 15 : printf("x < z produit :%d\n", x < z) ;

16 : printf("y > z produit :%d\n",


y > z) ;
17 : printf("x != y - 18 produit : %d\n", x != y - 18)
; 18 : printf("x + y != z produit :%d\n", x + y != z) ;
19: return 0 ;
20 : }
Manipulation des 103
données

Après l'exécution de l'exécutable 06L03.exe, la sortie suivante s'affiche sur l'écran de


mon ordinateur :
Etant donné x = 7, y = 25, et z = 24,46,
SORTIE x >= y produit :
0 x == y produit
: 0 x < z produit
: 1 y > z produit
: 1 1
x != y - 18 produit :
0 x + y != z produit :
1

Deux variables entières, x et y, et une variable à virgule flottante, z, sont


ANALYSE
déclarées respectivement aux lignes 6 et 7.
Les lignes 9 à 11 initialisent les trois variables. La ligne 12 imprime les valeurs
affectées aux variables.
Comme la valeur de x est 7 et la valeur de y est 25, y est plus grand que x. Par conséquent,
la ligne 13 affiche 0, qui est le résultat de l'expression relationnelle x >= y.
De même, à la ligne 14, l'expression relationnelle x == y donne 0.
Les lignes 15 et 16 affichent le résultat de 1, qui est obtenu par les évaluations des deux lignes suivantes
x < z et y > z.

L'instruction de la ligne 17 affiche 0, qui est le résultat de l'expression relationnelle


x != y - 18. Puisque y - 18 donne 7 et que la valeur de x est 7, la relation != ne
tient pas. A la ligne 18, l'expression x + y != z donne 1, qui est affiché à l'écran.

Soyez prudent lorsque vous comparez deux valeurs pour vérifier l'égalité. En
raison de la troncature ou de l'arrondi, certaines expressions relationnelles,
qui sont vraies d'un point de vue algébrique, peuvent donner 0 au lieu de 1.
Par exemple, regardez l'expression relationnelle suivante :
1 / 2 + 1 / 2 == 1
C'est vrai d'un point de vue algébrique et on s'attendrait à ce que la valeur soit
égale à 1.
L'expression, cependant, donne 0, ce qui signifie que la relation égal à
n'est pas valable. En effet, la troncature de la division entière - c'est-à-dire
1 / 2 - produit un entier : 0, et non 0,5.
Un autre exemple est 1,0 / 3,0, ce qui donne 0,33333...............Il s'agit d'un nombre
avec un nombre infini de décimales. Mais l'ordinateur ne peut contenir qu'un
nombre limité de décimales. Par conséquent, l'expression
1.0 / 3.0 + 1.0 / 3.0 + 1.0 / 3.0 == 1.0
pourrait ne pas donner 1 sur certains ordinateurs, bien que l'expression soit
104 Heure
6

Utilisation de l'opérateur de moulage


En C, vous pouvez convertir le type de données d'une variable, d'une expression ou
d'une constante en un type différent en préfixant l'opérateur cast. Cette conversion ne
modifie pas l'opérande lui-même ; lorsque l'opérateur cast est évalué, il produit la même
valeur (mais représentée comme un type différent), que vous pouvez ensuite utiliser
dans le reste de l'expression.
La forme générale de l'opérateur de distribution est la suivante
(type de données) x

Ici, le type de données spécifie le nouveau type de données que vous souhaitez. x est
une variable (ou une constante ou une expression) d'un type de données différent. Vous
devez inclure les parenthèses ( et ) autour du nouveau type de données pour créer un
opérateur de conversion.
Par exemple, l'expression (float)5 convertit l'entier 5 en un nombre à virgule flottante,
5.0.

Le programme de la liste 6.4 présente un autre exemple d'utilisation de l'opérateur cast.

TYPE
LISTE 6.4 Jouer avec l'opérateur Cast
1 : /* 06L04.c : Utilisation de
l'opérateur cast */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : int x, y
;
7 :
8: x = 7 ;
9: y = 5 ;
10 : printf("Given x = %d, y = %d\n", x,
y) ; 11 : printf("x / y produit :
%d\n", x / y) ;
12 : printf("(float)x / y produit : %f\n", (float)x / y)
; 13: return 0 ;
14 : }
6
Le résultat suivant est obtenu en exécutant l'exécutable 06L04.exe sur mon ordinateur :
Étant donné x =
SORTIE 7, y = 5 x / y
produit : 1
(float)x / y produit : 1.400000

ANALYSE Dans le Listing 6.4, deux variables entières, x et y, sont déclarées à la ligne 6 et
Manipulation des 105
i itialisées aux lignes 8 etdonnées
9, respectivement. La ligne 10 affiche ensuite les valeurs
n contenues dans
par les variables entières x et y.
106 Heure
6

L'instruction de la ligne 11 imprime la division entière de x/y. Comme la partie


fractionnaire est tronquée, le résultat de la division entière est 1.
Cependant, à la ligne 12, l'opérateur cast (float) convertit la valeur de x en une valeur
à virgule flottante. Par conséquent, l'expression (float)x/y devient une division en
virgule flottante qui renvoie un nombre en virgule flottante. C'est pourquoi le nombre à
virgule flottante 1,400000 apparaît à l'écran après l'exécution de l'instruction de la
ligne 12.

Résumé
Dans cette leçon, vous avez appris à connaître les opérateurs importants suivants :
• L'opérateur d'affectation =, qui a deux opérandes (un de chaque côté). La valeur
de l'opérande de droite est affectée à l'opérande de gauche. L'opérande de gauche
doit être une forme de variable qui peut accepter la nouvelle valeur.
• Les opérateurs d'affectation arithmétique +=, -=, *=, /= et %=, qui sont des
combinaisons des opérateurs arithmétiques avec l'opérateur d'affectation.
• L'opérateur unaire moins (-), qui évalue la négation d'une valeur numérique.
• Les deux versions de l'opérateur d'incrémentation, ++. Vous savez que dans ++x, l'opérateur
L'opérateur ++ est appelé opérateur de pré-incrémentation ; et dans x++, ++ est
l'opérateur de post-incrémentation.
• Les deux versions de l'opérateur de décrémentation, --. Vous avez appris que, par exemple, dans
--x, l'opérateur -- est l'opérateur de prédécrémentation, et dans x--, -- est
appelé l'opérateur de post-décrémentation.
• Les six opérateurs relationnels en C : == (égal à), != (différent de), > (supérieur à),
< (inférieur à), >= (supérieur ou égal à) et <= (inférieur ou égal à).
• Comment changer le type de données d'une expression en préfixant un opérateur
cast aux données.
Dans la prochaine leçon, vous découvrirez les boucles en langage C.

Q&R
Q Quelle est la différence entre l'opérateur pré-incrémenté et l'opérateur post-
incrémenté ?
R L'opérateur de pré-incrémentation augmente d'abord la valeur de l'opérande de 1,
puis donne la valeur modifiée. En revanche, l'opérateur de post-incrémentation
fournit d'abord la valeur originale de son opérande, puis incrémente l'opérande. Par
exemple, étant donné
x = 1, l'expression ++x donne 2, alors que l'expression x++ évalue à 1
avant de modifier x.
Manipulation des 107
données

Q L'opérateur unaire moins (-) est-il le même que l'opérateur de soustraction (-) ?
R Non, ce ne sont pas les mêmes, bien que les deux opérateurs partagent le même
symbole. La signification du symbole est déterminée par le contexte dans lequel il
apparaît. L'opérateur unaire moins est utilisé pour changer le signe d'une valeur
numérique. En d'autres termes, l'opérateur unaire moins donne la négation de la
valeur. L'opérateur de soustraction est un opérateur arithmétique qui effectue une
soustraction entre ses deux opérandes.
Q Lequel a la priorité la plus élevée, un opérateur relationnel ou un opérateur
arithmétique ?
A Un opérateur arithmétique a une priorité plus élevée qu'un opérateur relationnel. Par
exemple, dans l'expression x * y + z > x + y, la priorité de l'opérateur va de * à
+ et enfin >. L'expression entière est donc interprétée comme ((x * y) + z) > (x
+ y).

Q Quelle est la valeur produite par une expression relationnelle ?


A Une expression relationnelle est évaluée à 0 ou 1. Si la relation indiquée par un
opérateur relationnel dans une expression est vraie, l'expression est évaluée à 1 ;
sinon, l'expression est évaluée à 0.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe C, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Quelle est la différence entre l'opérateur = et l'opérateur == ?
2. Dans l'expression x + - y - - z, quel(s) opérateur(s) sont des opérateurs de
soustraction et quel(s) opérateur(s) sont des opérateurs unaires de minoration ?
3. Étant donné x = 15 et y = 4, quelles sont les valeurs des expressions x / y et
6
Rendement (float)x / y, respectivement ?
4. L'expression y *= x + 5 est-elle équivalente à l'expression y = y * x + 5 ?

Exercices
1. Étant donné x = 1 et y = 3, écrivez un programme pour imprimer les résultats
des expressions suivantes : x += y, x += -y, x -= y, x -= -y, x *= y et x *= -
y.
108 Heure
6

2. Étant donné x = 3 et y = 6, quelle est la valeur de z après l'énoncé


z = x * y == 18 ;
est exécuté ?
3. Ecrivez un programme qui initialise la variable entière x avec 1 et produit des
résultats avec les deux instructions suivantes :
printf("x++ produit: %d\n", x++)
; printf("Maintenant x contient :
%d\n", x) ;

4. Réécrivez le programme que vous avez écrit dans l'exercice 3. Cette fois-ci,
incluez les deux déclarations suivantes :
printf("x = x++ produit : %d\n", x =
x++) ;printf("Maintenant x contient :
", x) ;
Qu'obtenez-vous après avoir exécuté l'exécutable du programme ? Pouvez-vous
expliquer pourquoi vous obtenez un tel résultat ?
5. Le programme suivant est censé comparer l'égalité de deux variables, x et y.
Qu'est-ce qui ne va pas dans ce programme ? (Indice : exécutez le programme
pour voir ce qu'il imprime).
#include <stdio.h>

main()
{
int x, y ;

x = y = 0 ;
printf("Le résultat de la comparaison est : %d\n", x
= y) ; return 0 ;
}
HEURE 7
Travailler avec des boucles
Le ciel et la terre :
Chant de sutra inaudible
Répétition...
-Le dicton zen
Dans les leçons précédentes, vous avez appris les bases du programme C,
plusieurs fonctions C importantes, les E/S standard et quelques opérateurs
utiles. Dans cette leçon, vous apprendrez une caractéristique très importante
du langage C : le bouclage.
Le bouclage, également appelé itération, est utilisé en programmation pour
exécuter la même série d'instructions à plusieurs reprises jusqu'à ce que
certaines conditions spécifiées soient remplies.
Trois instructions en C sont conçues pour faire des boucles :
• L'instruction while
• L'instruction "do-while
• L'énoncé for
Les sections suivantes examinent ces déclarations.
106 Heure
106

La boucle while
Le but du mot-clé while est d'exécuter une instruction de manière répétée tant qu'une
condition donnée est vraie. Lorsque la condition de la boucle while n'est plus
logiquement vraie, la boucle se termine et l'exécution du programme reprend à
l'instruction suivante de la boucle.
La forme générale de l'instruction while est la suivante
while (expression)
déclaration ;

Cette expression est la condition de l'instruction while. Cette expression est évaluée
en premier. Si l'expression est évaluée à une valeur non nulle, l'instruction est
exécutée. Ensuite, l'expression est évaluée une nouvelle fois. L'instruction est alors
exécutée une nouvelle fois si l'expression est toujours évaluée à une valeur non nulle. Ce
processus est répété à l'infini jusqu'à ce que l'expression soit évaluée à zéro, ou à une
valeur logique fausse.
L'idée est que le code à l'intérieur de la boucle, (déclaration ; ci-dessus) finira par causer
de manière à ce que l'expression soit logiquement fausse lors de sa prochaine évaluation, ce qui met fin à
la boucle.
Bien entendu, vous souhaitez souvent utiliser le mot-clé while pour contrôler le
bouclage sur plusieurs états. Dans ce cas, utilisez un bloc d'instructions entouré
d'accolades { et }. Chaque fois que l'expression while est évaluée, l'ensemble du bloc
d'instructions est exécuté si l'expression est évaluée comme vraie.
Voyons maintenant un exemple d'utilisation de l'instruction while. Le programme du
listing 7.1 utilise une boucle while pour lire continuellement, puis afficher, les caractères
entrés tant qu'ils ne sont pas égaux à "x".

TYPE LISTE 7.1 Utilisation d'une boucle while


1 : /* 07L01.c : Utilisation d'une
boucle while */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : int c ;
7 :
8: c = ' ' ;
9 : printf("Entrez un caractère:\n(entrez x pour
quitter)\n") ; 10: while (c != 'x') {
11: c = getc(stdin) ;
12 : putchar(c) ;
13 : }
14 : printf("\NSortie de la boucle while.
Bye!\N") ; 15: return 0 ;
Travailler avec des 107
16 : } boucles
108 Heure
108

Voici une copie de la sortie de l'écran de mon ordinateur. (Les caractères que j'ai saisis sont
en gras.)
Saisir un caractère :
SORTIE (tapez x pour quitter)
H
H
i
i
x
x
7
Sortie de la boucle while. Au revoir !

Comme vous pouvez le voir dans la sortie, le programme imprime chaque


ANALYSE
caractère saisi, puis s'arrête après x.
La ligne 8 donne à la variable c la valeur ' ' (un caractère d'espacement). C'est ce
qu'on appelle initialiser la variable, et il suffit de l'initialiser à une valeur autre que "x".
La ligne 10 est l'instruction while. La condition à l'intérieur des parenthèses, c !=
'x', signifie que la boucle continuera à s'exécuter encore et encore jusqu'à ce que c soit
effectivement égal à 'x'. Puisque nous venons d'initialiser c pour qu'il soit égal au
caractère ' ', la relation c != x est bien sûr vraie.
La parenthèse fermante est suivie d'une accolade ouvrante, de sorte que la boucle
s'exécute jusqu'à ce qu'une accolade fermante soit rencontrée.
Les lignes 11 et 12 lisent un caractère et l'impriment à nouveau, et ce faisant, assignent la
valeur du caractère à la variable c. La ligne 13 est l'accolade fermante, de sorte que la
boucle est terminée et que l'exécution revient à la ligne 10, l'instruction while. Si le
caractère saisi est autre chose que "x", la boucle se poursuit ; dans le cas contraire, c !=
x est logiquement faux, et l'exécution passe à l'instruction suivante après l'accolade
fermante de la ligne 13. Dans ce cas, elle passe à l'appel printf() à la ligne 14.

La boucle "do-while
Dans l'instruction while que nous avons vue, l'expression conditionnelle est placée
tout en haut de la boucle. Cependant, dans cette section, vous allez voir une autre
instruction utilisée pour les boucles, do-while, qui place l'expression au bas de la
boucle. De cette manière, les instructions de la boucle sont assurées d'être exécutées au
moins une fois avant que l'expression ne soit testée. Notez que les instructions d'une
boucle while ne sont pas exécutées du tout si l'expression conditionnelle est évaluée à
zéro la première fois.
Travailler avec des 109
boucles

La forme générale de l'instruction "do-while" est la suivante


do {
statement1 ;
statement2 ;
.
.
.
} while (expression) ;

Ici, les instructions à l'intérieur du bloc d'instructions sont exécutées une fois, puis
l'expression est évaluée afin de déterminer si le bouclage doit se poursuivre. Si
l'expression est évaluée à une valeur non nulle, la boucle do-while continue ; sinon, le
bouclage s'arrête et l'exécution passe à l'instruction suivante de la boucle.
Notez que l'instruction do-while se termine par un point-virgule, ce qui constitue une
distinction importante par rapport aux instructions if et while.
Le programme du Listing 7.2 affiche les caractères A à G en utilisant une boucle do-
while pour répéter l'impression et l'ajout.

TYPE LISTE 7.2 Utilisation d'une boucle "do-while


1 : /* 07L02.c : Utilisation d'une
boucle do-while */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 :
8: i = 65 ;
9: do {
10 : printf("La valeur numérique de %c est %d.\n",
i, i) ; 11 : i++ ;
12 : } while (i<72)
; 13: return 0 ;
14 : }

Après avoir exécuté l'exécutable 07L02.exe de la liste 7.6, les caractères A à G, ainsi que
leurs valeurs numériques, s'affichent à l'écran comme suit :
La valeur numérique de A est 65.
SORTIE La valeur numérique de B est
66. La valeur numérique de C
est 67. La valeur numérique
de D est 68. La valeur
numérique de E est 69. La
valeur numérique de F est
70. La valeur numérique de G
est 71.
110 Heure
110

ANALYSE L'instruction de la ligne 8 du Listing 7.6 initialise la variable entière i avec 65.
La variable entière a été déclarée à la ligne 6.
Les lignes 9 à 12 contiennent la boucle "do-while". L'expression i<72 se trouve au
bas de la boucle à la ligne 12. Au début de la boucle, les deux instructions des lignes 10
et 11 sont exécutées avant que l'expression ne soit évaluée. Comme la variable entière i
contient la valeur initiale de 65, la fonction printf() de la ligne 10 affiche la valeur
numérique ainsi que le caractère A correspondant à l'écran.
7
Après que la variable entière i a été augmentée de 1 à la ligne 11, le contrôle du
programme atteint la fin de la boucle "do-while". L'expression i<72 est alors évaluée.
Si la relation dans l'expression est toujours valable, la commande du programme
remonte au début de la boucle "do-while", puis le processus est répété. Lorsque
l'expression est évaluée à 0 après que i a été augmenté à 72 (i est alors égal à 72 et
n'est donc pas inférieur à 72), la boucle "do-while" se termine immédiatement.

Boucle sous l'instruction for


La forme générale de l'instruction for est la suivante
for (expression1 ; expression2 ; expression3) {
déclaration ;
}

ou
for (expression1 ; expression2 ; expression3) {
déclaration1 ;
déclaration2 ;
.
.
.
}

Cet exemple montre que l'instruction for utilise trois expressions (expression1,
expression2, et expression3) qui sont séparés par des points-virgules.

Une boucle for peut contrôler une seule instruction, comme dans le premier exemple, ou
plusieurs instructions, telles que l'instruction 1 et l'instruction 2, placées entre les
accolades ({ et }).
La première fois que l'instruction for est exécutée, elle évalue d'abord l'expression1, qui est
généralement utilisée pour initialiser une ou plusieurs variables.
La deuxième expression, expression2, agit de la même manière que l'expression
conditionnelle d'une boucle "do" ou "do-while". Cette seconde expression est évaluée
immédiatement après l'expression1, puis est évaluée à nouveau après chaque bouclage
réussi par l'expression
Travailler avec des 111
boucles

pour. Si l'expression2 est évaluée à une valeur non nulle (logique vraie), les instructions
entre accolades sont exécutées. Dans le cas contraire, la boucle est arrêtée et l'exécution
reprend à l'instruction suivante après la boucle.
La troisième expression de l'instruction for, expression3, n'est pas évaluée lorsque
l'instruction for est rencontrée pour la première fois. Cependant, l'expression3 est
évaluée après chaque bouclage et avant que l'instruction ne retourne tester à nouveau
l'expression2.
Dans l'heure 5, "Manipuler l'entrée et la sortie standard", vous avez vu un exemple
(Listing 5.5) qui convertit les nombres décimaux 0 à 15 en nombres hexadécimaux. À
l'époque, les conversions devaient être écrites dans des instructions séparées.
Aujourd'hui, grâce à l'instruction for, vous pouvez réécrire le programme de la liste 5.5
de manière très efficace. La liste 7.3 présente la version réécrite du programme.

TYPE LISTING 7.3 Conversion de 0 à 15 en nombres hexadécimaux


1 : /* 07L03.c : Conversion de 0 à 15 en nombres
hexagonaux */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 :
8 : printf("Hex(uppercase) Hex(minuscules Decimal\n") ;
)
9 : for (i=0 ; i<16 ; i++){
10 : printf("%X %x %d\n", i, i, i) ;
11 : }
12 : retour 0 ;
13 :
}

Après avoir créé le fichier exécutable 07L03.exe, j'obtiens le résultat suivant en exécutant le programme
07L03.exe. (La sortie est en fait la même que celle de 05L05.exe dans l'Heure 5).
Hex(majuscule) Hex(minuscules Décimal
SORTIE ) e
0 0 0
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6
7 7 7
8 8 8
9 9 9
A a 10
B b 11
112 Heure
112

C c 12
D d 13
E e 14
F f 15

Examinons maintenant le code du Listing 7.3. Comme vous le savez, la ligne 2


ANALYSE
inclut le fichier d'en-tête stdio.h pour la fonction printf() utilisée plus loin
dans le programme.
7
Dans le corps de la fonction main(), l'instruction de la ligne 6 déclare une variante
entière, i. La ligne 8 affiche le titre de la sortie à l'écran.
Les lignes 9 à 11 contiennent l'instruction for. Notez que la première expression de l'instruction for
est i = 0, qui est une expression d'affectation qui initialise la variable entière i à 0.
La deuxième expression de l'instruction for est i < 16, qui est une expression
relationnelle. Cette expression est évaluée à une valeur non nulle (vraie) tant que la
relation indiquée par l'opérateur moins que (<) existe. Comme indiqué précédemment,
la seconde expression est évaluée par l'instruction for à chaque fois que la boucle est
réussie. Si la valeur de i est inférieure à 16, ce qui signifie que l'expression
relationnelle reste vraie, l'instruction for commencera une autre boucle. Dans le cas
contraire, elle arrêtera la boucle et sortira.
La troisième expression de l'instruction for est i++. Lorsque cette expression est
évaluée, la variable entière i est augmentée de 1. Cette opération est effectuée après
l'exécution de chaque instruction dans le corps de l'instruction for. Ici, il n'y a pas de
grande différence entre l'opérateur de post-incrémentation (i++) et l'opérateur de pré-
incrémentation (++i) utilisé dans la troisième expression.
En d'autres termes, lorsque la boucle for est rencontrée pour la première fois, i est mis à
0, l'expression i<16 est évaluée et considérée comme vraie, et les instructions dans le
corps de la boucle for sont donc exécutées. Après l'exécution de la boucle for, la
troisième expression i++ est exécutée, incrémentant i à 1, et l'expression i<16 est à
nouveau évaluée et considérée comme vraie, de sorte que le corps de la boucle est à
nouveau exécuté. Le bouclage dure jusqu'à ce que l'expression conditionnelle i<16 ne
soit plus vraie.
Il n'y a qu'une seule instruction dans le corps de l'instruction for, comme vous pouvez le
voir à la ligne 10. Cette instruction contient la fonction printf(), qui est utilisée pour
afficher les nombres hexadécimaux (en majuscules et en minuscules) convertis à partir
des valeurs décimales en utilisant les spécificateurs de format %X et %x.
La valeur décimale est fournie par la variable entière i. Comme expliqué précédemment,
i contient la valeur initiale de 0 juste avant et pendant le premier bouclage. Après
chaque bouclage, i est augmenté de 1 en raison de la troisième expression, i++, dans
l'instruction for. La dernière valeur fournie par i est 15. Lorsque i atteint 16, la
Travailler avec des 113
boucles
relation indiquée par la deuxième expression,
114 Heure
114

i<16, n'est plus vrai. Par conséquent, le bouclage est arrêté et l'exécution de la fonction for
est terminée.
Ensuite, l'instruction de la ligne 12 renvoie 0 pour indiquer la fin normale du
programme, et enfin, la fonction main() se termine et renvoie le contrôle au
système d'exploitation.
Comme vous le voyez, l'instruction for permet d'écrire un programme très concis. En
fait, le programme de l'illustration 7.3 est plus de 10 lignes plus court que celui de
l'illustration 5.5, bien que les deux programmes fassent exactement la même chose.
En fait, vous pouvez rendre le programme de l'illustration 7.3 encore plus court en
supprimant les accolades ({ et }) puisqu'il n'y a qu'une seule instruction à l'intérieur du
bloc d'instructions.

La déclaration nulle
Comme vous pouvez le remarquer, l'instruction for ne se termine pas par un point-
virgule. L'instruction for contient soit un bloc d'instructions qui se termine par l'accolade
fermante (}), soit une instruction unique qui se termine par un point-virgule. L'instruction
for suivante contient une seule instruction :

for (i=0 ; i<8 ;


i++) sum +=
i ;

Notez que les accolades ({ et }) sont supprimées car l'instruction for ne contient qu'une
seule instruction.
Considérons maintenant une déclaration comme celle-ci :
pour (i=0 ; i<8 ; i++) ;

Ici, l'instruction for est immédiatement suivie d'un point-virgule.


Dans le langage C, il existe une instruction spéciale appelée instruction null. Une
instruction null ne contient rien d'autre qu'un point-virgule. En d'autres termes, une
instruction null est une instruction sans expression.
Par conséquent, lorsque vous examinez l'instruction for (i=0 ; i<8 ; i++) ;, vous
pouvez voir qu'il s'agit en fait d'une instruction for avec une instruction null. En d'autres
termes, vous pouvez la réécrire comme suit
pour (i=0 ; i<8 ; i++)
;

Comme l'instruction null n'a pas d'expression, l'instruction for ne fait rien d'autre que
tourner en boucle. Vous verrez des exemples d'utilisation de l'instruction null avec
l'instruction for plus loin dans le livre.
Travailler avec des 115
boucles

L'instruction null étant parfaitement légale en C, vous devez veiller à placer


des points-virgules dans vos instructions for. Par exemple, supposons
que vous ayez l'intention d'écrire une boucle for comme suit :
for (i=0 ; i<8 ; i++)
sum += i ;

Cependant, si vous placez accidentellement un point-virgule à la fin de


l'instruction for, comme ceci,
for (i=0 ; i<8 ; i++)
7
; sum += i ;

votre compilateur C l'acceptera quand même, mais les résultats des deux for-
mations seront très différents. (Voir l'exercice 1 de cette leçon pour un
exemple).

N'oubliez pas que la boucle do-while est la seule instruction de bouclage dont la
syntaxe utilise un point-virgule immédiatement après. Les instructions while et for
sont suivies immédiatement par une boucle, qui peut être une instruction unique suivie
d'un point-virgule, un bloc d'instructions sans point-virgule, ou simplement un point-
virgule (instruction nulle).

Utilisation d'expressions complexes dans une instruction for


Le langage C permet d'utiliser l'opérateur virgule pour combiner plusieurs expressions
dans les trois parties de l'instruction for.
Par exemple, la forme suivante est valide en C :
for (i=0, j=10 ; i!=j ; i++, j--){
/* bloc de déclaration */
}

Ici, dans la première expression, les deux variables entières i et j sont initialisées,
respectivement, avec 0 et 10 lorsque l'instruction for est rencontrée pour la première
fois. Ensuite, dans le deuxième champ, l'expression relationnelle i!=j est évaluée et
testée. Si elle est évaluée à zéro (faux), la boucle est terminée. Après chaque itération de
la boucle, i est augmenté de 1 et j est réduit de 1 dans la troisième expression. Ensuite,
l'expression i!=j est évaluée pour déterminer si la boucle doit être exécutée à nouveau.
Examinons maintenant un vrai programme. La liste 7.4 montre un exemple d'utilisation
d'expressions multiples dans l'instruction for.
116 Heure
116

TYPE LISTE 7.4 Ajout d'expressions multiples à l'instruction for


1 : /* 07L04.c : Expressions multiples
*/ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i, j
;
7 :
8 : for (i=0, j=8 ; i<8 ; i++, j--)
9: printf("%d + %d = %d\n", i, j, i+j) ;
10: return 0 ;
11 : }

J'obtiens le résultat suivant à l'écran après avoir exécuté le fichier exécutable,


07L04.exe :

0 + 8 = 8
SORTIE 1 + 7 = 8
2 + 6 = 8
3 + 5 = 8
4 + 4 = 8
5 + 3 = 8
6 + 2 = 8
7 + 1 = 8

ANALYSE Dans le Listing 7.4, la ligne 6 déclare deux variables entières, i et j, qui sont utilisées dans un
fichier
pour la boucle.
À la ligne 8, i est initialisé à 0 et j est fixé à 8 dans la première expression de l'état
for. La deuxième expression contient une condition, i < 8, qui indique à l'ordinateur
de continuer à tourner en boucle tant que la valeur de i est inférieure à 8.
À chaque fois, après l'exécution de l'instruction contrôlée par for à la ligne 9, la
troisième expression est évaluée, ce qui a pour effet d'augmenter (incrémenter) i de 1
tandis que j est réduit (décrémenter) de 1. Étant donné qu'il n'y a qu'une seule
instruction à l'intérieur de la boucle for, aucune accolade ({ et }) n'est utilisée pour
former un bloc d'instructions.
L'instruction de la ligne 9 affiche l'addition de i et j à l'écran pendant le bouclage, ce
qui produit huit résultats pendant le bouclage en additionnant les valeurs des deux
variables i et j.
L'ajout de plusieurs expressions dans l'instruction for est un moyen très pratique de
manipuler plus d'une variable dans une boucle. Pour en savoir plus sur l'utilisation
d'expressions multiples dans une boucle for, consultez l'exemple de la liste 7.5.
Travailler avec des 117
boucles

LISTE 7.5 Autre exemple d'utilisation d'expressions multiples dans le


TYPE
pour la déclaration
1 : /* 07L05.c : Un autre exemple d'expressions
multiples */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i, j
; 7
7 :
8 : for (i=0, j=1 ; i<8 ; i++, j++)
9: printf("%d - %d = %d\n", j, i, j - i) ;
10: return 0 ;
11 : }

La sortie suivante s'affiche à l'écran après l'exécution de l'exécutable 07L05.exe sur ma


machine :
1 - 0 = 1
SORTIE 2 - 1 = 1
3 - 2 = 1
4 - 3 = 1
5 - 4 = 1
6 - 5 = 1
7 - 6 = 1
8 - 7 = 1

Dans le programme présenté dans le Listing 7.5, deux variables entières, i et j,


ANALYSE
sont déclarées à la ligne 6.
Notez qu'à la ligne 8, il y a deux expressions d'affectation, i=0 et j=1, dans la première
expression de l'instruction for. Ces deux expressions d'affectation initialisent les
variables entières i et j, respectivement.
Le deuxième champ contient une expression relationnelle, i<8, qui est la condition qui
doit être remplie avant que la boucle puisse être exécutée. Comme i commence à 0 et
est incrémenté de 1 après chaque boucle, il y a au total 8 boucles qui seront exécutées
par l'instruction for.
La troisième expression contient deux expressions, i++ et j++, qui augmentent les deux
variables entières de 1 à chaque fois que l'instruction de la ligne 9 est exécutée.
La fonction printf() de la ligne 9 affiche la soustraction des deux variables entières,
j et i, dans la boucle for. Comme il n'y a qu'une seule instruction dans le bloc
d'instructions, les accolades ({ et }) ne sont pas nécessaires.
118 Heure
118

Utilisation de boucles imbriquées


Il est souvent nécessaire de créer une boucle même lorsque vous êtes déjà dans une
boucle. Vous pouvez placer une boucle (une boucle interne) à l'intérieur d'une autre
(une boucle externe) pour créer des boucles imbriquées. Lorsque le programme atteint
une boucle interne, il s'exécute comme n'importe quelle autre instruction à l'intérieur de
la boucle externe.
La liste 7.6 est un exemple du fonctionnement des boucles imbriquées.

TYPE LISTE 7.6 Utilisation de boucles imbriquées


1 : /* 07L06.c : Démonstration de boucles
imbriquées */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i, j
;
7 :
8 : for (i=1 ; i<=3 ; i++) { /* boucle extérieure */
9 : printf("Le début de l'itération %d de la boucle
extérieure.\n", i) ; 10: for (j=1 ; j<=4 ; j++) /* boucle
intérieure */
11 : printf(" Itération %d de la boucle intérieure.\n", j) ;
12 : printf("Fin de l'itération %d de la boucle
extérieure.\n", i) ; 13 : }
14 : retour 0 ;
15 : }

Le résultat suivant est obtenu en exécutant le fichier exécutable 07L06.exe


: Le début de l'itération 1 de la boucle extérieure.
SORTIE Itération 1 de la boucle
intérieure. Itération 2 de la
boucle intérieure. Itération 3
de la boucle intérieure.
Itération 4 de la boucle
intérieure.
La fin de l'itération 1 de la boucle
extérieure. Début de l'itération 2 de la
boucle extérieure.
Itération 1 de la boucle
intérieure. Itération 2 de la
boucle intérieure. Itération 3
de la boucle intérieure.
Itération 4 de la boucle
intérieure.
Travailler avec des 119
L l'itération3 boucles
de la boucle extérieure.
a Itération 1 de la boucle
intérieure. Itération 2 de la
f
i boucle intérieure. Itération 3
n de la boucle intérieure.
Itération 4 de la boucle
d intérieure.
e
Fin de l'itération 3 de la boucle extérieure.
l
'
i
t
é
r
a
t
i
o
n

d
e

l
a

b
o
u
c
l
e

e
x
t
é
r
i
e
u
r
e
.

D
é
b
u
t

d
e
120 Heure
120

ANALYSE Dans la liste 7.6, deux boucles for sont imbriquées l'une dans l'autre. La boucle
for externe commence à la ligne 8 et se termine à la ligne 13, tandis que la
boucle for interne commence à la ligne 10 et se termine à
ligne 11.
La boucle interne ne comporte qu'une seule instruction qui imprime le numéro d'itération
en fonction de la valeur numérique de la variable entière j. Comme vous le voyez à la
ligne 10, j est initialisé à 1 et est augmenté de 1 après chaque bouclage (c'est-à-dire
chaque itération). L'exécution de la boucle interne s'arrête lorsque la valeur de j est
supérieure à 4.
7
Outre la boucle interne, la boucle externe comporte deux instructions aux lignes 9 et 12,
respectivement. La fonction printf() de la ligne 9 affiche un message indiquant le
début d'une itération de la boucle externe. Un message de fin est envoyé à la ligne 12
pour indiquer la fin de l'itération de la boucle externe.
La sortie montre que la boucle interne est terminée avant que la boucle externe ne
commence une autre itération. Lorsque la boucle externe commence une autre itération,
la boucle interne est rencontrée et exécutée à nouveau. La sortie du programme de la liste
7.6 montre clairement les ordres d'exécution des boucles internes et externes.

Ne confondez pas les deux opérateurs relationnels (< et <=) et ne les


utilisez pas à mauvais escient dans les expressions des boucles.
Par exemple, le texte suivant
for (j=1 ; j<10 ; j++){
/* bloc de déclaration */
}

signifie que si j est inférieur à 10, on continue à boucler. Le nombre total


d'itérations est donc de 9. Cependant, dans l'exemple suivant,
for (j=1 ; j<=10 ; j++){
/* bloc de déclaration */
}

le nombre total d'itérations est de 10 car l'expression relationnelle j<=10


est évaluée dans ce cas. Notez que l'expression est évaluée à 1 (logique
vraie) tant que j est inférieur ou égal à 10.
Par conséquent, vous voyez que la différence entre les opérateurs < et <=
fait que le bouclage du premier exemple est plus court d'une itération que
celui du deuxième exemple.
Travailler avec des 121
boucles

Résumé
Dans cette leçon, vous avez appris les concepts et déclarations importants suivants :
• Le bouclage peut être utilisé pour exécuter la même série d'instructions à
plusieurs reprises jusqu'à ce que les conditions spécifiées soient remplies.
• Les boucles rendent votre programme plus concis.
• Trois instructions, while, do-while et for, sont utilisées pour les boucles en
C.
• L'instruction while contient une expression, qui est l'expression conditionnelle
qui contrôle la boucle.
• L'instruction while ne se termine pas par un point-virgule.
• L'instruction "do-while" place son expression conditionnelle à la fin de la boucle.
• L'instruction do-while se termine par un point-virgule.
• Il y a trois expressions dans l'instruction for. La deuxième expression est
l'expression con- ditionnelle.
• L'instruction for ne se termine pas par un point-virgule.
• Plusieurs expressions, combinées par des virgules, peuvent être utilisées comme une seule expression
dans le champ
pour la déclaration.
• Dans une boucle imbriquée, la boucle intérieure se termine avant que la boucle
extérieure ne reprenne son itération.
Dans la prochaine leçon, vous découvrirez d'autres opérateurs utilisés dans le langage C.

Q&R
Q Quelle est la différence entre les instructions while et do-while ?
R La principale différence est que dans l'instruction while, l'expression
conditionnelle est évaluée au début de la boucle, alors que dans l'instruction do-
while, l'expression conditionnelle est évaluée au bas de la boucle. Par conséquent,
les instructions contenues dans l'instruction do-while sont assurées d'être
exécutées au moins une fois, alors que la boucle d'une instruction while peut ne
jamais être exécutée.
Q Comment fonctionne une boucle for ?
A L'instruction for contient trois expressions. Le premier champ contient un
initialisateur qui est évalué en premier et une seule fois avant l'itération. La
deuxième expression est l'expression conditionnelle qui doit être évaluée à une
valeur non nulle (logique vraie) avant que les instructions contrôlées par
l'instruction for ne soient exécutées. Si l'expression conditionnelle
122 Heure
122

est évaluée à une valeur non nulle (vraie), ce qui signifie que la condition spécifiée
est remplie, une itération de la boucle for est exécutée. Après chaque itération, la
troisième expression est évaluée, puis le deuxième champ est à nouveau évalué. Ce
processus avec les deuxième et troisième expressions est répété jusqu'à ce que
l'expression conditionnelle soit évaluée à zéro (faux logique).
Q L'instruction while peut-elle se terminer par un point-virgule ?
R Par définition, l'instruction while ne se termine pas par un point-virgule.
Cependant, il est légal en C de placer un point-virgule juste après l'instruction
7
while comme ceci : while(expression) ;, ce qui signifie qu'il y a une instruction
null contrôlée par l'instruction while. Rappelez-vous que le résultat sera très
différent de ce que vous attendez si vous placez accidentellement un point-virgule
à la fin de l'instruction while.
Q Si deux boucles sont imbriquées l'une dans l'autre, laquelle doit se terminer en
premier, la boucle intérieure ou la boucle extérieure ?
R La boucle interne doit se terminer en premier. Ensuite, la boucle extérieure
continuera jusqu'à la fin, puis commencera une autre itération si la condition
spécifiée est toujours remplie.

Atelier
Pour vous aider à consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à essayer de répondre aux questions du quiz et à terminer les exercices
proposés dans l'atelier avant de passer à la leçon suivante. Les réponses et les conseils
pour les questions et les exercices sont donnés dans l'annexe D, "Réponses aux quiz et
aux exercices".

Quiz
1. La boucle while suivante peut-elle imprimer quelque chose ?
int k = 100 ;
while (k<100){
printf("%c", k) ;
k++ ;
}

2. La boucle "do-while" suivante peut-elle produire quelque chose ?


int k = 100 ;
do {
printf("%c", k) ;
k++ ;
} while (k<100) ;

3. Les deux boucles for suivantes ont-elles le même nombre d'itérations ?


for (j=0 ; j<8 ;
j++) ; for (k=1 ;
Travailler avec des 123
k<=8 ; k++) ; boucles
124 Heure
124

4. La boucle for suivante est-elle


for (j=65 ; j<72 ; j++) printf("%c",
j) ; équivalent à la boucle while
suivante ? int k = 65 ;
while (k<72)
printf("%c", k) ;
k++ ;
}

Exercices
1. Quelle est la différence entre les deux morceaux de code suivants ?
for (i=0, j=1 ; i<8 ; i++, j++)
printf("%d + %d = %d\n", i, j, i+j) ;

pour (i=0, j=1 ; i<8 ; i++, j++) ;


printf("%d + %d = %d\n", i, j, i+j) ;

2. Écrivez un programme qui contient les deux morceaux de code montrés dans
l'exercice 1, puis exécutez le programme. Qu'allez-vous voir à l'écran ?
3. Réécrivez le programme de la liste 7.1. Cette fois, vous voulez que l'instruction
while continue à tourner en boucle jusqu'à ce que l'utilisateur saisisse le
caractère K.
4. Réécrivez le programme présenté dans le Listing 7.2 en remplaçant la boucle "do-while" par une
boucle "do-while".
pour la boucle.
5. Réécrivez le programme de la liste 7.6. Cette fois, utilisez une boucle while
comme boucle externe et une boucle do-while comme boucle interne.
HEURE 8
Utilisation des
opérateurs
conditionnels
La civilisation progresse en augmentant le nombre d'opérations importantes
que nous pouvons effectuer sans y penser.
-A. N. Whitehead
Dans l'Heure 6, "Manipuler des données", vous avez appris à connaître
certains opérateurs importants en C, tels que les opérateurs d'affectation
arithmétique, l'opérateur unaire moins, les opérateurs d'incrémentation et de
décrémentation, et les opérateurs relationnels. Dans cette leçon, vous
apprendrez d'autres opérateurs très importants dans la programmation C,
notamment
• La taille de l'opérateur
• Opérateurs logiques
• Opérateurs de manipulation de bits
• L'opérateur conditionnel
122 Heure
122

Mesure de la taille des données


Vous vous souvenez peut-être que dans l'heure 4, "Comprendre les types de données et
les mots-clés", j'ai mentionné que chaque type de données a sa propre taille. La taille
d'un type de données varie en fonction du système d'exploitation et du compilateur C
que vous utilisez. Par exemple, sur la plupart des postes de travail UNIX, un entier a une
longueur de 32 bits, alors que la plupart des compilateurs C ne prennent en charge que
les entiers de 16 bits sur une machine DOS.
Alors, comment connaître la taille d'un type de données sur votre machine ? La réponse
est que vous pouvez mesurer la taille du type de données en utilisant l'opérateur sizeof
fourni par C.
La forme générale de l'opérateur sizeof est la suivante
taille de (expression)

L'expression est le type de données ou la variable dont la taille est mesurée par
l'opérateur sizeof. L'opérateur sizeof évalue la taille, en octets, de son opérande.
L'opérande de l'opérateur sizeof peut être un mot-clé du langage C désignant un type de
données (tel que int, char ou float) ou une expression faisant référence à un type de
données dont la taille peut être déterminée (telle qu'une constante ou le nom d'une
variable).
Les parenthèses sont facultatives dans la forme générale de l'opérateur. Si l'expression
n'est pas un mot-clé C pour un type de données, les parenthèses peuvent être
supprimées. Par exemple, l'instruction suivante :
size = sizeof(int) ;

Le programme du Listing 8.1 trouve les tailles des types de données char, int, float et
double sur ma machine.

TYPE LISTE 8.1 Utilisation de l'opérateur sizeof


1 : /* 08L01.c : Utilisation de l'opérateur
sizeof */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : charch = ' ' ;
7 : int int_num = 0 ;
8 : float flt_num = 0.0f
; 9: double dbl_num = 0.0
;
10 :
11 : printf("La taille de char est : %d-byte\n",
sizeof(char)) ; 12 : printf("La taille de ch est :
%d-byte\n", sizeof ch ) ;
13 : printf("The size of int is : %d-byte\n", sizeof(int)) ;
Utilisation des opérateurs 123
14 : printf("La conditionnels
taille de int_num est : %d-byte\n", sizeof int_num) ;
124 Heure
124

15 : printf("La taille de float est : %d-byte\n",


sizeof(float)) ; 16 : printf("La taille de flt_num est : %d-
byte\n", sizeof flt_num) ; 17 : printf("La taille de double est 8
: %d-byte\n", sizeof(double)) ; 18 : printf("La taille de
dbl_num est : %d-byte\n", sizeof dbl_num) ; 19: return 0 ;
20 : }

Une fois ce programme compilé et lié, un fichier exécutable, 08L01.exe, est créé. Voici la
sortie imprimée à l'écran après l'exécution de l'exécutable sur ma machine :
La taille d'un caractère est de : 1 octet
SORTIE La taille de ch est de 1
octet La taille de int
est de 2 octets
La taille de int_num est de 2
octets La taille de float est
de 4 octets La taille de
flt_num est de 4 octets La
taille de double est de 8
octets 4 octets La taille de
flt_num est : 4 octets La
taille de double est : 8
octets

La taille de dbl_num est de 8 octets[ic:analysis]La ligne 2 du Listing 8.1 inclut le fichier


d'en-tête stdio.h pour la fonction printf() utilisée dans les déclarations à l'intérieur du
corps de la fonction main(). Les lignes 6 à 9 déclarent respectivement une variable char
(ch), une variable int (int_num), une variable float (flt_num) et une variable double
(dbl_num). Ces quatre variables sont également initialisées. Notez qu'à la ligne 8, la
valeur initiale de flt_num est suffixée par f pour indiquer float. (Comme vous l'avez
appris dans l'heure 4, vous pouvez utiliser f ou F pour spécifier le type float pour un
nombre à virgule flottante).
Les lignes 11 et 12 affichent la taille du type de données char, ainsi que de la
variable char ch. Notez que l'opérateur sizeof est utilisé à la fois à la ligne 11 et à la
ligne 12 pour obtenir le nombre d'octets que le type de données char ou la variable ch
peut avoir. Comme la variable ch n'est pas un mot-clé en C, les parenthèses sont
supprimées pour l'opérateur sizeof à la ligne 12.
Les deux premières lignes de la sortie sont imprimées par les deux instructions des lignes
11 et 12, respectivement. La sortie montre que la taille du type de données char est de 1
octet, ce qui correspond à la taille de la variable ch. Cela n'est pas surprenant car la
variable ch est déclarée comme étant la variable char.
De même, les lignes 13 et 14 affichent les tailles du type de données int et de la variable int
int_num en utilisant l'opérateur sizeof. Vous constatez que la taille de chacun est de 2 octets.

De plus, en utilisant l'opérateur sizeof, les lignes 15 à 18 donnent les tailles du type de
données float, de la variable float flt_num, du type de données double et de la
Utilisation des opérateurs 125
conditionnels
variable double dbl_num, respectivement. Les résultats de la section de sortie montrent
que le type de données float et la variable flt_num ont la même taille (4 octets). Les
tailles du type de données double et de la variable dbl_num sont toutes deux de 8 octets.
126 Heure
126

Tout est logique


Il est maintenant temps de découvrir un nouvel ensemble d'opérateurs :
les opérateurs logiques. Il existe trois opérateurs logiques dans le
langage C :
&& L'opérateur logique ET
|| L'opérateur logique OR
! L'opérateur logique NEGATION

Les deux premiers, les opérateurs AND et OR, sont des opérateurs binaires, c'est-à-dire
qu'ils prennent deux opérandes (un à gauche et un à droite de l'opérateur). L'opérateur
logique ET (&&) est utilisé pour évaluer la vérité ou la fausseté d'une paire
d'expressions. Si l'une des deux expressions vaut 0 (c'est-à-dire qu'elle est logiquement
fausse), l'opérateur donne une valeur de 0.
Dans le cas contraire, si - et seulement si - les deux expressions de l'opérande sont
évaluées à des valeurs non nulles, l'opérateur logique ET donne une valeur de 1
(logiquement vrai).
L'opérateur logique OR (||) donne une valeur de 1 chaque fois que l'une des
expressions de l'opérande ou les deux s'évaluent à une valeur différente de zéro
(logiquement vrai). L'opérateur || ne donne 0 que si les deux expressions de
l'opérande sont évaluées à 0 (faux). L'opérateur de négation logique ( !) est un opérateur
unaire, c'est-à-dire qu'il ne prend qu'un seul opérande (l'expression à sa droite). Si
l'opérande est évalué à une valeur non nulle, l'opérateur ! donne 0 (logiquement
faux) ; ce n'est que lorsque l'expression de l'opérande est évaluée à 0 que l'opérateur
donne 1 (logiquement vrai).
Les trois sections suivantes contiennent des exemples qui montrent comment utiliser les
trois opérateurs logiques.

L'opérateur logique ET (&&)


Le format général de l'opérateur logique ET est le suivant :
exp1 && exp2

où exp1 et exp2 sont deux expressions d'opérande évaluées par l'opérateur AND.
Une bonne façon de comprendre l'opérateur ET est de regarder un tableau qui montre
les valeurs produites par l'opérateur ET en fonction des valeurs possibles de exp1 et
exp2. Voir le tableau 8.1, que l'on peut appeler la table de vérité de l'opérateur ET.

TABLEAU 8.1 Valeurs renvoyées par l'opérateur ET


exp1 exp2 && Rendements
non nul non nul 1
non nul 0 0
Utilisation des opérateurs 127
conditionnels

La liste 8.2 est un exemple d'utilisation de l'opérateur logique ET (&&).


8
TYPE LISTE 8.2 Utilisation de l'opérateur logique ET (&&)
1 : /* 08L02.c : Utilisation de l'opérateur
logique ET */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int num ;
7 :
8: num = 0 ;
9 : printf("L'opérateur AND donne : %d\n",
10 : (num%2 == 0) && (num%3 == 0))
;
11: num = 2 ;
12 : printf("L'opérateur AND donne : %d\n",
13 : (num%2 == 0) && (num%3 == 0))
;
14: num = 3 ;
15 : printf("L'opérateur AND donne : %d\n",
16 : (num%2 == 0) && (num%3 == 0))
;
17: num = 6 ;
18 : printf("L'opérateur AND donne : %d\n",
19 : (num%2 == 0) && (num%3 == 0))
;
20 :
21 : retour 0 ;
22 : }

Une fois ce programme compilé et lié, un fichier exécutable, 08L02.exe, est créé. La sortie
suivante s'affiche après l'exécution de l'exécutable sur ma machine :
L'opérateur AND permet d'obtenir 1
SORTIE L'opérateur ET donne : 0 0
L'opérateur ET donne : 0
L'opérateur ET donne : 0
L'opérateur ET donne : 0
L'opérateur ET donne : 0 0
L'opérateur ET permet
d'obtenir 1

Dans le Listing 8.2, une variable entière, num, est déclarée à la ligne 6 et
ANALYSE
initialisée pour la première fois à la ligne 8. Les lignes 9 et 10 affichent la valeur
obtenue par l'opération logique ET
dans l'expression suivante :
(num%2 == 0) && (num%3 == 0)

Vous voyez ici deux expressions relationnelles, num%2 == 0 et num%3 == 0. Dans


128 Heure
128 C", vous avez appris que l'opérateur
l'Heure 3 "Apprendre la structure d'un programme
arithmétique % peut être utilisé pour obtenir le reste après que son premier opérande a
été divisé par le second opérande. Par conséquent, num%2 donne le reste de num divisé
par 2. L'expression relationnelle num%2 == 0 donne 1 si le reste est égal à 0, c'est-à-
dire si la valeur de num peut être complètement divisée par 2. De même, si la valeur
de num peut être divisée par 3, l'expression relationnelle
Utilisation des opérateurs 129
conditionnels

L'expression num%3 == 0 donne également 1. Ensuite, d'après la table de vérité de


l'opérateur && (voir tableau 8.1), vous savez que la combinaison de l'opérateur logique ET
(&&) et des deux expressions relationnelles donne 1 si les deux expressions relationnelles
sont toutes les deux évaluées comme étant non nulles ; sinon, elle donne 0.
Dans notre cas, lorsque num est initialisé à 0 à la ligne 8, 0%2 et 0%3 produisent tous
deux des restes de zéro, de sorte que les deux expressions relationnelles sont évaluées à
1. Par conséquent, l'opérateur logique ET produit 1.

Cependant, lorsque num se voit attribuer la valeur 2 ou 3, comme indiqué aux lignes
11 et 14, l'opérateur logi- cal ET de la ligne 13 ou de la ligne 16 donne 0. La raison en est
que 2 ou 3 ne peut pas être divisé à la fois par 2 et par 3.
La ligne 17 attribue ensuite à num la valeur 6. Comme 6 est un multiple de 2 et de 3,
l'opérateur logi- cal ET de la ligne 19 donne 1, qui est imprimé par la fonction printf()
aux lignes 18 et 19.

L'opérateur logique OR (||)


Comme indiqué précédemment, l'opérateur logique OR donne 1 (logiquement vrai) si
l'une ou les deux expressions sont évaluées à une valeur non nulle. L'opérateur || donne
0 si (et seulement si) les deux expressions donnent 0.

Le format général de l'opérateur logique OR est le suivant :


exp1 || exp2

où exp1 et exp2 sont deux expressions opérandes évaluées par l'opérateur OR. Le
tableau 8.2 est la table de vérité de l'opérateur OU.

TABLEAU 8.2 Valeurs retournées par l'opérateur OR


exp1 exp2 || Rendement
non nul non nul 1

non nul 0 1

0 non nul 1

0 0 0

Le programme de la liste 8.3 montre comment utiliser l'opérateur logique OR (||).

TYPE LISTE 8.3 Utilisation de l'opérateur logique OR ||


1 : /* 08L03.c : Utilisation de l'opérateur
logique OR */ 2 : #include <stdio.h>
130 Heure
130

3 :
4 : main()
5 : { 8
6 : int num ;
7 :
8 : printf("Entrez un seul chiffre qui peut être divisé par 2 et 3:\N") ;
9: for (num = 1 ; (num%2 != 0) || (num%3 != 0) ; )
10 : num = getchar() - '0' ;
11 : printf("You got such a number : %d\n",
num) ; 12: return 0 ;
13 : }

Voici le résultat affiché après l'exécution du fichier exécutable 08L03.exe sur ma


machine. Les chiffres en gras sont ceux que j'ai saisis. (Dans l'intervalle 0-9, 0 et 6 sont
les deux seuls nombres qui peuvent être complètement divisés à la fois par 2 et par 3 :
Entrez un chiffre unique qui peut être divisé
SORTIE par les points 2 et 3 :
2
3
4
5
6

Vous avez obtenu un tel nombre : 6 Dans le Listing 8.3, une variable entière, num,
ANALYSE
est déclarée à la ligne 6. La ligne 8 du Listing 8.3 imprime deux titres demandant
à l'utilisateur d'entrer un
un seul chiffre.
À la ligne 9, la variable entière num est initialisée dans la première expression de l'état
for. La raison pour laquelle num est initialisé avec 1 est que 1 est un nombre qui
n'est divisible ni par 2 ni par 3. De cette façon, la boucle for est assurée d'être
exécutée au moins une fois.
La partie clé du programme de la liste 8.3 est l'expression logique de l'instruction for :

(num%2 != 0) || (num%3 != 0)

Ici, les deux expressions relationnelles num%2 != 0 et num%3 != 0 sont évaluées.


D'après la table de vérité de l'opérateur || (voir tableau 8.2), vous savez que si l'une des
expressions relationnelles est évaluée à une valeur non nulle, indiquant dans ce cas que
la valeur de num ne peut pas être divisée complètement par 2 ou 3, alors l'expression
logique OR est évaluée à 1, ce qui permet à la boucle for de continuer.
La boucle for ne s'arrête que si l'utilisateur saisit un chiffre qui peut être divisé à la fois
par 2 et par 3. En d'autres termes, lorsque les deux expressions relationnelles sont
évaluées à 0, l'opérateur logique OR donne 0, ce qui entraîne la fin de la boucle for.
Utilisation des opérateurs 131
conditionnels

L'opérateur logique de négation ( !)


Le format général d'utilisation de l'opérateur logique NEGATION est le suivant :
!expression

où expression est l'opérande de l'opérateur NÉGATION. La


table de vérité de l'opérateur NÉGATION est présentée dans le
tableau 8.3.

TABLEAU 8.3 Valeurs retournées par l'opérateur ! Opérateur


expression Valeur retournée par !
non nul 0

0 1

Examinons maintenant l'exemple du Listing 8.4, qui montre comment utiliser


l'opérateur logique de négation ( !).

TYPE LISTE 8.4 Utilisation de l'opérateur logique de négation ( !)


1 : /* 08L04.c : Utilisation de l'opérateur de
négation logique */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int num ;
7 :
8: num = 7 ;
9 : printf("Given num = 7\n") ;
10 : printf(" !(num < 7) yields : %d\n", !(num < 7)) ;
11 : printf(" !(num > 7) yields : %d\n", !(num > 7)) ;
12 : printf(" !(num == 7) yields : %d\n", !(num == 7)) ;
13 : retour 0 ;
14 : }

Le résultat suivant est affiché en exécutant le fichier exécutable 08L04.exe :


Numéro donné = 7
SORTIE !(num < 7) renvoie : 1
!(num > 7) retourne : 1
!(num == 7) renvoie : 0

A la ligne 8, notez qu'une variable entière num est initialisée à 7, qui est ensuite
ANALYSE
affichée par la fonction printf() à la ligne 9.
132 Heure
132

A la ligne 10, l'expression relationnelle num < 7 est évaluée à 0 car la valeur de num
n'estpas inférieure à 7. Cependant, en utilisant l'opérateur de négation logique, !(num
< 7) donne 1. (Voir la table de vérité de l'opérateur ! dans le tableau 8.3).
8
De même, l'expression logique !(num > 7) est évaluée à 1 à la ligne 11.
Comme num a la valeur 7, l'expression relationnelle num == 7 donne 1 ; cependant,
l'expression logique !(num == 7) à la ligne 12 donne 0.

Manipulation des bits


Dans les heures précédentes, vous avez appris que les données et les fichiers
informatiques sont constitués de bits. Dans cette section, vous découvrirez un ensemble
d'opérateurs qui vous permettent d'accéder à des bits spécifiques et de les manipuler.
Mais avant d'aller plus loin, nous allons en apprendre davantage sur les nombres binaires
et hexadécimaux, et sur la manière de convertir un nombre décimal en sa représentation
binaire ou hexadécimale.

Conversion du décimal en hexadécimal ou en binaire


Comme je l'ai déjà mentionné, un bit est la plus petite unité de stockage dans le monde
informatique. Un bit ne peut contenir que les valeurs 0 et 1 (0 et 1 sont utilisés pour
représenter les états d'activation et de désactivation des interrupteurs électroniques qui
composent l'unité centrale et la mémoire d'un ordinateur). Chaque chiffre d'un nombre
hexagonal se compose de 4 bits. Il est facile de convertir un nombre décimal en un
nombre hexagonal ou binaire. Le tableau 8.4 présente les nombres hexadécimaux de 0 à
F et leurs représentations binaires et décimales correspondantes.

TABLEAU 8.4 Nombres exprimés sous différentes formes


Hexago Binaire Décimale
ne
0 0000 0

1 0001 1

2 0010 2

3 0011 3

4 0100 4

5 0101 5

6 0110 6

7 0111 7

8 1000 8

9 1001 9

A 1010 10
Utilisation des opérateurs 133
conditionnels continue
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

130 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
8

TABLEAU suite
8.4
Hexagone Binaire Décimale
B 1011 11

C 1100 12

D 1101 13

E 1110 14

F 1111 15

Voyons comment convertir un nombre décimal en un nombre binaire ou hexadécimal, et


vice versa. Comme vous le savez, le binaire est un système de numération basé sur deux
chiffres. Chaque chiffre d'un nombre binaire est appelé bit et peut être égal à 1 ou 0. Si la
position d'un bit dans un nombre binaire est n, le bit peut avoir une valeur de 0 ou de 2 à
la puissance n. La position d'un bit dans un nombre binaire est comptée à partir de la
droite du nombre binaire. Le bit le plus à droite est à la position zéro. Ainsi, étant donné
un nombre binaire 1000 (sa valeur hexadécimale est 8), nous pouvons calculer sa valeur
décimale comme suit :

1000 → 1 * + 0 * + 0 * + 0 * → → 8 (décimal) C'est-


23 22 21 20 23

à-dire que la valeur décimale du nombre binaire 1000 est 8.


De même, étant donné un nombre binaire 1110, dont la valeur hexagonale est E, vous
pouvez calculer sa valeur décimale comme suit :

1110 → 1 * + 1 * + 1 * + 0 * → → 14 (décimal) En
23 22 21 20 23

d'autres termes, la valeur décimale du nombre binaire 1110 est 14.


Si vous voulez convertir un nombre décimal, par exemple 10, en son équivalent binaire,
vous avez le processus suivant :

10 → + →1* +0* +1* +0* → 1010 (binaire) ou A (hexadécimal)


23 21 23 22 21 20

De même, vous pouvez convertir le reste des nombres décimaux du tableau 8.4 en leurs
contreparties binaires, ou vice versa.

Utilisation des opérateurs bitwise


Il existe six opérateurs de manipulation de bits dans le langage C :
Opérateur Description de l'activité
& L'opérateur ET binaire
| L'opérateur OR binaire
Utilisation des opérateurs 131
^ conditionnels
L'opérateur OU exclusif (XOR) dans le sens des bits
132 Heure
8

Opérateur Description de l'activité


~ L'opérateur de complément binaire 8
>> L'opérateur de décalage vers la droite
<< L'opérateur de décalage à gauche

Cette section et la suivante donnent des explications et des exemples des opérateurs de
manipulation de bits.
Les formes générales des opérateurs bitwise sont les suivantes :
x &
y x
| y
x ^
y
~x

Ici, x et y sont des opérandes.


L'opérateur & compare chaque bit de x au bit correspondant de y. Si les deux bits sont
à 1, 1 est placé à la même position du bit dans le résultat. Si l'un des bits ou les deux
sont à 0, 0 est placé dans le résultat.
Par exemple, l'expression avec deux opérandes binaires, 01 & 11, donne 01.
L'opérateur | place 1 dans le résultat si l'un des opérandes est 1. Par exemple,
l'expression 01 | 11 donne 11. L'opérateur | produit un bit 0 si - et seulement si -
les deux bits de l'opérande sont 0.
L'opérateur ^ place 1 dans le résultat si l'un des opérandes, mais pas les deux, vaut 1. Par
conséquent, l'expression 01 ^ 11 donne 10.
Enfin, l'opérateur ~ ne prend qu'un seul opérande. Cet opérateur inverse chaque bit de
l'opérande. Par exemple, ~01 donne 10.
Le tableau 8.5 présente d'autres exemples d'utilisation des opérateurs bitwise aux formats
décimal, hexadécimal et binaire (dans les trois colonnes de gauche). Les résultats
correspondants, aux formats binaire, hexadécimal et décimal, sont répertoriés dans les
trois colonnes de droite. Les nombres hexadécimaux sont préfixés par 0x.

TABLEAU 8.5 Exemples d'utilisation des opérateurs bitwise


Expressions Résultats
Décimale Hexagone Binaire Décimale Hexag Binaire
one
12 & 10 0x0C & 0x0A 1100 & 1010 8 0x08 1000

12 | 10 0x0C | 0x0A 1100 | 1010 14 0x0E 1110

12 ^ 10 0x0C ^ 0x0A 1100 ^ 1010 6 0x06 0110

~12 ~0x000C ~0000000000001100 65523 FFF3 1111111111110011


Utilisation des opérateurs 133
conditionnels

Notez que la valeur complémentaire de 12 est 65523 car le type de données entier non
signé (16 bits) a le nombre maximum de 65535. En d'autres termes, 65 523 est le résultat
de la soustraction de 12 à 65 535 (le modificateur de données non signées est présenté
dans l'Heure 9, "Travailler avec des modificateurs de données et des fonctions
mathématiques").
Le programme du Listing 8.5 illustre l'utilisation des opérateurs bitwise.

TYPE LISTE 8.5 Utilisation des opérateurs bitwise


1 : /* 08L05.c : Utilisation des
opérateurs bitwise */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : intx , y, z
;
7 :
8: x = 4321 ;
9: y = 5678 ;
10 : printf("Given x = %u, i.e., 0X%04X\n", x,
x) ; 11 : printf(" y = %u, i.e., 0X%04X\n", y, y)
; 12 : z = x & y ;
13 : printf("x & y returns : %6u, i.e., 0X%04X\n", z,
z) ; 14: z = x | y ;
15 : printf("x | y returns : %6u, i.e., 0X%04X\n", z,
z) ; 16: z = x ^ y ;
17: printf("x ^ y returns : %6u, i.e., 0X%04X\n", z,
z) ; 18: printf(" ~xreturns : %6u, i.e.,
0X%04X\n", ~x, ~x) ; 19: return 0 ;
20 : }

Une fois le fichier exécutable, 08L05.exe, créé et exécuté sur mon ordinateur, la sortie
suivante s'affiche à l'écran :
Étant donné x = 4321, soit 0X10E1
SORTIE y = 5678, soit 0X162E
x & y renvoie : 4128, soit
0X1020 x | y retourne : 5871,
soit 0X16EF x ^ y retourne :
1743, soit 0X06CF
~x retourne : 61214, c'est-à-dire 0XEF1E

Dans le Listing 8.5, trois variables entières, x, y et z, sont déclarées à la ligne 6.


ANALYSE
Les lignes 8 et 9 définissent x et y à 4321 et 5678, respectivement. Les lignes 10
et 11 impriment ensuite
les valeurs de x et y en format décimal et hexadécimal. Les nombres hexagonaux
sont préfixés par 0X.
134 Heure
8
L'instruction de la ligne 12 affecte le résultat de l'opération effectuée par l'opérateur
bitwise AND (&) avec les variables x et y. Ensuite, la ligne 13 affiche le résultat en
format décimal et hexadécimal.
Utilisation des opérateurs 135
conditionnels

Les lignes 14 et 15 effectuent l'opération spécifiée par l'opérateur OU binaire (|) et


impriment le résultat en format décimal et hexadécimal. De même, les lignes 16 et 17
donnent le résultat de l'opération effectuée par l'opérateur XOR (^).
8
Enfin, l'instruction de la ligne 18 imprime la valeur complémentaire de x en utilisant
l'opérateur de complément de bits (~). Le résultat est affiché à l'écran en format décimal
et hexadécimal.
Notez que le spécificateur de format d'entier non signé avec une largeur de champ
minimale de 6, %6u, et le spécificateur de format hexadécimal majuscule avec une
largeur minimale de 4, %04X, sont utilisés dans la fonction printf(). Le type de
données entier non signé est utilisé ici pour que la valeur complète d'un entier puisse
être affichée et comprise facilement. De plus amples détails sur le modificateur de
données non signées sont présentés dans l'heure 9.

Ne confondez pas les opérateurs binaires & et | avec les opérateurs logiques &&.
et ||. Par exemple,
(x=1) & (y=10)
est une expression complètement différente de
(x=1) && (y=10)

Utilisation des opérateurs de décalage


Il existe deux opérateurs de décalage en C. L'opérateur >> décale les bits d'un opérande
vers la droite ; l'opérateur << décale les bits vers la gauche.
Les formes générales des deux opérateurs de changement sont les suivantes
x >>
y x
<< y

Ici, x est un opérande qui va être décalé. y contient le nombre spécifié de places à
décaler.
Par exemple, l'expression 8 >> 2 indique à l'ordinateur de décaler l'opérande 8 vers la droite
2 bits, ce qui donne le nombre 2 en décimal. Ce qui suit :
8 >> 2 qui est équivalent à (1 * 23 + 0 * 22 + 0 * 21 + 0 * 20) >> 2 produit

ce qui suit :
(0 * 23 + 0 * 22 + 1 * 21 + 0 * 20), ce qui équivaut à 0010 (en format binaire) ou à
2 (en format décimal).
136 Heure
8

De même, l'expression 5 << 1 décale l'opérande 5 d'un bit vers la gauche et donne 10
en décimal.
Le programme du Listing 8.6 imprime plus de résultats en utilisant les opérateurs de décalage.

TYPE LISTE 8.6 Utilisation des opérateurs Shift


1 : /* 08L06.c : Utilisation des
opérateurs de décalage */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : intx , y, z
;
7 :
8: x = 255 ;
9: y = 5 ;
10 : printf("Given x = %4d, i.e., 0X%04X\n", x,
x) ; 11 : printf(" y = %4d, i.e., 0X%04X\n", y,
y) ; 12 : z = x >> y ;
13 : printf("x >> y yields : %6d, i.e., 0X%04X\n", z,
z) ; 14: z = x << y ;
15 : printf("x << y yields : %6d, i.e., 0X%04X\n", z,
z) ; 16: return 0 ;
17 : }

Le résultat suivant est obtenu en exécutant le fichier exécutable 08L06.exe sur mon
ordinateur :
Étant donné x = 255, c'est-à-dire 0X00FF
SORTIE y = 5, c'est-à-dire 0X0005
x >> y donne: 7, c'est-à-dire
0X0007 x << y donne : 8160, soit
0X1FE0

Trois variables entières, x, y et z, sont déclarées à la ligne 6 du Listing 8.6. x est


ANALYSE
initialisé à 255 à la ligne 8 ; y est initialisé à 5 à la ligne 9. Ensuite, les lignes 10
et 11 dis-
jouer les valeurs de x et y à l'écran.
L'instruction de la ligne 12 décale y bits de l'opérande x vers la droite, puis affecte le
résultat à z. La ligne 13 imprime le résultat du décalage effectué à la ligne 12. Le résultat
est 7 en décimal, ou 0X0007 en hexadécimal.
Les lignes 14 et 15 décalent l'opérande x vers la gauche de y bits et affichent également
le résultat à l'écran. Le résultat du décalage vers la gauche est 8160 en décimal, ou
0x1FE0 en hexadécimal.
Utilisation des opérateurs 137
conditionnels

L'opération de l'opérateur shift-right (>>) est équivalente à la division par


des puissances de deux. En d'autres termes, ce qui suit : 8
x >> y
est équivalent à ce qui suit :
x / 2
Ici, x est un nombre entier non négatif.
D'autre part, le décalage vers la gauche équivaut à une multiplication par
des puissances de deux, c'est-à-dire qu'il n'y a pas de décalage vers la
droite,
x << y
est équivalent à
x * 2

Que signifie x?y:z ?


L'opérateur ? : est appelé l'opérateur conditionnel, qui est le seul à prendre trois
opérandes. La forme générale de l'opérateur conditionnel est
x ? y : z

Ici, x, y et z sont trois expressions d'opérande. Parmi elles, x contient la condition de


test, et y et z représentent les deux valeurs finales possibles de l'expression. Si x est
différent de zéro (logiquement vrai), y est choisi ; sinon, z est le résultat de l'expression
condi- tionnelle. L'opérateur conditionnel est utilisé comme une sorte d'abréviation pour
une instruction if.
Par exemple, l'expression
x > 0 ? 'T' : 'F'

est évaluée à "T" si la valeur de x est supérieure à 0. Sinon, l'expression conditionnelle est
évaluée à la valeur "F".
La liste 8.7 illustre l'utilisation de l'opérateur conditionnel.

TYPE LISTE 8.7 Utilisation de l'opérateur conditionnel


1 : /* 08L07.c : Utilisation de
l'opérateur ? : */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {

continue
138 Heure
8

LISTE 8.7 suite


6 : int x ;
7 :
8: x = sizeof(int) ;
9 : printf("%s\n",
10 : (x == 2)
11 : ? "Le type de données int a 2
octets." 12: : "int n'a pas 2
octets") ;
13 : printf("La valeur maximale de int est : %d\n",
14 : (x != 2) ? ~(1 << x * 8 - 1) : ~(1 << 15) ) ;
15 : retour 0 ;
16 : }

Le résultat suivant s'affiche à l'écran lorsque j'exécute le fichier exécutable 08L07.exe


sur ma machine :
Le type de données int a 2 octets
SORTIE La valeur maximale de int est de : 32767

Dans le Listing 8.7, la taille du type de données int est mesurée en premier lieu à la ligne 8 en
ANALYSE
utilisant la fonction
et le nombre d'octets est affecté à la variable entière x.
Les lignes 9 à 12 contiennent une instruction dans laquelle l'opérateur conditionnel (?
:) est utilisé pour tester si le nombre d'octets enregistrés dans x est égal à 2, et le
résultat est imprimé. Si le nombre d'octets sauvegardés dans x est égal à 2, le résultat
est imprimé.
x == 2 est évaluée comme non nulle, la chaîne Le type de données int a 2 octets
est imprimée par la fonction printf() dans l'instruction. Sinon, la deuxième chaîne, int
n'a pas 2 octets, est affichée à l'écran.

En outre, l'instruction des lignes 11 et 12 tente de trouver la valeur maximale du type de


données int sur la machine actuelle. L'expression x != 2 est évaluée en premier dans
l'instruction. Si l'expression renvoie une valeur non nulle (c'est-à-dire que le numéro
d'octet du type de données int n'est pas égal à 2), l'expression ~(1 << x * 8 - 1) est
évaluée et le résultat est choisi comme valeur de retour. Ici, l'expression ~(1 << x * 8 -
1) est une forme générale permettant de calculer la valeur maximale du type de données
int, qui est équivalente à2

[ic:super](x * 8 - 1) - 1. (L'opérateur de complément, ~, et l'opérateur de décalage,


<<, ont été présentés dans les sections précédentes de cette heure).
En revanche, si la condition de test x != 2 à la ligne 12 renvoie 0, ce qui signifie que la
valeur de x est effectivement égale à 2, c'est le résultat de l'expression ~(1 << 15) qui
est choisi. Vous avez peut-être déjà compris que ~(1 << 15) est équivalent à 215-1, qui
est la valeur maximale que le type de données int 16 bits peut avoir.
Utilisation des opérateurs 139
conditionnels
Le résultat affiché à l'écran montre que le type de données int sur ma machine a une
longueur de 2 octets (ou 16 bits) et que la valeur maximale du type de données int est
32767.
140 Heure
8

Résumé
Dans cette leçon, vous avez appris les opérateurs logiques et de manipulation de bits 8
suivants, très importants en C :
• L'opérateur sizeof évalue le nombre d'octets d'un type de données spécifié. Vous
pouvez utiliser cet opérateur pour mesurer la taille d'un type de données sur votre
machine.
• L'opérateur logique ET (&&) ne donne 1 (vrai logique) que si les deux
expressions de ses opérandes) sont évaluées à des valeurs non nulles. Dans le cas
contraire, l'opérateur donne 0.
• L'opérateur logique OR (||) ne produit 0 que si ses deux opérandes sont évalués à
0. Dans le cas contraire, l'opérateur donne 1.
• L'opérateur de négation logique ( !) produit 0 lorsque son opérande est différent
de zéro et produit 1 uniquement si son opérande est égal à 0.
• Il existe six opérateurs de manipulation de bits : l'opérateur ET (&), l'opérateur OU
(|), l'opérateur XOR (^), l'opérateur de complément (^) et l'opérateur de
complément (^).
~), l'opérateur de décalage vers la droite (>>) et l'opérateur de décalage vers la gauche (<<).
• L'opérateur conditionnel (? :) est le seul opérateur en C qui peut prendre
trois opérandes.
Dans la prochaine leçon, vous découvrirez les modificateurs de type de données du langage C.

Q&R
Q Pourquoi avons-nous besoin de l'opérateur sizeof ?

A L'opérateur sizeof peut être utilisé pour mesurer la taille de tous les types de données définis en
C. Lorsque vous écrivez un programme C portable qui a besoin de connaître la
taille d'une variable entière, c'est une mauvaise idée de coder en dur la taille en
fonction de la machine que vous utilisez actuellement. La meilleure façon
d'indiquer au programme la taille de la variable est d'utiliser l'opérateur sizeof, qui
donne la taille de la variable entière au moment de l'exécution.
Q Quelle est la différence entre | et || ?
Un | est l'opérateur OR bit à bit qui prend deux opérandes. L'opérateur | compare
chaque bit d'un opérande au bit correspondant d'un autre opérande. Si les deux bits
sont à 0, 0 est placé à la même position que le bit dans le résultat. Sinon, 1 est
placé dans le résultat.
En revanche, ||, en tant qu'opérateur logique OR, nécessite deux opérandes
(ou des expressions). L'opérateur ne produit 0 que si ses deux opérandes sont
évalués à 0. Sinon, l'opérateur produit 1.
Utilisation des opérateurs 141
conditionnels

Q Pourquoi 1 << 3 est-il équivalent à 1 * 23 ?


R L'expression 1 << 3 indique à l'ordinateur de décaler de 3 bits l'opérande 1 vers la
gauche. Le format binaire de l'opérande est 0001. (Après avoir été décalé de 3 bits
vers la gauche, le nombre binaire devient 1000, ce qui équivaut à 1 * 23+0 * 22+0 * 21+0
* 20 ; c'est-à-dire 1 * 23.
Q Que peut faire l'opérateur conditionnel (? :)?
A S'il existe deux réponses possibles sous certaines conditions, vous pouvez utiliser
l'opéra- teur ? : pour choisir l'une des deux réponses sur la base du résultat obtenu
en testant les condi- tions. Par exemple, l'expression (âge > 65) ? "Retraité" :
"Non retraité" indique à l'ordinateur que si la valeur de l'âge est supérieure à
65, c'est la chaîne Retraité qui doit être choisie ; sinon, c'est la chaîne Non
retraité qui est choisie.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe D, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Que donnent les expressions (x=1) && (y=10) et (x=1) & (y=10),
respectivement ?

2. Étant donné x = 96, y = 1 et z = 69, à quoi correspond l'expression !y ? x == z : y ?


évaluer ?
3. Si vous avez deux variables int x et y, avec x fixé à la valeur binaire
0011000000111001 et y fixé à la valeur binaire 1100111111000110, quelles
valeurs sont produites par les deux expressions ~x et ~y ?
4. Étant donné x=9, que donne (x%2==0)||(x%3==0) ? Qu'en est-il de
(x%2==0)&&(x%3==0) ?
5. 8 >> 3 est-il équivalent à 8 / 23 ? Et 1 << 3 ?

Exercices
1. Étant donné x = 0xEFFF et y = 0x1000 (c'est-à-dire EFFF et 1000 en tant que
valeurs hexagonales), quelles valeurs hexagonales obtenez-vous en évaluant ~x
et ~y ?
2. En prenant les valeurs de x et y attribuées dans l'exercice 1, écrivez un
programme qui imprime les valeurs de !x et !y en utilisant les formats %d et %u
dans la fonction printf().
142 Heure
8

3. Étant donné x = 123 et y = 4, écrivez un programme qui affiche les résultats des
expressions x << y et x >> y.
8
4. Ecrivez un programme qui affiche les valeurs (en hexadécimal) des expressions
0xFFFF^0x8888, 0xABCD & 0x4567, et 0xDCBA | 0x1234.
5. Utilisez l'opérateur ? : et l'instruction for pour écrire un programme qui continue
à prendre les caractères saisis par l'utilisateur jusqu'à ce que le caractère q soit
comptabilisé. (Astuce : Mettez x!='q'
? 1 : 0 comme deuxième expression dans une instruction for).
Utilisation des opérateurs 143
conditionnels
HEURE 9
Travailler avec des
modificateurs de
données et des
fonctions
mathématiques
Si vous n'y parvenez pas, transformez vos données.
-Les lois de l'informatique de Murphy
Dans l'heure 4, "Comprendre les types de données et les mots-clés", vous
avez découvert plusieurs types de données, tels que char, int, float et
double, dans le langage C. Dans cette heure, vous découvrirez quatre
modificateurs de données qui vous permettent de mieux contrôler les
données. Dans cette heure, vous découvrirez quatre modificateurs de
données qui vous permettent de mieux contrôler les données. Les mots-clés
C pour les quatre modificateurs de données sont les suivants
• signé
• non signé
• court
• long
142 Heure
9

Vous apprendrez également à connaître plusieurs fonctions mathématiques fournies par


le langage C, telles que
• La fonction sin()
• La fonction cos()
• La fonction tan()
• La fonction pow()
• La fonction sqrt()

Activation ou désactivation du bit de signe


Comme vous le savez, il est très facile d'exprimer un nombre négatif en décimal. Il suffit
de mettre un signe moins devant la valeur absolue du nombre (la valeur absolue étant la
distance du nombre par rapport à zéro). (La valeur absolue étant la distance du nombre
par rapport à zéro.) Mais comment l'ordinateur représente-t-il un nombre négatif en
format binaire ?
Normalement, un bit peut être utilisé pour indiquer si la valeur d'un nombre représenté en
format binaire est négative. Ce bit est appelé bit de signe. Les deux sections suivantes
présentent deux modificateurs de données, signés et non signés, qui peuvent être
utilisés pour activer ou désactiver le bit de signe.
Il convient de noter que certains ordinateurs n'expriment pas les nombres négatifs de la
manière décrite ; en fait, la norme du langage C n'exige pas l'utilisation d'un bit de signe,
bien qu'il s'agisse d'une méthode courante. Il est important de comprendre les différences
entre les types de données signées et non signées.

Le modificateur signé
Pour les entiers, le bit le plus à gauche peut être utilisé comme bit de signe. Par exemple,
si le type de données int a une longueur de 16 bits et que le bit le plus à droite est
compté comme le bit 0, vous pouvez utiliser le bit 15 comme bit de signe. Lorsque le bit
de signe est à 1, le compilateur C sait que la valeur représentée par la variable de données
est négative.
Il existe plusieurs façons de représenter une valeur négative des types de données float
ou double. Les implémentations des types de données float et double dépassent le
cadre de ce livre. Vous pouvez vous référer au livre de Kernighan et Ritchie The C
Programming Language pour plus de détails sur les implémentations des valeurs
négatives de type float ou double.
Le langage C fournit un modificateur de données, signed, qui peut être utilisé pour
indiquer au compilateur que les types de données entières (char, int, short int et long
int) utilisent le signe
Travailler avec des modificateurs de données et 143
des fonctions mathématiques

bit. (Les modificateurs short et long sont présentés plus loin dans ce chapitre). Par défaut,
tous les types de données entières, à l'exception du type de données char, sont des
quantités signées. Mais la norme ANSI n'exige pas que le type de données char soit
signé ; c'est aux vendeurs de compilateurs de le faire. Par conséquent, si vous souhaitez
utiliser une variable de caractère signée et vous assurer que le compilateur le sait, vous
pouvez déclarer la variable de caractère comme suit :
signé char ch ;

pour que le compilateur sache que la variable de caractere ch est signee, ce qui signifie 9
que la variante peut contenir des valeurs negatives et positives. Si le type de caractère a
7 7
une longueur de 8 bits, un caractère signé peut contenir des valeurs allant de -128
8
(c'est-à-dire 2 ) à 127 (2 -1). En revanche, un caractère non signé s'étendrait dans ce cas
de 0 à 255 (2 -1).

Le modificateur non signé


Le langage C offre également le modificateur non signé, qui peut être utilisé pour
indiquer au compilateur C que le type de données spécifié ne peut contenir que des
valeurs non négatives.
Comme le modificateur signé, le modificateur non signé n'est significatif que pour les
types de données entiers (char, int, short int et long int).
Par exemple, la déclaration
unsigned int x ;

indique au compilateur C que la variable entière x ne peut prendre que des valeurs
216-1
positives comprises entre 0 et 65535 (c'est-à-dire ), si le type de données int a une
-
longueur de 16 bits ; un int (signé) contiendrait des valeurs comprises entre -32768 (
215-1 215
) et 32767 ( ).

En fait, unsigned int est équivalent à unsigned (par lui-même) selon la norme ANSI.
En d'autres termes, unsigned int x ; est la même chose que unsigned x ;.
La norme ANSI permet également d'indiquer qu'une constante est de type non signé
en ajoutant le suffixe u ou U à la constante. Par exemple,
unsigned int x, y
; x = 12345U ;
y = 0xABCDu ;

Ici, les constantes entières non signées 12345U et 0xABCDu sont affectées aux variables x et
y, respectivement.

Le programme de la liste 9.1 est un exemple d'utilisation des modificateurs signés et non signés.
144 Heure
9

LISTE 9.1 Modifier des données avec des données signées et non signées
1 : /* 09L01.c : Utilisation des modificateurs
signés et non signés */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: signé char ch ;
7 : int x ;
8 : unsigned int y
;
9 :
10: ch = 0xFF ;
11: x = 0xFFFF ;
12: y = 0xFFFFu ;
13 : printf("La décimale de signed 0xFF est %d.\n",
ch) ; 14 : printf("La décimale de signed 0xFFFF est
%d.\n", x) ;
15 : printf("La décimale du non signé 0xFFFFu est %u.\n",
y) ; 16 : printf("L'hexagone de la décimale 12345 est
0x%X.\n", 12345) ;
17 : printf("L'hexagone de la décimale -12345 est 0x%X.\n", -12345) ;
18 : retour 0 ;
19 : }

Sur ma machine, le fichier exécutable du programme du Listing 9.1 s'appelle 09L01.exe.


(Notez que lorsque vous compilez le programme du Listing 9.1, vous pouvez voir un
mes- sage d'avertissement concernant l'instruction d'affectation ch = 0xFF ; à la ligne
10 en raison du fait que ch est déclaré comme une variable char signée. Vous pouvez
ignorer ce message d'avertissement).
Voici la sortie affichée à l'écran après avoir exécuté l'exécutable sur mon ordinateur :

SORTIE La décimale de 0xFF signé est -1


La décimale de 0xFFFF signé est -
1.
La valeur décimale de la valeur non signée
0xFFFFu est 65535. L'hexagone de la
décimale 12345 est 0x3039.
L'hexagone de la décimale -12345 est 0xCFC7.

Comme vous le voyez dans le Listing 9.1, la ligne 6 déclare une variable char signée, ch. La
ANALYSE
variable int
La variable x et la variable int non signée y sont déclarées aux lignes 7 et 8, respectivement.
tivement. Les trois variables, ch, x et y, sont initialisées aux lignes 10 à 12. Notez qu'à
la ligne 12, u est suffixé à 0xFFFF pour indiquer que la constante est un entier non
signé.
L'instruction de la ligne 13 affiche la valeur décimale de la variable char signée ch. La
sortie à l'écran montre que la valeur décimale correspondante de 0xFF est -1 pour la
Travailler avec des modificateurs de données et 145
variable charfonctions
des signée ch.mathématiques
Les lignes 14 et 15 affichent les valeurs décimales de la variable int x (qui est signée
par défaut) et de la variable unsigned int y, respectivement. Notez que pour la
variable y, le spécificateur de format entier non signé %u est utilisé dans la fonction
printf() de la ligne 15.
146 Heure
9

(En fait, vous vous souvenez peut-être que %u a été utilisé pour spécifier le type de
données unsigned int comme format d'affichage dans l'heure précédente).
D'après la sortie, vous constatez que 0xFFFF est égal à -1 pour le type de données int signé, et que
65535 pour le type de données unsigned int. Ici, le type de données "integer" a une longueur de 16 bits.

Les lignes 16 et 17 affichent 0x3039 et 0xCFC7, qui sont les formats hexadécimaux des
valeurs décimales 12345 et -12345, respectivement. Selon la méthode mentionnée dans la
dernière section, 0xCFC7 est obtenu en ajoutant 1 à la valeur complétée de 0x3039. 9
Cet exemple peut donner des résultats différents, en fonction de la largeur des différents
types de données sur votre système. Il est important de comprendre la différence entre
les types de données signées et non signées.

Modification de la taille des données


Il arrive que l'on veuille réduire la mémoire occupée par les variables ou que l'on doive
augmenter l'espace de stockage de certains types de données. Heureusement, le langage C
offre la possibilité de modifier la taille des types de données. Les deux modificateurs de
données, short et long, sont présentés dans les deux sections suivantes.

Le modificateur court
Un type de données peut être modifié pour occuper moins de mémoire en utilisant le
modificateur court. Par exemple, vous pouvez appliquer le modificateur court à une
variable entière de 32 bits, ce qui peut réduire la mémoire occupée par la variable à 16
bits seulement.
Vous pouvez utiliser le modificateur court comme suit :
court x ;

ou
unsigned short y ;

Par défaut, le type de données short int est un nombre signé. Par conséquent, dans l'exemple court x ;
x est une variable signée d'un entier court.

Le modificateur long
Si vous avez besoin de plus de mémoire pour conserver des valeurs d'un éventail plus large, vous pouvez utiliser
la fonction long
pour définir un type de données avec un espace de stockage plus important.
Par exemple, pour une variable entière x d'une longueur de 16 bits, la déclaration
long int x ;

augmente la taille de x à au moins 32 bits.


Travailler avec des modificateurs de données et 147
des fonctions mathématiques

La norme ANSI permet d'indiquer qu'une constante est de type long en lui attribuant le suffixe l ou
L à la constante :
long int x, y
; x =
123456789l ;
y = 0xABCD1234L ;

Ici, les constantes de type long int, 123456789l et 0xABCD1234L, sont affectées
aux variables x et y, respectivement.
Vous pouvez également déclarer une variable de type "long integer" de la manière suivante :
long x ;

ce qui équivaut à
long int x ;

La liste 9.2 contient un programme qui peut imprimer les nombres d'octets fournis par le
compilateur C utilisé pour compiler le programme pour différents types de données
modifiées.

LISTE 9.2 Modification des données avec short et long


1 : /* 09L02.c : Utilisation des modificateurs
court et long */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : printf("The size of short int is :
%d.\n", 7: sizeof(short int)) ;
8 : printf("The size of long int is :
%d.\n", 9: sizeof(long int)) ;
10 : printf("The size of float is :
%d.\n", 11 : sizeof(float))
;
12 : printf("La taille du double est :
%d.\n", 13 : sizeof(double))
;
14 : printf("The size of long double is :
%d.\n", 15: sizeof(long double)) ;
16 : retour 0 ;
17 : }

J'obtiens le résultat suivant après avoir lancé l'exécutable 09L02.exe sur mon ordinateur :
La taille d'un short int est : 2.
SORTIE La taille d'un long int est
: 4. La taille de float est
: 4 : 4.
La taille du double est : 8.
La taille du double long est : 10.
148 Heure
9

ANALYSE Dans le Listing 9.2, l'opérateur sizeof et la fonction printf() sont utilisés
pour mesurer la taille des types de données modifiés et afficher les résultats à
l'écran.
Par exemple, les lignes 6 et 7 obtiennent la taille du type de données short int et
impriment le nombre d'octets, 2, à l'écran. D'après la sortie, vous savez que le type de
données short int a une longueur de 2 octets sur ma machine.
De même, les lignes 8 et 9 indiquent que la taille du type de données long int est de 4
octets, soit la même longueur que le type de données float obtenu aux lignes 10 et 11. 9
Les lignes 12 et 13 obtiennent la taille du type de données double, qui est de 8 octets
sur ma machine. Ensuite, après avoir été modifiée par le modificateur long, la taille du
type de données double est portée à 10 octets (c'est-à-dire 80 bits), ce qui est imprimé
par l'appel à printf() aux lignes 14 et 15.
Comme dans l'exemple précédent, vos résultats seront probablement différents si votre
système prend en charge des largeurs de données différentes de celles de ma machine.
En exécutant ce programme sur votre propre machine, vous pouvez déterminer les
largeurs de ces types de données pour votre système.

Ajout de h, l ou L aux spécificateurs de format


printf et fprintf
Les fonctions printf et fprintf doivent connaître le type de données exact des
arguments qui leur sont transmis afin d'évaluer correctement ces arguments et
d'imprimer leurs valeurs de manière significative. La chaîne de format des fonctions
printf et fprintf utilise les spécificateurs de con- version d, i, o, u, x ou X pour
indiquer que l'argument correspondant est un entier et qu'il est de type int ou unsigned
int.
Vous pouvez ajouter h au spécificateur de format d'entier (comme ceci : %hd, %hi, ou %hu)
pour spécifier que l'argument correspondant est un short int ou unsigned short int.
En revanche, l'utilisation de %ld ou %Ld indique que l'argument correspondant est un
long int. %lu ou %Lu est alors utilisé pour les données du long unsigned int.

Le programme de la liste 9.3 montre l'utilisation de %hd, %lu et %ld.

LISTE 9.3 Utilisation de %hd, %ld et %lu


1 : /* 09L03.c : Utilisation des spécificateurs %hd,
%ld, et %lu */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : short int x ;
continue
Travailler avec des modificateurs de données et 149
des fonctions mathématiques

LISTE 9.3 suite


7 : unsigned int y ;
8 : long int s ; 9 :
unsigned long int t ; 10
:
11: x = 0xFFFF ;
12: y = 0xFFFFU ;
13: s = 0xFFFFFFl ;
14: t = 0xFFFFFFL ;
15 : printf("Le short int de 0xFFFF est %hd.\n", x)
; 16 : printf("L'int non signé de 0xFFFF est %u.\n",
y) ; 17 : printf("Le long int de 0xFFFFFF est %ld.\n",
s) ;
18 : printf("The unsigned long int of 0xFFFFFF is %lu.\n", t) ;
19: return 0 ;
20 : }

Après la création du fichier exécutable 09L03.exe et son exécution sur ma machine, la


sortie suivante s'affiche à l'écran :
Le short int de 0xFFFF est -1.
SORTIE L'int non signé de 0xFFFF vaut 65535.
Le long int de 0xFFFFFFFF est -1.
Le long int non signé de 0xFFFFFFFF est 4294967295

Quatre types de données sont déclarés dans le Listing 9.3 : la variable short int x, la variable
ANALYSE
la variable y de type unsigned int, la variable s de type long int et la variable y de type
unsigned long int.
t. Les quatre variables sont initialisées aux lignes 6 à 9.
Pour afficher les valeurs décimales de x, y, s et t, les spécificateurs de format %hd, %u, %ld et
%lu sont utilisés, respectivement, dans les lignes 15-18 pour convertir les nombres
hexadécimaux correspondants en nombres décimaux. La sortie du programme de la liste
9.3 montre que les valeurs contenues dans x, y, s et t ont été correctement affichées à
l'écran.

Fonctions mathématiques en C
Fondamentalement, les fonctions mathématiques fournies par le langage C peuvent être
classées en trois groupes :
• Fonctions trigonométriques et hyperboliques, telles que acos(), cos() et cosh().
• Fonctions exponentielles et logarithmiques, telles que exp(), pow() et log10().
• Diverses fonctions mathématiques, telles que ceil(), fabs() et floor().
Vous devez inclure le fichier d'en-tête math.h dans votre programme C avant de pouvoir
utiliser les fonctions mathématiques définies dans le fichier d'en-tête.
150 Heure
Les deux sections suivantes présentent9plusieurs fonctions mathématiques et décrivent
comment les utiliser dans vos programmes.
Travailler avec des modificateurs de données et 151
des fonctions mathématiques

Appel de sin(), cos() et tan()


Vous pouvez sauter les deux sections suivantes si vous n'êtes pas un amateur de
mathématiques, car elles ne sont pas essentielles à la compréhension du langage C lui-
même. Cependant, si vous avez besoin de faire des calculs mathématiques, vous
apprécierez que le langage C vous offre cet ensemble de fonctions mathématiques.
Par exemple, étant donné un angle x en radians, l'expression sin renvoie le sinus de l'angle.
La formule suivante permet de convertir la valeur d'un angle en degrés en valeur en
radians :
9
radians = degrés * (3,141593 / 180,0).

Ici, 3,141593 est la valeur approximative de pi. Si nécessaire, vous pouvez utiliser plus
de chiffres décimaux de pi.
Examinons maintenant la syntaxe des fonctions sin(), cos() et tan().
La syntaxe de la fonction sin() est la suivante
SYNTAX

#include <math.h>
double sin(double x)
;

Ici, la variable double x contient la valeur d'un angle en radians. La fonction sin() renvoie le
sinus de x dans le type de données double.
La syntaxe de la fonction cos() est la suivante
SYNTAX

#include <math.h>
double cos(double x)
;

Ici, la variable double x contient la valeur d'un angle en radians. La fonction cos() renvoie le
cosinus de x dans le type de données double.
La syntaxe de la fonction tan() est la suivante
SYNTAX

#include <math.h>
double tan(double x)
;

Ici, la variable double x contient la valeur d'un angle en radians. La fonction tan() renvoie la
tangente de x dans le type de données double.
La liste 9.4 montre comment utiliser les fonctions sin(), cos() et tan().

LISTE 9.4 Calcul des valeurs trigonométriques avec sin(), cos() et tan()
1 : /* 09L04.c : Utilisation des fonctions sin(), cos(),
et tan() */ 2 : #include <stdio.h>
3 : #include <math.h>
4 :
152 Heure
9 continue
Travailler avec des modificateurs de données et 153
des fonctions mathématiques

LISTE 9.4 suite


5 : main()
6 : {
7 : double x ;
8 :
9 : x = 45.0 ; /* 45 degrés */
10 x *= 3.141593 / 180.0 ; /* conversion en radians */
:
11 : printf("Le sinus de 45 est : %f.\n", sin)
; 12 : printf("Le cosinus de 45 est : %f.\n",
cos) ; 13 : printf("La tangente de 45 est :
%f.\n", tan) ; 14 : return 0 ;
15 : }

Le résultat suivant s'affiche à l'écran lorsque le fichier exécutable 09L04.exe est exécuté
:
Le sinus de 45 est : 0.707107.
SORTIE Le cosinus de 45 est : 0.707107.
La tangente de 45 est : 1.000000.

Notez que le fichier d'en-tête math.h est inclus dans la ligne 3, ce qui est
ANALYSE
nécessaire pour les fonctions mathématiques en C.
La variable double x du Listing 9.4 est initialisée avec 45.0 à la ligne 9. Ici, 45.0 est la
valeur de l'angle en degrés, qui est convertie en valeur correspondante en radians à la
ligne 10.
Ensuite, l'instruction de la ligne 11 calcule le sinus de x en appelant la fonction
sin() et imprime le résultat à l'écran. De même, la ligne 12 obtient le cosinus de x
et
l'affiche également à l'écran. Comme x contient la valeur d'un angle de 45 degrés, il n'est
pas surprenant de constater que les valeurs du sinus et du cosinus sont les mêmes, soit
environ 0,707107.
La ligne 13 donne la valeur de la tangente de x en utilisant la fonction tan(). Comme
vous le savez peut-être, la tangente de x est égale au sinus de x divisé par le cosinus
de x. Le sinus d'un angle de 45 degrés étant identique au cosinus d'un angle de 45
degrés, la tangente d'un angle de 45 degrés est égale à 1. Le résultat (en format flottant)
de 1,000000, à la troisième ligne de la sortie du listing, le prouve.
Pour simplifier les choses, vous pouvez déclarer une variable PI initialisée à 3,141593,
et une autre variable initialisée à 180,0, et les utiliser dans vos calculs. Vous pouvez
également déclarer une seule constante initialisée au résultat de 3,141593/180,0.

Appeler pow() et sqrt()


Les fonctions pow() et sqrt() sont deux autres fonctions mathématiques utiles en C.
Contrairement à d'autres langages, le C n'a pas d'opérateur intrinsèque pour élever un
154 Heure
nombre à une puissance. 9
Travailler avec des modificateurs de données et 155
des fonctions mathématiques

La syntaxe de la fonction pow() est la suivante


SYNTAX

#include <math.h>
double pow(double x, double y) ;

Ici, la valeur de la variable double x est élevée à la puissance de y. La fonction pow()


renvoie le résultat dans le type de données double.
La syntaxe de la fonction sqrt() est la suivante
SYNTAX

#include <math.h> 9
double sqrt(double x)
;

Ici, la fonction sqrt() renvoie la racine carrée non négative de x dans le type de
données double. Une erreur se produit si x est négatif.
Si vous donnez 0,5 à la fonction pow() comme deuxième argument et que x contient une
valeur non négative, les deux expressions, pow(x, 0,5) et sqrt, sont équivalentes.
Voyons maintenant comment appeler les fonctions pow() et sqrt() dans le programme
présenté dans le Listing 9.5.

LISTE 9.5 Application des fonctions pow() et sqrt()


1 : /* 09L05.c : Utilisation des fonctions pow()
et sqrt() */ 2 : #include <stdio.h>
3 : #include <math.h>
4 :
5 : main()
6 : {
7 : double x, y, z
;
8 :
9: x = 64.0 ;
10: y = 3.0 ;
11: z = 0.5 ;
12 : printf("pow(64.0, 3.0) returns : %7.0f\n", pow(x,
y)) ; 13 : printf("sqrt(64.0)
returns : .0f\n", sqrt) ;
14 : printf("pow(64.0, 0.5) returns : %2.0f\n", pow(x,
z)) ; 15:return 0 ;
16 : }

Ensuite, le résultat suivant s'affiche à l'écran après l'exécution du fichier 09L05.exe


est exécuté :
pow(64.0, 3.0) donne : 262144
SORTIE sqrt(64.0) renvoie : 8
pow(64.0, 0.5) retourne : 8

ANALYSE Les trois variables doubles de la liste 9.5, x, y et z, sont initialisées avec 64,0,
156 Heure
3 11. 9
,
0

e
t

0
,
5
,

r
e
s
p
e
c
t
i
v
e
m
e
n
t
,

d
a
n
s

l
e
s

l
i
g
n
e
s

à
Travailler avec des modificateurs de données et 157
des fonctions mathématiques

La fonction pow() de la ligne 12 prend x et y et calcule ensuite la valeur de x élevée


à la puissance de y. Comme le résultat est un double, mais que je sais que la partie
fractionnaire sera constituée de chiffres décimaux de 0, le spécificateur de format
%7.0f est utilisé dans la fonction printf() pour ne convertir que la partie non
fractionnaire de la valeur. Le résultat s'affiche à l'écran sous la forme 262144.
À la ligne 13, la racine carrée non négative de x est calculée en appelant la fonction
sqrt(). Comme à la ligne 12, le spécificateur de format %2.0f est utilisé à la ligne 13
pour convertir la partie non fractionnaire de la valeur renvoyée par la fonction sqrt(),
car la partie fractionnaire est constituée de tous les 0. Comme vous le voyez dans la
sortie, la racine carrée non négative de x est 8.
Comme je l'ai mentionné précédemment, l'expression pow(x, 0.5) est équivalente à
l'expression sqrt. Il n'est donc pas surprenant de voir que pow(x, z) dans l'énoncé de la
ligne 14 produit le même résultat que sqrt à la ligne 13.

Tous les calculs en virgule flottante, y compris les types de données float
et double, sont effectués en arithmétique double précision. En d'autres
termes, une variante de données flottantes doit être convertie en
double pour que le calcul puisse être effectué. Après le calcul, le double
doit être reconverti en flottant avant que le résultat puisse être
affecté à la variable flottante. Par conséquent, un calcul sur des
flottants peut prendre plus de temps.
La principale raison pour laquelle le C supporte le type de données float
est d'économiser de l'espace mémoire, car le type de données double
prend deux fois plus d'espace mémoire pour le stockage que le type de
données float. Dans de nombreux cas, une précision supérieure à celle

Résumé
Dans cette leçon, vous avez appris les modificateurs et fonctions mathématiques importants suivants :
• Le modificateur signé peut être utilisé pour déclarer des types de données char
et int capables de contenir des valeurs négatives et non négatives.
• Toutes les variables int en C sont signées par défaut.
• Le modificateur non signé peut être utilisé pour déclarer des types de données
char et int qui ne peuvent pas contenir de valeurs négatives. Cela permet de
doubler la plage de valeurs positives que la variable peut contenir.
• L'espace mémoire occupé par une variable de données peut être réduit ou
augmenté en utilisant le modificateur de données courtes ou longues,
respectivement.
158 Heure
9

• Il existe un ensemble de fonctions de la bibliothèque C, telles que sin(), cos()


et tan(), qui peuvent être utilisées pour effectuer des calculs trigonométriques ou
hyperboliques.
• Il existe un autre groupe de fonctions mathématiques en C - par exemple,
pow() - qui permettent d'effectuer des calculs exponentiels et logarithmiques.
• La fonction sqrt() renvoie une racine carrée non négative. L'expression sqrt
est équivalente à l'expression pow(x, 0.5), si x a une valeur non négative.
Vous ne pouvez pas passer une valeur négative à la fonction sqrt(), car cela
entraînera une erreur.
9
• Le fichier d'en-tête math.h doit être inclus dans votre programme C si vous
appelez des fonctions mathématiques déclarées dans ce fichier d'en-tête.
Dans la prochaine leçon, vous apprendrez plusieurs instructions de flux de contrôle très importantes en C.

Q&R
Q Quel bit peut être utilisé comme bit de signe dans un nombre entier ?
A Le bit le plus à gauche peut être utilisé comme bit de signe pour un entier. Par
exemple, supposons que le type de données int ait une longueur de 16 bits. Si
vous comptez la position des bits de droite à gauche et que le premier bit compté
est le bit 0, le bit 15 est le bit le plus à gauche qui peut être utilisé comme bit de
signe.
Q Que peut faire le spécificateur de format %lu ?
A Le spécificateur de format %lu peut être utilisé dans une chaîne printf() pour
convertir l'argument correspondant au type de données unsigned long int. En
outre, le spécificateur de format %lu est équivalent à %Lu.
Q Quand dois-je utiliser la version courte et la version longue ?
A Si vous avez besoin d'économiser de l'espace mémoire et que vous savez que la
valeur d'une variable de données entière reste dans un intervalle plus petit, vous
pouvez essayer d'utiliser le modificateur court pour indiquer au compilateur C de
réduire l'espace mémoire par défaut attribué à la variable, par exemple, de 32 bits à
16 bits.
D'autre part, si une variable doit contenir un nombre qui dépasse la plage actuelle
d'un type de données, vous pouvez utiliser le modificateur long pour augmenter
l'espace de stockage de la variable afin de contenir le nombre.
Q La fonction sin() prend-elle une valeur en degrés ou en radians ?
R Comme les autres fonctions mathématiques trigonométriques en C, la fonction
sin() prend une valeur en radians. Si vous avez un angle en degrés, vous devez
le convertir en radians. La formule est la suivante :
radians = degrés * (3,141593 / 180,0).
Travailler avec des modificateurs de données et 159
des fonctions mathématiques

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe D, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Etant donné une variable int x et une variable int non signée y, ainsi que x = 0x8765
et y = 0x8765, et si le bit le plus à gauche est utilisé comme bit de signe, x est-il égal à y ?
2. Que faire si vous essayez d'assigner une grande valeur à une variable int, mais
que la valeur assignée est trop grande et que vous vous retrouvez avec un
nombre négatif auquel vous ne vous attendiez pas ?
3. Quel spécificateur de format, %ld ou %lu, doit être utilisé pour spécifier une
variable de type "unsigned long int" ?
4. Quel est le nom du fichier d'en-tête que vous devez inclure si vous appelez des
fonctions mathématiques C à partir de votre programme C ?

Exercices
1. Compte tenu des affirmations suivantes,
int x ;
unsigned int y
; x = 0xAB78 ;
y = 0xAB78 ;

écrire un programme pour afficher les valeurs décimales de x et y à l'écran.


2. Écrire un programme pour mesurer les tailles de short int, long int et long double.
sur votre machine.
3. Ecrire un programme pour multiplier deux variables int signées avec des
valeurs positives, et afficher le résultat sous la forme d'un long int.
4. Ecrire un programme pour afficher les nombres entiers négatifs au format
hexadécimal ainsi que leurs équivalents en nombres entiers signés.
5. Étant donné un angle de 30 degrés, écrivez un programme pour calculer les
valeurs du sinus et de la tangente.
6. Ecrivez un programme pour calculer la racine carrée non négative de 0x19A1.
160 Heure
9

HEURE 10
Contrôle du
déroulement du
programme
Il est plus difficile de commander que d'obéir.
-F. Nietzsche
Dans l'heure 7, "Travailler avec des boucles", vous avez appris à utiliser les
instructions while, do-while et for pour faire les mêmes choses à
plusieurs reprises. Ces trois instructions peuvent être regroupées dans la
catégorie des instructions de bouclage utilisées pour le flux de contrôle en C.
Dans cette leçon, vous découvrirez les instructions qui appartiennent à un
autre groupe d'instructions de flux de contrôle - le branchement conditionnel
(ou saut), comme par exemple
• L'instruction if
• L'instruction if-else
• L'instruction de commutation
• La déclaration de rupture
• La déclaration continue
• L'instruction goto
156 Heure
156

Toujours dire "si..."


Si la vie était une ligne droite, elle serait très ennuyeuse. Il en va de même pour la
programmation. Ce serait trop ennuyeux si les instructions de votre programme ne
pouvaient être exécutées que dans l'ordre où elles apparaissent.
En fait, une tâche importante d'un programme est d'ordonner à l'ordinateur de passer
(c'est-à-dire de sauter) à différentes parties du code et d'effectuer différentes tâches
lorsque les conditions spécifiées sont remplies.
Cependant, dans la plupart des cas, vous ne savez pas à l'avance ce qui va se passer. Ce
que vous savez, c'est que quelque chose doit se produire si certaines conditions sont
remplies. Par conséquent, vous pouvez simplement écrire des tâches et des conditions
dans le programme. Les décisions relatives au moment de l'exécution des tâches sont
prises par les instructions de branchement conditionnel.
En C, l'instruction if est l'instruction de branchement conditionnel la plus populaire ;
elle peut être utilisée pour évaluer les conditions ainsi que pour décider si le bloc de code
contrôlé par l'instruction va être exécuté.
La forme générale de l'instruction if est la suivante
if (expression) {
statement1 ;
statement2 ;
.
.
.
}

Ici, l'expression est le critère conditionnel. Si l'expression est évaluée à une valeur
non nulle, les instructions à l'intérieur des accolades ({ et }), telles que l'instruction 1
et l'instruction 2, sont exécutées. Si l'expression est évaluée à une valeur nulle, les
instructions sont ignorées.
Notez que les accolades ({ et }) forment un bloc d'instructions sous le contrôle de
l'instruction if. S'il n'y a qu'une seule instruction à l'intérieur du bloc, les accolades
peuvent être omises. Les parenthèses (( et )) doivent cependant toujours être utilisées
pour entourer l'expression conditionnelle.
Par exemple, l'expression suivante :
si (x > 0,0)
printf("La racine carrée de x est : %f\n", sqrt) ;

indique à l'ordinateur que si la valeur de la variable à virgule flottante x est supérieure à


0,0 (c'est-à-dire positive), il doit calculer la racine carrée de x en appelant la fonction
sqrt(), puis imprimer le résultat. Ici, le critère conditionnel est l'expression
relationnelle
Contrôle du déroulement 157
du programme

x > 0.0, qui prend la valeur 1 (logiquement vrai) si x est supérieur à zéro, et prend la
valeur 0 (logiquement faux) si x est inférieur ou égal à zéro.
La liste 10.1 donne un autre exemple d'utilisation de l'instruction if.

LISTE 10.1 Utilisation de l'instruction if dans la prise de décision


1 : /* 10L01.c Utilisation de
l'instruction if */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 :
8 : printf("Entiers divisibles à la fois par 2 et par 3") ;
9 :
10 :
printf("(dans l'intervalle de 0 à 100):\n") ;
for (i=0 ; i<=100 ; i++){
10
11 : si ((i%2 == 0) && (i%3 == 0))
12 : printf(" %d\n", i)
; 13 : }
14 : retour 0 ;
15 : }

Après la création et l'exécution de 10L01.exe, l'exécutable du programme de la liste 10.1, la


sortie suivante s'affiche à l'écran :
Entiers divisibles à la fois par 2 et par 3
SORTIE (entre 0 et 100) :
0
6
12
18
24
30
36
42
48
54
60
66
72
78
84
90
96

Comme vous le voyez dans le Listing 10.1, la ligne 6 déclare une variable entière,
ANALYSE
i. Les lignes 8 et 9 impriment deux titres. À partir de la ligne 10, l'instruction for
fois. continue de tourner en boucle 101
158 Heure
158

Dans la boucle for, l'instruction if des lignes 11 et 12 évalue l'expression logique (i%2
== 0) && (i%3 == 0). Si l'expression vaut 1 (c'est-à-dire que la valeur de i peut être
divisée à la fois par 2 et par 3), la valeur de i est affichée à l'écran en appelant la
fonction printf() à la ligne 12. Dans le cas contraire, l'instruction de la ligne 12 est
ignorée.
Notez que les accolades ({ et }) ne sont pas utilisées parce qu'il n'y a qu'une seule
instruction sous le contrôle de l'instruction if.
Le résultat affiché à l'écran donne tous les nombres entiers compris entre 0 et 100 qui
peuvent être divisés uniformément par 2 et 3.

La déclaration if-else
Dans l'instruction if, lorsque l'expression conditionnelle est évaluée à une valeur non
nulle, l'ordinateur passe aux instructions contrôlées par l'instruction if et les exécute
immédiatement. Si l'expression est évaluée à zéro, l'ordinateur ignore les instructions
contrôlées par l'instruction if.
Vous souhaiterez souvent que l'ordinateur exécute un autre ensemble d'instructions
lorsque l'expression conditionnelle de l'instruction if est logiquement fausse. Pour ce
faire, vous pouvez utiliser une autre instruction de branchement conditionnel en C,
l'instruction if-else.
En tant qu'extension de l'instruction if, l'instruction if-else se présente sous la forme suivante :
if (expression) {
statement1 ;
statement2 ;
.
.
.
}
else {
déclaration_A ;
déclaration_B ;
.
.
.
}

Si l'expression est évaluée à une valeur non nulle, les instructions contrôlées par if, y
compris les instructions 1 et 2, sont exécutées. Toutefois, si l'expression est
évaluée à une valeur nulle, les instructions_A et_B suivant le mot-clé else sont
exécutées à la place.
Le programme de la liste 10.2 montre comment utiliser l'instruction if-else.
Contrôle du déroulement 159
du programme

LISTE 10.2 Utilisation de l'instruction if-else


1 : /* 10L02.c Utilisation de l'instruction
if-else */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 :
8: printf("Even NumberOdd
Number\n") ; 9: for (i=0 ;
i<10 ; i++)
10 : si (i%2 == 0)
11 : printf("%d", i) ;
12 : autre
13 : printf("%14d\n", i) ;
14 :
15 :
16 : }
retour 0 ;
10

Le résultat suivant est obtenu en exécutant le fichier exécutable 10L02.exe


: Nombre pair Nombre impair
SORTIE 0 1
2 3
4 5
6 7
8 9

La ligne 6 du Listing 10.2 déclare une variable entière, i. La fonction printf()


ANALYSE de la ligne 8 affiche un titre à l'écran.

La variable entière i est initialisée dans la première expression de l'instruction for de la


ligne 9. Contrôlée par l'instruction for, l'instruction if-else des lignes 10 à 13 est
exécutée 10 fois. Selon l'instruction if-else, l'appel à printf() à la ligne 11 imprime
les nombres pairs si l'expression relationnelle i%2 == 0 à la ligne 10 est évaluée à 1
(logiquement vrai). Si l'expression relationnelle est évaluée à 0 (logiquement fausse),
l'appel printf() contrôlé par le mot-clé else à la ligne 13 imprime des nombres
impairs sur la sortie standard.
L'instruction if-else étant traitée comme une seule instruction, les accolades { et }
ne sont pas nécessaires pour former un bloc d'instructions sous l'instruction for. De
même, aucune accolade n'est utilisée dans l'instruction if-else, car les mots-clés if et
else contrôlent chacun une seule instruction, respectivement aux lignes 11 et 13.

Notez que la largeur minimale de 14 est spécifiée dans la fonction printf() à la ligne
13, de sorte que la sortie des nombres impairs est listée à droite des nombres pairs,
comme vous pouvez le voir dans la section de sortie. Le programme du Listing 10.2
vérifie les nombres compris entre 0 et 9 et montre que 0, 2, 4, 6 et 8 sont des nombres
pairs et que 1, 3, 5, 7 et 9 sont des nombres impairs.
160 Heure
160

Déclarations "if" imbriquées


Comme vous l'avez vu dans les sections précédentes, une instruction if permet à un
programme de prendre une décision. Dans de nombreux cas, un programme doit prendre
une série de décisions connexes. Pour ce faire, vous pouvez utiliser des instructions if
imbriquées.
La liste 10.3 illustre l'utilisation d'instructions if imbriquées.

LISTE 10.3 Utilisation d'instructions if imbriquées


1 : /* 10L03.c Utilisation d'instructions
if imbriquées */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 :
8 : for (i=-5 ; i<=5 ; i++){
9: if (i > 0)
10 : si (i%2 == 0)
11 : printf("%d is an even number.\n", i)
; 12 : else
13 : printf("%d is an odd number.\n", i)
; 14: else if (i == 0)
15 : printf("Le nombre est zéro.\n")
; 16 : else
17 : printf("Nombre négatif : %d\n", i)
; 18 : }19: return 0 ;
20 : }

Après avoir exécuté le fichier exécutable 10L03.exe, j'obtiens le résultat suivant :


Nombre négatif : -5
SORTIE Nombre négatif : -4
Nombre négatif : -3
Nombre négatif : -2
Nombre négatif : -1
Le nombre est nul.
1 est un nombre impair.
2 est un nombre pair.
3 est un nombre impair.
4 est un nombre pair.
5 est un nombre impair.

ANALYSE La liste 10.3 contient une boucle for, commençant à la ligne 8 et se terminant à la
ligne 18. D'après les expressions de l'instruction for de la ligne 8, toutes les
tâches contrôlées par l'instruction for de la ligne 18 doivent être exécutées.
par l'instruction for sont exécutées jusqu'à 11 fois.
Contrôle du déroulement 161
du programme

Tout d'abord, une décision doit être prise sur la base de l'évaluation de l'expression
relationnelle i > 0 dans l'instruction if de la ligne 9. L'expression i > 0 est utilisée
pour tester si la valeur de i est positive ou autre (nombres négatifs, y compris zéro). Si
l'expression est évaluée à 1, l'ordinateur passe à la deuxième instruction if (c'est-à-dire
imbriquée) de la ligne 11.
Notez que la ligne 11 contient une autre expression relationnelle, i%2 == 0, qui teste si
la variable entière i est paire ou impaire. Par conséquent, la deuxième décision
d'afficher les nombres pairs ou impairs doit être prise en fonction de l'évaluation de la
deuxième expression relationnelle, i%2 == 0. Si cette évaluation donne 1, l'appel
printf() de la ligne 11 imprime un nombre pair. Sinon, l'instruction de la ligne 13 est
exécutée et un nombre impair s'affiche à l'écran.
L'ordinateur passe à la ligne 14 si l'expression i > 0 de la ligne 9 est égale à 0, c'est-à-
dire si la valeur de i n'est pas supérieure à 0. À la ligne 14, une autre instruction if est
imbriquée dans une phrase else, et l'expression relationnelle i == 0 est évaluée. Si i == 10
0 est logiquement vrai, ce qui signifie que i contient la valeur zéro, la chaîne Le nombre
est zéro s'affiche à l'écran. Sinon, la valeur de i doit être négative, conformément à
l'évaluation précédente de l'expression i > 0 à la ligne 9. L'instruction de la ligne 17
affiche alors le nombre négatif sur la sortie standard.
Comme vous pouvez le voir dans l'exemple, la valeur de i est comprise entre 5 et -5. Ainsi, -5, -4,
-3, -2 et -1 sont imprimés comme des nombres négatifs. Un message est ensuite
imprimé lorsque i est égal à zéro, puis les nombres impairs 1, 3 et 5, ainsi que les
nombres pairs 2 et 4 sont imprimés.

Le commutateur
Déclaration
Dans la dernière section, vous avez vu que les instructions if imbriquées sont utilisées
lorsqu'il s'agit de prendre des décisions successives et connexes. Cependant, les
instructions if imbriquées peuvent devenir très complexes si de nombreuses décisions
doivent être prises. Parfois, un programmeur aura des difficultés à suivre une série
d'instructions if imbriquées complexes.
Heureusement, il existe une autre instruction en C, l'instruction switch, que vous
pouvez utiliser pour prendre des décisions ou faire des choix illimités en fonction de la
valeur d'une expression conditionnelle et des cas spécifiés.
La forme générale de l'instruction switch est la suivante
switch (expression) {
case constant-expression1 :
déclaration1 ;

case constant-expression2 :
162 Heure
162

déclaration2 ;

.
.
.
par défaut :
déclaration par défaut ;
}

Ici, l'expression conditionnelle, expression, est évaluée en premier. L'instruction switch


passe alors à l'étiquette de cas appropriée et exécute, dans l'ordre, les instructions qui
suivent. Si l'expression produit une valeur égale à l'expression constante
expression1, l'instruction1 est exécutée, suivie de l'instruction2 et de toutes les
autres jusqu'à l'instruction par défaut. Si la valeur obtenue par expression est égale
à la valeur de l'expression constante2, l'instruction2 est exécutée en premier. Si, par
contre, la valeur de l'expression n'est égale à aucune des valeurs des expressions
constantes étiquetées par le mot-clé case, l'énoncé (énoncé par défaut) suivant le mot-clé
default est exécuté.

Vous devez utiliser le mot-clé case pour étiqueter chaque case. Notez que chaque
étiquette de cas se termine par deux points, et non par un point-virgule. Il s'agit de la
syntaxe des étiquettes en C. Le mot-clé default doit être utilisé pour le cas "par
défaut", c'est-à-dire lorsqu'aucune étiquette case ne correspond à l'expression
conditionnelle. Notez qu'aucune des expressions constantes associées aux étiquettes de
cas ne peut être identique à l'intérieur de l'instruction switch.

En C, une étiquette est utilisée comme une sorte de marque-page dans votre
code pour être utilisée par l'instruction de branchement condi- tionnelle. Une
étiquette n'est pas en soi une instruction ; elle indique plutôt un endroit où
sauter lorsque l'on veut s'écarter du flux normal de l'exécution de haut en
bas.
La syntaxe correcte d'une étiquette est un identifiant unique suivi de deux
points, et non d'un point-virgule. Dans ce chapitre, vous verrez plusieurs
façons d'utiliser les étiquettes, ainsi que les noms d'étiquettes réservés

Le programme du Listing 10.4 vous donne un exemple d'utilisation de l'instruction


switch. Il démontre également une caractéristique importante de l'instruction switch.

LISTE 10.4 Utilisation de l'instruction switch


1 : /* 10L04.c Utilisation de
l'instruction switch */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
Contrôle du déroulement 163
6 : jour int ; du programme
164 Heure
164

7 :
8 : printf("Veuillez entrer un seul chiffre pour un jour") ;
9 : printf("(entre 1 et 3):\n") ;
10 day = getchar() ;
:
11 switch (jour){
:
12 cas '1' :
:
13 printf("Jour 1") ;
:
14 cas '2' :
:
15 printf("Jour 2") ;
:
16 cas '3' :
:
17 printf("Jour 3\n") ;
:
18 par défaut :
:
19 ;
:
20 }
:
21 retour 0 ;
:
22 }
:
10
Si je lance le fichier exécutable 10L04.exe et que j'entre 3, j'obtiens le résultat suivant :
Veuillez saisir un seul chiffre pour un jour
SORTIE (entre 1 et 3) :
3
Troisième jour

ANALYSE Comme vous pouvez le voir à la ligne 6, une variable int, day, est déclarée ; elle est
affectée à l'entrée saisie par l'utilisateur à la ligne 10.
À la ligne 11, la valeur de la variable entière day est évaluée dans l'instruction switch.
Si la valeur est égale à l'une des valeurs des expressions constantes, l'ordinateur
commence à exécuter les instructions à partir de là. Les expressions constantes sont
étiquetées en les précédant du préfixe case.
Par exemple, j'ai saisi 3 et j'ai appuyé sur la touche Entrée. La valeur numérique de 3 est
affectée au jour à la ligne 10. Ensuite, après avoir trouvé un cas dans lequel la valeur
de l'expression constante correspond à la valeur contenue dans jour, l'ordinateur passe à
la ligne 17 pour exécuter la fonction printf() et afficher Jour 3 à l'écran.
Notez que sous l'étiquette par défaut du Listing 10.4, il y a une instruction vide (c'est-à-
dire nulle) se terminant par un point-virgule à la ligne 19. L'ordinateur ne fait rien avec
l'instruction vide. Cela signifie que si aucune des expressions constantes ne s'applique,
Contrôle du déroulement 165
l'instruction switch ne faitdu
rienprogramme
du tout.
Cependant, si j'entre 1 sur mon clavier et que j'appuie sur la touche Entrée lors de
l'exécution du fichier exécutable 10L04.exe, j'obtiens le résultat suivant :
Veuillez saisir un seul chiffre pour un jour
166 Heure
166

(entre 1 et 3) :
1
Jour 1
Jour 2
Troisième jour

La sortie montre que l'instruction contrôlée par le cas sélectionné, le cas 1, et les
instructions contrôlées par les autres cas, sont exécutées, parce que le jour 1,
Le jour 2 et le jour 3 s'affichent à l'écran. De même, si je tape 2 sur mon clavier,
les jours 2 et 3 s'affichent à l'écran.
Il s'agit d'une caractéristique importante de l'instruction switch : L'ordinateur continue
d'exécuter les instructions qui suivent le cas sélectionné jusqu'à la fin de l'instruction
switch.

Dans la section suivante, vous apprendrez à sortir prématurément de l'exécution de


l'instruction switch.

La déclaration de rupture
Si vous souhaitez quitter complètement le commutateur après chaque étiquette de cas,
vous pouvez ajouter une instruction break à la fin de la liste d'instructions qui suit
chaque étiquette de cas. L'instruction break quitte simplement l'interrupteur et
reprend l'exécution après la fin du bloc d'instructions de l'interrupteur.
Le programme du Listing 10.5 ressemble à celui du Listing 10.4, mais cette fois-ci, la fonction
est utilisée et les résultats sont différents.

LISTE 10.5 Ajout de l'instruction break


1 : /* 10L05.c Ajout de l'instruction
break */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : jour int ;
7 :
8 : printf("Veuillez entrer un seul chiffre pour un
jour") ; 9: printf("(entre 1 et 7):\n") ;
10: day = getchar()
;
11 : switch (jour){
12 : cas '1' :
13 : printf("Day 1 is Sunday.\n") ;
14 : pause ;
15 : cas '2' :
16 : printf("Le deuxième jour est lundi.\n") ;
17 : pause ;
18 : cas '3' :
19 : printf("Le troisième jour est le mardi.\n") ;
Contrôle du déroulement 167
du programme

20 : pause ;
21 : cas '4' :
22 : printf("Le quatrième jour est mercredi.\n") ;
23 : pause ;
24 : cas '5' :
25 : printf("Le 5e jour est le jeudi.\n") ;
26 : pause ;
27 : cas '6' :
28 : printf("Le sixième jour est le vendredi.\n") ;
29 : pause ;
30 : cas '7' :
31 : printf("Le 7e jour est le samedi.\n") ;
32 : pause ;
33 : par défaut :
34 : printf("Le chiffre n'est pas compris entre 1 et 7.\n") ;
35 : pause ;
36
37
:
:
}
retour 0 ;
10
38 : }

Avec l'aide de l'instruction break, je peux exécuter le fichier exécutable 10L05.exe et


obtenir uniquement la sortie du cas sélectionné :
Veuillez saisir un seul chiffre pour un jour
SORTIE (entre 1 et 7) :
1
Le premier jour est le dimanche.

Ce programme comporte sept étiquettes de cas suivies des expressions


ANALYSE
constantes '1', '2', '3', '4', '5', '6' et '7', respectivement. (Voir lignes 12, 15,
18, 21, 24, 27,
et 30.)
Dans chaque cas, il y a une instruction suivie d'une instruction break. Comme nous
l'avons mentionné, les instructions break quitteront la construction switch avant que
l'ordinateur n'atteigne l'étiquette case suivante et les instructions qui la suivent.
Par exemple, après que la valeur 1 a été attribuée à la variable int day et évaluée dans
l'instruction switch, le cas avec '1' est sélectionné et l'instruction de la ligne 13 est
exécutée. Ensuite, l'instruction break de la ligne 14 est exécutée, ce qui interrompt le
contrôle de l'instruction switch et renvoie le contrôle à l'instruction suivante en dehors de
la construction switch. Dans le Listing 10.5, l'instruction suivante est l'instruction return
de la ligne 37, qui met fin à la fonction principale.
L'appel printf() de la ligne 13 affiche à l'écran une chaîne de Jour 1 est dimanche.

Notez que dans une instruction switch, les accolades ne sont pas nécessaires pour
regrouper les instructions à l'intérieur d'un cas individuel, puisque le cas n'est qu'une
étiquette et ne contrôle pas les instructions qui le suivent. Elles sont simplement
exécutées dans l'ordre, en commençant par l'étiquette.
168 Heure
168

Rompre une boucle infinie


Vous pouvez également utiliser l'instruction break pour interrompre une boucle infinie.
Une boucle infinie est une boucle dans laquelle l'expression conditionnelle est
entièrement omise ; c'est donc au code à l'intérieur de la boucle de déterminer les
conditions de fin de la boucle. Voici des exemples de boucles infinies for et while :
for (; ;){
déclaration1 ;
déclaration2 ;
.
.
.
}

while {
déclaration1 ;
déclaration2 ;
.
.
.
}

La boucle for ci-dessus ne tient pas compte des trois expressions. L'instruction for
exécute donc toujours la boucle. L'instruction while utilise 1 comme expression
conditionnelle, et comme cette expression n'est bien sûr jamais évaluée à 0, l'instruction
while continue toujours la boucle.
Le programme du Listing 10.6 montre un exemple d'utilisation de l'instruction
break dans une boucle while infinie.

LISTE 10.6 Rupture d'une boucle infinie


1 : /* 10L06.c : Rupture d'une boucle
infinie */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int c ;
7 :
8 : printf("Entrez un caractère:\n(entrez x pour
quitter)\n") ; 9: while {
10: c = getc(stdin) ;
11 : if (c ==
'x') 12 : break ;
13 : }
14 : printf("Break the infinite while loop. Bye!\n")
; 15: return 0 ;
16 : }
Contrôle du déroulement 169
du programme

Voici le résultat que j'ai obtenu après avoir exécuté le fichier exécutable (10L06.exe)
sur ma machine :
Saisir un caractère :
SORTIE (tapez x pour quitter)
H
I
x
Interrompre la boucle infinie while. Au revoir !

Le Listing 10.6 contient une boucle while infinie qui commence à la ligne 9 et se
ANALYSE termine à la ligne 13. Dans cette boucle infinie, les caractères saisis par
l'utilisateur sont assignés,
une à la fois, à la variable entière c (voir ligne 10).
L'expression relationnelle c == 'x' dans l'instruction if (voir ligne 11) est évaluée à
chaque fois pendant le bouclage. Si l'expression est évaluée à la valeur 0 (c'est-à-dire que
l'utilisateur n'a pas saisi la lettre x), le bouclage se poursuit. Dans le cas contraire,
10
l'instruction break de la ligne 12 est exécutée, ce qui permet à l'ordinateur de sortir de la
boucle infinie et de commencer à exécuter l'instruction suivante, qui figure à la ligne 14.
Vous pouvez voir dans l'exemple de sortie que la boucle while continue jusqu'à ce que
j'entre la lettre x, ce qui entraîne la rupture de la boucle infinie et l'affichage à l'écran du
message Break the infinite while loop (Rompre la boucle infinie while).
Bye ! s'affiche à l'écran.

La déclaration continue
Au lieu d'interrompre une boucle, il arrive que vous souhaitiez rester dans une boucle mais
sauter certaines instructions à l'intérieur de la boucle. Pour ce faire, vous pouvez utiliser
l'instruction continue. L'instruction continue permet de passer immédiatement à la fin de
la boucle.
Par exemple, le listing 10.7 montre comment utiliser l'instruction continue dans une
boucle effectuant des additions.

LISTE 10.7 Utilisation de l'instruction continue


1 : /* 10L07.c : Utilisation de l'instruction
continue */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i, sum
;
7 :
8: sum = 0 ;
9 : for (i=1 ; i<8 ; i++){
10 : si ((i==3) || (i==5))
170 Heure
11 : continuer ; 170
continue
Contrôle du déroulement 171
du programme

LISTE 10.7 suite


12 : sum += i
;
13 : }
14 : printf("La somme de 1, 2, 4, 6 et 7 est : %d\n", sum) ;
15 : retour 0 ;
16 : }

Après l'exécution du fichier exécutable 10L07.exe sur mon ordinateur, la sortie


suivante s'affiche à l'écran :
La somme de 1, 2, 4, 6 et 7 est : 20
SORTIE
Dans le Listing 10.7, nous voulons calculer la somme des valeurs entières de 1, 2,
ANALYSE 4, 6 et 7. Comme les entiers sont presque consécutifs, une boucle for est
construite dans les lignes
9-13. L'instruction de la ligne 12 additionne tous les entiers consécutifs de 1 à 7 (à
l'exception de 3 et 5, qui ne figurent pas dans la liste et sont ignorés dans la boucle for).
Pour sauter ces deux nombres, l'expression (i==3) || (i==5) est évaluée dans
l'instruction if de la ligne 10. Si l'expression est évaluée à 1 (c'est-à-dire que la valeur
de i est égale à 3 ou 5), l'instruction continue de la ligne 11 est exécutée, ce qui permet
de sauter l'opération de somme de la ligne 12 et de commencer une autre itération à
l'instruction for. De cette façon, vous obtenez la somme des valeurs entières de 1, 2,
4, 6 et 7, mais vous sautez 3 et 5, automatiquement en utilisant une boucle for.

Après la boucle for, la valeur de la somme, 20, est affichée à l'écran par l'appel printf()
dans l'instruction de la ligne 14.

La déclaration de goto
Ce livre ne serait pas complet s'il ne mentionnait pas l'instruction goto, bien que je ne
recommande pas son utilisation. La principale raison pour laquelle l'instruction goto est
déconseillée est que son utilisation risque de rendre le programme C peu fiable et difficile
à déboguer.
Les programmeurs sont souvent tentés d'utiliser l'instruction goto, surtout s'ils ont
utilisé d'autres langages ne disposant pas du riche ensemble d'instructions de
branchement conditionnel structuré que le langage C propose. En réalité, toute
utilisation de l'instruction goto peut être évitée en utilisant les autres instructions de
branchement.
Au moins, vous devriez savoir ce qu'est une instruction goto afin de pouvoir
grimacer lorsque vous en verrez une dans le code de quelqu'un d'autre.
172 Heure
172

Voici la forme générale de l'instruction goto :

nom de
l'étiquette :
déclaration1 ;
déclaration2 ;
.
.
.
aller au nom de l'étiquette ;

Ici, labelname est un nom d'étiquette qui indique à l'instruction goto où sauter. Vous
devez placer labelname à deux endroits : L'un est l'endroit où l'instruction goto va
sauter (notez qu'un deux-points doit suivre le nom de l'étiquette), et l'autre est l'endroit
qui suit le mot-clé goto.
L'étiquette de l'instruction goto à laquelle il faut passer peut apparaître avant ou après l'instruction.
10
L'une des meilleures caractéristiques du langage C est qu'il encourage la
programmation structurée. Les programmes doivent agir de manière prévisible et
leur comportement doit être raisonnablement évident à la simple lecture du
code source.
Bien sûr, l'un des autres avantages du C est que le langage lui-même
n'impose pas cet idéal. C'est à vous, le programmeur, d'utiliser les outils qui
vous sont donnés pour écrire un code propre, élégant, lisible et structuré.
Dans ce chapitre, nous avons vu les instructions break, continue et
goto. L'utilisation incorrecte de ces instructions de branchement peut
conduire à ce que l'on appelle le "code spaghetti". (Si vous imprimiez le
code source et dessiniez des flèches sur la page pour indiquer le flux
d'exécution, vous obtiendriez un dessin de spaghetti). Lorsque l'exécution
d'un programme saute d'un endroit à l'autre sans qu'on le sache, il en résulte
un "code spaghetti".
En effet, il est très difficile (voire presque impossible dans le cas d'un
projet complexe et de grande envergure) de déterminer le comportement
prévu ou réel d'un pro- gramme.
L'utilisation de l'instruction goto peut facilement conduire à un code spaghetti,
en particulier si elle est utilisée pour revenir en arrière ou pour sortir des
instructions de contrôle de flux. Lorsque vous voyez une étiquette
aléatoire dans le code, vous savez seulement qu'un autre code va sauter
à cet endroit à l'aide de l'instruction goto - vous ne savez pas quand, ni
pourquoi, sans vous arrêter pour chercher dans le reste de la fonction.
Les effets de l'instruction continue, au moins, sont limités à l'instruction de
boucle dans laquelle elle est utilisée. Toutefois, les boucles complexes peuvent
être difficiles à déchiffrer si l'on ne sait pas exactement quand et pourquoi la
boucle se terminera.
Contrôle du déroulement 173
du programme

Résumé
Dans cette leçon, vous avez appris les déclarations et les mots-clés importants suivants
pour les branchements et les boucles conventionnels en C :
• Une tâche importante d'un programme est d'ordonner à l'ordinateur de passer à
différentes parties du code en fonction des conditions de branchement
spécifiées.
• L'instruction if est une instruction très importante pour le branchement conditionnel en C.
• L'instruction if peut être imbriquée pour prendre une série de décisions
connexes dans votre programme.
• L'instruction if-else est une extension de l'instruction if.
• L'instruction switch vous aide à rendre votre programme plus lisible lorsqu'il y
a plus que quelques décisions à prendre dans votre code.
• Le mot-clé case, suivi de deux points et d'une valeur constante intégrale, est utilisé
comme étiquette dans l'instruction switch. L'étiquette par défaut : est utilisée
à la fin d'une instruction de commutation lorsqu'aucun cas ne s'applique à la
condition.
• L'instruction break peut être utilisée pour sortir de la construction d'un
commutateur ou d'une boucle (généralement une boucle infinie).
• L'instruction continue est utilisée pour vous permettre de rester dans une boucle
tout en sautant certaines instructions.
• L'instruction goto permet à l'ordinateur de sauter à un autre endroit de votre code.
L'utilisation de cette instruction n'est pas recommandée car elle peut rendre votre
programme peu fiable et difficile à déboguer.
Dans la prochaine leçon, vous découvrirez un concept très important : les pointeurs.

Q&R
Q Combien d'expressions y a-t-il dans l'instruction if ?
A L'instruction if ne prend qu'une seule expression pour contenir les critères
conditionnels. Lorsque l'expression est évaluée à une valeur non nulle (c'est-à-dire
que les conditions sont remplies), les instructions contrôlées par l'instruction if sont
exécutées. Dans le cas contraire, ces instructions sont ignorées et l'instruction
suivant le bloc d'instructions if est exécutée.
Q Pourquoi l'instruction if-else est-elle une extension de l'instruction if ?
R Lorsque l'expression conditionnelle de l'instruction if est évaluée à zéro, le flux de
contrôle du programme est ramené à la piste d'origine. Cependant, lorsque
l'expression conditionnelle de l'instruction if-else est évaluée à zéro, le flux de
contrôle du programme passe au bloc d'instructions sous le mot-clé else et
174 Heure
retourne 174
Contrôle du déroulement 175
du programme

à sa piste d'origine après l'exécution des instructions contrôlées par l'instruction


else. En d'autres termes, l'instruction if permet d'exécuter un seul bloc
d'instructions ou de le sauter entièrement, tandis que l'instruction if-else exécute
l'un des deux blocs d'instructions contrôlés par l'instruction if-else.
Q Pourquoi faut-il normalement ajouter l'instruction break dans le switch ?
déclaration ?
R Lorsque l'un des cas de l'instruction switch est sélectionné, le contrôle du
programme passe au cas et exécute toutes les instructions du cas sélectionné et
des autres cas qui le suivent. Par conséquent, vous risquez d'obtenir plus de
résultats que prévu. Pour demander à l'ordinateur de n'exécuter que les instructions
à l'intérieur d'un cas sélectionné, vous pouvez placer une instruction break à la
fin du cas afin que le flux de contrôle du programme quitte la construction switch
après que les instructions à l'intérieur du cas ont été exécutées. 10
Q Que peut faire l'instruction continue à l'intérieur d'une boucle ?
R Lorsque l'instruction continue à l'intérieur d'une boucle est exécutée, le contrôle
du programme est ramené à la fin de la boucle afin que l'instruction while ou for
qui la contrôle puisse être exécutée et qu'une autre itération puisse être lancée si
l'expression conditionnelle est toujours valable. À l'intérieur de la boucle, toutes
les instructions qui suivent l'instruction continue sont ignorées à chaque fois que
l'instruction continue est exécutée.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Étant donné que x = 0, les opérations arithmétiques contenues dans l'instruction
if suivante seront-elles effectuées ?
si (x != 0)
y = 123 / x + 456 ;

2. Étant donné x = 4, y = 2 et l'opérateur = '-', quelle est la valeur finale de x


après l'exécution de l'instruction de commutation suivante ?
switch (operator){
case '+' : x +=
y ; case '-' : x
-= y ; case '*'
: x *= y ;
176 Heure
176

case '/' : x /=
y ; default :
break
;
}

3. Comme à la question 2, en utilisant x = 4, y = 2 et l'opérateur = '-', quelle


est la valeur finale de x après l'exécution de l'instruction de commutation
suivante ?
switch (opérateur){
case '+' : x += y ;
break ; case '-' : x -=
y ; break ; case '*' :
x *= y ; break ; case
'/' : x /= y ; break ;
default : break ;
}

4. Quelle est la valeur de la variable entière x après l'exécution du code suivant ?


x = 1 ;
for (i=2 ; i<10 ;
i++){ if (i%3 ==
0)
continue
; x += i ;
}

Exercices
1. Réécrivez le programme de la liste 10.1. Cette fois, utilisez l'expression logique
i%6 == 0 dans l'instruction if.
2. Réécrivez le programme de la liste 10.1 en utilisant des instructions if imbriquées.
3. Écrivez un programme pour lire les caractères de l'E/S standard. Si les caractères
sont A, B et C, affichez leurs valeurs numériques à l'écran. (L'instruction switch est
nécessaire.)
4. Ecrivez un programme qui continue à lire les caractères de l'entrée standard
jusqu'à ce que le caractère q soit saisi.
5. Réécrivez le programme de la liste 10.7. Cette fois, au lieu de sauter 3 et 5,
sautez l'entier qui peut être divisé uniformément par 2 et 3.
PARTIE III
Pointeurs et tableaux
Heure
11 Comprendre les pointeurs
12 Comprendre les tableaux
13 Manipuler des chaînes de caractères
14 Comprendre la portée et les classes de stockage
HEURE 11
Comprendre les pointeurs
Les fonctions du pointeur consistaient à indiquer, en les appelant par leur
nom, les personnes de l'assemblée qui devaient prendre note d'un point du
sermon.

-H. B. Otis, Simple Truth


Au cours des 10 dernières heures, vous avez découvert de nombreux types
de données, opérateurs, fonctions et boucles importants du langage C. Dans
cette leçon, vous découvrirez l'une des fonctions les plus importantes et les
plus puissantes du langage C : les pointeurs. Dans cette leçon, vous
découvrirez l'une des fonctionnalités les plus importantes et les plus
puissantes du langage C : les pointeurs. Les sujets abordés dans cette
heure sont

• Variables pointeurs
• Adresses mémoire
• Le concept d'indirection
• Déclaration d'un pointeur
• L'opérateur "adresse de" (address-of)
• L'opérateur de déréférence
D'autres exemples d'application des pointeurs seront présentés dans les
prochaines heures du livre, en particulier dans l'Heure 16, "Application des
pointeurs".
176 Heure
11

Qu'est-ce qu'un pointeur ?


Comprendre les pointeurs est essentiel pour être un programmeur C efficace. Jusqu'à
présent, vous n'avez eu affaire qu'à des variables directement, en leur assignant des
valeurs. Cette heure introduit un nouveau concept, connu sous le nom d'indirection.
Au lieu d'assigner des valeurs directement aux variables, vous pouvez manipuler
indirectement une variable en créant une variable appelée pointeur, qui contient l'adresse
mémoire d'une autre variable.
Pourquoi est-ce si important ? Tout d'abord, l'utilisation de l'adresse mémoire de vos
données est souvent le moyen le plus rapide et le plus simple d'y accéder. Il y a beaucoup
de choses qui sont difficiles, voire carrément impossibles, à faire sans pointeurs, comme
l'allocation dynamique de mémoire, le passage de grandes structures de données entre les
fonctions, et même la communication avec le matériel de votre ordinateur.
En fait, vous avez déjà utilisé un pointeur - dans l'heure 1 de ce livre "Faire le
premier pas" ! Vous souvenez-vous de la chaîne de caractères "Howdy, neighbor !
C'est mon premier programme C" ? Une chaîne de caractères est en fait un
tableau, qui est lui-même une sorte de pointeur.
Nous reviendrons plus loin dans ce livre sur les tableaux, l'allocation de mémoire et les
merveilleuses choses que l'on peut faire avec les pointeurs. Pour l'instant, la première
étape consiste à comprendre ce qu'est un pointeur et comment l'utiliser dans vos
programmes.
D'après la définition d'un pointeur, vous savez deux choses : premièrement, qu'un
pointeur est une variable, donc vous pouvez assigner différentes valeurs à une variable
de pointeur, et deuxièmement, que la valeur contenue par un pointeur doit être une
adresse qui indique l'emplacement d'une autre variable dans la mémoire. C'est
pourquoi un pointeur est également appelé variable d'adresse.

Adresse (valeur de gauche) par rapport au


contenu (valeur de droite)
Comme vous le savez peut-être, la mémoire de votre ordinateur est utilisée pour contenir
le code binaire de votre programme, qui se compose d'instructions et de données, ainsi
que le code binaire du système d'exploitation de votre machine.
Chaque emplacement de mémoire doit avoir une adresse unique afin que l'ordinateur
puisse lire ou écrire dans l'emplacement de mémoire sans confusion. Ceci est similaire au
concept selon lequel chaque maison dans une ville doit avoir une adresse unique.
Lorsqu'une variable est déclarée, une partie de la mémoire inutilisée est réservée pour
la variable et l'adresse unique de la mémoire est associée au nom de la variable.
L'adresse associée au nom de la variable est généralement appelée valeur gauche de la
Comprendre les 177
variable. pointeurs
178 Heure
11

Ensuite, lorsqu'une valeur est attribuée à la variable, cette valeur est stockée dans
l'emplacement de mémoire réservé en tant que contenu. Le contenu est également appelé
la valeur droite de la variable.
Par exemple, après la déclaration de la variable entière x et son affectation à une valeur comme suit :
int x
; x =
7 ;

la variable x a maintenant deux


valeurs : Valeur de gauche : 1000
Valeur de droite : 7

Ici, la valeur de gauche, 1000, est l'adresse de l'emplacement de mémoire réservé à x. La


valeur de droite, 7, est le contenu stocké dans l'emplacement de mémoire. Notez que
selon les ordinateurs et les systèmes d'exploitation, la valeur gauche de x peut être
différente d'une machine à l'autre.
Vous pouvez imaginer que la variable x est la boîte aux lettres devant votre maison,
dont l'adresse (normalement le numéro de la rue) est 1000. La bonne valeur, 7, peut être
considérée comme un courrier déposé dans la boîte aux lettres.
Notez que lorsque votre programme C est compilé et qu'une valeur est assignée à une
variable, le compilateur C doit vérifier la valeur gauche de la variable. Si le compilateur 11
ne trouve pas la valeur gauche, il émettra un message d'erreur indiquant que la variable
est indéfinie dans votre programme. C'est pourquoi, en C, vous devez déclarer une
variable avant de pouvoir l'utiliser. (Imaginez un facteur se plaignant de ne pas pouvoir
déposer les lettres qui vous sont adressées parce que vous n'avez pas encore construit de
boîte aux lettres).
En utilisant la valeur gauche d'une variable, le compilateur C peut facilement localiser la
mémoire appropriée réservée à une variable, puis lire ou écrire la valeur droite de la variable.

L'opérateur d'adresse (&)


Le langage C fournit même un opérateur, &, au cas où vous souhaiteriez connaître la
valeur gauche d'une variable. Cet opérateur est appelé opérateur d'adresse, car il évalue
l'adresse (c'est-à-dire la valeur gauche) d'une variable.
Le code suivant, par exemple,
long int x
; long int
*y ; y = &x
;

affecte l'adresse de la variable entière longue x à une variable pointeur, y. (Nous


reviendrons sur ce point et sur la signification de *y plus loin dans ce chapitre).
Comprendre les 179
pointeurs

La liste 11.1 montre un autre exemple d'obtention d'adresses (c'est-à-dire de valeurs


gauches) de variables.

TYPE LISTE 11.1 Obtention des valeurs de gauche des variables

1 : /* 11L01.c : Obtention des


adresses */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: char c ;
7 : int x ;
8 : float y ;
9 :
10 : printf("c : address=%p, content=%c\n", &c, c)
; 11 : printf("x : address=%p, content=%d\n", &x, x)
; 12 : printf("y : address=%p, content=%5.2f\n", &y,
y) ; 13: c = 'A' ;
14: x = 7 ;
15: y = 123.45 ;
16 : printf("c : address=%p, content=%c\n", &c, c)
; 17 : printf("x : address=%p, content=%d\n", &x, x)
; 18 : printf("y : address=%p, content=%5.2f\n", &y,
y) ; 19: return 0 ;
20 : }

Une fois le fichier exécutable (11L01.exe) de ce programme créé et exécuté sur mon
ordinateur, la sortie suivante s'affiche à l'écran :

Vous pouvez obtenir des résultats différents en fonction de votre ordinateur et


de votre système d'exploitation, et surtout en fonction de la situation de la
mémoire dans votre ordinateur.
lorsque vous exécutez le programme.

c : address=0x1AF4, content=@
SORTIE x : address=0x1AF2, content=-
32557 y : address=0x1AF6,
content=0.00 c :
address=0x1AF4, content=A
x : address=0x1AF2, content=7
y : address=0x1AF6, content=123.45
ANALYSE Comme vous pouvez le voir dans la liste 11.1, trois variables, c, x et y, sont
déclarées respectivement aux lignes 6 à 8.
180 Heure
11

L'instruction de la ligne 10 affiche l'adresse (c'est-à-dire la valeur de gauche) et le


contenu (c'est-à-dire la valeur de droite) de la variable caractère c à l'écran. Ici,
l'expression &c produit l'adresse de c.
Notez que le spécificateur de format %p est utilisé dans la fonction printf() de la ligne
10 pour afficher l'adresse produite par &c.
De même, les lignes 11 et 12 impriment les adresses de x et y, ainsi que le contenu
de x et y. La première partie de la sortie indique que les adresses de c, x et y sont
0x1AF4, 0x1AF2 et 0x1AF6. Mon ordinateur a imprimé ces adresses au format
hexadécimal.
Toutefois, le spécificateur de format %p ne garantit pas l'impression des adresses au
format hexadécimal, mais seulement la conversion des adresses en une séquence de
caractères imprimables. Vous devez consulter le manuel de votre compilateur C pour
savoir à quel format vous attendre. Comme ces trois variables n'ont pas encore été
initialisées, le contenu de leurs emplacements en mémoire est conservé depuis la dernière
écriture en mémoire.
Cependant, apres les initialisations effectuees dans les lignes 13-15, les emplacements
de memoire reserves aux trois variables ont le contenu des valeurs initiales. Les lignes
16-18 montrent les adresses et le contenu de c, x et y après l'initialisation.

Vous pouvez voir dans la deuxième partie de la sortie que les contenus de c, x et y sont 11
maintenant respectivement 'A', 7 et 123,45, avec les mêmes adresses mémoire.

Le spécificateur de format %p utilisé dans la fonction printf() est pris en


charge par la norme ANSI. Si, pour une raison ou une autre, votre
compilateur ne prend pas en charge %p, vous pouvez essayer d'utiliser %u
ou %lu dans la fonction printf() pour convertir et imprimer une valeur
de gauche (c'est-à-dire une adresse).
En outre, les adresses imprimées par les exemples de cette leçon ont été
obtenues en exécutant les exemples sur ma machine. Les valeurs peuvent
être différentes de celles que vous pouvez obtenir en exécutant les
exemples sur votre machine. En effet, l'adresse d'une variable peut varier

Déclaration des pointeurs


Comme indiqué au début de cette leçon, un pointeur est une variable, ce qui signifie qu'il
a une valeur gauche et une valeur droite. Cependant, les valeurs de gauche et de droite
sont toutes deux des adresses. La valeur gauche d'un pointeur est utilisée pour se référer
au pointeur lui-même, tandis que la valeur droite d'un pointeur, qui est le contenu du
pointeur, est l'adresse d'une autre variable.
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

180 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
11

La forme générale d'une déclaration de pointeur est la suivante


type de données *nom du pointeur ;

Ici, data-type spécifie le type de données vers lequel le pointeur pointe. pointer-name
est le nom de la variable du pointeur, qui peut être n'importe quel nom de variable valide
en C.
Notez que juste avant le nom du pointeur se trouve un astérisque *, qui indique que la
variable est un pointeur. Lorsque le compilateur voit l'astérisque dans la déclaration, il
note que la variable peut être utilisée comme un pointeur.
Le tableau suivant présente les différents types de pointeurs :
char *ptr_c; /* déclarer un pointeur sur un

caractère */ int *ptr_int ; /* déclarer un pointeur

sur un entier */

float *ptr_flt ; /* déclarer un pointeur sur une virgule flottante */

Le programme du Listing 11.2 montre comment déclarer des pointeurs et leur attribuer
des valeurs.

TYPE LISTE 11.2 Déclaration et attribution de valeurs aux pointeurs

1 : /* 11L02.c : Déclarer et assigner des valeurs aux


pointeurs */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: char c, *ptr_c ;
7 : intx , *ptr_x ;
8 : float y, *ptr_y
;
9 :
10: c = 'A' ;
11: x = 7 ;
12: y = 123.45 ;
13 : printf("c : address=%p, content=%c\n", &c, c)
; 14 : printf("x : address=%p, content=%d\n", &x, x)
; 15 : printf("y : address=%p, content=%5.2f\n", &y,
y) ; 16: ptr_c = &c ;
17 : printf("ptr_c : address=%p, content=%p\n", &ptr_c,
ptr_c) ; 18: printf("*ptr_c => %c\n", *ptr_c) ;
19 : ptr_x = &x ;
20 : printf("ptr_x : address=%p, content=%p\n", &ptr_x,
ptr_x) ; 21: printf("*ptr_x => %d\n", *ptr_x) ;
22 : ptr_y = &y ;
Comprendre les 181
23 : pointeurs
printf("ptr_y : address=%p, content=%p\n", &ptr_y,
ptr_y) ; 24 : printf("*ptr_y => %5.2f\n", *ptr_y) ;
25 : retour 0 ;
26 : }
182 Heure
11

J'obtiens la sortie suivante à l'écran après avoir exécuté le fichier exécutable


11L02.exe sur ma machine :
c : address=0x1B38,
SORTIE content=A x :
address=0x1B36, content=7
y : address=0x1B32, content=123.45
ptr_c : address=0x1B30, content=0x1B38
*ptr_c => A
ptr_x : adresse=0x1B2E, contenu=0x1B36
*ptr_x => 7
ptr_y : adresse=0x1B2C, contenu=0x1B32
*ptr_y => 123.45

Dans le Listing 11.2, il y a trois variables, c, x et y, et trois variables pointeurs,


ANALYSE
ptr_c, ptr_x et ptr_y, déclarés respectivement aux lignes 6 à 8.
Les instructions des lignes 10 à 12 initialisent les trois variables c, x et y. Ensuite, les
lignes 13 à 15 impriment les adresses ainsi que le contenu des trois variables.

À la ligne 16, la valeur gauche de la variable caractère c est affectée à la variable


pointeur ptr_c. La sortie de l'instruction de la ligne 17 montre que la variable pointeur
ptr_c contient l'adresse de c. En d'autres termes, le contenu (c'est-à-dire la valeur de
droite) de ptr_c est l'adresse (c'est-à-dire la valeur de gauche) de c. 11
Ensuite, à la ligne 18, la valeur à laquelle se réfère le pointeur *ptr_c est imprimée. La
sortie prouve que le pointeur *ptr_c pointe bien vers l'emplacement de mémoire c.
La ligne 19 affecte la valeur gauche de l'entier x à la variable pointeur d'entier ptr_x.
Les instructions des lignes 20 et 21 impriment la valeur gauche et la valeur droite de la
variable pointeur ptr_x, ainsi que la valeur à laquelle se réfère le pointeur *ptr_x.
De même, la valeur gauche de la variable flottante y est affectée à la variable pointeur
flottante ptr_y à la ligne 22. Pour prouver que ptr_y contient l'adresse de y et que
*ptr_y donne le contenu de y, les lignes 23 et 24 affichent les valeurs de droite de ptr_y
et *ptr_y, respectivement.
Les instructions des lignes 16, 19 et 22 vous montrent comment affecter la valeur d'une
variable à une autre, de manière indirecte. En d'autres termes, la valeur gauche d'une
variable peut être affectée à une autre variable afin que cette dernière puisse être utilisée
comme variable pointeur pour obtenir la valeur droite de la première. Dans ce cas, le
nom de la variable et le pointeur se réfèrent à la variable
même emplacement mémoire. Par conséquent, si le nom de la variable ou le pointeur est
utilisé dans une expression pour modifier le contenu de l'emplacement mémoire, le
contenu de l'emplacement mémoire a également été modifié pour l'autre.
Pour vous aider à comprendre l'indirection de l'affectation des valeurs, la figure 11.1
illustre l'image mémoire des relations entre c et ptr_c, x et ptr_x, et y et ptr_y,
d'après les résultats obtenus sur ma machine.
Comprendre les 183
pointeurs

FIGURE 11.1 .
.
L'image mémoire des .
ptr_y
variables et de leurs 0x1B2C
pointeurs. 0x1B32
ptr_x 0x1B2E
0x1B36
ptr_c *ptr_y
0x1B30
0x1B38
y 0x1B32
*ptr_x
123.45
x
0x1B36
7 *ptr_c
c 0x1B38
'A'

.
.
.

L'opérateur de déréférencement (*)


Vous avez déjà vu l'astérisque (*) dans la déclaration d'un pointeur. En C, l'astérisque
est appelé opérateur de déréférence lorsqu'il est utilisé comme opérateur unaire (parfois,
il est également appelé opérateur d'indirection). (Parfois, il est également appelé
opérateur d'indirection.) La valeur d'une variable peut être référencée par la
combinaison de
l'opérateur * et son opérande, qui contient l'adresse de la variable.
Par exemple, dans le programme présenté dans le Listing 11.2, une fois que l'adresse de
la variable caractère c est affectée à la variable pointeur ptr_c, l'expression *ptr_c fait
référence à la valeur contenue dans c. Par conséquent, vous pouvez utiliser l'expression
*ptr_c, au lieu d'utiliser directement la variable c, pour obtenir la valeur de c.

De même, étant donné une variable entière x et x = 1234, vous pouvez déclarer une
variable pointeur entière, ptr_x, par exemple, et affecter la valeur gauche (adresse) de x
à ptr_x - c'est-à-dire ptr_x = &x. L'expression *ptr_x produit alors 1234, qui est la
valeur droite (con- tente) de x.

Ne confondez pas l'opérateur de déréférence avec l'opérateur de


multiplication, bien qu'ils partagent le même symbole, *.
L'opérateur de déréférence est un opérateur unaire, qui ne prend qu'un seul
opérande. L'opérande contient l'adresse (c'est-à-dire la valeur gauche)
d'une variable.
En revanche, l'opérateur de multiplication est un opérateur binaire qui
nécessite deux opérandes pour effectuer l'opération de multiplication.
La signification du symbole * est déterminée par le contexte dans lequel vous
184 Heure
11

Pointeurs nuls
On dit d'un pointeur qu'il est nul lorsque sa valeur droite est 0. Rappelez-vous qu'un
pointeur nul ne peut jamais pointer sur des données valides. C'est pourquoi vous pouvez
tester un pointeur pour voir s'il est assigné à 0 ; si c'est le cas, vous savez qu'il s'agit d'un
pointeur nul et qu'il n'est pas valide.
Pour définir un pointeur nul, il suffit d'assigner 0 à la variable du pointeur. Par exemple, il suffit d'assigner 0 à la
variable pointeur :
char *ptr_c ;
int *ptr_int
;

ptr_c = ptr_int = 0 ;

Ici, ptr_c et ptr_int deviennent des pointeurs nuls après que la valeur entière 0 leur a
été attribuée.
Vous verrez des applications de pointeurs nuls dans les instructions de flux de contrôle et
les tableaux plus loin dans ce livre.

Mise à jour des variables via des pointeurs


Comme vous l'avez appris dans la section précédente, tant que vous reliez une variable à
une variable pointeur, vous pouvez obtenir la valeur de la variable en utilisant la variable
11
pointeur. En d'autres termes, vous pouvez lire la valeur en pointant sur l'emplacement
mémoire de la variable et en utilisant l'opérateur de déréférencement.
Cette section vous montre que vous pouvez écrire une nouvelle valeur à l'emplacement
mémoire d'une variable en utilisant un pointeur qui contient la valeur gauche de la
variable. Le Listing 11.3 en donne un exemple.

TYPE LISTE 11.3 Modification des valeurs des variables à l'aide de pointeurs

1 : /* 11L03.c : Changement de valeurs via des


pointeurs */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: char c, *ptr_c ;
7 :
8: c = 'A' ;
9 : printf("c : address=%p, content=%c\n", &c,
c) ; 10: ptr_c = &c ;
11 : printf("ptr_c : address=%p, content=%p\n", &ptr_c,
ptr_c) ; 12: printf("*ptr_c => %c\n", *ptr_c) ;
13 : *ptr_c = 'B' ;
14 : printf("ptr_c : address=%p, content=%p\n", &ptr_c, ptr_c) ;

continue
Comprendre les 185
pointeurs

LISTE 11.3 suite


15 : printf("*ptr_c => %c\n", *ptr_c) ;
16 : printf("c : address=%p, content=%c\n", &c,
c) ; 17: return 0 ;
18 : }

Après avoir exécuté le fichier exécutable 11L03.exe sur ma machine, j'obtiens la sortie
suivante à l'écran :
c : address=0x1828, content=A
SORTIE ptr_c : adresse=0x1826, contenu=0x1828
*ptr_c => A
ptr_c : adresse=0x1826, contenu=0x1828
*ptr_c => B
c : address=0x1828, content=B

Une variable char, c, et une variable pointeur char, ptr_c, sont déclarées à la
ANALYSE ligne 6 du Listing 11.3.

La variable c est initialisée avec 'A' à la ligne 8, qui est imprimée, avec l'adresse
de la variable, par la fonction printf() à la ligne 9.
A la ligne 10, la variable pointeur ptr_c se voit attribuer la valeur gauche (adresse) de c.
Il n'est pas surprenant de voir la sortie imprimée par les instructions des lignes 11 et 12,
où la valeur droite de ptr_c est la valeur gauche de c, et le pointeur *ptr_c pointe vers
la valeur droite de c.
À la ligne 13, l'expression *ptr_c = 'B' demande à l'ordinateur d'écrire 'B' à
l'emplacement pointé par le pointeur ptr_c. La sortie imprimée par l'instruction de la
ligne 15 prouve que le contenu de l'emplacement mémoire pointé par ptr_c est mis à
jour. L'instruction de la ligne 14 imprime les valeurs gauche et droite de la variable
pointeur ptr_c et montre que ces valeurs restent inchangées. Comme vous le savez,
l'emplacement pointé par ptr_c est celui où réside la variable caractère c. Par
conséquent, l'expression *ptr_c = 'B' met à jour le contenu (c'est-à-dire la valeur
droite) de la variable c en 'B'. Pour le prouver, l'instruction de la ligne 16 affiche les
valeurs gauche et droite de c à l'écran. La sortie montre bien que la valeur droite de c a
été modifiée.

Pointer vers le même emplacement de mémoire


Un emplacement de mémoire peut être pointé par plus d'un pointeur. Par exemple,
étant donné que c = 'A' et que ptr_c1 et ptr_c2 sont deux variables pointeurs de
caractères, ptr_c1 = &c et ptr_c2 = &c font pointer les deux variables pointeurs
sur le même emplacement dans la mémoire.
186 Heure
11

Le programme de la liste 11.4 montre un autre exemple de pointer sur la même chose
avec plusieurs pointeurs.

LISTE 11.4 Pointer sur le même emplacement mémoire avec


TYPE plus d'un pointeur

1 : /* 11L04.c : Pointer vers la même chose


*/ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int x ;
7 : int *ptr_1, *ptr_2, *ptr_3
; 8 :
9: x = 1234 ;
10 : printf("x : address=%p, content=%d\n", &x,
x) ; 11: ptr_1 = &x ;
12 : printf("ptr_1 : address=%p, content=%p\n", &ptr_1,
ptr_1) ; 13: printf("*ptr_1 => %d\n", *ptr_1) ;
14 : ptr_2 = &x ;
15 : printf("ptr_2 : address=%p, content=%p\n", &ptr_2,
ptr_2) ; 16: printf("*ptr_2 => %d\n", *ptr_2) ;
17 :
18 :
ptr_3 = ptr_1 ;
printf("ptr_3 : address=%p, content=%p\n", &ptr_3,
11
ptr_3) ; 19: printf("*ptr_3 => %d\n", *ptr_3) ;
20 : retour 0 ;
21 : }

La sortie suivante s'affiche à l'écran en exécutant le fichier exécutable 11L04.exe sur ma


machine (notez que vous pouvez obtenir des valeurs d'adresse différentes en fonction de
votre système) :
x : adresse=0x1838, contenu=1234
SORTIE ptr_1 : adresse=0x1834, contenu=0x1838
*ptr_1 => 1234
ptr_2 : adresse=0x1836, contenu=0x1838
*ptr_2 => 1234
ptr_3 : adresse=0x1832, contenu=0x1838
*ptr_3 => 1234

Comme le montre la liste 11.4, la ligne 6 déclare une variable entière, x, et la


ANALYSE
ligne 7 déclare trois variables de pointeur entières, ptr_1, ptr_2 et ptr_3.
L'instruction de la ligne 10 imprime les valeurs gauche et droite de x. Sur ma machine,
la valeur gauche (adresse) de x est 0x1838. La valeur droite (contenu) de x est 1234, qui
est la valeur initiale assignée à x à la ligne 9
Comprendre les 187
pointeurs

La ligne 11 affecte la valeur gauche de x à la variable pointeur ptr_1, de sorte que ptr_1
puisse être utilisé pour faire référence à la valeur droite de x. Pour s'assurer que la
variable pointeur ptr_1 contient maintenant l'adresse de x, la ligne 12 imprime la valeur
droite de ptr_1, ainsi que sa valeur gauche. La sortie montre que ptr_1 contient bien
l'adresse de x, 0x1838. Ensuite, la ligne 13 affiche la valeur 1234, à laquelle fait
référence l'expression *ptr_1. Notez que l'astérisque * dans l'expression est l'opérateur
de déréférencement
A la ligne 14, l'expression *ptr_2 = &x affecte la valeur gauche de x à une autre
variante de pointeur, ptr_2 ; autrement dit, la variable de pointeur ptr_2 est maintenant
liée à l'adresse de x. L'instruction de la ligne 16 affiche l'entier 1234 à l'écran en utilisant
l'opérateur de déréférence * et son opérande, ptr_2. En d'autres termes, l'emplacement de
mémoire x est désigné par le second pointeur *ptr_2.
À la ligne 17, la variable pointeur ptr_3 se voit attribuer la valeur droite de ptr_1.
Comme ptr_1 contient maintenant l'adresse de x, l'expression ptr_3 = ptr_1 est
équivalente à ptr_3 = &x. Ensuite, à partir de la sortie des instructions des lignes 18 et
19, vous voyez à nouveau l'entier 1234 à l'écran. Cette fois, l'entier est désigné par le
troisième pointeur, ptr_3.

Résumé
Dans cette leçon, vous avez appris les concepts très importants suivants concernant les pointeurs en C :

• Un pointeur est une variable dont la valeur pointe vers une autre variable.
• Une variable déclarée en C a deux valeurs : la valeur de gauche et la valeur de droite.
• La valeur gauche d'une variable est l'adresse ; la valeur droite est le
contenu de la variable.
• L'opérateur d'adresse (&) peut être utilisé pour obtenir la valeur gauche
(adresse) d'une variable.
• L'astérisque (*) dans une déclaration de pointeur indique au compilateur que la
variable est une variable de pointeur.
• L'opérateur de déréférence (*) est un opérateur unaire ; en tant que tel, il
ne nécessite qu'un seul opérande.
• L'expression *nom_ptr évalue la valeur pointée par la variable pointeur
ptr_name, où ptr_name peut être n'importe quel nom de variable valide en C.

• Si la valeur droite de la variable pointeur a été affectée à la valeur 0, le pointeur


est un pointeur nul. Un pointeur nul ne peut pas pointer vers des données valides.
• Vous pouvez mettre à jour la valeur d'une variable référencée par une variable pointeur.
• Plusieurs pointeurs peuvent pointer sur le même emplacement d'une variable dans la mémoire.
188 Heure
11

Vous verrez d'autres exemples d'utilisation de pointeurs dans la suite du livre.


Dans la prochaine leçon, vous découvrirez un type d'agrégat, un tableau, qui est
étroitement lié aux pointeurs en C.

Q&R
Q Quelles sont les valeurs de gauche et de droite ?
A La valeur de gauche est l'adresse d'une variable, et la valeur de droite est le contenu
stocké dans l'emplacement mémoire d'une variable. Il existe deux façons d'obtenir
la valeur droite d'une variable : utiliser directement le nom de la variable ou utiliser
la valeur gauche de la variable et l'opérateur de déréférence pour faire référence à
l'emplacement de la valeur droite. La seconde méthode est également appelée
méthode indirecte.
Q Comment obtenir l'adresse d'une variable ?
R En utilisant l'opérateur d'adresse, &. Par exemple, étant donné une variable entière
x, l'expression &x est évaluée à l'adresse de x. Pour imprimer l'adresse de x, vous
pouvez utiliser le spécificateur de format %p dans la fonction printf().
Q Quel est le concept d'indirection en termes d'utilisation de pointeurs ?
A Avant cette heure, la seule façon de lire ou d'écrire dans une variable était
11
d'invoquer directement la variable. Par exemple, si vous vouliez écrire une
décimale, 16, dans une variable entière x, vous pouviez utiliser l'instruction x =
16 ;.
Comme vous l'avez appris au cours de cette heure, le langage C vous permet
d'accéder à une variable d'une autre manière, en utilisant des pointeurs. Par
conséquent, pour écrire 16 dans x, vous pouvez d'abord déclarer un pointeur
d'entier (ptr) et assigner la valeur gauche (adresse) de x à ptr - c'est-à-dire, ptr
= &x ;. Ensuite, au lieu d'exécuter l'instruction x = 16 ;, vous pouvez utiliser une
autre instruction :
*ptr = 16 ;
Ici, le pointeur *ptr fait référence à l'emplacement mémoire réservé par x, et le
contenu stocké dans l'emplacement mémoire est mis à jour à 16 après l'exécution
de l'instruction. Vous voyez donc que l'utilisation de pointeurs pour accéder aux
emplacements mémoire des variables est un moyen d'indirection.
Q Un pointeur nul peut-il pointer sur des données valides ?
R Non. Un pointeur nul ne peut pas pointer vers des données valides, car la valeur
contenue dans un pointeur nul a été fixée à 0. En effet, la valeur contenue dans un
pointeur nul a été fixée à 0. Vous verrez des exemples d'utilisation de pointeurs
nuls dans les tableaux, les chaînes de caractères et l'allocation de mémoire plus
loin dans le livre.
Comprendre les 189
pointeurs

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices sont donnés dans l'annexe B, "Réponses aux questions du quiz et aux
exercices".

Quiz
1. Comment obtenir la valeur gauche d'une variable de caractère ch ?
2. Dans les expressions suivantes, quel astérisque (*) est un opérateur de
déréférence et lequel est un opérateur de multiplication ?
• *ptr

• x * y

• y *= x + 5

• *y *= *x + 5

3. Étant donné que x = 10, que l'adresse de x est 0x1A38 et que ptr_int = &x, quel sera le résultat de
l'opération suivante
ptr_int et *ptr_int produisent respectivement ?
4. Étant donné que x = 123 et que ptr_int = &x après l'exécution de *ptr_int
= 456, que contient x ?

Exercices
1. Étant donné trois variables entières, x = 512, y = 1024 et z = 2048, écrivez un
programme pour imprimer leurs valeurs de gauche ainsi que leurs valeurs de
droite.
2. Ecrire un programme pour mettre à jour la valeur de la variable double flt_num de 123.45
à 543,21 en utilisant un double pointeur.
3. Étant donné une variable de caractère ch et ch = 'A', écrivez un programme pour mettre à jour la
valeur de
ch à la décimale 66 en utilisant un pointeur.
4. Sachant que x=5 et y=6, écrivez un programme qui calcule la multiplication des
deux entiers et imprime le résultat, qui est sauvegardé dans x, le tout de manière
indirecte (c'est-à-dire en utilisant des pointeurs).
190 Heure
11

HEURE 12
Comprendre les tableaux
Rassemblez les fragments qui restent, afin que rien ne soit perdu.
-Jean 6:12
Dans la leçon de l'heure dernière, vous avez découvert les pointeurs et le
concept d'indirec- tion. Dans cette leçon, vous découvrirez les tableaux, qui
sont des collections d'éléments de données similaires et qui sont étroitement
liés aux pointeurs. Les principaux sujets abordés dans
cette leçon sont

• Tableaux unidimensionnels
• Indexation des tableaux
• Pointeurs et tableaux
• Tableaux de caractères
• Tableaux multidimensionnels
• Tableaux non dimensionnés
190 Heure
190

Qu'est-ce qu'un tableau ?


Vous savez maintenant comment déclarer une variable avec un type de données
spécifique, tel que char, int, float ou double. Dans de nombreux cas, vous devez
déclarer un ensemble de variables ayant le même type de données. Au lieu de les
déclarer individuellement, le langage C vous permet de déclarer collectivement un
ensemble de variables du même type de données sous la forme d'un tableau.
Un tableau est une collection de variables du même type de données. Chaque élément
d'un tableau est appelé élément. Tous les éléments d'un tableau sont référencés par le nom
du tableau et sont stockés dans un ensemble d'emplacements de mémoire consécutifs et
adjacents.

Déclaration des tableaux


Voici la forme générale de déclaration d'un tableau :
data-type Array-Name[Array-Size] ;

Ici, data-type est le spécificateur de type qui indique le type de données du tableau
déclaré. Array-Name est le nom du tableau déclaré. Array-Size définit le nombre
d'éléments que le tableau peut contenir. Notez que les crochets ([ et ]) sont nécessaires
pour déclarer un tableau. La paire de crochets ([ et ]) est également appelée opérateur
d'indice de tableau.
Par exemple, un tableau d'entiers est déclaré dans l'instruction suivante,
int array_int[8] ;

où int spécifie le type de données du tableau dont le nom est array_int. La taille du
tableau est de 8, ce qui signifie que le tableau peut stocker huit éléments (c'est-à-dire des
entiers en
dans ce cas).
En C, vous devez déclarer explicitement un tableau, comme vous le faites pour les
autres variables, avant de pouvoir l'utiliser.

Indexation des tableaux


Après avoir déclaré un tableau, vous pouvez accéder à chacun de ses éléments
séparément. Par exemple, la déclaration suivante déclare un tableau de caractères :
char day[7] ;

Vous pouvez accéder aux éléments du tableau de jour les uns après les autres. Pour ce
faire, vous utilisez le nom du tableau dans une expression suivie d'un nombre, appelé
index, placé entre parenthèses.
Il est important de se rappeler que tous les tableaux en C sont indexés à partir de 0. En
Comprendre les 191
tableaux
d'autres termes, l'index du premier élément d'un tableau est 0, et non 1. Par conséquent, le
premier élément du tableau day est day[0]. Comme il y a 7 éléments dans le tableau day,
le dernier élément est day[6], et non pas day[7].
192 Heure
192

Les sept éléments du tableau ont les expressions suivantes : jour[0], jour[1], jour[2],
jour[3], jour[4], jour[5] et jour[6].

Comme ces expressions font référence aux éléments du tableau, elles sont parfois appelées
les références des éléments du tableau.

Initialisation des tableaux


Les références d'éléments de tableau permettent d'initialiser chaque élément d'un tableau.
Par exemple, vous pouvez initialiser le premier élément du tableau de jours, qui a été
déclaré dans la dernière section, comme suit :
day[0] = 'S' ;

Ici, la valeur numérique de S est attribuée au premier élément du jour, day[0].


De même, l'instruction jour[1] = 'M' ; attribue 'M' au deuxième élément, jour[1],
du tableau.
La deuxième façon d'initialiser un tableau consiste à initialiser tous les éléments du tableau
ensemble. Par exemple, l'instruction suivante initialise un tableau d'entiers, arInteger :
int arInteger[5] = {100, 8, 3, 365, 16} ;

Ici, les entiers à l'intérieur des accolades ({ et }) sont affectés aux éléments du tableau
arInteger. Ainsi, 100 est attribué au premier élément (arInteger[0]), 8 au deuxième
élément (arInteger[1]), 3 au troisième (arInteger[2]), et ainsi de suite.
La liste 12.1 donne un autre exemple d'initialisation de tableaux.
12
TYPE LISTE 12.1 Initialisation d'un tableau

1 : /* 12L01.c : Initialisation d'un


tableau */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int i ;
7 : int list_int[10] ;
8 :
9: for (i=0 ; i<10 ;
i++){ 10 :
list_int[i] = i + 1 ;
11 : printf("list_int[%d] is initialized with %d.\n", i, list_int[i])
; 12 : }
13 : retour 0 ;
14 : }
Comprendre les 193
tableaux

La sortie suivante s'affiche à l'écran après que l'exécutable (12L01.exe) du programme


de la liste 12.1 a été créé et exécuté sur mon ordinateur :
list_int[0] es initialisé avec 1.
SORTIE list_int[1] t initialisé avec 2.
es
t
list_int[2] es initialisé avec 3.
t
list_int[3] es initialisé avec 4.
t
list_int[4] es initialisé avec 5.
t
list_int[5] es initialisé avec 6.
t
list_int[6] es initialisé avec 7.
t
list_int[7] es initialisé avec 8.
t
list_int[8] es initialisé avec 9.
t
list_int[9] es initialisé avec 10.
t

Comme vous pouvez le voir dans le Listing 12.1, il existe un tableau d'entiers,
ANALYSE
appelé list_int, qui est déclaré à la ligne 7. Le tableau list_int contient 10
éléments.
Les lignes 9 à 12 constituent une boucle for qui itère 10 fois. L'instruction de la ligne
10 initialise list_int[i], le ième élément du tableau list_int, avec le résultat de
l'expres-
sion i + 1.
La ligne 11 affiche ensuite le nom de l'élément, list_int[i], et la valeur attribuée à
l'élément.

La taille d'un tableau


Comme nous l'avons déjà mentionné dans cette leçon, un tableau est constitué
d'emplacements de mémoire consécutifs. Étant donné un tableau, comme celui-ci :
data-type Array-Name[Array-Size] ;

vous pouvez alors calculer le nombre total d'octets du tableau à l'aide de l'expression suivante :
sizeof(data-type) * Array-Size

Ici, le type de données est le type de données du tableau ; la taille du tableau


spécifie le nombre total d'éléments que le tableau peut contenir. Cette expression est
évaluée au nombre total d'octets que prend le tableau.
Une autre façon de calculer le nombre total d'octets d'un tableau est plus simple ;
elle utilise l'expression suivante :
194 Heure
taille du nom du tableau 194

Ici, Array-Name est le nom du tableau.


Le programme du Listing 12.2 montre comment calculer l'espace mémoire occupé par un tableau.
Comprendre les 195
tableaux

TYPE LISTE 12.2 Calculer la taille d'un tableau

1 : /* 12L02.c : Total des octets d'un


tableau */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int total_byte ;
7 : int list_int[10] ;
8 :
9 : total_byte = sizeof (int) * 10 ;
10 : printf("The size of int is %d-byte long.\n", sizeof (int)) ;
11 : printf("Le tableau de 10 octets a un total de %d octets.\n",
total_byte) ; 12 : printf("L'adresse du premier élément : %p\n",
&list_int[0]) ; 13 : printf("L'adresse du dernier élément : %p\n",
&list_int[9]) ; 14: return 0 ;
15 : }

Après avoir exécuté l'exécutable 12L02.exe, l'écran de mon ordinateur affiche la sortie
suivante :
La taille d'un int est de 2 octets.
SORTIE Le tableau de 10 octets a un total de
20 octets L'adresse du premier élément
: 0x1806 L'adresse du dernier élément :
0x1818

Notez que vous pouvez obtenir des valeurs d'adresse différentes lorsque vous
ANALYSE
exécutez le programme de la liste 12.2 sur votre machine. Cependant, la différence
entre l'adresse de
le premier élément et l'adresse du dernier élément doivent être égaux au nombre total
12
d'octets du tableau.
Dans le Listing 12.2, un tableau d'entiers, list_int, est déclaré à la ligne 7. L'espace
mémoire total occupé par le tableau est le résultat de la multiplication de la taille de int
et du nombre total d'éléments dans le tableau. Tel que déclaré dans cet exemple, il y a un
total de 10 éléments dans le tableau list_int.
L'instruction de la ligne 10 imprime la taille de int sur ma machine. Vous pouvez voir
dans la sortie que chaque élément entier du tableau prend 2 octets. Par conséquent,
l'espace mémoire total (en octets) occupé par le tableau est de 10 * 2. En d'autres termes,
l'instruction de la ligne 9 affecte la valeur 20, produite par l'expression sizeof (int) *
10, à la variable entière total_octet. La ligne 11 affiche ensuite à l'écran la valeur
contenue dans la variable total_octet.
Pour prouver que le tableau occupe bien l'espace mémoire consécutif de 20 octets,
l'adresse du premier élément du tableau est imprimée par l'instruction de la ligne 12.
Notez que l'esperluette (&), qui a été introduite en tant qu'opérateur d'adresse dans l'heure
196 Heure
196
11, "Comprendre les pointeurs", est utilisée à la ligne 12 pour obtenir l'adresse du premier
élément,
Comprendre les 197
tableaux

list_int[0], dans le tableau. Ici, l'adresse du premier élément est l'adresse de départ du
tableau. La sortie montre que l'adresse de l'élément list_int[0] est 0x1806 sur ma
machine.
Ensuite, l'expression &list_int[9] de la ligne 13 est évaluée à l'adresse du dernier
élément du tableau, qui est 0x1818 sur ma machine. Ainsi, la distance entre le dernier
élément et le premier élément est de 0x1818-0x1806, soit 18 octets.
Comme nous l'avons déjà mentionné, l'hexadécimal est un système de numération basé
sur 16. Nous savons que 0x1818 moins 0x1806 donne 0x0012 (c'est-à-dire 0x12). 0x12
en hexadécimal est donc égal à 1*16 + 2 qui donne 18 en décimal.
Comme chaque élément prend 2 octets et que l'adresse de l'élément final est le début de
cet élément de 2 octets, le nombre total d'octets pris par le tableau list_int est bien de
20 octets. Vous pouvez le calculer d'une autre manière : La distance entre le dernier
élément et le premier élément est de 18 octets. Le nombre total d'octets pris par le tableau
doit être compté à partir du tout premier octet du premier élément jusqu'au dernier octet
du dernier élément. Par conséquent, le nombre total d'octets pris par le tableau est égal à
18 plus 2, soit 20 octets.

La figure 12.1 montre l'espace mémoire occupé par le tableau list_int

FIGURE 12.1
L'espace mémoire
occupé par le &list_int[0] 0x1806 1 octet
tableau 2 octets
1 octet
list_int.
1 octet

1 octet
2 octets

&list_int[9]
0x1818 1 octet

1 octet } 2 octets

Tableaux et pointeurs
Comme je l'ai mentionné plus tôt dans cette heure, les pointeurs et les tableaux ont une
relation étroite en C. En fait, vous pouvez créer un pointeur qui fait référence au premier
élément d'un tableau en assignant simplement-
198 Heure
198

Le nom du tableau est ajouté à la variable du pointeur. Si un tableau est référencé par un
pointeur, les éléments du tableau sont accessibles à l'aide du pointeur.
Par exemple, les instructions suivantes déclarent un pointeur et un tableau, et attribuent
l'adresse du premier élément à la variable pointeur :
char *ptr_c ;
char list_c[10]
; ptr_c = list_c
;

Comme l'adresse du premier élément du tableau list_c est l'adresse de début du


tableau, la variable pointeur ptr_c fait maintenant référence au tableau via l'adresse de
début.
Le listing 12.3 montre comment référencer un tableau avec un pointeur.

TYPE LISTE 12.3 Référencement d'un tableau à l'aide d'un pointeur

1 : /* 12L03.c : Référencement d'un tableau avec un


pointeur */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int *ptr_int ;
7 : int list_int[10] ;
8 : int i ;
9 :
10: for (i=0 ; i<10 ;
i++) 11 : list_int[i]
= i + 1 ; 12: ptr_int =
list_int ; 12
13 : printf("L'adresse de départ du tableau : %p\n", ptr_int)
; 14 : printf("La valeur du premier élément : %d\n", *ptr_int) ;
15: ptr_int = &list_int[0] ;
16 : printf("L'adresse du premier élément : %p\n", ptr_int) ;
17 : printf("La valeur du premier élément : %d\n", *ptr_int) ;
18: return 0 ;
19 : }

Après l'exécution de l'exécutable 12L03.exe sur mon ordinateur, la sortie suivante


s'affiche à l'écran :
L'adresse de début du tableau : 0x1802
SORTIE La valeur du premier élément : 1
L'adresse du premier élément : 0x1802 La
valeur du premier élément : 1

Dans le Listing 12.3, une variable pointeur d'entier, ptr_int, est déclarée à la
ANALYSE
ligne 6. Ensuite, un tableau d'entiers, list_int, déclaré à la ligne 7, est initialisé
par la fonction
Comprendre les 199
L'expression list_int[i] tableaux
= i + 1 dans une boucle for. (Voir les lignes 10 et 11.)
200 Heure
200

L'instruction de la ligne 12 affecte l'adresse du premier élément du tableau à la variable


pointeur ptr_int. Pour ce faire, le nom du tableau list_int est simplement placé à
droite de l'opérateur d'affectation (=) à la ligne 12.
La ligne 13 affiche l'adresse attribuée à la variable pointeur ptr_int. La sortie montre
que 0x1802 est l'adresse de début du tableau. (L'expression *ptr_int de la ligne 14 est
évaluée à la valeur référencée par le pointeur. Cette valeur est la même que celle contenue
dans le premier élément du tableau, qui est la valeur initiale, 1, attribuée dans la boucle
for. Vous pouvez voir que la sortie de l'état de la ligne 14 affiche correctement la
valeur.
L'instruction de la ligne 15 est équivalente à celle de la ligne 12, qui assigne l'adresse du
premier élément à la variable pointeur. Les lignes 16 et 17 impriment ensuite l'adresse et
la valeur conservée par le premier élément, respectivement 0x1802 et 1.
Dans l'Heure 16, "Application des pointeurs", vous apprendrez à accéder à un élément
d'un tableau en incrémentant ou décrémentant un pointeur.

Affichage de tableaux de caractères


Cette sous-section se concentre sur les tableaux de caractères. Le type de données char
prend un octet. Par conséquent, chaque élément d'un tableau de caractères a une
longueur d'un octet. Le nombre total d'éléments d'un tableau de caractères correspond au
nombre total d'octets que le tableau occupe dans la mémoire.
Plus important encore, en C, une chaîne de caractères est définie comme une séquence
contiguë de caractères terminée par le premier caractère nul ('\0'). L'heure 13,
"Manipulation des chaînes de caractères", présente plus de détails sur les chaînes de
caractères.
Dans le Listing 12.4, vous voyez différentes façons d'afficher un tableau de caractères à l'écran.

TYPE LISTE 12.4 Impression d'un tableau de caractères

1 : /* 12L04.c : Impression d'un tableau de


caractères */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char array_ch[7] = {'H', 'e', 'l', 'l', 'o', '!', '\0'}
; 7: int i ;
8 :
9 : for (i=0 ; i<7 ; i++)
10 : printf("array_ch[%d] contains : %c\n", i,
array_ch[i]) ; 11: /*--- méthode I ---*/
12 : printf("Put all elements together(Method I):\n")
; 13: for (i=0 ; array_ch[i] != '\0' && i<7 ; i++)
14 : printf("%c", array_ch[i]) ;
Comprendre les 201
tableaux

15 : /*--- méthode II ---*/


16 : printf("\nPut all elements together(Method II):\n")
; 17: printf("%s\n", array_ch) ;
18 :
19 : retour 0 ;
20 : }

Lorsque j'exécute l'exécutable 12L04.exe, la sortie suivante s'affiche :


array_ch[0] contient : H
SORTIE array_ch[1] contient :
e array_ch[2] contient
: l array_ch[3]
contient : l
array_ch[4] contient :
o array_ch[5] contient
: ! array_ch[6]
contient :
Rassembler tous les éléments (méthode I) :
Bonjour !
Rassembler tous les éléments (méthode II) :
Bonjour !

Comme vous pouvez le voir dans le Listing 12.4, un tableau de caractères,


ANALYSE
array_ch, est déclaré et initialisé à la ligne 6. Chaque élément du tableau de
caractères est imprimé par la commande
printf() dans une boucle for présentée aux lignes 9 et 10. Le tableau comporte sept
éléments au total ; ils contiennent les constantes de caractères suivantes : 'H', 'e', 'l',
'l', 'o', '!' et '\0'.

Il y a deux façons d'afficher ce tableau : afficher tous les caractères individuellement ou


les traiter comme une chaîne de caractères.
12
Les lignes 12 à 14 montrent la première méthode, qui récupère chaque élément
individuel, array_ch[i], consécutivement dans une boucle, et imprime un caractère à la
suite d'un autre en utilisant le spécificateur de format de caractère %c dans l'appel
printf() à la ligne 14.

Chaque fois que vous avez affaire à un tableau de caractères, comme nous l'avons
mentionné précédemment, le caractère nul '\0' signale la fin de la chaîne (même s'il ne
s'agit pas encore de la fin du tableau). C'est une bonne idée de guetter le caractère nul
pour savoir quand arrêter l'impression. L'expression conditionnelle de la ligne 13 mettra
donc fin à la boucle for si l'élément courant est un caractère nul.
La deuxième méthode est plus simple. Il suffit d'indiquer à la fonction printf() où
trouver le premier élément du tableau (l'adresse de son premier élément). Vous devez
également utiliser le spécificateur de for- mat de chaîne %s dans l'appel à printf(),
comme indiqué à la ligne 17. Notez que l'expression array_ch de la ligne 17 contient
l'adresse du premier élément du tableau, c'est-à-dire l'adresse de départ du tableau. Le
202 Heure
nom du tableau, en lui-même, est une façon202
abrégée de dire
array_ch[0] ; ils signifient la même chose.
Comprendre les 203
tableaux

Vous vous demandez peut-être comment la fonction printf() sait où se trouve la fin du
tableau de caractères. Vous souvenez-vous que le dernier élément du tableau de
caractères array_ch est le caractère '\0' ? C'est ce caractère nul qui marque la fin de la
chaîne. Comme je l'ai mentionné précédemment, une séquence contiguë de caractères se
terminant par un caractère nul s'appelle un caractère
en C. Nous ne disons pas à printf() combien d'éléments il y a dans le tableau, donc le
spécificateur de format %s dit à printf() de continuer à imprimer des caractères jusqu'à
ce qu'il trouve un caractère nul - tout comme nous l'avons fait, nous-mêmes, dans la
première méthode.

Le caractère nul ("\0")


Le caractère nul ('\0') est traité comme un caractère en C ; c'est un caractère spécial
qui marque la fin d'une chaîne de caractères. Par conséquent, lorsque des fonctions
comme printf() agissent sur une chaîne de caractères, elles traitent un caractère
après l'autre jusqu'à ce qu'elles rencontrent le caractère null. (Vous en apprendrez
plus sur les chaînes de caractères dans l'heure 13.)
Le caractère nul ('\0'), qui est toujours évalué comme une valeur de zéro, peut
également être utilisé pour un test logique dans une instruction de flux de contrôle. Le
Listing 12.5 montre un exemple d'utilisation du caractère null dans une boucle for.

TYPE LISTING 12.5 Fin de la sortie au caractère nul

1 : /* 12L05.c : Arrêt au caractère nul */ 2 :


#include <stdio.h>
3 :
4 : main()
5 : {
6 : char array_ch[15] = {'C', ' ',
7 : "i", "s", ' ',
8 : "p", "o", "w", "e", "r",
9 : 'f', 'u', 'l', '!', '\0'} ;
10 : int i ;
11 : /* array_ch[i] dans le test
logique */ 12: for (i=0 ;
array_ch[i] ; i++)
13 : printf("%c", array_ch[i]) ;
14 :
15 : printf("\N") ;
16 : retour 0 ;
17 : }

En exécutant l'exécutable 12L05.exe, j'obtiens le résultat suivant :


Le C est puissant !
SORTIE
204 Heure
204

Dans le Listing 12.5, un tableau de caractères, array_ch, est déclaré et initialisé avec
ANALYSE
les caractères (y compris les caractères d'espacement) de la chaîne de caractères C is
powerful !
lignes 6-9.
Notez que le dernier élément du tableau contient le caractère nul ('\0'), qui est
nécessaire pour terminer la chaîne.
La boucle for des lignes 12 et 13 imprime chaque élément du tableau array_ch pour
afficher la chaîne C is powerful ! à l'écran. Ainsi, dans la première expression de
l'instruction for, la variable entière i, qui sert d'index au tableau, est initialisée à 0.
Ensuite, l'expression conditionnelle, array_ch[i], est évaluée. Si l'expression est
évaluée à une valeur non nulle, la boucle for itère ; sinon, la boucle s'arrête. En
commençant par le premier élément du tableau, l'expression array_ch[i] continue à
produire une valeur non nulle jusqu'à ce que le caractère null soit rencontré. Par
conséquent, la boucle for peut afficher tous les caractères du tableau à l'écran et arrêter
l'impression juste après que l'expression array_ch[i] a produit une valeur de zéro,
lorsqu'elle a trouvé le caractère nul

Tableaux multidimensionnels
Jusqu'à présent, tous les tableaux que vous avez vus étaient des tableaux
unidimensionnels, dans lesquels les tailles des dimen- sions sont placées à l'intérieur
d'une paire de crochets ([ et ]).
Outre les tableaux unidimensionnels, le langage C prend également en charge les
tableaux multidimensionnels. Vous pouvez déclarer des tableaux avec autant de
dimensions que votre compilateur le permet.
La forme générale de déclaration d'un tableau à N dimensions est la suivante
12
data-type Nom-du-réseau[Taille-du-réseau1][Taille-du-réseau2]. . . [Taille-TableauN] ;

où N peut être un nombre entier positif.


Comme le tableau à deux dimensions, qui est largement utilisé, est la forme la plus
simple du tableau multidimensionnel, nous nous concentrerons sur les tableaux à deux
dimensions dans cette section. Cependant, tout ce que vous apprendrez dans cette section
peut être appliqué à des tableaux de plus de deux dimensions.
Par exemple, l'instruction suivante déclare un tableau d'entiers à deux dimensions :
int array_int[2][3] ;

Ici, il y a deux paires de parenthèses qui représentent deux dimensions avec une taille de
2 et 3 éléments entiers, respectivement.
Vous pouvez initialiser le tableau bidimensionnel array_int de la manière suivante :
Comprendre les 205
array_int[0][0] = 1 ; tableaux
array_int[0][1] = 2 ;
206 Heure
206

array_int[0][2] = 3 ;
array_int[1][0] = 4 ;
array_int[1][1] = 5 ;
array_int[1][2] = 6 ;

ce qui équivaut à l'affirmation


int array_int[2][3] = {1, 2, 3, 4, 5, 6} ;

Vous pouvez également initialiser le tableau array_int de la manière suivante :


int array_int[2][3] = {{1, 2, 3}, {4, 5, 6}} ;

Notez que array_int[0][0] est le premier élément du tableau bidimensionnel


array_int ; array_int[0][1] est le deuxième élément du tableau ; array_int[0][2]
est le troisième élément ; array_int[1][0] est le quatrième élément ; array_int[1][1]
est le cinquième élément ; et array_int[1][2] est le sixième élément du tableau.
Le programme du Listing 12.6 montre un tableau d'entiers à deux dimensions qui est
initialisé et affiché à l'écran.

TYPE LISTE 12.6 Impression d'un tableau à deux dimensions

1 : /* 12L06.c : Impression d'un tableau


2-D */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int two_dim[3][5] = {1, 2, 3, 4, 5,
7 : 10, 20, 30, 40, 50,
8 : 100, 200, 300, 400, 500} ;
9 : int i, j
;
10 :
11 : for (i=0 ; i<3 ;
i++){ 12 :
printf("\n") ;
13 : for (j=0 ; j<5 ; j++)
14 : printf("%6d", two_dim[i][j]) ;
15 : }
16 : printf("\n") ;
17 : retour 0 ;
18 : }

Le résultat suivant est obtenu en lançant l'exécutable 12L06.exe :


1 2 3 4 5
SORTIE 10 20 30 40 50
100 200 300 400 500
Comprendre les 207
tableaux

Comme vous pouvez le voir dans le Listing 12.6, un tableau d'entiers à deux
ANALYSE
dimensions, two_dim, est déclaré et initialisé aux lignes 6 à 8.
Dans les lignes 11 à 15, deux boucles for sont imbriquées l'une dans l'autre. La
boucle for externe incrémente la variable entière i et imprime le caractère de retour
à la ligne "\n" à chaque itération. Ici, la variable entière i est utilisée comme index
de la première dimension du tableau, two_dim.
La boucle for interne des lignes 13 et 14 imprime chaque élément, représenté par
l'expression two_dim[i][j], en incrémentant l'index de la deuxième dimension du
tableau.
J'obtiens donc le résultat suivant :
1 2 3 4 5
10 20 30 40 50
100 200 300 400 500

après que les deux boucles for imbriquées ont été exécutées avec succès.

Tableaux non dimensionnés


Comme vous l'avez vu, la taille d'une dimension est normalement indiquée lors de la
déclaration d'un tableau. Cela signifie que vous devez compter chaque élément d'un
tableau pour en déterminer la taille. Cette opération peut s'avérer fastidieuse, surtout si le
tableau contient beaucoup d'éléments.
La bonne nouvelle est que le compilateur C peut calculer automatiquement la taille d'un
tableau si celui-ci est déclaré comme un tableau non dimensionné. Par exemple, lorsque
le compilateur voit le tableau non dimensionné suivant :
int list_int[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90} ;
12
il créera un tableau suffisamment grand pour stocker tous les éléments.
De même, vous pouvez déclarer un tableau multidimensionnel non dimensionné.
Cependant, vous devez spécifier la taille de toutes les dimensions sauf la plus à gauche
(c'est-à-dire la première). Par exemple, le compilateur peut réserver suffisamment
d'espace mémoire pour contenir tous les éléments du tableau bidimensionnel non
dimensionné suivant :
char list_ch[][2] = {
'a', 'A',
'b', 'B',
'c', 'C',
'd', 'D',
'e', 'E',
'f', 'F',
'g', 'G'} ;

Le programme du Listing 12.7 initialise un tableau unidimensionnel de caractères et un


208 Heure
208l'espace mémoire occupé pour stocker les
tableau bidimensionnel d'entiers, puis mesure
deux tableaux.
Comprendre les 209
tableaux

TYPE LISTE 12.7 Initialisation de tableaux non dimensionnés

1 : /* 12L07.c : Initialisation de tableaux


non dimensionnés */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char array_ch[] = {'C', ' ',
7 : "i", "s", ' ',
8 : "p", "o", "w", "e", "r",
9 : 'f', 'u', 'l', '!', '\0'} ;
10 : int list_int[][3] =
{ 11: 1, 1, 1,
12: 2, 2, 8,
13: 3, 9, 27,
14 : 4, 16, 64,
15 : 5, 25, 125,
16 : 6, 36, 216,
17 : 7, 49, 343} ;
18 :
19 : printf("La taille de array_ch[] est %d bytes.\n", sizeof (array_ch))
; 20 : printf("La taille de list_int[][3] est %d bytes.\n", sizeof
(list_int)) ; 21: return 0 ;
22 : }

Le résultat suivant est obtenu lorsque l'exécutable 12L07.exe est lancé sur mon ordinateur :
La taille de array_ch[] est de 15 octets.
SORTIE La taille de list_int[][3] est de 42 octets.

Un tableau de caractères non dimensionné, array_ch, est déclaré et initialisé aux


ANALYSE lignes 6 à 9. Aux lignes 10 à 17, un tableau d'entiers non dimensionné à deux
dimensions, list_int, est déclaré et initialisé.
initialisé également.
L'instruction de la ligne 19 mesure et imprime l'espace mémoire total (en octets) occupé
par le tableau array_ch. Le résultat montre que le tableau de caractères non dimensionné
se voit attribuer 15 octets en mémoire pour contenir tous ses éléments après la
compilation. Lorsque vous calculez manuellement le nombre total d'éléments du tableau
de caractères, vous constatez qu'il y a effectivement 15 éléments. Comme chaque
caractère occupe un octet en mémoire, le tableau de caractères array_ch occupe un total
de 15 octets.
De même, la déclaration de la ligne 20 donne le nombre total d'octets réservés dans la
mémoire pour le tableau d'entiers bidimensionnel non dimensionné list_int. Comme il
y a un total de 21 éléments entiers dans le tableau, et qu'un entier prend 2 octets sur ma
machine, le compilateur devrait allouer 42 octets pour le tableau d'entiers list_int. Le
résultat imprimé par la fonction printf() à la ligne 20 prouve qu'il y a 42 octets
réservés dans la mémoire
210 Heure
210

pour le tableau d'entiers à deux dimensions. (Si la taille de int est différente sur votre
machine, il se peut que vous obteniez des valeurs différentes pour la taille du tableau
list_int dans le programme Listing 12.7).

Résumé
Dans cette leçon, vous avez appris les concepts très importants suivants sur les tableaux en C :

• Un tableau est une collection de variables qui sont du même type de données.
• En C, l'index d'un tableau commence à 0.
• Vous pouvez initialiser chaque élément individuel d'un tableau après la
déclaration du tableau, ou vous pouvez placer toutes les valeurs initiales dans un
bloc de données entouré de { et } pendant la déclaration d'un tableau.
• L'espace mémoire occupé par un tableau est déterminé en multipliant la taille du
type de données et les dimensions du tableau.
• On dit qu'un pointeur fait référence à un tableau lorsque l'adresse du premier
élément du tableau est attribuée au pointeur. L'adresse du premier élément d'un
tableau est également appelée adresse de départ du tableau.
• Pour affecter l'adresse de départ d'un tableau à un pointeur, vous pouvez soit
combiner l'opérateur d'adresse (&) et le nom du premier élément du tableau, soit
simplement utiliser le nom du tableau à droite d'un opérateur d'affectation (=).
• Le caractère nul ('\0') marque la fin d'une chaîne de caractères. Les fonctions C,
telles que printf(), cessent de traiter la chaîne lorsque le caractère nul est
rencontré.
• Le langage C prend également en charge les tableaux multidimensionnels. Une 12
paire de crochets vides (l'opérateur de sous-scripteur de tableau-[ et ]) indique
une dimension.
• Le compilateur peut calculer automatiquement l'espace mémoire nécessaire à un
tableau non dimensionné.
Dans la prochaine leçon, vous en apprendrez plus sur les cordes en do.

Q&R
Q Pourquoi faut-il utiliser des tableaux ?
A Dans de nombreux cas, vous devez déclarer un ensemble de variables qui sont du
même type de données. Au lieu de déclarer chaque variable séparément, vous
pouvez déclarer toutes les variables collectivement sous la forme d'un tableau.
Chaque variable, en tant qu'élément du tableau, est accessible soit par la référence
de l'élément du tableau, soit par un pointeur qui fait référence au tableau.
Comprendre les 211
tableaux

Q Quel est l'indice minimum dans un tableau ?


A En C, l'indice minimum d'un tableau à une dimension est 0, qui marque le premier
élément du tableau. Par exemple, étant donné un tableau d'entiers,
int array_int[8] ;

le premier élément du tableau est array_int[0].


De même, pour un tableau multidimensionnel, l'indice minimum de chaque
dimension commence à 0.
Q Comment référencer un tableau à l'aide d'un pointeur ?
R Vous pouvez utiliser un pointeur pour référencer un tableau en lui affectant
l'adresse de début d'un tableau. Par exemple, étant donné une variable pointeur
ptr_ch et un tableau de caractères array_ch, vous pouvez utiliser l'une des
instructions suivantes pour référencer le tableau par le pointeur :
ptr_ch = array_ch ;
ou
ptr_ch = &array_ch[0] ;
Q Que peut faire le personnage null ?
A Le caractère nul ('\0') en C peut être utilisé pour marquer la fin d'une chaîne de
caractères. Par exemple, la fonction printf() continue d'afficher le caractère
suivant à l'écran jusqu'à ce que le caractère null soit rencontré. De plus, le caractère
null est toujours évalué à une valeur de zéro.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Quel est le rôle de la déclaration suivante ?
int array_int[4] = {12, 23, 9, 56} ;
2. Etant donné un tableau, int data[3], qu'est-ce qui ne va pas avec l'initialisation suivante ?
données[1] = 1 ;
données[2] = 2 ;
données[3] = 3 ;
212 Heure
212

3. Combien de dimensions ont les tableaux suivants ?


• char array1[3][19] ;

• int array2[] ;
• float array3[][8][16] ;

• char array4[][80] ;
4. Qu'est-ce qui ne va pas dans la déclaration suivante ?
char list_ch[][] = {
A", "a",
B", "b",
C", "c",
D", "d",

Exercices
1. Étant donné ce tableau de caractères :
char array_ch[5] = {'A', 'B', 'C', 'D', 'E'} ;

écrire un programme pour afficher chaque élément du tableau à l'écran.


2. Réécrivez le programme de l'exercice 1, mais cette fois-ci, utilisez une boucle
for pour initialiser le tableau de caractères avec "a", "b", "c", "d" et "e",
puis imprimez la valeur de chaque élément du tableau.
3. Etant donné ce tableau bidimensionnel non dimensionné :
char list_ch[][2] = {
'1', 'a',
'2', 'b',
'3', 'c', 12
'4', 'd',
'5', 'e',
'6', 'f'} ;

écrire un programme pour mesurer le nombre total d'octets pris par le tableau, puis
imprimer tous les éléments du tableau.
4. Réécrivez le programme du Listing 12.5. Cette fois, placez une chaîne de
caractères, I like C !, sur l'écran.
5. Etant donné le tableau suivant :
double list_data[6] = {
1.12345,
2.12345,
3.12345,
4.12345,
5.12345} ;

utiliser les deux méthodes équivalentes enseignées dans cette leçon pour mesurer
l'espace mémoire total occupé par le tableau, puis afficher les résultats à l'écran.
Comprendre les 213
tableaux
HEURE 13
Manipuler des chaînes de caractères
J'ai fait cette lettre plus longue que d'habitude, parce que je n'ai pas le
temps de la faire courte.
-B. Pascal
Dans la dernière heure de cours, vous avez appris à utiliser les tableaux pour
rassembler des variables de même type. Vous avez également appris qu'une
chaîne de caractères est en fait un tableau de caractères, le caractère nul \0
marquant la fin de la chaîne. Dans cette leçon, vous en apprendrez
davantage sur les chaînes de caractères et sur les fonctions C qui peuvent être
utilisées pour manipuler les chaînes de caractères. Les sujets suivants sont
abordés :

• Déclarer une chaîne de caractères


• La longueur d'une chaîne de caractères
• Copier des chaînes de caractères
• Lire des chaînes de caractères avec scanf()
• Les fonctions gets() et puts()
208 Heure
13

Déclaration des chaînes de caractères


Cette section vous apprend comment déclarer et initialiser les chaînes de caractères,
ainsi que la différence entre les constantes de chaînes et les constantes de caractères.
Tout d'abord, passons en revue la définition d'une chaîne de caractères.

Qu'est-ce qu'une chaîne ?


Comme présenté dans l'Heure 12, "Comprendre les tableaux", une chaîne de caractères
est un tableau de caractères, avec un caractère nul (\0) utilisé pour marquer la fin de la
chaîne. (La longueur d'une chaîne peut être inférieure à celle de son tableau de
caractères).
Par exemple, un tableau de caractères, array_ch, déclaré dans la déclaration suivante,
est considéré comme une chaîne de caractères :
char array_ch[7] = {'H', 'e', 'l', 'l', 'o', '!', '\0'} ;

En C, le caractère null est utilisé pour marquer la fin d'une chaîne de caractères, et il vaut
toujours 0. Le C traite \0 comme un caractère. Chaque caractère d'une chaîne ne prend
qu'un octet.
Une série de caractères entre guillemets ("") est appelée constante de chaîne. Le
compilateur C ajoute automatiquement un caractère nul (\0) à la fin d'une constante de
chaîne pour indiquer la fin de la chaîne.
Par exemple, la chaîne de caractères "Une chaîne de caractères" est considérée
comme une chaîne de con- stants, tout comme "Hello !

Initialisation des chaînes de caractères


Comme nous l'avons appris dans la dernière leçon, un tableau de caractères peut être déclaré et initialisé
comme suit :
char arr_str[6] = {'H', 'e', 'l', 'l', 'o', '!'} ;

Ici, le tableau arr_str est traité comme un tableau de caractères. Cependant, si vous
ajoutez un charac- ter nul (\0) dans le tableau, vous pouvez obtenir la déclaration suivante
:
char arr_str[7] = {'H', 'e', 'l', 'l', 'o', '!', '\0'} ;

Ici, le tableau arr_str est développé pour contenir sept éléments ; le dernier élément
contient un caractère nul. Le tableau de caractères arr_str est maintenant considéré
comme une chaîne de caractères en raison du caractère nul qui est ajouté à la fin des
données de caractères.
Vous pouvez également initialiser un tableau de caractères avec une constante de chaîne,
au lieu d'une liste de constantes de caractères. Par exemple, l'instruction suivante
initialise un tableau de caractères, str, avec une constante de chaîne, "Hello !":
Manipuler des 209
char str[7] = "Hello chaînes
!"; de caractères
210 Heure
13

Le compilateur ajoutera automatiquement un caractère nul (\0) à la fin de "Hello !", et


traitera le tableau de caractères comme une chaîne de caractères. Notez que la taille du
tableau est spécifiée pour contenir jusqu'à sept éléments, bien que la constante de la
chaîne ne comporte que six caractères entre guillemets. L'espace supplémentaire est
réservé au caractère nul qui sera ajouté ultérieurement par le compilateur.
Vous pouvez déclarer un tableau de caractères non dimensionné si vous souhaitez que le
compilateur calcule le nombre total d'éléments du tableau. Par exemple, la déclaration suivante
:
char str[] = "J'aime C." ;

initialise un tableau de caractères non dimensionné :, str, avec une constante de chaîne.
Plus tard, lorsque le compilateur verra la déclaration, il calculera l'espace mémoire total
nécessaire pour contenir la constante de chaîne plus un caractère nul supplémentaire
ajouté par le compilateur lui-même.
Si vous le souhaitez, vous pouvez également déclarer un pointeur de caractères et
initialiser le pointeur avec une constante de chaîne de caractères. La déclaration suivante
en est un exemple :
char *ptr_str = "I teach myself C." ;

Ne spécifiez pas une taille de tableau de caractères trop petite. Sinon, il ne


peut pas contenir une constante de chaîne plus un caractère nul
supplémentaire. Par exemple, la déclaration suivante est considérée
comme illégale :
char str[4] = "text" ;
Notez que de nombreux compilateurs C n'émettront pas d'avertissement ou
de message d'erreur sur cette déclaration incorrecte. Les erreurs d'exécution
qui pourraient éventuellement survenir en tant que
Le résultat pourrait être très difficile à déboguer. Il est donc de votre
responsabilité de
veillez à spécifier suffisamment d'espace pour une chaîne de caractères.
La déclaration suivante est correcte, car elle spécifie la taille du tableau de
caractères str qui est suffisamment grand pour contenir la chaîne constante
plus un caractère nul supplémentaire :
13

Constantes de chaînes et constantes de caractères


Comme vous le savez déjà, une constante de chaîne est une série de caractères entre
guillemets doubles (" "). En revanche, une constante de caractère est un caractère entre
guillemets simples
(' ').

Lorsqu'une variable de caractère ch et un tableau de caractères str sont initialisés avec


le même caractère, x, comme dans le cas suivant :
Manipuler des 211
chaînes de caractères

char ch = 'x' ;
char str[] = "x" ;

1 octet est réservé à la variable de caractère ch, et deux octets sont alloués au tableau de
caractères str. La raison pour laquelle un octet supplémentaire est nécessaire pour str
est que le compilateur doit ajouter un caractère nul au tableau.
Une autre chose importante est qu'une chaîne de caractères, puisqu'il s'agit d'un tableau,
est en réalité un pointeur de caractères. Par conséquent, vous pouvez assigner une
chaîne de caractères à une variable pointeur directement, comme ceci :
char *ptr_str ;
ptr_str = "Une chaîne de caractères" ;

Cependant, vous ne pouvez pas assigner une constante de caractère à la variable


pointeur, comme le montre l'exemple suivant :
ptr_str = 'x' ; /* C'est faux. */

En d'autres termes, la constante de caractère "x" contient une valeur de droite, et la


variable pointeur ptr_str attend une valeur de gauche. Mais le langage C exige les
mêmes types de valeurs des deux côtés de l'opérateur d'affectation =.
Il est légal d'assigner une constante de caractère à un pointeur de caractère déréférencé comme ceci :
char *ptr_str ;
*ptr_str = 'x' ;

Les valeurs situées de part et d'autre de l'opérateur = sont désormais du même type.
Le programme du Listing 13.1 montre comment initialiser, ou assigner, des tableaux de
caractères avec des constantes de chaîne.

LISTE 13.1 Initialisation des chaînes de caractères


1 : /* 13L01.c : Initialisation des
chaînes */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char str1[] = {'A', ' ',
7 : "s", "t", "r", "i", "n", "g", ' ',
8 : "c", "o", "n", "s", "t", "a", "n", "t", "\0"} ;
9 : char str2[] = "Une autre constante de
chaîne" ; 10: char *ptr_str ;
11 : int i ;
12 :
13: /* imprimer str1 */
14 : for (i=0 ; str1[i] ;
i++)
15 : printf("%c", str1[i]) ;
212 Heure
13

16 : printf("\n") ;
17: /* imprimer str2 */
18 : for (i=0 ; str2[i] ;
i++)
19 : printf("%c", str2[i]) ;
20 : printf("\N") ;
21 : /* assigner une chaîne à un pointeur */
22 : ptr_str = "Affecter une chaîne de
caractères à un pointeur" ; 23: for (i=0 ;
*ptr_str ; i++)
24 : printf("%c", *ptr_str++) ;
25 : retour 0 ;
26 : }

La sortie suivante s'affiche après la création et l'exécution de l'exécutable 13L01.exe du


programme de la liste 13.1.
Une constante de chaîne de caractères
SORTIE Une autre constante de
chaîne Attribuer une chaîne à
un pointeur.

Comme vous pouvez le voir dans le Listing 13.1, deux tableaux de caractères,
ANALYSE
str1 et str2, sont déclarés et initialisés dans les lignes 6 à 9. Dans la déclaration
de str1, un ensemble de
y compris un caractère nul, est utilisée pour initialiser le tableau. Pour str2, un caractère
est affectée au tableau à la ligne 9. Le compilateur ajoutera un caractère nul à str2.
Notez que str1 et str2 sont déclarés comme des tableaux non dimensionnés pour
lesquels le compilateur calculera automatiquement la quantité de mémoire nécessaire.
L'instruction de la ligne 10 déclare une variable de pointeur char, ptr_str.
La boucle for des lignes 14 et 15 imprime ensuite tous les éléments de str1. Comme le
dernier élément contient un caractère nul (\0) qui est évalué à 0, str1[i] est utilisé
comme expression conditionnelle de l'instruction for. L'expression str1[i] est
évaluée comme non nulle pour chaque élément de str1 à l'exception de celui qui
contient le caractère nul. Après l'exécution de la boucle for, la chaîne A string
constant est affichée à l'écran....

De même, une autre boucle for aux lignes 18 et 19 affiche la constante de chaîne
13
assignée à str2 en affichant chaque élément du tableau à l'écran. Comme le
compilateur ajoute un caractère nul au tableau, l'expression str2[i] est évaluée dans
l'instruction for. La boucle for arrête l'itération lorsque str2[i] est évaluée à 0. À ce
moment-là, le contenu de la constante de chaîne, Une autre constante de chaîne, a
déjà été affiché à l'écran.
L'instruction de la ligne 22 affecte une constante de chaîne, "Assign a string to a
pointer.", : à la variable de pointeur de caractère ptr_str. En outre, une boucle for
Manipuler des 213
chaînes de
est utilisée pour imprimer la constante dechaîne
caractères
en affichant chaque caractère de la
chaîne à l'écran (voir lignes 23 et 24). Notez que le pointeur déréférencé *ptr_str est
utilisé pour faire référence à l'un des caractères de la chaîne de caractères
214 Heure
13

constante. Lorsque le caractère null ajouté à la chaîne est rencontré, *ptr_str est évalué
à 0, ce qui entraîne l'arrêt de l'itération de la boucle for. À la ligne 24, l'expression
*ptr_str++ déplace le pointeur vers le caractère suivant de la chaîne après que le
caractère actuel auquel le pointeur fait référence ait été récupéré. Dans l'Heure 16,
"Application des pointeurs", vous en apprendrez plus sur l'arithmétique des pointeurs.

Quelle est la longueur d'une corde ?


Parfois, il est nécessaire de savoir combien d'octets occupe une chaîne de caractères, car
sa longueur peut être inférieure à celle de son tableau de caractères. En C, vous
pouvez utiliser une fonction appelée strlen() pour mesurer la longueur d'une chaîne.

La fonction strlen()
Examinons la syntaxe de la fonction strlen().
La syntaxe de la fonction strlen() est la suivante
#include <string.h>
size_t strlen(const char *s) ;

Ici, s est une variable de type pointeur de caractères. La valeur de retour de la


E
S

fonction est le nombre d'octets de la chaîne, sans compter le caractère nul '\0'.
size_t est un type de données défini dans le fichier d'en-tête string.h. La taille
du type de données dépend du système informatique particulier. Notez que le fichier
E

string.h doit être inclus dans votre programme avant que vous puissiez appeler
la fonction strlen().
La liste 13.2 donne un exemple d'utilisation de la fonction strlen() pour mesurer la longueur des chaînes
de caractères.

LISTE 13.2 Mesure de la longueur des chaînes de caractères


1 : /* 13L02.c : Mesure de la longueur
d'une chaîne de caractères */ 2 : #include
<stdio.h>
3 : #include <string.h>
4 :
5 : main()
6 : {
7 : char str1[] = {'A', ' ',
8 : "s", "t", "r", "i", "n", "g", ' ',
9 : "c", "o", "n", "s", "t", "a", "n", "t", "\0"} ;
10 : char str2[] = "Une autre constante de chaîne" ;
11 : char *ptr_str = "Attribuer une chaîne de
caractères à un pointeur" ; 12 :
13 : printf("La longueur de str1 est : %d bytes\n",
strlen(str1)) ; 14 : printf("La longueur de str2 est : %d
bytes\n", strlen(str2)) ;
15 : printf("La longueur de la chaîne assignée à ptr_str est : %d
Manipuler des 215
bytes\n", 16 : chaînes de caractères
strlen(ptr_str)) ;
216 Heure
13

17 : retour 0 ;
18 : }

J'obtiens le résultat suivant en exécutant l'exécutable, 13L02.exe, du programme de la


liste 13.2.
La longueur de str1 est de : 17 octets
SORTIE La longueur de str2 est de : 23 octets
La longueur de la chaîne attribuée à ptr_str est de : 29 octets

Dans le Listing 13.2, deux tableaux de caractères, str1 et str2, et une variable pointeur,
ANALYSE
ptr_str, sont déclarées et initialisées aux lignes 7 à 11, respectivement.
Ensuite, l'instruction de la ligne 13 obtient la longueur de la constante de chaîne contenue
dans str1 et imprime le résultat. Le résultat montre que le caractère nul (\0) à la fin de
str1 n'est pas pris en compte par la fonction strlen().

Aux lignes 14 à 16, les longueurs des constantes de chaîne référencées par str2 et
ptr_str sont mesurées et affichées à l'écran. Les résultats indiquent que la fonction
strlen() ne compte pas non plus les caractères nuls ajoutés aux deux constantes de
chaîne par le compilateur.

Copier des chaînes de caractères avec strcpy()


Si vous souhaitez copier une chaîne de caractères d'un tableau à un autre, vous pouvez
copier chaque élément du premier tableau dans l'élément correspondant du second
tableau, ou vous pouvez simplement appeler la fonction C strcpy() pour qu'elle fasse
le travail à votre place.
La syntaxe de la fonction strcpy() est la suivante
#include <string.h>
char *strcpy(char *dest, const char *src) ;

Ici, le contenu de la chaîne src est copié dans le tableau référencé par dest. La
E
S

fonction strcpy() renvoie la valeur de src en cas de succès. Le fichier d'en-tête


E

string.h doit être inclus dans votre programme avant que la fonction strcpy() ne
soit appelée. 13
Le programme du Listing 13.3 montre comment copier une chaîne de caractères d'un
tableau à un autre, soit en appelant la fonction strcpy(), soit en le faisant soi-même.

LISTE 13.3 Copie de chaînes de caractères


1 : /* 13L03.c : Copie de chaînes
de caractères */ 2 : #include
<stdio.h>
3 : #include <string.h>
4 :
Manipuler des 217
5 : main() chaînes de caractères

continue
218 Heure
13

LISTE 13.3 suite


6 : {
7 : char str1[] = "Copier une chaîne de caractères" ;
8 : char str2[15] ;
9 : char str3[15] ;
10 int i ;
:
11
:
12 /* avec strcpy() */
:
13 strcpy(str2, str1) ;
:
14 /* sans strcpy() */
:
15 for (i=0 ; str1[i] ; i++)
:
16 str3[i] = str1[i] ;
:
17 str3[i] = '\0' ;
:
18 /* afficher str2 et str3 */
:
19 printf("Le contenu de str2 en utilisant strcpy : %s\n", str2) ;
:
20 printf("Le contenu de str3 sans utiliser strcpy : %s\n", str3) ;
:
21 retour 0 ;
:
22 }
:

Après la création et l'exécution de l'exécutable, 13L03.exe, la sortie suivante s'affiche :


Le contenu de str2 en utilisant strcpy : Copier une chaîne de caractères.
SORTIE
Le contenu de str3 sans utiliser strcpy : Copier une chaîne de caractères.

ANALYSE Trois tableaux de caractères, str1, str2 et str3, sont déclarés dans le Listing
13.3. En outre, str1 est initialisé avec une constante de chaîne, "Copy a
string.", à la ligne 7.

L'instruction de la ligne 13 appelle la fonction strcpy() pour copier le contenu de str1 (y


compris le caractère nul ajouté par le compilateur) dans le tableau référencé par str2.
Les lignes 15 à 17 montrent une autre façon de copier le contenu de str1 dans un
tableau référencé par str3. Pour ce faire, la boucle for des lignes 15 et 16 continue à
copier les caractères de str1 vers les éléments correspondants de str3, l'un après
l'autre, jusqu'à ce que le caractère nul (\0) ajouté par le compilateur soit rencontré.
Lorsque le caractère nul est rencontré, la boucle
L'expression str1[i] utilisée comme condition de l'instruction for à la ligne 15 est
évaluée à 0, ce qui met fin à la boucle.
Comme la boucle for ne copie pas le caractère null de str1 à str3, l'instruction de la
ligne 17 ajoute un caractère null au tableau référencé par str3. En C, il est très important
Manipuler des 219
chaînes de caractères
de s'assurer que tout tableau utilisé pour stocker une chaîne de caractères comporte un
caractère nul à la fin de la chaîne.
Pour prouver que la constante de chaîne référencée par str1 a été copiée dans str2 et str3
avec succès, les contenus de str2 et str3 sont affichés à l'écran. Notez que
220 Heure
13

le spécificateur de format de chaîne %s et les adresses de départ de str2 et str3 sont


transmis à l'appel printf() aux lignes 19 et 20 pour imprimer tous les caractères, à
l'exception du caractère nul, stockés dans str2 et str3. Les résultats affichés à l'écran
montrent que str2 et str3 ont exactement le même contenu que str1.

Lire et écrire des chaînes de caractères


Voyons maintenant comment lire ou écrire des chaînes de caractères à l'aide des flux
d'entrée et de sortie standard, c'est-à-dire stdin et stdout. En C, il existe plusieurs
fonctions que vous pouvez utiliser pour lire ou écrire des chaînes de caractères. Les sous-
sections suivantes présentent certaines de ces fonctions.
tions.

Les fonctions gets() et puts()


La fonction gets() peut être utilisée pour lire des caractères à partir du flux d'entrée standard.
La syntaxe de la fonction gets() est la suivante
#include <stdio.h>
char *gets(char *s)
;
E

Ici, les caractères lus à partir du flux d'entrée standard sont stockés dans le tableau
S

de caractères identifié par s. La fonction gets() arrête la lecture et ajoute un


caractère nul \0 au tableau lorsqu'elle rencontre une nouvelle ligne ou un caractère
de fin de fichier (EOF).
La fonction renvoie s si elle se termine avec succès. Dans le cas contraire, elle
E

renvoie un pointeur nul.


La fonction puts() peut être utilisée pour écrire des caractères sur le flux de sortie
standard (c'est-à-dire stdout).
La syntaxe de la fonction puts est la suivante
#include <stdio.h>
int puts(const char *s) ;

Ici, s fait référence au tableau de caractères qui contient une chaîne de caractères. 13
E
S

La fonction puts() écrit la chaîne sur stdout. Si la fonction réussit, elle renvoie 0.
Sinon, elle renvoie une valeur non nulle.
La fonction puts() ajoute un caractère de nouvelle ligne pour remplacer le
caractère null à la fin d'un tableau de caractères.
E

Les fonctions gets() et puts() nécessitent le fichier d'en-tête stdio.h. Dans le Listing
13.4, vous pouvez voir l'application de ces deux fonctions.
Manipuler des 221
chaînes de caractères

LISTE 13.4 Utilisation des fonctions gets() et puts()


1 : /* 13L04.c : Utilisation de gets() et
puts() */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char str[80] ;
7 : int i, delt = 'a' -
'A' ; 8 :
9 : printf("Entrez une chaîne de moins de 80
caractères:\n") ; 10: gets( str ) ;
11: i = 0 ;
12 : while (str[i]){
13 : if ((str[i] >= 'a') && (str[i] <= 'z'))
14 : str[i] -= delt ; /* conversion en
majuscules */ 15 : ++i ;
16 : }
17 : printf("La chaîne saisie est (en majuscules):\n")
; 18: puts( str ) ;
19 : retour 0 ;
20 : }

Lors de l'exécution de l'exécutable 13L04.exe, j'entre une ligne de caractères (en gras
ci-dessous) au clavier et les caractères (tous en majuscules) s'affichent à l'écran.
Entrez une chaîne de moins de 80 caractères :
SORTIE Il s'agit d'un test.
La chaîne saisie est (en majuscules) :
IL S'AGIT D'UN TEST.

Le programme du Listing 13.4 accepte une chaîne de caractères saisie à partir du


ANALYSE
clavier (c'est-à-dire stdin), puis convertit tous les caractères minuscules en
majuscules.
les autres. Enfin, la chaîne modifiée est renvoyée à l'écran...
À la ligne 6, un tableau de caractères (str) pouvant contenir jusqu'à 80 caractères est
déclaré. La fonction gets() de la ligne 10 lit tous les caractères saisis par l'utilisateur sur
le clavier jusqu'à ce que l'utilisateur appuie sur la touche Enter, qui est interprétée comme
un caractère de retour à la ligne. Les caractères lus par la fonction gets() sont stockés
dans le tableau de caractères indiqué par str. Le caractère de retour à la ligne n'est pas
enregistré dans str. Au lieu de cela, un caractère nul est ajouté au tableau en tant que
terminateur.
La boucle while des lignes 12 à 15 comporte une expression conditionnelle, str[i]. La
boucle while continue d'itérer tant que str[i] est évaluée à une valeur non nulle. À
l'intérieur de la boucle, la valeur de chaque caractère représenté par str[i] est évaluée à
la ligne 13, afin de déterminer si l'expression
222 Heure
13

est un caractère minuscule compris entre a et z. Si le caractère est un caractère


minuscule, il est converti en majuscule en soustrayant la valeur d'une variable int, delt,
de sa valeur actuelle à la ligne 14. La variable delt est initialisée à la ligne 7 par la
valeur de l'expression 'a' - 'A', qui est la différence entre la valeur numérique d'un
caractère minuscule et sa contrepartie majuscule. En d'autres termes, en soustrayant la
différence entre 'a' et 'A' de la valeur entière en minuscules, on obtient la valeur
entière en majuscules.
Ensuite, la fonction puts() de la ligne 18 transmet la chaîne avec tous les caractères
majuscules à stdout, qui est affichée à l'écran par défaut. Un caractère de retour à la
ligne est ajouté par la fonction puts() lorsqu'elle rencontre le caractère null à la fin de la
chaîne....

Utilisation de %s avec la fonction printf()


Nous avons utilisé la fonction printf() dans de nombreux exemples de programmes de
ce livre. Comme vous le savez, de nombreux spécificateurs de format peuvent être utilisés
avec la fonction printf() pour spécifier différents formats d'affichage pour des données
de différents types.
Par exemple, vous pouvez utiliser le spécificateur de format de chaîne, %s, avec la
fonction printf() pour afficher une chaîne de caractères enregistrée dans un tableau.
(Voir l'exemple de la liste 13.3.)
Dans la section suivante, la fonction scanf() est présentée comme un moyen de lire des
valeurs de différents types de données avec différents spécificateurs de format, y compris
le spécificateur de format %s.

La fonction scanf()
La fonction scanf() offre un autre moyen de lire des chaînes de caractères à partir du
flux d'entrée standard. De plus, cette fonction peut être utilisée pour lire différents types
de données d'entrée. Les formats des arguments de la fonction scanf() sont assez
similaires à ceux utilisés dans la fonction printf().
La syntaxe de la fonction scanf() est la suivante
#include <stdio.h>
int scanf(const char *format,...) ; 13
Ici, divers spécificateurs de format peuvent être inclus dans la chaîne de format
E
S

référencée par la variable de pointeur de caractères format. Si la fonction scanf()


se termine avec succès, elle renvoie le nombre d'éléments de données lus à partir de
E

stdin. En cas d'erreur, la fonction scanf() renvoie EOF (end-of-file).

L'utilisation du spécificateur de format de chaîne %s indique à la fonction scanf() de


continuer à lire les caractères jusqu'à ce qu'elle rencontre un espace, une nouvelle ligne,
une tabulation, une tabulation verticale ou un saut de page.
Manipuler des 223
chaînes
Les caractères lus par la fonction de caractères
scanf() sont stockés dans un tableau référencé par
l'argument correspondant. Le tableau doit être suffisamment grand pour stocker les caractères
saisis.
224 Heure
13

Un caractère nul est automatiquement ajouté au tableau après la lecture de la chaîne.


Notez qu'avec scanf(), contrairement à printf(), vous devez passer des pointeurs à
vos arguments pour que la fonction scanf() puisse modifier leurs valeurs.
Le programme du Listing 13.5 montre comment utiliser différents spécificateurs de format avec la fonction
la fonction scanf().

LISTE 13.5 Utilisation de la fonction scanf() avec divers spécificateurs de format


1 : /* 13L05.c : Utilisation
de scanf() */ 2 : #include
<stdio.h>
3 :
4 : main()
5 : {
6 : char str[80] ;
7 : int x, y ;
8 : float z ;
9 :
10 : printf("Enter two integers separated by a space:\n")
; 11: scanf("%d %d", &x, &y) ;
12 : printf("Enter a floating-point number:\n")
; 13: scanf("%f", &z) ;
14 : printf("Enter a string:\n")
; 15: scanf("%s", str) ;
16 : printf("Voici ce que vous avez saisi:\N")
; 17 : printf("%d %d\n%f\n%s\n", x, y, z, str)
; 18: return 0 ;
19 : }

La sortie suivante s'affiche à l'écran après que j'ai lancé l'exécutable 13L05.exe et saisi
des données (qui apparaissent en gras) à l'aide de mon clavier :
Entrez deux nombres entiers séparés par un espace :
SORTIE 10 12345
Saisir un nombre à virgule flottante :
1.234567
Saisissez une chaîne de caractères :
Test
Voici ce que vous avez saisi :
10 12345
1.234567
Test

Dans le Listing 13.5, il y a un tableau de caractères (str), deux variables int (x et y) et une
ANALYSE variable
variable float déclarée aux lignes 6-8.
Manipuler des 225
chaînes de caractères

Ensuite, la fonction scanf() de la ligne 11 lit deux entiers entrés par l'utilisateur et les
enregistre dans les emplacements mémoire réservés aux variables entières x et y.
L'opérateur address-of est utilisé pour obtenir les adresses mémoire des variables.
L'instruction de la ligne 13 lit et enregistre un nombre à virgule flottante dans z. Notez
que les spécificateurs de format, %d et
%f, sont utilisés pour spécifier les formats appropriés pour les nombres saisis dans les lignes 11 et 13.

La ligne 15 utilise la fonction scanf() et le specificateur de format %s pour lire une


serie de caracteres entrees par l'utilisateur, puis enregistre les caracteres (plus un
caracteres nul comme termi- nateur) dans le tableau pointe par str. L'opérateur adresse-
de n'est pas utilisé ici, puisque str pointe lui-même vers l'adresse de départ du tableau.
Pour prouver que la fonction scanf() lit tous les nombres et caractères saisis par
l'utilisateur, la fonction printf() de la ligne 17 affiche à l'écran le contenu sauvegardé
dans x, y, z et str. Le résultat montre bien que la fonction scanf() a fait son travail.
Il faut savoir que la fonction scanf() ne commence pas à lire les données saisies tant
que la touche Entrée n'est pas enfoncée. Les données saisies au clavier sont placées dans
un tampon d'entrée. Lorsque la touche Entrée est enfoncée, la fonction scanf()
recherche ses données dans la mémoire tampon. Vous en apprendrez plus sur l'entrée et
la sortie en mémoire tampon dans l'Heure 21,
"Lire et écrire avec des fichiers".

Résumé
Dans cette leçon, vous avez appris les fonctions et concepts importants suivants
concernant les chaînes de caractères en C :

• Une chaîne est un tableau de caractères dont la fin est marquée par un caractère nul.
• Une constante de chaîne est une série de caractères entre guillemets.
• Le compilateur C ajoute automatiquement un caractère nul à une constante de
chaîne qui a été utilisée pour initialiser un tableau.
• Il n'est pas possible d'affecter une constante de chaîne à un pointeur de caractère déréférencé.
• La fonction strlen() permet de mesurer la longueur d'une chaîne de caractères.
13
Cette fonction ne compte pas le caractère nul.
• Vous pouvez copier une chaîne de caractères d'un tableau à un autre en appelant la fonction C
strcpy().

• La fonction gets() peut être utilisée pour lire une série de caractères. Cette
fonction arrête la lecture lorsque le caractère de nouvelle ligne ou de fin de fichier
(EOF) est rencontré. La fonction ajoute un caractère nul à la fin de la chaîne.
226 Heure
13

• La fonction puts() envoie tous les caractères, à l'exception du caractère nul,


d'une chaîne à la sortie (stdout) et ajoute un caractère de retour à la ligne à
la sortie.
• Vous pouvez lire différents éléments de données avec la fonction scanf() en
utilisant divers spécificateurs de for- mat.
Dans la prochaine leçon, vous découvrirez les concepts de portée et de stockage en C.

Q&R
Q Qu'est-ce qu'une corde ? Comment connaître sa longueur ?
A En C, une chaîne de caractères est stockée dans un tableau de caractères et se
termine par un caractère nul ('\0'). Le caractère nul indique aux fonctions de
chaîne de caractères (telles que puts() et strcpy) qu'elles ont atteint la fin de
la chaîne.
La fonction C strlen() peut être utilisée pour mesurer la longueur d'une chaîne
de caractères. Si elle réussit, la fonction strlen() renvoie le nombre total d'octets
pris par la chaîne ; toutefois, le caractère nul de la chaîne n'est pas compté.
Q Quelles sont les principales différences entre une constante de chaîne et une
constante de caractère ?
Une constante de chaîne est une série de caractères entourés de guillemets doubles,
tandis qu'une constante de caractère est un caractère unique entouré de guillemets
simples. Le compilateur ajoute un caractère nul à la chaîne lorsqu'elle est utilisée
pour initialiser un tableau.
Par conséquent, un octet supplémentaire doit être réservé pour le caractère
nul. D'autre part, une constante de caractère ne prend qu'un octet en mémoire
et n'est pas stockée dans un tableau.
Q La fonction gets() enregistre-t-elle le caractère "newline" du flux d'entrée
standard ?
La fonction gets() continue de lire les caractères du flux d'entrée standard jusqu'à ce
qu'un caractère de nouvelle ligne ou de fin de fichier soit rencontré. Au lieu
d'enregistrer le caractère de retour à la ligne, la fonction gets() ajoute un
caractère nul à la chaîne et le stocke dans le tableau référencé par l'argument de la
fonction gets().
Q Quels types de données la fonction scanf() peut-elle lire ?
R En fonction des spécificateurs de format de type printf() que vous transmettez à
la fonction, scanf() peut lire différents types de données, tels qu'une série de
caractères, des nombres entiers ou des nombres à virgule flottante. Contrairement
à gets(), scanf() arrête la lecture de l'élément d'entrée actuel (et passe à
l'élément d'entrée suivant, le cas échéant) lorsqu'il rencontre un espace, une
nouvelle ligne, une tabulation, une tabulation verticale ou un saut de page.
Manipuler des 227
chaînes de caractères

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Dans la liste suivante, quelles sont les affirmations légales ?
• char str1[5] = "Texas" ;

• char str2[] = "Une chaîne de caractères" ;


• char str3[2] = "A" ;

• char str4[2] = "TX" ;

2. Etant donné une variable de pointeur char ptr_ch, les déclarations suivantes sont-elles légales ?
• *ptr_ch = 'a' ;

• ptr_ch = "Une chaîne de caractères" ;


• ptr_ch = 'x' ;

• *ptr_ch = "Ceci est le Quiz 2." ;

3. La fonction puts() peut-elle imprimer le caractère nul dans un tableau de caractères ?


4. Quel spécificateur de format utilisez-vous avec la fonction scanf() pour lire
une chaîne de caractères et lequel utilisez-vous pour lire un nombre à virgule
flottante ?

Exercices
1. Étant donné un tableau de caractères dans la déclaration suivante,
char str1[] = "Ceci est l'exercice 1" ;

écrire un programme pour copier la chaîne de caractères de str1 dans un autre tableau, appelé str2.
2. Écrivez un programme pour mesurer la longueur d'une chaîne de caractères en
13
évaluant les éléments d'un tableau de caractères un par un jusqu'à ce que vous
atteigniez le caractère null. Pour prouver que vous obtenez le bon résultat, vous
pouvez utiliser la fonction strlen() pour mesurer à nouveau la même chaîne.
3. Réécrivez le programme de la liste 13.4. Cette fois, convertissez tous les
caractères majuscules en caractères minuscules.
4. Ecrivez un programme qui utilise la fonction scanf() pour lire deux entiers
entrés par l'utilisateur, additionner les deux entiers et imprimer la somme à
l'écran.
HEURE 14
Comprendre la portée
et les classes de
stockage
Personne ne possède rien et tout ce que chacun possède, c'est l'usage
de ses biens présumés.
-P. Wylie
Dans les heures précédentes, vous avez appris à déclarer des variables de
différents types de données, ainsi qu'à initialiser et à utiliser ces variables. On
a supposé que vous pouviez accéder aux variables de n'importe où. La
question qui se pose maintenant est la suivante : pouvez-vous déclarer des
variables qui ne sont accessibles qu'à certaines parties d'un programme ?
Dans cette leçon, vous découvrirez la portée et les classes de stockage des
données en C. Les principaux sujets abordés dans cette leçon sont les
suivants
• Champ d'application du bloc
• Champ d'application de la fonction
• Champ d'application du dossier
• Champ d'application du programme
224 Heure
14

• Le spécificateur auto
• Le spécificateur statique
• Le spécificateur de registre
• Le spécificateur extern
• Le modificateur const
• Le modificateur volatile

Cacher des données


Pour résoudre un problème complexe dans la pratique, le programmeur divise
normalement le problème en plusieurs parties et traite chaque partie du problème en
écrivant une ou deux fonctions (ou routines). Ensuite, toutes les fonctions sont
rassemblées pour former un programme complet qui peut être utilisé pour résoudre le
problème complexe.
Dans le programme complet, il peut y avoir des variables qui doivent être partagées par
toutes les fonctions. D'autre part, l'utilisation de certaines autres variables peut être
limitée à certaines fonctions seulement. En d'autres termes, la visibilité de ces variables
est limitée et les valeurs affectées à ces variables sont cachées à de nombreuses fonctions.
La limitation de la portée des variables est très utile lorsque plusieurs programmeurs
travaillent sur différentes parties d'un même programme. S'ils limitent la portée de leurs
variables à leurs parties de code, ils n'ont pas à s'inquiéter des conflits avec les variables
du même nom utilisées par d'autres dans d'autres parties du programme.
En C, vous pouvez déclarer une variable et indiquer son niveau de visibilité en désignant
sa portée. Ainsi, les variables de portée locale ne sont accessibles qu'à l'intérieur du bloc
dans lequel elles sont déclarées.
Les sections suivantes vous apprennent à déclarer des variables de différentes portées.

Champ d'application du bloc


Dans cette section, un bloc fait référence à tout ensemble d'instructions entourées
d'accolades ({ et }). Une variable déclarée dans un bloc a la portée du bloc. Ainsi, la
variable est active et accessible depuis son point de déclaration jusqu'à la fin du bloc.
Parfois, la portée du bloc est également appelée portée locale.
Par exemple, la variable i déclarée dans le bloc de la fonction principale suivante a
une portée de bloc :
int main()
{
int i ; /* champ d'application du bloc */
Comprendre la portée et les classes de 225
stockage

.
.
.
retour 0 ;
}

En général, une variable ayant la portée d'un bloc est appelée variable locale. Notez que
les variables locales doivent être déclarées au début du bloc, avant les autres instructions.

Portée des blocs imbriqués


Vous pouvez également déclarer des variables à l'intérieur d'un bloc imbriqué. Si une
variable déclarée dans le bloc externe partage le même nom que l'une des variables du
bloc interne, la variable du bloc externe est masquée par celle du bloc interne. Ceci est
vrai pour la portée du bloc intérieur.
La liste 14.1 montre un exemple de portée des variables dans les blocs imbriqués.

TYPE LISTE 14.1 Impression de variables avec différents niveaux de portée


1 : /* 14L01.c : Scopes dans un bloc
imbriqué */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6: int i = 32; /* block scope
1*/ 7 :
8 : printf("Within the outer block : i=%d\n",
i) ; 9 :
10 : { /* le début du bloc intérieur */
11: int i, j; /* bloc scope 2, int i cache l'int i
extérieur*/ 12 :
13 : printf("Within the inner block:\n")
; 14: for (i=0, j=10 ; i<=10 ; i++, j--
)
15 : printf("i=%2d, j=%2d\n", i, j)
; 16 : }/* fin du bloc interne */
17 : printf("Within the outer block : i=%d\n",
i) ; 18: return 0 ;
19 : }

La sortie suivante s'affiche à l'écran après la création et l'exécution de l'exécutable


(14L01.exe) du programme de la liste 14.1 :
Dans le bloc extérieur : i=32 14
SORTIE Dans le bloc intérieur :
i= 0, j=10
i= 1, j= 9
i= 2, j= 8
226 Heure
14

i= 3, j= 7
i= 4, j= 6
i= 5, j= 5
i= 6, j= 4
i= 7, j= 3
i= 8, j= 2
i= 9, j= 1
i=10, j= 0
Dans le bloc extérieur : i=32

L'objectif du programme de l'illustration 14.1 est de vous montrer les


ANALYSE
différentes portées des variables dans les blocs imbriqués. Comme vous pouvez
le voir, il y a deux blocs imbriqués dans
Liste 14.1. La variable entière i déclarée à la ligne 6 est visible dans le bloc extérieur
délimité par les accolades ({ et }) aux lignes 5 et 19. Deux autres variables entières, i et
j, sont déclarées à la ligne 11 et ne sont visibles que dans le bloc intérieur, de la ligne
10 à la ligne 16.
Bien que la variable entière i du bloc extérieur porte le même nom que l'une des
variables entières du bloc intérieur, les deux variables entières ne sont pas accessibles
en même temps en raison de leurs portées différentes.
Pour le prouver, la ligne 8 imprime pour la première fois la valeur 32 contenue dans i à
l'intérieur du bloc extérieur. Ensuite, la boucle for des lignes 14 et 15 affiche 10 paires
de valeurs attribuées à i et j dans le bloc intérieur. À ce stade, rien n'indique que la
variable entière i dans le bloc externe ait un quelconque effet sur celle du bloc interne.
Lorsque le bloc interne est quitté, les variables qu'il contient ne sont plus accessibles. En
d'autres termes, toute tentative d'accès à j à partir du bloc extérieur serait illégale.
Enfin, l'instruction de la ligne 17 imprime à nouveau la valeur de i dans le bloc
extérieur afin de déterminer si la valeur a été modifiée par la variable entière i dans le
bloc intérieur. Le résultat montre que ces deux variables entières se cachent l'une l'autre
et qu'il n'y a pas de conflit.

Champ d'application de la fonction


La portée d'une fonction indique qu'une variable est active et visible du début à la fin
d'une fonction.
En C, seule une étiquette goto a une portée de fonction. Par exemple, l'étiquette goto,
start, présentée dans la portion de code suivante a une portée de fonction :
int main()
{
int i ; /* champ d'application du bloc */
.
.
.
début : /* Une étiquette goto a une portée fonctionnelle */
Comprendre la portée et les classes de 227
stockage

.
.
.
goto start ; /* l'instruction goto */
.
.
.
retour 0 ;
}

Ici, le début de l'étiquette est visible du début à la fin de la fonction main(). Par
conséquent, il ne doit pas y avoir plus d'une étiquette portant le même nom dans la
fonction main().

Champ d'application du programme


On dit qu'une variable a une portée de programme lorsqu'elle est déclarée en dehors
d'une fonction. Par exemple, regardez le code suivant :
int x = 0 ; /* portée du programme
*/ float y = 0.0 ; /* portée du
programme */ int main()
{
int i ; /* champ d'application du bloc */
.
.
.
retour 0 ;
}

Ici, la variable int x et la variable float y ont la portée du programme.


Les variables ayant une portée de programme sont également appelées variables
globales. Elles sont visibles dans tous les fichiers sources qui composent un programme
exécutable. Notez qu'une variable globale est déclarée avec un initialisateur en dehors
d'une fonction.
Le programme de la liste 14.2 illustre la relation entre les variables de portée pro-
gramme et les variables de portée bloc.

LISTE 14.2 La relation entre la portée du programme et la portée


TYPE du bloc
1 : /* 14L02.c : Portée du programme vs portée
du bloc */ 2 : #include <stdio.h> 14
3 :
4 : int x = 1234; /* champ
d'application du programme */ 5 : double y =
1.234567 ; /* champ d'application du
programme */

continue
228 Heure
14

LISTE 14.2 suite


6 :
7 : void function_1()
8 : {
9 : printf("From function_1:\n x=%d, y=%f\n", x, y)
; 10 : }
11 :
12 : main()
13 : {
14: int x = 4321; /* block scope
1*/ 15 :
16 : function_1() ;
17 : printf("Within the main block:\n x=%d, y=%f\n", x, y)
; 18: /* un bloc imbriqué */
19 : {
20 : double y = 7.654321 ; /* block scope 2
*/ 21 : function_1() ;
22 : printf("Within the nested block:\n x=%d, y=%f\n", x, y)
; 23 : }
24 : retour 0 ;
25 : }

La sortie suivante s'affiche à l'écran après que l'exécutable 14L02.exe a été créé et
exécuté sur ma machine :
De la fonction_1 :
SORTIE x=1234, y=1.234567
A l'intérieur du bloc
principal :
x=4321, y=1.234567
De la fonction_1
:
x=1234, y=1.234567
A l'intérieur du bloc
imbriqué :
x=4321, y=7.654321

Comme vous pouvez le voir dans le Listing 14.2, il y a deux variables globales, x
ANALYSE et y, avec une portée pro- gramme ; elles sont déclarées aux lignes 4 et 5.

Aux lignes 7 à 10, une fonction, appelée function_1(), est déclarée. (Plus de détails sur
les déclarations de fonctions et les prototypes sont enseignés dans l'heure suivante). La
fonction function_1() ne contient qu'une seule instruction ; elle imprime les valeurs
détenues par x et y. Comme il n'y a pas de déclaration de variable pour x ou y dans le
bloc de fonction, les valeurs des variables globales x et y sont utilisées pour l'instruction
à l'intérieur de la fonction. Pour le prouver, la fonction_1() est appelée deux fois aux
lignes 16 et 21, respectivement, à partir de deux blocs imbriqués.
La sortie montre que les valeurs des deux variables globales x et y sont transmises à
printf() dans le corps de la fonction function_1().
Comprendre la portée et les classes de 229
stockage
Ensuite, la ligne 14 déclare une autre variable entière, x, à portée de bloc, qui peut
remplacer la variable globale x dans le bloc de la fonction main(). Le résultat obtenu
par la fonction
230 Heure
14

à la ligne 17 montre que la valeur de x est la valeur de la variable locale x avec la


portée du bloc, tandis que la valeur de y est toujours celle de la variable globale y.
Les lignes 19 et 23 contiennent un bloc imbriqué dans lequel une autre variable double
y, avec une portée de bloc, est déclarée et initialisée. Comme la variable x dans le bloc
main(), cette variable, y, dans le bloc imbriqué remplace la variable globale y.
L'instruction de la ligne 22 affiche les valeurs des variables locales x et y à l'écran

Comme une variable globale est visible dans les différents fichiers source
d'un programme, l'utilisation de variables globales augmente la complexité de
votre programme, ce qui le rend difficile à maintenir ou à déboguer. En
général, il n'est pas recommandé de déclarer et d'utiliser des variables
globales, sauf en cas d'absolue nécessité. Par exemple, vous pouvez
déclarer une variable globale dont la valeur est utilisée (mais jamais
modifiée) par plusieurs sous-programmes de votre programme. (Dans l'heure
23, "Compiler des programmes : Le préprocesseur C", vous apprendrez à
utiliser la directive #define pour définir des constantes qui sont utilisées à
plusieurs endroits dans un programme).

Avant de présenter l'étendue des fichiers, permettez-moi de parler des spécificateurs de classe de stockage.

Les spécificateurs de classe de stockage


En C, la classe de stockage d'une variable fait référence à la combinaison de ses régions
spatiales et temporelles.
Vous avez appris ce qu'est la portée, qui spécifie la région spatiale d'une variable. Nous
allons maintenant nous intéresser à la durée, qui indique la région temporelle d'une
variable.
Quatre spécificateurs et deux modificateurs peuvent être utilisés pour indiquer la durée d'une
variable. Ces spécificateurs et modificateurs sont présentés dans les sections suivantes.

Le spécificateur automatique
Le spécificateur auto est utilisé pour indiquer que l'emplacement en mémoire d'une
variable est temporaire. En d'autres termes, l'espace réservé d'une variable dans la
mémoire peut être effacé ou déplacé lorsque la variable est hors de sa portée.
Seules les variables ayant une portée de bloc peuvent être déclarées avec le spécificateur 14
auto. Le mot-clé auto est toutefois rarement utilisé dans la pratique, car la durée d'une
variable ayant une portée de bloc est temporaire par défaut.
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

230 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
14

Le spécificateur statique
Le spécificateur statique, quant à lui, peut être appliqué à des variables ayant une portée
de bloc ou de programme. Lorsqu'une variable au sein d'une fonction est déclarée avec le
spécificateur statique, la variable a une durée permanente. En d'autres termes, la
mémoire allouée à la variable n'est pas détruite lorsque l'on quitte la portée de la variable,
la valeur de la variable est maintenue en dehors de la portée. Si l'exécution revient dans la
portée de la variable, la dernière valeur stockée dans la variable s'y trouve toujours.
Par exemple, dans la portion de code suivante :
int main()
{
int i; /* portée du bloc et durée temporaire */
static int j ; /* portée du bloc et durée permanente
*/
.
.
retour 0 ;
}

la variable entière i a une durée temporaire (auto) par défaut. Mais l'autre variable
entière, j, a une durée permanente en raison du spécificateur de classe de stockage
static.

Le programme de la liste 14.3 montre l'effet du spécificateur statique sur les variables.

TYPE LISTE 14.3 Utilisation du spécificateur statique


1 : /* 14L03.c : Utilisation du spécificateur
statique */ 2 : #include <stdio.h>
3 : /* la fonction add_two
*/ 4 : int add_two(int x,
int y) 5 : {
6 : statique int counter = 1 ;
7 :
8 : printf("This is the function call of %d,\n", counter++)
; 9: return (x + y) ;
10 : }
11 : /* la fonction
principale */ 12 : main()
13 : {
14 : int i, j
;
15 :
16 : for (i=0, j=5 ; i<5 ; i++, j--)
17 : printf("l'addition de %d et %d est %d.\n\n",
18: i, j, add_two(i, j)) ;
19 : retour 0 ;
20 : }
Comprendre la portée et les classes de 231
stockage

La sortie suivante s'affiche à l'écran après l'exécution de l'exécutable (14L03.exe) :


Il s'agit de l'appel de fonction
SORTIE de 1, l'addition de 0 et de 5
donne 5.

Il s'agit de l'appel de fonction


de 2, l'addition de 1 et 4
donne 5.

Il s'agit de l'appel de fonction


de 3, l'addition de 2 et 3
donne 5.

Il s'agit de l'appel de fonction


de 4, l'addition de 3 et 2
donne 5.

C'est l'appel de fonction de 5,


l'addition de 4 et de 1 donne
5.

Le but du programme de la liste 14.3 est d'appeler une fonction pour additionner
ANALYSE
deux entiers, puis d'imprimer à l'écran le résultat retourné par la fonction. Le
programme
est appelée plusieurs fois. Un compteur est mis en place pour garder une trace du nombre
de fois que la fonction a été appelée.
Cette fonction, appelée add_two(), est déclarée aux lignes 4 à 10. Deux arguments int, x
et y, sont passés à la fonction, et l'addition des deux arguments est renvoyée à la ligne 9.
Notez qu'il existe une variable entière, counter, qui est déclarée avec le spécificateur
static à la ligne 6. Les valeurs stockées par counter sont conservées car la durée de la
variable est permanente. En d'autres termes, bien que la portée de counter soit à
l'intérieur du bloc de la fonction add_two(), l'emplacement mémoire de counter et la
valeur enregistrée dans cet emplacement ne sont pas modifiés après l'appel de la fonction
add_two() et le retour du contrôle d'exécution à la fonction main(). Notez que
l'initialisation du compteur à 1 n'a lieu que lors du premier appel de la fonction
add_two() ; par la suite, il conserve sa valeur précédente chaque fois que la fonction est
appelée.
Par conséquent, la variable counter est utilisée comme un compteur pour suivre le
nombre d'appels reçus par la fonction add_two(). En fait, la fonction printf() de la
ligne 8 imprime la valeur sauvegardée par la variable counter chaque fois que la
fonction add_two() est appelée. De plus, le compteur est incrémenté d'une unité à
chaque fois que la fonction printf() est exécutée.
La boucle for, déclarée aux lignes 16-18 de la fonction main(), appelle cinq fois la
fonction add_two(). Les valeurs des deux variables entières, i et j, sont transmises à
la fonction add_two() où elles sont additionnées. Ensuite, la valeur de retour de la
14
232 Heure
fonction add_two() 14l'appel à printf() aux lignes 17 et 18.
est affichée à l'écran par
La sortie montre que la valeur enregistrée par counter est effectivement incrémentée
d'une unité chaque fois que la fonction add_two() est appelée, et qu'elle est conservée
après la sortie de la fonction parce que la variable integer counter est déclarée avec
static. Vous pouvez voir que le compteur n'est initialisé à 1 qu'une seule fois, lorsque
la fonction add_two() est appelée pour la première fois.
Comprendre la portée et les classes de 233
stockage

Champ d'application du dossier et hiérarchie des champs


d'application
Dans la première partie de cette heure, j'ai mentionné trois des quatre types de portée : la
portée de bloc, la portée de fonction et la portée de programme. Il est temps maintenant
de présenter le quatrième type de portée : la portée de fichier.
En C, une variable globale déclarée avec le spécificateur static est dite avoir une portée
de fichier. Une variable ayant une portée de fichier est visible depuis son point de
déclaration jusqu'à la fin du fichier. Ici, le fichier fait référence au fichier de programme
qui contient le code source. La plupart des grands programmes sont constitués de
plusieurs fichiers de programme.
La partie suivante du code source montre les variables avec la portée du fichier :
int x = 0; /* portée du
programme */static int y = 0 ;
portée du fichier */
staticfloat z = 0.0 ; /* portée du
fichier */ int main()
{
int i ; /* champ d'application du bloc */
.
.
.
retour 0 ;
}

Ici, la variable int y et la variable float z ont toutes deux la portée d'un fichier.
La figure 14.1 présente la hiérarchie des quatre portées. Comme vous pouvez le constater,
une variable de portée bloc est la plus limitée et n'est pas visible en dehors du bloc dans
lequel la variable est déclarée. En revanche, une variable de portée programme est visible
dans tous les fichiers, fonctions et autres blocs qui composent le programme.

FIGURE 14.1 Champ d'application du programme


La hiérarchie des
quatre champs Champ
d'application. d'applicatio
n du
dossier

Bloc
Cham
p
234 Heure
14

Le spécificateur de registre
Le mot registre est emprunté à la terminologie du matériel informatique. Chaque
ordinateur dispose d'un certain nombre de registres pour contenir des données et effectuer
des calculs arithmétiques ou logiques. Comme les registres sont situés à l'intérieur de la
puce de l'unité centrale de traitement (CPU), il est beaucoup plus rapide d'accéder à un
registre qu'à un emplacement de mémoire qui se trouve à l'extérieur de la puce.
Par conséquent, le stockage des variables dans des registres peut contribuer à accélérer votre programme.
Le langage C vous fournit le spécificateur de registre. Vous pouvez appliquer ce
spécificateur aux variables lorsque vous pensez qu'il est nécessaire de placer les variables dans
les registres de l'ordinateur.
Cependant, le spécificateur de registre ne donne au compilateur qu'une suggestion. En
d'autres termes, une variable spécifiée par le mot-clé register n'est pas garantie d'être
stockée dans un registre. Le compilateur peut ignorer la suggestion s'il n'y a pas de
registre disponible ou si d'autres restrictions s'appliquent.
Il est illégal de prendre l'adresse d'une variable déclarée avec le spécificateur de
registre parce que la variable est destinée à être stockée dans un registre, et non dans la
mémoire. Un registre du processeur n'a pas d'adresse mémoire à laquelle vous pouvez
accéder.
Dans la portion de code suivante, la variable entière i est déclarée avec le registre
spécificateur :
int main()
{
/* champ d'application du bloc avec le
spécificateur de registre */ registre int i ;
. . .
for (i=0 ; i<MAX_NUM ; i++){
/* quelques déclarations */
}
. . .
retour 0 ;
}

La déclaration de i suggère que le compilateur stocke la variable dans un registre. Étant


donné que i est utilisé de manière intensive dans la boucle for, le stockage de i dans un
registre pourrait augmenter la vitesse du code présenté ici.

Le spécificateur externe
Comme indiqué dans la section intitulée "Portée du programme", plus tôt dans cette
heure, une variable ayant une portée de programme est visible dans tous les fichiers 14
source qui composent un programme exécutable. Une variable ayant une portée de
programme est également appelée variable globale.
Comprendre la portée et les classes de 235
Voici une questionstockage
: Comment une variable globale déclarée dans le fichier A peut-elle
être vue dans le fichier B ? En d'autres termes, comment le compilateur peut-il savoir
que la variable utilisée dans le fichier B est en fait la même que celle déclarée dans le
fichier A ?
236 Heure
14

La solution consiste à utiliser le spécificateur extern fourni par le langage C pour faire
allusion à une variable globale définie ailleurs. Dans ce cas, vous déclarez une variable
globale dans le fichier A, puis vous déclarez à nouveau la variable en utilisant le
spécificateur extern dans le fichier B. Il ne s'agit pas d'une déclaration séparée, mais
d'une spécification de la déclaration originale dans le fichier A.
Par exemple, supposons que vous ayez deux variables globales int, y et z, définies dans
un fichier, et que, dans un autre fichier, vous ayez les déclarations suivantes :
int x = 0 ; /* une variable globale */
extern int y; /* une allusion à une variable globale
y */ int main()
{
extern int z ; /* une allusion à une variable
globale z */int i ; une variable locale */
.
.
.
retour 0 ;
}

Comme vous pouvez le voir, deux variables entières, y et z, sont déclarées avec le
spécificateur extern, à la fois à l'extérieur et à l'intérieur de la fonction main(),
respectivement. Lorsque le compilateur voit les deux déclarations, il sait qu'il s'agit en
fait d'allusions aux variables globales y et z définies ailleurs.

Pour que votre programme soit portable sur différentes plates-formes


informatiques, vous pouvez appliquer les règles suivantes dans votre
programme lorsque vous déclarez des variables globales ou que vous y
faites allusion :
• Vous pouvez ignorer le spécificateur extern, mais inclure un
initialisateur, lorsque vous déclarez une variable globale.
• Vous devez utiliser le spécificateur extern (sans initialisateur)
lorsque vous faites allusion à une variable globale définie ailleurs.

Les modificateurs de la classe de stockage


Outre les quatre spécificateurs de classe de stockage présentés dans les sections
précédentes, le langage C fournit également deux modificateurs de classe de stockage
(ou qualificateurs, comme on les appelle parfois) que vous pouvez utiliser pour indiquer
au compilateur C comment les variables peuvent être accédées.

Le modificateur const
Si vous déclarez une variable avec le modificateur const, le contenu de la variable ne
peut pas être modifié après son initialisation.
Comprendre la portée et les classes de 237
stockage

Par exemple, l'expression suivante indique au compilateur que circle_ratio est une
variable dont la valeur ne doit pas être modifiée :
const double circle_ratio = 3.141593 ;

De même, la valeur du tableau de caractères str déclaré dans l'instruction suivante ne


peut pas non plus être modifiée :
const char str[] = "Une constante de chaîne" ;

Il est donc illégal d'agir de la sorte :


str[0] = 'a' ; /* Ce n'est pas autorisé ici. */

En outre, vous pouvez déclarer une variable de pointeur avec le modificateur const afin
que l'objet pointé par le pointeur ne puisse pas être modifié. Par exemple, considérons la
déclaration de pointeur suivante avec le modificateur const :
char const *ptr_str = "Une constante de chaîne" ;

Après l'initialisation, vous ne pouvez pas modifier le contenu de la chaîne pointée par le pointeur
ptr_str. Par exemple, l'instruction suivante n'est pas autorisée :

*ptr_str = 'a' ; /* Ce n'est pas autorisé ici. */

Cependant, le pointeur ptr_str lui-même peut se voir attribuer une adresse différente d'une
chaîne de caractères déclarée avec char const.

Le modificateur volatil
Parfois, vous souhaitez déclarer une variable dont la valeur peut être modifiée sans
déclaration d'affectation explicite dans votre programme. C'est particulièrement vrai
lorsque vous traitez directement avec le matériel. Par exemple, vous pouvez déclarer une
variable globale qui contient les caractères saisis par l'utilisateur. L'adresse de la variable
est transmise à un registre de périphérique qui accepte les caractères du clavier.
Cependant, lorsque le compilateur C opti- mise automatiquement votre programme, il a
l'intention de ne pas mettre à jour la valeur détenue par la variable à moins que celle-ci ne
se trouve du côté gauche d'un opérateur d'affectation (=). En d'autres termes, la valeur de
la variable ne sera probablement pas modifiée même si l'utilisateur tape des caractères au
clavier.
Pour demander au compilateur de désactiver certaines optimisations sur une variable,
vous pouvez déclarer la variable avec le spécificateur volatile. Par exemple, dans la
portion de code suivante, une variante, keyboard_ch, déclarée avec le spécificateur 14
volatile, indique au compilateur de ne pas optimiser les expressions de la variable parce
que la valeur enregistrée par la variable peut être modifiée sans l'exécution d'une
instruction d'affectation explicite :
void read_keyboard()
{
238 Heure
14

volatile char keyboard_ch ; /* une variable volatile */


.
.
.
}

Résumé
Dans cette leçon, vous avez appris les concepts importants suivants sur les scopes et les
classes de stockage en C :
• Une variable déclarée à l'intérieur d'un bloc a la portée du bloc. Une telle variable
est également appelée variable locale et n'est visible qu'à l'intérieur du bloc.
• Une étiquette goto a une portée fonctionnelle, ce qui signifie qu'elle est visible
dans tout le bloc de la fonction dans laquelle l'étiquette est placée. Il n'y a pas
deux étiquettes goto qui portent le même nom dans un bloc de fonction.
• Une variable déclarée avec le spécificateur static en dehors d'une fonction a une
portée de fichier, ce qui signifie qu'elle est visible dans l'ensemble du fichier source
dans lequel la variable est déclarée.
• Une variable déclarée en dehors d'une fonction est dite avoir une portée de
programme. Une telle variable est également appelée variable globale. Une
variable globale est visible dans tous les fichiers source qui composent un
programme exécutable.
• Une variable ayant la portée d'un bloc a la visibilité la plus limitée. En revanche,
une variable ayant la portée d'un programme est la plus visible et peut être vue à
travers tous les fichiers, fonctions et autres blocs qui composent le programme.
• La classe de stockage d'une variable fait référence à la combinaison de ses régions
spatiales et temporelles (c'est-à-dire sa portée et sa durée).
• Par défaut, une variable à portée de bloc a une durée automatique et son stockage
en mémoire est temporaire.
• Une variable déclarée avec le spécificateur static est stockée en mémoire de
façon permanente, même après que la fonction dans laquelle la variable est
déclarée a été appelée et que la portée de la fonction a été quittée.
• Une variable déclarée avec le spécificateur de registre peut être stockée dans
un registre pour accélérer les performances d'un programme ; cependant, le
compilateur peut ignorer le spécificateur s'il n'y a pas de registre disponible ou
si d'autres restrictions s'appliquent.
• Vous pouvez également faire allusion à une variable globale définie ailleurs en utilisant l'option
extern
à partir du fichier source actuel.
Comprendre la portée et les classes de 239
stockage

• Pour s'assurer que la valeur enregistrée par une variable ne peut pas être modifiée,
vous pouvez déclarer la variable avec le modificateur const.
• Si vous souhaitez indiquer au compilateur que la valeur d'une variable peut être
modifiée sans déclaration d'affectation explicite, déclarez la variable avec le
modificateur volatile afin que le compilateur désactive les optimisations sur les
expressions impliquant la variable.
Dans la prochaine leçon, vous apprendrez ce que sont les déclarations et les prototypes de fonctions en C.

Q&R
Q Une variable globale peut-elle être cachée par une variable locale ayant une portée de bloc ?
R Oui. Si une variable locale partage le même nom qu'une variable globale, la variable
globale peut être cachée par la variable locale pour la portée du bloc dans lequel la
variable locale est définie avec la portée du bloc. Toutefois, en dehors du bloc, la
variable locale ne peut être vue, mais la variable globale redevient visible.
Q Pourquoi avez-vous besoin du spécificateur statique ?
R Dans de nombreux cas, la valeur d'une variable est nécessaire, même si la portée du
bloc dans lequel la variable est déclarée est terminée. Par défaut, une variable ayant
la portée d'un bloc a une mémoire temporaire, c'est-à-dire que la durée de vie de la
variable commence lorsque le bloc est exécuté et que la variable est déclarée, et se
termine lorsque l'exécution de ce bloc est terminée. Par conséquent, pour déclarer
une variable avec une durée permanente, vous devez utiliser le spécificateur static
pour indiquer au compilateur que l'emplacement mémoire de la variable et la valeur
stockée dans l'emplacement mémoire doivent être conservés après l'exécution du
bloc.
Q L'utilisation du spécificateur de registre garantit-elle l'amélioration des
performances d'un programme ?
R Pas vraiment. La déclaration d'une variable avec le spécificateur de registre ne
fait que suggérer au compilateur que la variable doit être stockée dans un registre.
Mais il n'y a aucune garantie que la variable sera stockée dans un registre. Le
compilateur peut ignorer la demande en fonction de la disponibilité des registres ou
d'autres restrictions.
Q Lorsque vous déclarez une variable avec le spécificateur extern, définissez-
vous la variable ou faites-vous allusion à une variable globale ailleurs ?
R Lorsqu'une variable est déclarée avec le spécificateur extern, le compilateur
considère la déclaration de la variable comme une allusion plutôt que comme une
14
définition. Le compilateur cherchera donc ailleurs une variable globale à laquelle la
variable avec extern fait allusion.
240 Heure
14

Atelier
Afin de consolider votre compréhension de cette leçon, nous vous encourageons à
répondre aux questions du quiz et à terminer les exercices proposés dans l'atelier avant de
passer à la leçon suivante. Les réponses et les conseils aux questions et aux exercices sont
donnés dans l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Dans la portion de code suivante, quelles sont les variables globales et quelles
sont les variables locales avec une portée de bloc ?
int x = 0 ;
float y = 0.0
;
int myFunction()
{
int i, j
; float
y ;
. . .
{
int x, y ;
. . .
}
. . .
}

2. Lorsque deux variables portant le même nom sont définies, comment le


compilateur sait-il laquelle utiliser ?
3. Identifiez la classe de stockage de chaque déclaration dans la portion de code suivante :
int i = 0 ;
static int x ;
extern float y
; int
myFunction()
{
int i, j ;
extern float z
;
register long s ;
static int index ;
const char str[] = "Message d'avertissement" ;
. . .
}

4. Etant donné la déclaration suivante :


const char ch_str[] = "Le spécificateur const" ;
L'instruction ch_str[9] = '-' ; est-elle légale ?
Comprendre la portée et les classes de 241
stockage

Exercices
1. Compte tenu de ce qui suit :
• Une variable int avec une portée de bloc et un stockage temporaire
• Variable de caractère constante avec portée de bloc
• Variable locale flottante avec stockage permanent
• Un registre int variable
• Un pointeur char initialisé avec un caractère
null écrire des déclarations pour chacun d'entre eux.
2. Réécrivez le programme du Listing 14.2. Cette fois, passez la variable int x et la variable
la variable float y comme arguments de la fonction function_1(). Qu'obtenez-
vous à l'écran après avoir exécuté le programme ?
3. Compilez et exécutez le programme suivant. Qu'obtenez-vous à l'écran et pourquoi ?
#include <stdio.h>
int main()
{
int i ;

for (i=0 ; i<5 ;


i++){ int x = 0
;
statique int y = 0 ;
printf("x=%d, y=%d\n", x++, y++) ;
}
retour 0 ;
}

4. Réécrivez la fonction add_two() de l'illustration 14.3 pour imprimer le résultat


précédent de l'addition, ainsi que la valeur du compteur.

14
PARTIE IV
Fonctions et allocation
dynamique de la mémoire
Heure
15 Travailler avec des fonctions
16 Application des pointeurs
17 Attribution de la mémoire
18 Utilisation de types de données et de fonctions spéciales
HEURE 15
Travailler avec des fonctions
La forme suit la fonction.
-L. H. Sullivan
Dans l'heure 14, "Comprendre la portée et les classes de stockage", vous avez
peut-être remarqué qu'une définition de fonction est toujours donnée en
premier, avant que la fonction ne soit appelée à partir d'une fonction main().
En fait, vous pouvez placer une définition de fonction où vous voulez, tant
que vous placez la déclaration de fonction avant le premier endroit où la
fonction est appelée. Vous découvrirez de nombreuses caractéristiques des
fonctions grâce aux sujets suivants abordés dans cette leçon :
• Déclarations de fonctions
• Prototypage
• Valeurs renvoyées par les fonctions
• Arguments pour les fonctions
• Programmation structurée
En outre, plusieurs fonctions et macros de la bibliothèque C, telles
que time(), localtime(), asctime(), va_start(), va_arg() et
va_end() sont introduites dans cette heure.
244 Heure
15

Déclaration des fonctions


Comme vous le savez, vous devez déclarer ou définir une variable avant de pouvoir
l'utiliser. Il en va de même pour les fonctions. En C, vous devez déclarer ou définir une
fonction avant de pouvoir l'appeler.

Déclaration versus définition


Selon la norme ANSI, la déclaration d'une variable ou d'une fonction spécifie
l'interprétation et les attributs d'un ensemble d'identificateurs. La définition, quant à elle,
exige du compilateur C qu'il réserve un espace de stockage pour une variable ou une
fonction nommée par un identificateur.
Une déclaration de variable est une définition, ce qui n'est pas le cas d'une déclaration de
fonction. Une déclaration de fonction fait référence à une fonction définie ailleurs et
spécifie le type de valeur renvoyée par la fonction. Une définition de fonction définit ce
que fait la fonction et indique le nombre et le type d'arguments transmis à la fonction.
Une déclaration de fonction n'est pas une définition de fonction. Si une définition de
fonction est placée dans votre fichier source avant que la fonction ne soit appelée pour la
première fois, vous n'avez pas besoin de faire la déclaration de fonction. Dans le cas
contraire, la déclaration d'une fonction doit être faite avant que la fonction ne soit
invoquée.
Par exemple, j'ai utilisé la fonction printf() dans presque tous les exemples de
programmes de ce livre. À chaque fois, j'ai dû inclure un fichier d'en-tête, stdio.h, parce
que le fichier d'en-tête contient la déclaration de printf(), qui indique au compilateur
le type de retour et le proto-type de la fonction. La définition de la fonction printf() est
placée ailleurs. En C, la définition de cette fonction est sauvegardée dans un fichier de
bibliothèque qui est invoqué lors de l'établissement des liens.

Spécification des types de retour


Une fonction peut être déclarée pour renvoyer n'importe quel type de données, à
l'exception d'un tableau ou d'une fonction. L'instruction return utilisée dans une
définition de fonction renvoie une valeur unique dont le type doit correspondre à celui
déclaré dans la déclaration de la fonction.
Par défaut, le type de retour d'une fonction est int, si aucun type de données explicite
n'est spécifié pour la fonction. Un spécificateur de type de données est placé avant le nom
d'une fonction comme ceci :
nom_de_la_fonction_de_type_de_données() ;

Ici, data_type_specifier spécifie le type de données que la fonction doit renvoyer.


function_name est le nom de la fonction qui doit suivre les règles de dénomination des
identificateurs en C.
Travailler avec des 245
fonctions
En fait, cette forme de déclaration représente la forme traditionnelle de déclaration de
fonction avant la création de la norme ANSI. Après la mise en place de la norme
ANSI, le proto-type de fonction est ajouté à la déclaration de fonction.
246 Heure
15

Utilisation de prototypes
Avant la création de la norme ANSI, une déclaration de fonction ne comprenait que le 15
type de retour de la fonction. Avec la norme ANSI, le nombre et le type des arguments
transmis à une fonction peuvent être ajoutés à la déclaration de la fonction. Le nombre et
le type d'un argument sont appelés le prototype de la fonction.
La forme générale d'une déclaration de fonction, y compris son prototype, est la suivante :
data_type_specifier function_name(
data_type_specifier argument_name1,
data_type_specifier argument_name2,
data_type_specifier argument_name3,
.
.
.
spécificateur de type de données nom de l'argumentN,
) ;

L'utilisation d'un prototype de fonction a pour but d'aider le compilateur à vérifier si les
types de données des arguments passés à une fonction correspondent à ce que la fonction
attend. Le compilateur émet un message d'erreur si les types de données ne correspondent
pas.
Bien que les noms d'arguments, tels que nom_argument1, nom_argument2, etc., soient
facultatifs, il est recommandé de les inclure afin que le compilateur puisse identifier toute
incohérence entre les noms d'arguments.

Appels de fonctions
Comme le montre la figure 15.1, lorsqu'un appel de fonction est effectué, l'exécution du
programme saute à la fonction et termine la tâche assignée à la fonction. L'exécution du
programme reprend ensuite après le retour de la fonction appelée.
Un appel de fonction est une expression qui peut être utilisée comme une déclaration
unique ou à l'intérieur d'autres déclarations.
La liste 15.1 donne un exemple de déclaration et de définition de fonctions, ainsi que
d'appels de fonctions.

TYPE LISTE 15.1 Appel de fonctions après leur déclaration et leur définition
1 : /* 15L01.c : Faire des appels de
fonction */ 2 : #include <stdio.h>
3 :
4 : int function_1(int x, int y) ;
5 : double function_2(double x, double
y) 6 : {
7 : printf("Within function_2.\n") ;

continue
Travailler avec des 247
fonctions

LISTE 15.1 suite


8 : retour (x - y)
;
9 : }
10 :
11 : main()
12 : {
13: int x1 = 80 ;
14: int y1 = 10 ;
15 : double x2 = 100.123456 ;
16 : double y2 = 10.123456 ;
17 :
18 : printf("Pass function_1 %d and %d.\n", x1, y1) ;
19 : printf("function_1 returns %d.\n", function_1(x1, y1))
; 20: printf("Pass function_2 %f and %f.\n", x2, y2) ;
21 : printf("function_2 returns %f.\n", function_2(x2, y2))
; 22: return 0 ;
23 : }
24 : /* définition de function_1()
*/ 25 : int function_1(int x,
int y) 26 : {
27 : printf("Within function_1.\n")
; 28: return (x + y) ;
29 : }

FIGURE 15.1
L'exécution du Dém
arrag
programme saute à une
fonction invoquée
lorsqu'un appel de
fonction est effectué. Appel de
foncti
on
programme
Flux d'exécution du

Exécutio
n de la
fonction

Fonction
Retour

Fin
248 Heure
15

La sortie suivante s'affiche après la création et l'exécution de l'exécutable (15L01.exe) du


programme de la liste 15.1 :
Passez la fonction_1 80 et 10.
15
SORTIE Dans la fonction_1,
la fonction_1 renvoie
90.
Passez la fonction_2 100.123456. et
10.123456. Dans la fonction_2.
la fonction_2 renvoie 90.000000.

L'objectif du programme du Listing 15.1 est de vous montrer comment déclarer et


ANALYSE
définir des fonctions. La déclaration de la ligne 4 est une déclaration de fonction
avec un proto-
type. Cette déclaration fait référence à la fonction_1 définie plus loin dans le Listing
15.1. Le type de retour de la fonction_1 est int, et le prototype de la fonction
comprend deux variables int, appelées x et y.
Aux lignes 5 à 9, la deuxième fonction, function_2, est définie avant d'être appelée.
Comme vous pouvez le voir, le type de retour de la fonction_2 est double, et deux
variables doubles sont transmises à la fonction. Notez que les noms des deux variables
sont également x et y. Ne vous inquiétez pas car la fonction_1 et la fonction_2
partagent les mêmes noms d'arguments. Il n'y a pas de conflit car ces arguments se
trouvent dans des blocs de fonctions différents et les arguments des fonctions ont la
portée d'un bloc.
Ensuite, dans la fonction main() définie aux lignes 11 à 23, deux variables int, x1 et
y1, et deux variables double, x2 et y2, sont déclarées et initialisées aux lignes 13 à 16,
respectivement. L'instruction de la ligne 18 indique les valeurs de x1 et y1 qui sont
transmises à la fonction function_1. La ligne 19 appelle la fonction_1 et affiche la
valeur de retour de la fonction_1.
De même, les lignes 20 et 21 impriment les valeurs de x2 et y2 transmises à la
fonction_2, ainsi que la valeur renvoyée par la fonction_2 après l'appel et
l'exécution de la fonction.
Les lignes 25 à 29 contiennent la définition de la fonction function_1, spécifiant que la
fonction peut effectuer une addition de deux variables entières (voir ligne 28) et imprimer
la chaîne de caractères Within function_1. à la ligne 27.

Fonctions de prototypage
Dans les sous-sections suivantes, vous allez étudier trois cas concernant les arguments
passés aux fonctions. Dans le premier cas, les fonctions ne prennent aucun argument ;
dans le deuxième cas, les fonctions prennent un nombre fixe d'arguments ; dans le
troisième cas, les fonctions prennent un nombre variable d'arguments.
Travailler avec des 249
fonctions

Fonctions sans arguments


Le premier cas est une fonction qui ne prend aucun argument. Par exemple, la fonction de la bibliothèque C
getchar() n'a pas besoin d'arguments. Elle peut être utilisée dans un programme comme celui-ci :
int c ;
c = getchar() ;

Comme vous pouvez le constater, la deuxième instruction est laissée vide entre les
parenthèses (( et )) lorsque la fonction est appelée.
En C, la déclaration de la fonction getchar() peut ressembler à ceci :
int getchar(void) ;

Notez que le mot-clé void est utilisé dans la déclaration pour indiquer au compilateur
que cette fonction n'a pas besoin d'argument. Le compilateur émettra un message
d'erreur si un argument est passé à getchar() plus tard dans le programme lorsque cette
fonction est appelée.
Par conséquent, pour une fonction sans argument, le type de données void est utilisé
comme prototype dans la déclaration de la fonction.
Le programme de la liste 5.2 montre un autre exemple d'utilisation de void
dans les déclarations de fonctions.

TYPE LISTE 15.2 Utilisation de void dans les déclarations de fonctions


1 : /* 15L02.c : Fonctions sans arguments */
2 : #include <stdio.h>
3 : #include <time.h>
4 :
5 : void GetDateTime(void) ;
6 :
7 : main()
8 : {
9 : printf("Avant que la fonction GetDateTime() ne soit
appelée.\n") ; 10 : GetDateTime() ;
11 : printf("Après l'appel de la fonction GetDateTime().\n")
; 12: return 0 ;
13 : }
14 : /* Définition de GetDateTime()
*/ 15 : void GetDateTime(void)
16 : {
17 : time_t now ;
18 :
19 : printf("Within GetDateTime().\n") ;
20 : time(&now) ;
21 : printf("La date et l'heure actuelles
sont : %s\n", 22 :
asctime(localtime(&now))) ;
23 : }
250 Heure
15

J'obtiens le résultat suivant après avoir exécuté le fichier exécutable, 15L02.exe, du


programme de la liste 15.2 :
Avant que la fonction GetDateTime() ne soit appelée.
15
SORTIE Dans GetDateTime().
La date et l'heure actuelles sont : Sat Apr 05 11:50:10 1997

Après l'appel de la fonction GetDateTime().

Le but du programme du Listing 15.2 est de vous montrer comment déclarer et


ANALYSE
appeler une fonction sans passer d'arguments. Le programme affiche la valeur
actuelle de
la date et l'heure sur votre ordinateur en appelant la fonction GetDateTime(), déclarée à la ligne
5. Étant donné qu'aucun argument ne doit être transmis à la fonction, le type de données
void est utilisé comme prototype dans la déclaration de GetDateTime().
En outre, un autre mot-clé void est utilisé devant le nom de la fonction GetDateTime()
pour indiquer que cette fonction ne renvoie pas non plus de valeur (voir ligne 5).
Les instructions des lignes 9 et 11 impriment des messages avant et après la commande
La fonction GetDateTime() est appelée à partir de la fonction main().
A la ligne 10, la fonction est appelée par l'instruction GetDateTime() ;. Notez qu'aucun
argument ne doit être transmis à cette fonction car le prototype de la fonction est void.
La définition de GetDateTime() se trouve aux lignes 15 à 23 ; elle obtient l'heure du
calendrier et la convertit en une chaîne de caractères en appelant plusieurs fonctions de la
bibliothèque C : time(), local- time() et asctime(). Ensuite, la chaîne de caractères
contenant la date et l'heure actuelles est imprimée à l'écran par la fonction printf() avec
le spécificateur de format %s. Comme vous pouvez le voir, la sortie sur mon écran montre
qu'au moment où le fichier exécutable 15L02.exe est exécuté, la date et l'heure sont les
suivantes
Sat Apr 05 11:50:10 1997

time(), localtime(), et asctime() sont des fonctions de date et d'heure fournies par le
langage C. Ces fonctions sont abordées dans la sous-section suivante. Vous remarquerez
peut-être que le fichier d'en-tête time.h est inclus au début du programme du Listing
15.2 avant que ces fonctions temporelles puissent être utilisées.

Utilisation de time(), localtime() et asctime()


Il existe un groupe de fonctions C connues sous le nom de fonctions de date et d'heure.
Les déclarations de toutes les fonctions de date et d'heure sont incluses dans le fichier
d'en-tête time.h. Ces fonctions peuvent fournir trois types de date et d'heure :
• Heure du calendrier
• Heure locale
Travailler avec des 251
• Heure d'été fonctions
252 Heure
15

L'heure calendaire indique la date et l'heure actuelles sur la base du calendrier grégorien.
L'heure locale représente l'heure calendaire dans un fuseau horaire spécifique. L'heure
d'été est l'heure locale selon la règle de l'heure d'été.
Dans cette section, trois fonctions de date et d'heure - time(), localtime() et asctime() - sont présentées.
sont brièvement présentés.
La fonction time() renvoie l'heure du
calendrier. La syntaxe de la fonction time() est
la suivante
#include <time.h>
time_t time(time_t *timer) ;
E
S

Ici, time_t est le type arithmétique utilisé pour représenter le temps. timer est une
variante de pointeur pointant vers une mémoire pouvant contenir l'heure calendaire
renvoyée par cette fonction. La fonction time() renvoie -1 si l'heure du calendrier n'est
E

pas disponible sur l'ordinateur.


La fonction localtime() renvoie l'heure locale convertie à partir de l'heure du calendrier.
La syntaxe de la fonction localtime() est la suivante
#include <time.h>
struct tm *localtime(const time_t *timer) ;

Ici, tm est une structure qui contient les composants de l'heure du calendrier. struct est
E
S

le mot-clé pour structure, qui est un autre type de données en C. (Le concept de structures
est présenté dans l'Heure 19, "Comprendre les structures".) timer est une variable
E

pointeur pointant vers une mémoire qui contient l'heure du calendrier renvoyée par la
fonction time().
Pour convertir la date et l'heure représentées par la structure tm, vous pouvez appeler la fonction
asctime()
fonction...
La syntaxe de la fonction asctime() est la suivante
SYNTAX

#include <time.h>
char *asctime(const struct tm *timeptr) ;

Ici, timeptr est un pointeur référençant la structure tm renvoyée par des fonctions de
date et d'heure telles que localtime(). La fonction asctime() convertit la date et
E

l'heure représentées par tm en une chaîne de caractères....


Comme le montre le Listing 15.2, l'instruction de la ligne 17 déclare une variable time_t
appelée now. La ligne 20 stocke l'heure du calendrier dans l'emplacement mémoire
référencé par la variable now. Notez que l'argument passé à la fonction time() doit être
la valeur gauche d'une variante ; par conséquent, l'opérateur d'adresse (&) est utilisé
avant now. Ensuite, l'expression dans
Travailler avec des 253
fonctions

La ligne 22, asctime(localtime(&now)), obtient l'expression de l'heure locale du


calendrier en appelant localtime(), et convertit l'heure locale en une chaîne de
caractères avec l'aide de asctime(). La chaîne de caractères représentant la date et
15
l'heure est ensuite imprimée par l'appel à printf() dans les lignes 21 et 22, qui a le
format suivant :
Sat Apr 05 11:50:10 1997\N- Je ne sais pas ce que je vais faire, mais je ne sais pas ce que je vais
faire, mais je sais que je vais faire.

Notez qu'un caractère de retour à la ligne est ajouté juste avant le caractère nul dans la
chaîne de caractères convertie et renvoyée par la fonction asctime().

Fonctions avec un nombre fixe d'arguments


Vous avez déjà vu plusieurs exemples qui déclarent et appellent des fonctions avec un
nombre fixe d'arguments. Par exemple, dans le Listing 15.1, la déclaration de la fonction
function_1() à la ligne 4

int function_1(int x, int y) ;

contient le prototype de deux arguments, x et y.


Pour déclarer une fonction avec un nombre fixe d'arguments, vous devez spécifier le
type de données de chaque argument. Il est également recommandé d'indiquer les noms
des arguments afin que le compilateur puisse vérifier que les types et noms d'arguments
déclarés dans une déclaration de fonction correspondent à l'implémentation dans la
définition de la fonction.

Prototypage d'un nombre variable d'arguments


Comme vous vous en souvenez peut-être, la syntaxe de la fonction printf() est la suivante
int printf(const char *format[, argument, ...]) ;

Ici, le symbole d'ellipse ... (c'est-à-dire trois points) représente un nombre variable
d'arguments. En d'autres termes, outre le premier argument qui est une chaîne de
caractères, la fonction printf() peut prendre un nombre non spécifié d'arguments
supplémentaires, autant que le com- piler le permet. Les crochets ([ et ]) indiquent que
les arguments non spécifiés sont facultatifs.
Voici un formulaire général pour déclarer une fonction avec un nombre variable
d'arguments :
data_type_specifier function_name(
data_type_specifier argument_name1,
...
) ;

Notez que le premier nom d'argument est suivi d'une ellipse (...) qui représente le reste
des arguments non spécifiés.
254 Heure
15

Par exemple, la déclaration de la fonction printf() ressemblerait à ceci :


int printf(const char *format, ...) ;

Traitement des arguments des variables


Trois routines déclarées dans le fichier d'en-tête stdarg.h permettent d'écrire des
fonctions qui prennent un nombre variable d'arguments. Il s'agit de va_start(),
va_arg() et va_end().

Un type de données, va_list, est également inclus dans stdarg.h. Il définit un type de
tableau adapté pour contenir les éléments de données nécessaires à va_start(),
va_arg() et va_end().

Pour initialiser un tableau donné dont va_arg() et va_end() ont besoin, vous devez utiliser la fonction
va_start() avant que les arguments ne soient traités.
La syntaxe de la macro va_start() est la suivante
#include <stdarg.h>
void va_start(va_list ap, lastfix) ;

Ici, est le nom du tableau qui va être initialisé par la macro routine va_start().
ap
E
S

lastfix doit être l'argument avant l'ellipse (...) dans la déclaration de la fonction.
E

En utilisant la macro va_arg(), vous pouvez traiter une expression qui a le type et la
valeur de l'argument suivant. En d'autres termes, la macro va_arg() peut être utilisée
pour obtenir le prochain argument passé à la fonction.
La syntaxe de la macro va_arg() est la suivante
SYNTAX

#include <stdarg.h>
type va_arg(va_list ap, data_type) ;

Ici, ap est le nom du tableau initialisé par la macro routine va_start().


E

data_type est le type de données de l'argument transmis à la fonction.


Pour faciliter un retour normal de votre fonction, vous devez utiliser la fonction
va_end() dans votre programme après que tous les arguments ont été traités.

La syntaxe de la fonction va_end() est la suivante


SYNTAX

#include <stdarg.h>
void va_end(va_list ap) ;

Ici, ap est le nom du tableau initialisé par la macro routine va_start().


E

N'oubliez pas d'inclure le fichier d'en-tête stdarg.h dans votre programme avant d'appeler
va_start(), va_arg(), ou va_end().
Travailler avec des 255
fonctions

La liste 5.3 montre comment utiliser va_start(), va_arg() et va_end() dans une
fonction qui prend un nombre variable d'arguments.
15
TYPE LISTE 15.3 Traitement des arguments des variables
1 : /* 15L03.c : Traitement des arguments des
variables */ 2 : #include <stdio.h>
3 : #include <stdarg.h>
4 :
5 : double AddDouble(int x, ...)
; 6 :
7 : main ()
8 : {
9 : double d1 = 1,5 ;
10 : double d2 = 2,5 ;
11 : double d3 = 3,5 ;
12 : double d4 = 4,5 ;
13 :
14 : printf("Given an argument : %2.1f\n", d1) ;
15 : printf("Le résultat retourné par AddDouble() est :
%2.1f\n\n", 16: AddDouble(1, d1)) ;
17 : printf("Arguments donnés : %2.1f et %2.1f\n", d1, d2) ;
18 : printf("Le résultat retourné par AddDouble() est :
%2.1f\n\n", 19: AddDouble(2, d1, d2)) ;
20 : printf("Arguments donnés : %2.1f, %2.1f et %2.1f\n", d1, d2,
d3) ; 21 : printf("Le résultat retourné par AddDouble() est :
%2.1f\n",
22 : AddDouble(3, d1, d2, d3)) ;
23 : printf("Given arguments : %2.1f, %2.1f, %2.1f, and
%2.1f\n", 24: d1, d2, d3, d4) ;
25 : printf("Le résultat retourné par AddDouble() est :
%2.1f\n", 26: AddDouble(4, d1, d2, d3, d4)) ;
27 : retour 0 ;
28 : }
29 : /* définition de AddDouble()
*/ 30 : double AddDouble(int x,
...) 31 : {
32 : va_list arglist ;
33 : int i ;
34 : double result = 0.0 ;
35 :
36 : printf("The number of arguments is : %d\n",
x) ; 37: va_start (arglist, x) ;
38 : for (i=0 ; i<x ; i++)
39 : result += va_arg(arglist, double)
; 40: va_end (arglist) ;
41 : résultat du retour ;
42 : }
256 Heure
15

Le résultat suivant s'affiche à l'écran après l'exécution du fichier exécutable, 15L03.exe :

Compte tenu d'un argument : 1.5


SORTIE Le nombre d'arguments est le suivant 1
Le résultat retourné par AddDouble() est : 1.5

Arguments donnés : 1,5 et


2,5 Le nombre d'arguments
est : 2
Le résultat retourné par AddDouble() est : 4.0

Arguments donnés : 1,5, 2,5 et 3,5


Le nombre d'arguments est : 3
Le résultat retourné par AddDouble() est : 7.5

Arguments donnés : 1,5, 2,5, 3,5 et 4,5


Le nombre d'arguments est : 4
Le résultat retourné par AddDouble() est : 12.0

Le programme du Listing 15.3 contient une fonction qui peut prendre un nombre
ANALYSE variable d'arguments doubles, effectuer l'opération d'addition sur ces arguments
et
puis renvoie le résultat à la fonction main().
La déclaration de la ligne 5 indique au compilateur que la fonction AddDouble() prend
un nombre variable d'arguments. Le premier argument de AddDouble() est une variable
entière qui contient le nombre des autres arguments transmis à la fonction chaque fois
que AddDouble() est appelée. En d'autres termes, le premier argument indique le nombre
d'arguments restants à traiter.
La définition de AddDouble() est donnée aux lignes 29 à 41, dans laquelle un tableau
va_list, arglist, est déclaré à la ligne 31. Comme indiqué, la macro va_start()
doit être appelée avant que les arguments ne soient traités. Ainsi, la ligne 36 invoque
va_start() pour initialiser le tableau arglist. La boucle for des lignes 37 et 38
récupère le double argument suivant sauvegardé dans le tableau arglist en appelant
va_arg(). Ensuite, chaque argument est ajouté à une variable double locale appelée
result.

La fonction va_end() est appelée à la ligne 39 après que tous les arguments enregistrés
dans arglist ont été récupérés et traités. Ensuite, la valeur du résultat est renvoyée
à l'appelant de la fonction AddDouble(), qui est la fonction main() dans ce cas.
La fonction va_end() doit être appelée dans un programme C pour mettre fin au
traitement des arguments variables. Sinon, le comportement du programme est indéfini.
Comme vous pouvez le voir, dans la fonction main(), AddDouble() est appelé quatre
fois, avec un nombre différent d'arguments à chaque fois. Les arguments transmis à
AddDouble() sont affichés par les appels printf() aux lignes 14, 17, 20 et 23. De
même, les quatre résultats différents renvoyés par AddDouble() sont imprimés à l'écran.
Travailler avec des 257
fonctions

Apprendre la programmation structurée


Vous avez maintenant appris les bases de la déclaration et de la définition des fonctions. 15
Avant de passer à l'heure suivante, parlons un peu de la programmation structurée dans
la conception des programmes.
La programmation structurée est l'une des meilleures méthodologies de programmation.
Fondamentalement, il existe deux types de programmation structurée : la programmation
descendante et la programmation ascendante.
Lorsque vous commencez à écrire un programme pour résoudre un problème, l'une des
façons de procéder consiste à travailler sur les plus petits éléments du problème. Tout
d'abord, vous définissez et écrivez des fonctions pour chaque élément. Une fois que
chaque fonction est écrite et testée, vous commencez à les assembler pour construire un
programme capable de résoudre le problème. Cette approche est généralement appelée
programmation ascendante.
D'autre part, pour résoudre un problème, vous pouvez d'abord élaborer un plan et
commencer votre programmation à un niveau plus élevé. Par exemple, vous pouvez
travailler sur la fonction main() au début, puis passer au niveau inférieur suivant jusqu'à
ce que les fonctions de niveau inférieur soient écrites. Ce type d'approche est appelé
programmation descendante.
Vous constaterez qu'il est utile de combiner ces deux types de programmation structurée
et de les utiliser alternativement pour résoudre des problèmes réels.

Résumé
Dans cette leçon, vous avez appris les concepts importants suivants sur les fonctions en C :
• Une déclaration de fonction fait référence à une fonction définie ailleurs et spécifie
le type d'arguments et de valeurs transmis à la fonction et renvoyés par celle-ci.
• Une définition de fonction réserve l'espace mémoire et définit ce que fait la
fonction, ainsi que le nombre et le type d'arguments transmis à la fonction.
• Une fonction peut être déclarée pour renvoyer n'importe quel type de données, à l'exception d'un tableau ou
d'une fonction.
• L'instruction return utilisée dans une définition de fonction renvoie une valeur
unique dont le type doit correspondre à celui déclaré dans la déclaration de la
fonction.
• Un appel de fonction est une expression qui peut être utilisée comme une
déclaration unique ou à l'intérieur d'autres expressions ou déclarations.
• Le type de données void est nécessaire dans la déclaration d'une fonction qui
ne prend aucun argument.
• Pour déclarer une fonction qui prend un nombre variable d'arguments, vous devez
258 Heure
15et utiliser l'ellipse (...) pour représenter le
spécifier au moins le premier argument
reste des arguments passés à la fonction.
Travailler avec des 259
fonctions

• va_start(), va_arg() et va_end(), tous inclus dans stdarg.h, sont nécessaires


pour traiter un nombre variable d'arguments transmis à une fonction.
• time(), localtime() et asctime() sont trois fonctions temporelles fournies par le
langage C. Elles peuvent être utilisées ensemble pour obtenir une chaîne de
caractères contenant des informations sur la date et l'heure locales basées sur l'heure
du calendrier.
Dans la prochaine leçon, vous en apprendrez plus sur les pointeurs et leurs applications en C.

Q&R
Q Quelle est la principale différence entre une déclaration de fonction et une
définition de fonction ?
R La principale différence entre une déclaration de fonction et une définition de
fonction est que la première ne réserve pas d'espace mémoire et ne spécifie pas ce
que fait la fonction. Une déclaration de fonction ne fait que renvoyer à une
définition de fonction placée ailleurs. Elle spécifie également le type d'arguments et
de valeurs transmis à la fonction et renvoyés par celle-ci. Une définition de
fonction, en revanche, réserve l'espace mémoire et spécifie les tâches que la
fonction peut accomplir.
Q Pourquoi avons-nous besoin de prototypes de fonctions ?
A En déclarant une fonction avec des prototypes, vous spécifiez non seulement le type
de données renvoyées par la fonction, mais aussi les types et les noms des
arguments transmis à la fonction. À l'aide d'un prototype de fonction, le
compilateur peut automatiquement effectuer un contrôle de type sur la définition de
la fonction, ce qui permet de gagner du temps lors du débogage du programme.
Q Une fonction peut-elle renvoyer un pointeur ?
R Oui. En fait, une fonction peut renvoyer une seule valeur qui peut être n'importe
quel type de données, à l'exception d'un tableau ou d'une fonction. Une valeur de
pointeur - c'est-à-dire l'adresse - renvoyée par une fonction peut faire référence à un
tableau de caractères ou à un emplacement de mémoire qui stocke d'autres types de
données. Par exemple, la fonction asctime() de la bibliothèque C renvoie un
pointeur de caractères qui pointe sur une chaîne de caractères convertie à partir
d'une structure de date et d'heure.
Q Pouvez-vous utiliser conjointement la programmation descendante et la
programmation ascendante pour résoudre un problème ?
R Oui. Dans la pratique, il peut s'avérer judicieux de combiner les approches de
programmation descendante et ascendante pour résoudre les problèmes.
L'utilisation des deux types de programmation structurée peut rendre votre
programme facile à écrire et à comprendre.
260 Heure
15

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous 15
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Étant donné les déclarations de fonctions suivantes, lesquelles sont des fonctions
avec un nombre fixe d'arguments, lesquelles sont des fonctions sans arguments et
lesquelles sont des fonctions avec un nombre variable d'arguments ?
• int function_1(int x, float y) ;
• void function_2(char *str) ;
• char *asctime(const struct tm *timeptr) ;
• int function_3(void) ;
• char function_4(char c, ...) ;
• void function_5(void) ;

2. Laquelle des deux expressions suivantes est une définition de fonction ?


int function_1(int x, int y) ;
int function_2(int x, int y){return x+y;}

3. Quel est le type de données renvoyé par une fonction lorsqu'un spécificateur de type est omis ?
4. Dans les déclarations de fonctions suivantes, quelles sont celles qui sont illégales ?
• double function_1(int x, ...) ;
• void function_2(int x, int y, ...) ;
• char function_3(...) ;
• int function_4(int, int, int, int) ;

Exercices
1. Réécrivez le programme de l'illustration 15.2. Cette fois, utilisez le spécificateur
de format %c, au lieu de %s, pour imprimer la chaîne de caractères de l'heure locale
de votre ordinateur.
2. Déclarez et définissez une fonction, appelée MultiTwo(), qui peut effectuer une
multiplication sur deux variables entières. Appelez la fonction MultiTwo() à
partir de la fonction main() et passez deux entiers à MultiTwo(). Imprimez
ensuite à l'écran le résultat renvoyé par la fonction MultiTwo().
Travailler avec des 261
fonctions

3. Réécrivez le programme du Listing 15.3. Cette fois, créez une fonction qui prend
un nombre variable d'arguments int et qui effectue l'opération de multiplication
sur ces arguments.
4. Réécrivez à nouveau le programme de l'illustration 15.3. Cette fois, imprimez tous
les arguments passés à la fonction AddDouble(). Est-ce que va_arg() récupère
chaque argument dans le même ordre (c'est-à-dire de gauche à droite) que la liste
d'arguments passée à AddDouble() ?
262 Heure
15

HEURE 16
Application des pointeurs
Réfléchir deux fois et agir une fois.
-Proverbe chinois
Dans l'Heure 11, "Comprendre les pointeurs", vous avez appris les bases de
l'utilisation des pointeurs en C. Les pointeurs étant très utiles en
programmation, cela vaut la peine de passer une heure de plus pour en
apprendre davantage à leur sujet. Dans cette leçon, les points suivants
sont discutés :

• Arithmétique des pointeurs


• Passer des tableaux à des fonctions
• Passer des pointeurs à des fonctions
• Pointer vers des fonctions

Arithmétique des pointeurs


En C, vous pouvez déplacer la position d'un pointeur en ajoutant ou en
soustrayant des entiers au pointeur. Par exemple, étant donné une variable
de pointeur de caractère ptr_str, l'expression suivante :
ptr_str + 1
260 Heure
260

déplace le pointeur vers l'emplacement mémoire situé à un octet de la position actuelle de


ptr_str.

Notez que pour les pointeurs de différents types de données, les entiers ajoutés ou
soustraits aux pointeurs ont des tailles différentes. En d'autres termes, ajouter (ou
soustraire) 1 à un pointeur ne revient pas nécessairement à demander au compilateur
d'ajouter (ou de soustraire) un octet à l'adresse, mais plutôt d'ajuster l'adresse de manière
à ce qu'elle saute un élément du type du pointeur.
Vous trouverez plus de détails dans les sections suivantes.

La taille scalaire des pointeurs


Le format général pour modifier la position d'un pointeur est le suivant
nom_du_pointeur + n

Ici, n est un entier dont la valeur peut être positive ou négative. nom_pointeur est le nom
d'une variable pointeur qui a la déclaration suivante :
spécificateur de type de données *nom du pointeur ;

Lorsque le compilateur C lit l'expression nom_du_pointeur + n, il l'interprète comme suit


nom_du_pointeur + n * sizeof(data_type_specifier)

Notez que l'opérateur sizeof est utilisé pour obtenir le nombre d'octets du type de
données spécifié. Par conséquent, pour la variable pointeur char ptr_str, l'expression
ptr_str + 1 signifie en fait

ptr_str + 1 * sizeof(char).

La taille d'un caractère étant d'un octet, ptr_str + 1 indique au compilateur de se


déplacer vers l'emplacement mémoire situé un octet après l'emplacement actuel référencé
par le pointeur.
Le programme du Listing 16.1 montre comment les tailles scalaires des différents types
de données affectent les décalages ajoutés ou soustraits aux pointeurs.

TYPE LISTE 16.1 Déplacement de pointeurs de différents types de données


1 : /* 16L01.c : Arithmétique des
pointeurs */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : char *ptr_ch ;
7 : int *ptr_int ;
8 : double *ptr_db ;
9 : /* pointeur de caractères ptr_ch */
10 : printf("Position actuelle de ptr_ch : %p\n", ptr_ch) ;
Application des 261
pointeurs

11 : printf("La position après ptr_ch + 1 : %p\n", ptr_ch +


1) ; 12 : printf("La position après ptr_ch + 2 : %p\n",
ptr_ch + 2) ; 13 : printf("La position après ptr_ch - 1 :
%p\n", ptr_ch - 1) ; 14 : printf("La position après ptr_ch -
2 : %p\n", ptr_ch - 2) ; 15 : /* int pointer ptr_int */
16 : printf("Position actuelle de ptr_int : %p\n", ptr_int) ;
17 : printf("La position après ptr_int + 1 : %p\n", ptr_int +
1) ; 18 : printf("La position après ptr_int + 2 : %p\n",
ptr_int + 2) ; 19 : printf("La position après ptr_int - 1 :
%p\n", ptr_int - 1) ; 20 : printf("La position après ptr_int - 16
2 : %p\n", ptr_int - 2) ; 21 : /* double pointeur ptr_ch */
22 : printf("Position actuelle de ptr_db : %p\n", ptr_db) ;
23 : printf("La position après ptr_db + 1 : %p\n", ptr_db +
1) ; 24 : printf("La position après ptr_db + 2 : %p\n",
ptr_db + 2) ; 25 : printf("La position après ptr_db - 1 :
%p\n", ptr_db - 1) ; 26 : printf("La position après ptr_db -
2 : %p\n", ptr_db - 2) ; 27 :
28 : retour 0 ;
29 : }

La sortie suivante est obtenue en exécutant le fichier exécutable 16L01.exe du


programme de la liste 16.1 sur ma machine. Il se peut que vous obteniez des adresses
différentes sur votre ordinateur, ainsi que des décalages différents en fonction de la
taille des types de données sur votre système :
Position actuelle de ptr_ch :
SORTIE 0x000B Position après ptr_ch + 1 :
0x000C Position après ptr_ch + 2 :
0x000D Position après ptr_ch - 1 :
0x000A Position après ptr_ch - 2 :
0x0009 Position actuelle de ptr_int
: 0x028B
Position après ptr_int + 1 : 0x028D
Position après ptr_int + 2 : 0x028F
Position après ptr_int - 1 : 0x0289
Position après ptr_int - 2 : 0x0287
Position actuelle de ptr_db :
0x0128
Position après ptr_db + 1 : 0x0130
Position après ptr_db + 2 : 0x0138
Position après ptr_db - 1 : 0x0120
Position après ptr_db - 2 : 0x0118

Comme vous pouvez le voir dans la liste 16.1, il y a trois pointeurs de types différents-
ANALYSE
ptr_ch, ptr_int et ptr_db - déclarés aux lignes 6-8. Parmi eux, ptr_ch est un
est un pointeur sur un caractère, ptr_int est un pointeur sur un entier, et ptr_db est un pointeur sur une base
de données.
double.
Ensuite, l'instruction de la ligne 10 affiche l'adresse mémoire 0x000B, contenue dans la
262 Heure
variable de pointeur de caractère ptr_ch 262. Les lignes 11 et 12 affichent les deux
adresses, 0x000C et 0x000D, lorsque ptr_ch est additionné avec 1 et 2, respectivement.
De même, les lignes 13 et 14 donnent 0x000A
Application des 263
pointeurs

et 0x0009 lorsque ptr_ch est déplacé vers des adresses mémoire inférieures. La taille
d'un caractère étant de 1 octet, ptr_ch+1 signifie qu'il faut passer à l'emplacement de
mémoire qui est 1 octet plus haut que l'emplacement de mémoire actuel, 0x000B,
référencé par le pointeur ptr_ch.
La ligne 16 montre l'emplacement mémoire référencé par la variable pointeur int
ptr_int à 0x028B. Comme la taille de int est de 2 octets sur mon système, l'expression
ptr_int+1 déplace ptr_int vers l'emplacement mémoire qui est 2 octets plus haut que
l'emplacement actuel pointé par ptr_int. C'est exactement le résultat que vous voyez à
la ligne 17. De même, la ligne 18 montre que ptr_int+2 déplace la référence à 0x028F,
qui est 4 octets plus haut
(2*sizeof(int)) que 0x028B. L'emplacement mémoire 0x0289 est référencé par l'expres-
sion ptr_int-1 à la ligne 19 ; 0x0287 est référencé par ptr_int-2 à la ligne 20.
La taille du type de données double est de 8 octets sur mon système. Par conséquent,
l'expression ptr_db+1 est interprétée comme l'adresse mémoire référencée par ptr_db
plus 8 octets, c'est-à-dire 0x0128+8, ce qui donne 0x0130 au format hexadécimal,
comme vous pouvez le voir à la ligne 23.

Les lignes 24-26 affichent les adresses mémoire référencées par ptr_db+2, ptr_db-1
et ptr_db-2, respectivement, ce qui prouve que le compilateur a utilisé la même taille
scalaire de double dans l'arithmétique des pointeurs.

Les pointeurs sont très utiles lorsqu'ils sont utilisés correctement. Toutefois,
un pointeur peut rapidement vous causer des problèmes s'il contient une
valeur erronée. Une erreur courante, par exemple, consiste à attribuer une
valeur de droite à un pointeur qui attend en fait une valeur de gauche.
Heureusement, de nombreux compilateurs C détectent ce type d'erreur et
émettent un message d'avertissement.
Il existe une autre erreur courante que le compilateur ne détecte pas
toujours pour vous : l'utilisation de pointeurs non initialisés. Par exemple, le
code suivant présente un problème potentiel :
int x, *ptr_int
; x = 8 ;
*ptr_int = x ;

Le problème est que le pointeur ptr_int n'est pas initialisé ; il pointe


vers un emplacement de mémoire inconnu. Par conséquent, il est
dangereux d'assigner une valeur, comme 8 dans ce cas, à un
emplacement de mémoire inconnu. Elle risque d'écraser des données
importantes qui sont déjà enregistrées à cet emplacement, ce qui pose un
grave problème. La solution consiste à s'assurer qu'un pointeur pointe sur
un emplacement mémoire légal et valide avant de l'utiliser.
Vous pouvez réécrire le code ci-dessus pour éviter ce problème potentiel de
la manière suivante :
int x, *ptr_int
264 Heure
264

Soustraction de pointeurs
Vous pouvez soustraire une valeur de pointeur de l'autre pour obtenir la distance entre les
deux emplacements de mémoire. Par exemple, étant donné deux variables de pointeur de
caractère, ptr_str1 et ptr_str2, vous pouvez calculer le décalage entre les deux
emplacements de mémoire pointés par les deux pointeurs de la manière suivante :
ptr_str2 - ptr_str1

Pour obtenir des résultats significatifs, il est préférable de ne soustraire que les pointeurs 16
de même type de données. Le Listing 16.2 montre un exemple de soustraction sur une
variable pointeur int.

TYPE LISTING 16.2 Effectuer une soustraction sur des pointeurs


1 : /* 16L02.c : Soustraction de
pointeurs */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
6 : int *ptr_int1, *ptr_int2
; 7 :
8 : printf("La position de ptr_int1 : %p\n", ptr_int1)
; 9: ptr_int2 = ptr_int1 + 5 ;
10 : printf("La position de ptr_int2 = ptr_int1 + 5 : %p\n", ptr_int2)
; 11 : printf("La soustraction de ptr_int2 - ptr_int1 : %d\n", ptr_int2
- ptr_int1) ;
12 : ptr_int2 = ptr_int1 - 5 ;
13 : printf("La position de ptr_int2 = ptr_int1 - 5 : %p\n", ptr_int2)
; 14 : printf("La soustraction de ptr_int2 - ptr_int1 : %d\n", ptr_int2
- ptr_int1) ;
15 :
16 : retour 0 ;
17 : }

Après avoir exécuté l'exécutable (16L02.exe) du programme de la liste 16.2 sur ma


machine, j'obtiens la sortie suivante à l'écran :
Position de ptr_int1 : 0x0128
SORTIE Position de ptr_int2 = ptr_int1 + 5 : 0x0132
Soustraction de ptr_int2 - ptr_int1 : 5
Position de ptr_int2 = ptr_int1 - 5 : 0x011E
Soustraction de ptr_int2 - ptr_int1 : -5

Le programme du Listing 16.2 déclare deux variables de pointeur int, ptr_int1 et


ANALYSE
ptr_int2, à la ligne 6. L'instruction de la ligne 8 imprime la position mémoire détenue
par ptr_int1. La ligne 9 affecte l'adresse mémoire référencée par ptr_int1+5 à
ptr_int2. Le contenu de ptr_int2 est ensuite imprimé à la ligne 10.
Application des 265
pointeurs

L'instruction de la ligne 11 montre la différence entre les deux pointeurs int, c'est-à-dire
la soustraction de ptr_int2 et ptr_int1. Le résultat est 5.
La ligne 12 attribue ensuite une autre adresse mémoire, référencée par l'expression
ptr_int1-5, au pointeur ptr_int2. La différence entre ptr_int2 et ptr_int1 est
obtenue par la soustraction des deux pointeurs, qui est de -5 (puisqu'un int sur ma
machine est de deux octets), comme l'indique l'instruction de la ligne 14.

Pointeurs et tableaux
Comme indiqué dans les leçons précédentes, les pointeurs et les tableaux sont étroitement
liés. Vous pouvez accéder à un tableau par l'intermédiaire d'un pointeur qui contient
l'adresse de départ du tableau. La sous-section suivante explique comment accéder aux
éléments d'un tableau à l'aide de pointeurs.

Accès aux tableaux par le biais de pointeurs


Comme un nom de tableau non suivi d'un indice est interprété comme un pointeur sur le
premier élément du tableau, vous pouvez assigner l'adresse de départ du tableau à un
pointeur du même type de données ; vous pouvez ensuite accéder à n'importe quel
élément du tableau en ajoutant un entier approprié au pointeur. La valeur de l'integre que
vous utilisez est la meme que la valeur de l'indice de l'element auquel vous voulez
acceder.
En d'autres termes, étant donné un tableau, array, et un pointeur, ptr_array, si array et ptr_array
sont du même type de données, et ptr_array contient l'adresse de départ du tableau, c'est-à-dire
ptr_array = array ;

alors l'expression array[n] est équivalente à l'expression


*(ptr_array + n)

Ici, n est un indice du tableau.


Le listing 16.3 montre comment accéder aux tableaux et modifier les valeurs des
éléments d'un tableau en utilisant des pointeurs.

TYPE LISTE 16.3 Accès aux tableaux à l'aide de pointeurs


1 : /* 16L03.c : Accès aux tableaux via des
pointeurs */ 2 : #include <stdio.h>
3 :
4 : main()
5 : {
266 Heure
266

6 : char str[] = "C'est une chaîne de caractères !";


7 : char *ptr_str ;
8 : int list[] = {1, 2, 3, 4, 5} ;
9 : int *ptr_int ;
10
:
11 /* accès à un tableau de caractères */
:
12 ptr_str = str ;
:
13 printf("Avant la modification, str contient : %s\n", str) ; 16
:
14 printf("Avant la modification, str[5] contient : %c\n", str[5]) ;
:
15 *(ptr_str + 5) = 'A' ;
:
16 printf("Après la modification, str[5] contient : %c\n", str[5]) ;
:
17 printf("Après la modification, str contient : %s\n", str) ;
:
18 /* accès à un tableau d'int.
:
19 ptr_int = list ;

La sortie suivante s'affiche après la création et l'exécution du fichier exécutable


16L03.exe sur mon ordinateur :
Avant la modification, str contient : C'est une chaîne de caractères !
SORTIE Avant la modification, str[5]
contient : a Après la modification,
str[5] contient : A
Après la modification, str contient : C'est
une chaîne de caractères ! Avant la
modification, list[2] contient : 3
Après la modification, list[2] contient : -3

Le but du programme de la liste 16.3 est de vous montrer comment accéder à un char
ANALYSE
str, et un tableau int, list. Aux lignes 6 et 8, str et list sont déclarés
et initialisés avec une chaîne de caractères et un ensemble d'entiers, respectivement. Un
pointeur char, ptr_str, et un pointeur int, ptr_int, sont déclarés aux lignes 7 et 9.
La ligne 12 affecte l'adresse de départ du tableau str au pointeur ptr_str. Les
instructions des lignes 13 et 14 montrent le contenu de la chaîne sauvegardée dans le
tableau str, ainsi que le caractère contenu dans l'élément str[5] du tableau avant
toute modification de str.
L'instruction de la ligne 15 montre que la constante de caractère 'A' est affectée à
l'élément du tableau str pointé par l'expression
*(ptr_str + 5)

Pour vérifier que le contenu de l'élément dans str a été mis à jour, les lignes 16 et 17
impriment l'élément et la chaîne entière, respectivement. La sortie indique que "A" a
remplacé la constante de caractère originale, "a".
Application des 267
pointeurs

L'adresse de départ de la liste de tableaux d'int est assignée au pointeur ptr_int à la


ligne 19. Avant de faire quoi que ce soit avec l'élément list[2] du tableau list,
j'imprime sa valeur, qui est actuellement de 3 (voir la sortie de la ligne 20). À la ligne 21,
l'élément list[2] reçoit une autre valeur, -3, par le biais du pointeur déréférencé
*(ptr_int + 2). L'appel printf() de la ligne 22 imprime la valeur mise a jour de
list[2] .

Pointeurs et fonctions
Avant de commencer à parler du passage de pointeurs à des fonctions, voyons d'abord
comment passer des tableaux à des fonctions.

Transmettre des tableaux à des fonctions


En pratique, il est généralement difficile de passer plus de cinq ou six arguments à une
fonction. Une façon d'économiser le nombre d'arguments passés à une fonction est
d'utiliser des tableaux. Vous pouvez placer toutes les variables du même type dans un
tableau, puis passer le tableau en tant qu'argument unique.
Le programme du Listing 16.4 montre comment passer un tableau d'entiers à une fonction.

TYPE LISTE 16.4 Passage de tableaux à des fonctions


1 : /* 16L04.c : Passage de tableaux aux
fonctions */ 2 : #include <stdio.h>
3 :
4 : int AddThree(int list[])
; 5 :
6 : main()
7 : {
8 : int sum, list[3]
;
9 :
10 : printf("Entrez trois entiers séparés par des
espaces:\N") ; 11 : scanf("%d%d%d", &list[0], &list[1],
&list[2]) ;
12: sum = AddThree(list) ;
13 : printf("La somme des trois entiers est : %d\n", sum)
; 14 :
15 : retour 0 ;
16 : }
17 :
18 : int AddThree(int list[])
19 : {
20 : int i ;
21 : int result = 0 ;
22 :
23: for (i=0 ; i<3 ;
i++) 24 : result +=
list[i] ; 25: return
268 Heure
result ; 268
26 : }
Application des 269
pointeurs

La sortie suivante est obtenue après avoir exécuté l'exécutable, 16L04.exe, et saisi trois
nombres entiers, 10, 20 et 30, sur ma machine :
Entrez trois nombres entiers séparés par des espaces :
SORTIE 10 20 30
La somme des trois entiers est : 60

L'objectif du programme du Listing 16.4 est d'obtenir trois entiers entrés par
ANALYSE
l'utilisateur, puis de transmettre ces trois entiers sous forme de tableau à une
fonction appelée 16
AddThree() pour effectuer l'opération d'addition.

La ligne 4 donne la déclaration de la fonction AddThree(). Notez que le tableau non


dimensionné, list[], est utilisé dans l'expression de l'argument, ce qui indique que
l'argument contient l'adresse de départ du tableau list.
Le tableau de listes et une variable entière, sum, sont déclarés à la ligne 8. L'appel à
printf() à la ligne 10 affiche un message demandant à l'utilisateur d'entrer trois entiers.
Ensuite, la ligne 11 utilise scanf() pour récupérer les entiers entrés par l'utilisateur et les
stocke dans les trois emplacements mémoire des éléments du tableau d'entiers référencés
par &list[0], &list[1] et &list[2], respectivement.
L'instruction de la ligne 12 appelle la fonction AddThree() avec le nom du tableau
comme argument. L'expression AddThree(list) transmet en fait l'adresse de départ du
tableau de listes (&list[0]) à la fonction AddThree().
La définition de la fonction AddThree() se trouve aux lignes 18-26 ; elle additionne les
valeurs des trois éléments du tableau de listes et renvoie le résultat de l'addition. Le
résultat renvoyé par la fonction AddThree() est affecté à la variable entière sum à la
ligne 12 et est imprimé à la ligne 13.

Vous pouvez également spécifier la taille d'un tableau qui est transmis à une
fonction. Par exemple, ce qui suit :
function(char str[16]) ;
est équivalent à l'énoncé suivant :
function(char str[]) ;
Rappelez-vous que le compilateur peut déterminer la taille du tableau
non dimensionné str[].
Pour les tableaux multidimensionnels, le format d'un tableau non
dimensionné doit toujours être utilisé dans la déclaration. (Voir la section
intitulée "Passer des tableaux multidimensionnels en tant qu'arguments",
plus loin dans cette heure).
270 Heure
270

Passage de pointeurs à des fonctions


Comme vous le savez, un nom de tableau qui n'est pas suivi d'un indice est interprété
comme un pointeur sur le premier élément du tableau. En fait, l'adresse du premier
élément d'un tableau est l'adresse de départ du tableau. Par conséquent, vous pouvez
assigner l'adresse de début d'un tableau à un pointeur, puis passer le nom du pointeur, au
lieu du tableau non dimensionné, à une fonction.
La liste 16.5 montre un exemple de passage de pointeurs à des fonctions, ce qui est
similaire au passage de tableaux à des fonctions.

TYPE LISTE 16.5 Passage de pointeurs à des fonctions


1 : /* 16L05.c : Passage de pointeurs vers des
fonctions */ 2 : #include <stdio.h>
3 :
4 : void ChPrint(char *ch) ;
5 : int DataAdd(int *list, int max)
; 6 : main()
7 : {
8 : char str[] = "It's a string
!"; 9: char *ptr_str ;
10 : int list[5] = {1, 2, 3, 4, 5} ;
11 : int *ptr_int ;
12 :
13 : /* assigner l'adresse au
pointeur */ 14: ptr_str = str ;
15 : ChPrint(ptr_str) ;
16 : ChPrint(str) ;
17 :
18 : /* assigner l'adresse au
pointeur */ 19: ptr_int = list ;
20 : printf("La somme retournée par DataAdd() :
%d\n", 21: DataAdd(ptr_int, 5)) ;
22 : printf("La somme retournée par DataAdd() :
%d\n", 23: DataAdd(list, 5)) ;
24 : retour 0 ;
25 : }
26 : /* définition de la
fonction */ 27 : void
ChPrint(char *ch) 28 : {
29 : printf("%s\n", ch) ;
30 : }
31 : /* définition de la fonction */
32 : int DataAdd(int *list, int
max) 33 : {
34 : int i ;
35 : int sum = 0 ;
36 :
Application des 271
pointeurs

37 : for (i=0 ; i<max ;


i++) 38: sum +=
list[i] ; 39: return
sum ;
40 : }

Après avoir exécuté le programme 16L05.exe, j'obtiens la sortie suivante affichée sur
l'écran de mon ordinateur :

SORTIE
C'est une ficelle ! 16
C'est une ficelle !
La somme retournée par DataAdd()
: 15 La somme retournée par
DataAdd() : 15

L'objectif du programme de la liste 16.5 est de montrer comment passer deux


ANALYSE
pointeurs - un pointeur d'entier qui pointe vers un tableau d'entiers et un pointeur
de caractère qui pointe vers un tableau d'entiers.
qui fait référence à une chaîne de caractères - à deux fonctions déclarées aux lignes 4
et 5.
Notez que des expressions telles que char *ch et int *list sont utilisées comme
arguments dans les déclarations de fonctions, ce qui indique au compilateur qu'un
pointeur de char et un pointeur d'int sont respectivement transmis aux fonctions
ChPrint() et DataAdd().

Dans le corps de la fonction main(), les lignes 8 et 9 déclarent un tableau char (str)
qui est initialisé avec une chaîne de caractères et une variable pointeur char (ptr_str).
La ligne 10 déclare et initialise un tableau int (list) avec un ensemble d'entiers. Une
variable pointeur int, ptr_int, est déclarée à la ligne 11.
L'adresse de départ du tableau str est affectée au pointeur ptr_str par l'instruction
d'affectation de la ligne 14. Le pointeur ptr_str est ensuite transmis à la fonction
ChPrint() en tant qu'argument à la ligne 15. Conformément à la définition de
ChPrint() aux lignes 27-30, le contenu du tableau str dont l'adresse de départ est
transmise à la fonction en tant qu'argument est imprimé par l'appel printf() à
l'intérieur de la fonction ChPrint() à la ligne 29.
En fait, vous pouvez toujours utiliser le nom du tableau str comme argument et le
passer à la fonction ChPrint(). La ligne 16 montre que l'adresse de départ du tableau de
caractères est transmise à ChPrint() via le nom du tableau.
L'instruction de la ligne 19 affecte l'adresse de départ du tableau d'entiers list au
pointeur d'entiers ptr_int. Ensuite, le pointeur ptr_int est transmis à la fonction
DataAdd() à la ligne 21, avec 5, qui est le nombre maximum d'éléments contenus dans
le tableau de listes. L'argument max est utilisé parce que la fonction ne peut pas
déterminer la taille du tableau à partir de l'adresse de départ. La définition de la fonction
DataAdd() aux lignes 32-40 montre que DataAdd() additionne tous les éléments
272 Heure
272
entiers de la liste et renvoie la somme à l'appelant. Ensuite, l'instruction des lignes 20
et 21 imprime le résultat renvoyé par DataAdd().
Application des 273
pointeurs

L'expression de la ligne 23 invoque également la fonction DataAdd(), mais cette fois, le


nom du tableau de listes est utilisé comme argument de la fonction. Sans surprise,
l'adresse de départ du tableau de listes est transmise avec succès à la fonction
DataAdd() et l'instruction printf() des lignes 22 et 23 affiche le résultat correct à
l'écran.

Passage de tableaux multidimensionnels en tant qu'arguments


Dans l'Heure 12, "Comprendre les tableaux", vous avez appris à connaître les tableaux
multidimensionnels. Dans cette section, vous allez voir comment passer des tableaux
multidimensionnels à des fonctions.
Comme vous l'avez peut-être deviné, le passage d'un tableau multidimensionnel à une
fonction est similaire au passage d'un tableau unidimensionnel à une fonction. Vous
pouvez soit passer le format non dimensionné d'un tableau multidimensionnel, soit un
pointeur contenant l'adresse de début du tableau multidimen- sionnel à une fonction. Le
Listing 16.6 est un exemple de ces deux méthodes.

TYPE LISTE 16.6 Passage de tableaux multidimensionnels à des fonctions


1 : /* 16L06.c : Passage de tableaux multidimensionnels à des
fonctions */ 2 : #include <stdio.h>
3 : /* déclarations de fonctions */
4 : int DataAdd1(int list[][5], int max1, int max2)
; 5 : int DataAdd2(int *list, int max1, int max2)
;
6 : /* fonction main() */
7 : main()
8 : {
9 : int list[2][5] = {1, 2, 3, 4, 5,
10: 5, 4, 3, 2, 1} ;
11 : int *ptr_int ;
12 :
13 : printf("La somme retournée par DataAdd1() :
%d\n", 14: DataAdd1(list, 2, 5)) ;
15 : ptr_int = &list[0][0] ;
16 : printf("La somme retournée par DataAdd2() :
%d\n", 17: DataAdd2(ptr_int, 2, 5)) ;
18 :
19 : retour 0 ;
20 : }
21 : /* définition de la fonction */
22 : int DataAdd1(int list[][5], int max1, int max2)
23 : {
24 : int i, j ;
25 : int sum = 0 ;
26 :
27: for (i=0 ; i<max1 ;
i++) 28 : for (j=0 ; j<max2 ;
j++)
274 Heure
29 : sum += list[i][j] 274
; 30: return sum ;
31 : }
Application des 275
pointeurs

32 : /* définition de la fonction */
33 : int DataAdd2(int *list, int max1, int
max2) 34 : {
35 : int i, j ;
36 : int sum = 0 ;
37 :
38: for (i=0 ; i<max1 ;
i++) 39 : for (j=0 ;
j<max2 ; j++)
40 : sum += *(list + i*max2 + j) 16
; 41: return sum ;
42 : }

La sortie suivante s'affiche sur l'écran de mon ordinateur après l'exécution de l'exécutable
(16L06.exe) :
La somme retournée par DataAdd1() :
SORTIE
30 La somme retournée par DataAdd2()
: 30

Au début du programme du Listing 16.6, je déclare deux fonctions,


ANALYSE
DataAdd1() et DataAdd2(), aux lignes 4 et 5. Notez que le premier argument de
DataAdd1() à la ligne 4 est le tableau non dimensionné de la liste. En fait, la liste est
un tableau d'entiers à deux dimensions déclaré aux lignes 9 et 10 dans le corps de la
fonction main(). Les deux autres arguments, max1 et max2, sont des tailles
bidimensionnelles du tableau de la liste.
Comme le montre la définition de DataAdd1() aux lignes 22 à 31, chaque élément du
tableau de listes, exprimé sous la forme list[i][j], est ajouté et affecté à une variable
locale appelée sum qui est renvoyée à la fin de la fonction DataAdd1() à la ligne 30. Ici,
i est compris entre 0 et max1 - 1, et j est compris entre 0 et max2 - 1.

La fonction DataAdd1() est appelée à la ligne 14, avec le nom du tableau de listes et
les deux dimensions, 2 et 5. Le résultat retourné par DataAdd1() est imprimé par
l'instruction des lignes 13 et 14. Vous voyez donc que passer un tableau
multidimensionnel à une fonction est assez similaire à passer un tableau
unidimensionnel à une fonction. Dans ce cas, la fonction a besoin des tailles des deux
dimensions pour déterminer le nombre total d'éléments dans le tableau.
Une autre façon de procéder consiste à transmettre à une fonction un pointeur contenant
l'adresse de départ d'un tableau multidi- mensionnel. Dans cet exemple, la fonction DataAdd2()
est déclarée en
ligne 5 avec une expression de pointeur, int *list, comme premier argument de la
fonction. La définition de DataAdd2() est donnée aux lignes 33 à 42.
Notez qu'à la ligne 40, chaque élément du tableau de listes est récupéré en déplaçant le
pointeur pour qu'il pointe sur l'emplacement mémoire de l'élément. En d'autres termes, le
pointeur déréférencé *(list + i*max2 + j) est évalué à la valeur d'un élément situé à
276 Heure
la ligne i 276le tableau bidimensionnel est une matrice
et à la colonne j, si l'on imagine que
avec des dimensions horizontales et verticales. Par conséquent, l'ajout de i*max2 à list
calcule l'adresse de la ligne i (c'est-à-dire,
Application des 277
pointeurs

en sautant les lignes 0 à i-1), puis en ajoutant j calcule l'adresse de l'élément j (c'est-à-
dire la colonne j) dans la ligne actuelle (i). Dans cet exemple, la plage de la ligne est
comprise entre 0 et 1 (soit un total de 2 lignes) ; la plage de la colonne est comprise
entre 0 et 4 (soit un total de 5 colonnes). Voir la figure 16.1.
Le résultat retourné par la fonction DataAdd2() est affiché à l'écran par la fonction
l'instruction printf() aux lignes 16 et 17.

FIGURE 16.1 Colonne


0 1 2 3 4
Les coordonnées
bidimensionnelles
indiquent
0 1 2 3 4 5
l'emplacement des
Rangée
éléments dans le
tableau de la liste. 1 5 4 3 2 1

Tableaux de pointeurs
Dans de nombreux cas, il est utile de déclarer un tableau de pointeurs et d'accéder au
contenu pointé par le tableau en déréférençant chaque pointeur. Par exemple, la
déclaration suivante déclare un tableau de pointeurs int :
int *ptr_int[3] ;

En d'autres termes, la variable ptr_int est un tableau à trois éléments de pointeurs sur
des entiers. En outre, vous pouvez initialiser le tableau de pointeurs. Par exemple, vous
pouvez initialiser le tableau de pointeurs :
int x1 = 10 ;
int x2 = 100
; int x3 =
1000 ;
ptr_int[0] = &x1
; ptr_int[1] =
&x2 ; ptr_int[2]
= &x3 ;

Le Listing 16.7 présente un autre exemple. Ici, un tableau de pointeurs est utilisé pour
accéder à un tableau de chaînes de caractères.

TYPE LISTE 16.7 Utilisation d'un tableau de pointeurs sur des chaînes de caractères
1 : /* 16L07.c : Utilisation d'un tableau
de pointeurs */ 2 : #include <stdio.h>
3 : /* déclarations de fonctions */
4 : void StrPrint1(char **str1, int
size) ; 5 : void StrPrint2(char *str2)
278 Heure
; 278
Application des 279
pointeurs

6 : /* fonction main() */
7 : main()
8 : {
9 : char *str[4] = {"Il y a de la musique dans le soupir
d'un roseau ;", 10 : "Il y a de la musique dans le
jaillissement d'un ruisseau ;",
11 : "Il y a de la musique en toutes choses si les hommes ont des oreilles ;",
12 : "La terre n'est qu'un écho des sphères".
13 : } ;
14 : int i, size = 4 ;
15 : 16
16 : StrPrint1(str, size)
; 17 : for (i=0 ; i<size ;
i++) 18 :
StrPrint2(str[i]) ;
19 :
20 : retour 0 ;
21 : }
22 : /* définition de la fonction */
23 : void StrPrint1(char **str1, int
size) 24 : {
25 : int i ;
26 : /* Imprimer toutes les chaînes dans un tableau de
pointeurs vers des chaînes */ 27: for (i=0 ; i<size ; i++)
28 : printf("%s\n", str1[i]) ;
29 : }
30 : /* définition de la
fonction */ 31 : void
StrPrint2(char *str2) 32 : {
33 : /* Imprime une chaîne à la fois
*/ 34: printf("%s\n", str2) ;
35 : }

Un morceau d'un poème écrit par Lord Byron est imprimé après la création et l'exécution
de l'exécutable (16L07.exe) du programme de la liste 16.7 :
Il y a de la musique dans le soupir d'un roseau ;
SORTIE Il y a de la musique dans le jaillissement
d'un ruisseau ; Il y a de la musique dans
toutes les choses si les hommes avaient des
oreilles ; Là-bas, la terre n'est qu'un écho
des sphères. Il y a de la musique dans le
soupir d'un roseau ; Il y a de la musique
dans le jaillissement d'un ruisseau ; Il y a
de la musique dans toutes les choses si les
hommes avaient des oreilles ; Là-bas, la
terre n'est qu'un écho des sphères.

Examinons tout d'abord le tableau de pointeurs, str, qui est déclaré et initialisé
ANALYSE dans les lignes 9 à 13 à l'intérieur du corps de la fonction main() du programme
du Listing
16.7. Comme vous pouvez le voir, str est un tableau à quatre éléments de pointeurs vers un ensemble de
280 Heure
chaînes de caractères. 280
J'ai repris quatre phrases d'un poème écrit par Lord Byron et je les ai utilisées comme
chaînes de quatre caractères dans le programme.
Application des 281
pointeurs

Vous pouvez accéder à une chaîne de caractères en utilisant un pointeur correspondant


dans le tableau. En fait, il existe deux fonctions, StrPrint1() et StrPrint2(), dans le
Listing 16.7. Elles peuvent toutes deux être appelées pour accéder aux chaînes de
caractères. La déclaration de la fonction à la ligne 4 montre que la fonction StrPrint1()
prend un pointeur de pointeurs, c'est-à-dire **str1, qui est déréférencé dans la fonction
StrPrint1() pour représenter les quatre pointeurs qui pointent vers les quatre chaînes
de caractères. La définition de StrPrint1() se trouve aux lignes 23 à 29.
La fonction StrPrint2(), quant à elle, ne prend qu'une variable pointeur comme
argument et imprime une chaîne de caractères référencée par le pointeur. Les lignes 31
à 35 donnent la définition de la fonction StrPrint2().
Revenons maintenant à la fonction main(). La fonction StrPrint1() est appelée à la
ligne 16 avec le nom du tableau de pointeurs, str, comme argument. StrPrint1()
affiche alors les quatre phrases du poème de Byron à l'écran. La boucle for des lignes
17 et 18 fait la même chose en appelant quatre fois la fonction StrPrint2(). À chaque
fois, l'adresse de début d'une phrase est transmise à StrPrint2(). Par conséquent, toutes
les phrases du poème sont imprimées à l'écran deux fois...

Pointer vers des fonctions


Avant de terminer le cours pour cette heure, il y a encore une chose intéressante que vous
devez apprendre : les pointeurs de fonctions.
Comme pour les pointeurs sur les tableaux, vous pouvez déclarer un pointeur initialisé
avec la valeur gauche d'une fonction (la valeur gauche est l'adresse mémoire à laquelle se
trouve la fonction). (La valeur de gauche est l'adresse mémoire à laquelle se trouve la
fonction.) Vous pouvez ensuite appeler la fonction via le pointeur.
Le programme du Listing 16.8 est un exemple de déclaration d'un pointeur vers une fonction.

TYPE LISTE 16.8 Pointer vers une fonction


1 : /* 16L08.c : Pointer vers une
fonction */ 2 : #include <stdio.h>
3 : /* déclaration de
fonction */ 4 : int
StrPrint(char *str) ; 5 : /*
fonction main() */
6 : main()
7 : {
8 : char str[24] = "Pointer vers une
fonction" ; 9: int (*ptr)(char *str) ;
10 :
11: ptr = StrPrint ;
12: if (
!(*ptr)(str))
282 Heure
282

13 : printf("Done!\n") ;
14 :
15 : retour 0 ;
16 : }
17 : /* définition de la
fonction */ 18 : int
StrPrint(char *str) 19 : {
20 : printf("%s\n", str) ;
21 : retour 0 ;
22 : } 16
Une fois que l'exécutable 16L08.exe du programme de la liste 16.8 a été créé et
SORTIE
exécuté sur mon ordinateur, la sortie suivante s'affiche à l'écran :
Pointer vers une
ANALYSE fonction.
C'est fait !

Comme d'habitude, la déclaration d'une fonction vient en premier dans le Listing 16.8. La
fonction StrPrint() est déclarée avec le spécificateur de type de données int et un
argument de pointeur char à la ligne 4.
L'énoncé de la ligne 9 donne la déclaration d'un pointeur (ptr) à la fonction StrPrint()
: c'est-à-dire int (*ptr)(char *str) ;.

Notez que le pointeur, ptr, est spécifié avec le type de données int et transmis avec un
pointeur char. En d'autres termes, le format de la déclaration du pointeur à la ligne 9
est assez similaire à la déclaration de StrPrint() à la ligne 4. N'oubliez pas que vous
devez placer l'expression *ptr entre une paire de parenthèses (( et )) afin que le
compilateur ne la confonde pas avec un nom de fonction.
À la ligne 11, la valeur gauche (c'est-à-dire l'adresse) de la fonction StrPrint() est
affectée au pointeur ptr. Ensuite, l'expression (*ptr)(str) de la ligne 12 appelle la
fonction StrPrint() via le pointeur déréférencé ptr, et transmet l'adresse de la chaîne
déclarée à la ligne 8 à la fonction.
La définition de la fonction StrPrint() aux lignes 18-22 indique que la fonction
imprime le contenu d'une chaîne de caractères dont l'adresse est transmise à la fonction
en tant qu'argument. Ensuite, 0 est renvoyé à la fin de la fonction.
En fait, l'instruction if des lignes 12 et 13 vérifie la valeur renvoyée par la fonction
StrPrint(). Si la valeur est 0, l'appel printf() de la ligne 13 affiche la chaîne Done !
à l'écran.
La sortie du programme de l'illustration 16.8 montre que la fonction StrPrint() a été
invoquée avec succès en utilisant un pointeur qui contient l'adresse de la fonction.
Application des 283
pointeurs

Résumé
Dans cette leçon, vous avez appris les concepts et applications très importants suivants
concernant les pointeurs et les tableaux en C :

• Vous devez toujours vous assurer qu'un pointeur pointe vers un emplacement de
mémoire légal et valide avant de l'utiliser.
• La position d'un pointeur peut être déplacée en ajoutant ou en soustrayant un nombre entier.
• La taille scalaire d'un pointeur est déterminée par la taille de son type de
données, qui est spécifiée dans la déclaration du pointeur.
• Pour deux pointeurs de même type, vous pouvez soustraire la valeur d'un
pointeur de l'autre pour obtenir le décalage entre eux.
• Les éléments d'un tableau sont accessibles via un pointeur qui contient l'adresse
de départ du tableau.
• Vous pouvez passer un tableau non dimensionné comme argument unique à une fonction.
• Vous pouvez également transmettre un tableau à une fonction par
l'intermédiaire d'un pointeur. Le pointeur doit contenir l'adresse de départ du
tableau.
• Vous pouvez transmettre à une fonction soit le format non dimensionné d'un
tableau multidimensionnel, soit un pointeur contenant l'adresse de départ du
tableau multidimensionnel.
• Une fonction qui prend un tableau comme argument ne sait pas combien
d'éléments contient le tableau. Vous devez également transmettre le nombre
d'éléments en tant qu'autre argument de la fonction.
• Les tableaux de pointeurs sont utiles pour traiter les chaînes de caractères.
• Vous pouvez assigner un pointeur à l'adresse d'une fonction, puis appeler la
fonction à l'aide de ce pointeur.
Dans la prochaine leçon, vous apprendrez à allouer de la mémoire en C.

Q&R
Q Pourquoi l'arithmétique des pointeurs est-elle nécessaire ?
R L'intérêt d'utiliser des pointeurs est que vous pouvez les déplacer pour accéder aux
données valides qui sont enregistrées dans les emplacements de mémoire
référencés par les pointeurs. Pour ce faire, vous pouvez effectuer de l'arithmétique
de pointeur afin d'ajouter (ou de soustraire) un entier à (ou depuis) un pointeur.
Par exemple, si un pointeur de caractères, ptr_str, contient l'adresse de début
d'une chaîne de caractères, l'expression ptr_str+1 permet de passer à
l'emplacement mémoire suivant, qui contient le deuxième caractère de la chaîne.
284 Heure
284

Q Comment le compilateur détermine-t-il la taille scalaire d'un pointeur ?


R Le compilateur détermine la taille scalaire d'un pointeur en fonction de son type de
données, qui est spécifié dans la déclaration. Lorsqu'un entier est ajouté ou
soustrait à un pointeur, la valeur réelle utilisée par le compilateur est la
multiplication de l'entier et de la taille du type de pointeur. Par exemple, pour un
pointeur int ptr_int, l'expression ptr_int + 1 est interprétée par le
compilateur comme ptr_int + 1 * sizeof(int). Si la taille du type int est de 2
octets, l'expression ptr_int + 1 signifie en réalité qu'il faut déplacer de 2 octets 16
l'emplacement mémoire référencé par le pointeur ptr_int.
Q Comment accéder à un élément d'un tableau en utilisant un pointeur ?
R Pour un tableau unidimensionnel, vous pouvez affecter l'adresse de départ d'un
tableau à un pointeur du même type, puis déplacer le pointeur vers l'emplacement
de mémoire qui contient la valeur d'un élément qui vous intéresse. Ensuite, vous
déréférencez le pointeur pour obtenir la valeur de l'élément. Pour les tableaux
multidimensionnels, la méthode est similaire, mais il faut tenir compte des autres
dimensions en même temps que le pointeur.
temps (voir l'exemple de la liste 16.6).
Q Pourquoi faut-il utiliser des tableaux de pointeurs ?
R Dans de nombreux cas, il est utile d'utiliser des tableaux de pointeurs. Par exemple,
il est pratique d'utiliser un tableau de pointeurs pour pointer sur un ensemble de
chaînes de caractères afin de pouvoir accéder à n'importe laquelle des chaînes
référencées par un pointeur correspondant dans le tableau.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Étant donné un pointeur char, ptr_ch, un pointeur int, ptr_int, et un pointeur
float, ptr_flt, combien d'octets seront ajoutés, respectivement, dans les
expressions suivantes sur votre machine ?
• ptr_ch + 4

• ptr_int + 2
• ptr_flt + 1

• ptr_ch + 12

• ptr_int + 6
Application des 285
pointeurs
• ptr_flt + 3
286 Heure
286

2. Si l'adresse détenue par un pointeur int, ptr1, est 0x100A, et l'adresse


détenue par un autre pointeur int, ptr2, est 0x1006, qu'obtiendrez-vous en
soustrayant ptr1-ptr2 ?
3. Étant donné que la taille du type de données double est de 8 octets et que
l'adresse actuelle détenue par une variable pointeur double, ptr_db, est 0x0238,
quelles sont les adresses détenues, respectivement, par ptr_db-1 et ptr_db+5 ?
4. Etant donné les déclarations et affectations suivantes :
char ch[] = {'a', 'b', 'c', 'd', 'A', 'B', 'C', 'D' } ;
char *ptr ;
ptr = &ch[1]
;

Quel est le rôle de chacune de ces expressions ?

• *(ptr + 3)

• ptr - ch
• *(ptr - 1)

• *ptr = 'F'

Exercices
1. Étant donné une chaîne de caractères, j'aime le C !, écrivez un programme
pour passer la chaîne à une fonction qui affiche la chaîne à l'écran.
2. Réécrivez le programme de l'exercice 1. Cette fois, changez la chaîne de
caractères I like C ! en I love C ! en déplaçant un pointeur initialisé avec
l'adresse de début de la chaîne et en mettant à jour la chaîne avec de nouveaux
caractères. Ensuite, passez la chaîne mise à jour à la fonction pour afficher le
contenu de la chaîne à l'écran.
3. Étant donné un tableau de caractères à deux dimensions, str, initialisé comme suit
char str[2][15] = { "You know what,", "C is powerful." } ;
écrire un programme pour passer l'adresse de départ de str à une fonction qui
imprime le contenu du tableau de caractères.
4. Réécrivez le programme du Listing 16.7. Cette fois, le tableau de pointeurs est
initialisé avec les chaînes suivantes :
"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi" et "Samedi".
HEURE 17
Attribution de la mémoire
Il est tout aussi désagréable d'obtenir plus que ce que l'on a négocié que d'obtenir moins.

-G. B. Shaw
Jusqu'à présent, vous avez appris à déclarer et à réserver un espace mémoire
avant qu'il ne soit utilisé dans votre programme. Par exemple, vous devez
spécifier la taille d'un tableau dans votre programme (ou le compilateur doit
déterminer la taille si vous déclarez un tableau sans taille) avant d'y affecter
des données au moment de l'exécution. Dans cette leçon, vous apprendrez à
allouer dynamiquement de l'espace mémoire lorsque votre programme est en
cours d'exécution. Les quatre fonctions d'allocation dynamique de la
mémoire abordées dans cette leçon sont les suivantes

• La fonction malloc()
• La fonction calloc()
• La fonction realloc()
• La fonction free()
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

280 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
17

Allocation de mémoire au moment de l'exécution


Il existe de nombreux cas où vous ne connaissez pas la taille exacte des tableaux utilisés
dans vos programmes, jusqu'à ce qu'ils soient exécutés. Vous pouvez spécifier la taille
des tableaux à l'avance, mais les tableaux peuvent être trop petits ou trop grands si le
nombre d'éléments de données que vous souhaitez placer dans les tableaux change
radicalement au moment de l'exécution.
Heureusement, le langage C propose quatre fonctions d'allocation dynamique de la
mémoire que vous pouvez utiliser pour allouer ou réallouer certains espaces mémoire
pendant l'exécution de votre programme. Vous pouvez également libérer l'espace
mémoire alloué dès que vous n'en avez plus besoin. Ces quatre fonctions C, malloc(),
calloc(), realloc() et free(), sont présentées dans les sections suivantes

La fonction malloc()
Vous pouvez utiliser la fonction malloc() pour allouer un espace mémoire d'une taille donnée.
La syntaxe de la fonction malloc() est la suivante
SYNTAX

#include <stdlib.h>
void *malloc(size_t size) ;

Ici, la taille indique le nombre d'octets de stockage à allouer. La fonction malloc()


E

renvoie un pointeur vide.


Notez que le fichier d'en-tête, stdlib.h, doit être inclus avant que la fonction
malloc() puisse être appelée. Comme la fonction malloc() renvoie elle-même un
pointeur void, son type est automatiquement converti en type de pointeur du côté gauche
d'une opération d'affectation, conformément à la norme C ANSI.

Il existe sur le marché certains compilateurs C, généralement d'anciennes


versions, qui ne sont pas conformes à 100 % à la norme ANSI C. Si vous
utilisez l'un de ces compilateurs C, vous pouvez obtenir des messages
d'avertissement ou d'erreur concernant vos déclarations de mal- loc(),
calloc() ou realloc(). Par exemple, si vous écrivez quelque chose
comme ceci :
int *ptr ;
ptr = malloc ( 10 * sizeof(int) ) ;

il se peut que vous obteniez un message d'erreur concernant le type de


retour de la fonction malloc() lorsque vous essayez de compiler le
code. Cela est dû au fait que le compilateur C que vous utilisez n'est
peut-être pas compatible à 100 % avec la norme ANSI C et qu'il ne sait
pas comment convertir le type de retour de la fonction malloc(). Si
c'est le cas, vous pouvez utiliser l'opérateur de distribution (int *)
Attribution de la 281
mémoire

Ceci indique au compilateur le type du pointeur retourné par malloc(),


et coupe la plainte du compilateur C. Ici, vous devez vous assurer que le
type de la mémoire allouée, le type du pointeur et le type de l'opérateur
de distribution sont tous identiques. De même, vous pouvez ajouter un
opérateur de distribution devant les fonctions calloc() ou realloc()
lorsque vous utilisez un compilateur C non ANSI.
Étant donné que ce livre se concentre sur le C ANSI, qui est la norme
industrielle, je n'utilise pas d'opérateurs de distribution dans les
programmes d'exemple utilisant malloc(), cal- loc() ou realloc(),
dans le livre.

Si la fonction malloc() ne parvient pas à allouer un espace mémoire, elle renvoie un


pointeur nul. Normalement, cela se produit lorsqu'il n'y a pas assez de mémoire. Par
conséquent, vous devez toujours vérifier le pointeur renvoyé par malloc() avant de 17
l'utiliser.
La liste 17.1 illustre l'utilisation de la fonction malloc().

TYPE LISTE 17.1 Utilisation de la fonction malloc()


1 : /* 17L01.c : Utilisation de la fonction
malloc */ 2 : #include <stdio.h>
3 : #include <stdlib.h>
4 : #include <string.h>
5 : /* déclaration de fonction */
6 : void StrCopy(char *str1, char *str2)
; 7 : /* fonction main() */
8 : main()
9 : {
10 : char str[] = "Utilisez malloc() pour allouer de
la mémoire" ; 11: char *ptr_str ;
12 : résultat int ;
13 : /* appel à malloc() */
14 : ptr_str = malloc( strlen(str) + 1)
; 15: if (ptr_str != NULL){
16 : StrCopy(str, ptr_str) ;
17 : printf("La chaîne de caractères pointée par ptr_str
est:\n%s\n", 18 : ptr_str) ;
19 : résultat = 0 ;
20 : }
21 : else{
22 : printf("La fonction malloc() a
échoué.\n") ; 23: result = 1 ;
24 : }
25 : résultat du retour ;

continue
282 Heure
17

LISTE 17.1 suite


26 : }
27 : /* définition de la fonction */
28 : void StrCopy(char *str1, char
*str2) 29 : {
30 : int i ;
31 :
32 : for (i=0 ; str1[i] ; i++)
33 : str2[i] = str1[i] ;
34 : str2[i] = '\0' ;
35 : }

La sortie suivante s'affiche à l'écran après la création et l'exécution du programme


exécutable 17L01.exe de la liste 17.1.
La chaîne de caractères pointée par ptr_str est :
SORTIE Utilisez malloc() pour allouer de la mémoire.

L'objectif du programme du Listing 17.1 est d'utiliser la fonction malloc() pour


ANALYSE
allouer un espace mémoire de la même taille qu'une chaîne de caractères.
Ensuite, le contenu de la chaîne est copié dans la mémoire allouée référencée par le
pointeur renvoyé par la fonction malloc(). Le contenu de la mémoire est affiché à
l'écran pour prouver que l'espace mémoire contient bien le contenu de la chaîne après
l'allocation et la duplication.
Notez que deux autres fichiers d'en-tête, stdlib.h et string.h, sont inclus dans les
lignes 3 et 4, respectivement, pour les fonctions malloc() et strlen(), qui sont appelées
à la ligne 14.
La ligne 10 déclare un tableau de caractères, str, qui est initialisé avec la chaîne de
caractères "Utilisez malloc() pour allouer de la mémoire". Une variable
pointeur, ptr_str, est déclarée à la ligne 11.
L'instruction de la ligne 14 alloue un espace mémoire de strlen(str)+1 octets en
appelant la fonction malloc(). Comme la fonction strlen() ne compte pas le caractère
nul à la fin d'une chaîne, l'ajout de 1 à la valeur renvoyée par strlen(str) donne le
nombre total d'octets à allouer. La valeur du pointeur renvoyé est affectée à la variable de
pointeur de caractère ptr_str après l'appel de la fonction malloc() à la ligne 14.
L'instruction if-else des lignes 15 à 24 vérifie le pointeur renvoyé par la fonction
malloc(). S'il s'agit d'un pointeur nul, un message d'erreur est imprimé et la valeur de
retour de la fonction main() est fixée à 1 dans les lignes 22 et 23. (Rappelez-vous qu'une
valeur non nulle renvoyée par la fonction
par l'instruction de retour indique une terminaison anormale).
Attribution de la 283
mémoire

Mais si le pointeur retourné n'est pas un pointeur nul, l'adresse de départ du tableau str
et le pointeur ptr_str sont transmis à une fonction appelée StrCopy() à la ligne 16. La
fonction StrCopy(), dont la définition est donnée aux lignes 28 à 35, copie le contenu du
tableau str dans la mémoire allouée pointée par ptr_str. Ensuite, l'appel à printf()
aux lignes 17 et 18 imprime le contenu copié dans la mémoire allouée. La ligne 19 fixe la
valeur de retour à 0
après la réussite de l'allocation de la mémoire et de la duplication de la chaîne.
La sortie sur mon écran montre qu'un morceau de mémoire a été alloué et que la chaîne a
été copiée dans la mémoire.
Il existe un problème potentiel si vous continuez à allouer de la mémoire, car il y a
toujours une limite. Vous pouvez facilement manquer de mémoire si vous allouez
simplement de la mémoire sans jamais
la libérer. Dans la section suivante, vous apprendrez à utiliser la fonction free() pour
libérer les espaces mémoire qui vous ont été alloués lorsque vous n'en avez plus besoin.
17
Libération de la mémoire allouée avec free()
La mémoire étant une ressource limitée, vous devez allouer un morceau de mémoire de
taille exacte juste avant d'en avoir besoin et le libérer dès que vous avez fini de l'utiliser.
Le programme du Listing 17.2 montre comment libérer la mémoire allouée en appelant
la fonction free().

TYPE LISTE 17.2 Utilisation conjointe des fonctions free() et malloc()


1 : /* 17L02.c : Utilisation de la fonction
free() */ 2 : #include <stdio.h>
3 : #include <stdlib.h>
4 : /* déclarations de fonctions */
5 : void DataMultiply(int max, int *ptr)
; 6 : void TablePrint(int max, int
*ptr) ; 7 : /* main() function */
8 : main()
9 : {
10 : int *ptr_int, max
;
11 : terminaison int ;
12 : char key = 'c' ;
13 :
14: max = 0 ;
15: terminaison = 0 ;
16 : while (key !=
'x'){
17 : printf("Entrez un nombre à un chiffre:\n") ;

continue
284 Heure
17

LISTE 17.2 suite


18 : scanf("%d", &max) ;
19 :
20 : ptr_int = malloc(max * max * sizeof(int)) ; /* call malloc()
*/ 21: if (ptr_int != NULL){
22 : DataMultiply(max, ptr_int) ;
23 : TablePrint(max, ptr_int) ;
24 : free(ptr_int) ;
25 : }
26 : else{
27 : printf("La fonction malloc() a
échoué.\n") ; 28: terminaison = 1 ;
29 : key = 'x' ; /* stop while loop
*/ 30 : }
31 : printf("\NAppuyez sur la touche x pour quitter ; sur une
autre touche pour continuer.\N") ; 32: scanf("%s", &key) ;
33 : }
34 : printf("\nBye!\n") ;
35 : terminaison du retour ;
36 : }
37 : /* définition de la fonction */
38 : void DataMultiply(int max, int
*ptr) 39 : {
40 : int i, j
;
41 :
42: for (i=0 ; i<max ;
i++) 43 : for (j=0 ; j<max ;
j++)
44 : *(ptr + i * max + j) = (i+1) *
(j+1) ; 45 : }
46 : /* définition de la fonction */
47 : void TablePrint(int max, int *ptr)
48 : {
49 : int i, j
;
50 :
51 : printf("La table de multiplication de %d
est:\n", 52 : max) ;
53 : printf(" ") ;
54: for (i=0 ; i<max ;
i++) 55 : printf("%4d",
i+1) ;
56 : printf("\N") ;
57: for (i=0 ; i<max ;
i++) 58 : printf("----",
i+1) ; 59: for (i=0 ; i<max
; i++){ 60 :
printf("\n%d|", i+1) ;
61 : for (j=0 ; j<max ; j++)
62 : printf("%3d ", *(ptr + i * max + j))
; 63 : }
64 : }
Attribution de la 285
mémoire

Pendant l'exécution de l'exécutable 17L02.exe, je saisis deux nombres entiers, 4 et 2


(mis en évidence dans la sortie suivante), pour obtenir une table de multiplication pour
chacun d'eux ; puis je quitte l'exécution du programme en appuyant sur la touche x :
Saisir un nombre à un chiffre :
SORTIE 4
La table de multiplication de 4 est la suivante :
1 2 3 4
---------------
1| 1 2 3 4
2| 2 4 6 8
3| 3 6 9 12
4| 4 8 12 16
Appuyer sur la touche x pour quitter ; sur
l'autre touche pour continuer. C
Saisir un nombre à un chiffre :
2 17
La table de multiplication de 2 est la suivante :
1 2
-------
1| 1 2
2| 2 4
Appuyer sur la touche x pour quitter ; sur l'autre touche pour continuer.
x
Au revoir !

L'objectif du programme du Listing 17.2 est de construire une table de


ANALYSE
multiplication basée sur l'entier donné par l'utilisateur. Le programme peut
continuer à construire des tables de multiplication multi
Le programme s'arrête également en cas d'échec de la fonction malloc(). Le programme
s'arrête également en cas d'échec de la fonction malloc().
Pour vous montrer comment utiliser la fonction free(), le programme alloue une
mémoire temporaire pour contenir les éléments d'une table de multiplication. Dès que le
contenu d'une table de multiplication est imprimé, la mémoire allouée est libérée en
appelant la fonction free().
Les lignes 5 et 6 déclarent deux fonctions, DataMultiply() et TablePrint(),
respectivement. La première permet d'effectuer une multiplication et de construire un
tableau, tandis que la seconde imprime le tableau à l'écran. Les définitions des deux
fonctions sont données aux lignes 38 à 45
et les lignes 47 à 64, respectivement.
Dans la fonction main(), il y a une boucle while aux lignes 16-33 qui demande
constamment à l'utilisateur d'entrer un nombre entier (voir lignes 17 et 18) et qui
construit ensuite une table de multiplication basée sur le nombre entier.
Pour conserver le résultat de la multiplication, l'instruction de la ligne 20 alloue un
espace mémoire de la taille de max*max*sizeof(int), où la variable int max contient la
valeur de la variable int.
286 Heure
17

valeur entière introduite par l'utilisateur. Notez que l'expression sizeof(int) donne la
taille en octets du type de données int pour l'ordinateur sur lequel le programme est
exécuté.
Si la fonction malloc() renvoie un pointeur nul, la valeur de retour de la fonction
main() est fixée à 1 pour indiquer une terminaison anormale (voir ligne 28), et la
boucle while est arrêtée en affectant la variable key avec 'x' à la ligne 29.
Dans le cas contraire, si la fonction malloc() alloue la mémoire avec succès, la fonction
DataMultiply() est appelée à la ligne 22 pour calculer chaque multiplication. Les
résultats sont enregistrés dans la mémoire pointée par le pointeur ptr_int. La table de
multiplication est ensuite imprimée en appelant la fonction TablePrint() à la ligne 23.
Dès que je n'ai plus besoin de conserver la table de multiplication, j'appelle la fonction
free() à la ligne 24 pour libérer la mémoire allouée pointée par le pointeur ptr_int.

Si je ne libère pas la mémoire, le programme prendra de plus en plus de mémoire au fur


et à mesure que l'utilisateur entrera des nombres entiers pour construire d'autres tables de
multiplication. Le programme finirait par planter le système d'exploitation ou serait forcé
de s'arrêter. En utilisant les fonctions free() et malloc(), je peux continuer à exécuter
le programme en prenant la quantité exacte de mémoire dont j'ai besoin, ni plus ni moins.

La fonction calloc()
Outre la fonction malloc(), vous pouvez également utiliser la fonction calloc() pour
allouer dynamiquement de l'espace mémoire. Les différences entre les deux fonctions
sont que cette dernière prend deux arguments et que l'espace mémoire alloué par
calloc() est toujours initialisé à 0. Il n'y a aucune garantie que l'espace mémoire alloué
par malloc() soit initialisé à 0.
La syntaxe de la fonction calloc() est la suivante
#include <stdlib.h>
void *calloc(size_t nitem, size_t size) ;

Ici, nitem est le nombre d'éléments que vous souhaitez sauvegarder dans l'espace
E
S

mémoire alloué. size donne le nombre d'octets que prend chaque élément. La fonction
E

calloc() renvoie également un pointeur vide.

Si la fonction calloc() ne parvient pas à allouer un espace mémoire, elle renvoie


un pointeur nul.
La liste 17.3 contient un exemple d'utilisation de la fonction calloc(). La valeur initiale
de l'espace mémoire alloué par calloc() est imprimée.
Attribution de la 287
mémoire

TYPE LISTE 17.3 Utilisation de la fonction calloc()


1 : /* 17L03.c : Utilisation de la fonction
calloc() */ 2 : #include <stdio.h>
3 : #include <stdlib.h>
4 : /* fonction main()
*/ 5 : main()
6 : {
7 : float *ptr1, *ptr2
; 8: int i, n ;
9 : int terminaison = 1 ;
10 :
11: n = 5 ;
12 : ptr1 = calloc(n, sizeof(float))
; 13 : ptr2 = malloc(n * sizeof(float))
; 14: if (ptr1 == NULL)
15 : printf("malloc() failed.\n") 17
; 16: else if (ptr2 == NULL)
17 : printf("calloc() failed.\n") ;
18 : else {
19 : for (i=0 ; i<n ; i++)
20 : printf("ptr1[%d]=%5.2f, ptr2[%d]=%5.2f\n",
21: i, *(ptr1 + i), i, *(ptr2 + i)) ;
22 : free(ptr1) ;
23 : free(ptr2) ;
24 : terminaison = 0 ;
25 : }
26 : fin du retour ;
27 : }

Le résultat suivant apparaît à l'écran après l'exécution du programme exécutable


17L03.exe.
ptr1[0] = 0.00, ptr2[0] = 7042.23
SORTIE ptr1[1] = 0,00, ptr2[1] = 1427,00
ptr1[2] = 0,00, ptr2[2] = 2787,14
ptr1[3] = 0.00, ptr2[3] = 0.00
ptr1[4] = 0,00, ptr2[4] = 5834,73

Le but du programme du Listing 17.3 est d'utiliser la fonction calloc() pour


ANALYSE
allouer un espace mémoire. Pour prouver que la fonction calloc() initialise
l'espace mémoire alloué à 0, les valeurs initiales de la mémoire sont imprimées. De
même, un autre espace mémoire est alloué à l'aide de la fonction malloc(), et les valeurs
initiales du second espace mémoire sont également imprimées.
Comme vous le voyez à la ligne 12, la fonction calloc() est appelée avec deux
arguments qui lui sont transmis : la variable int n et l'expression sizeof(float). La
valeur retournée par la fonction calloc() est attribuée à la variable pointeur float
ptr1.
288 Heure
17

De même, la fonction malloc() est appelée à la ligne 13. Cette fonction ne prend qu'un
seul argument qui spécifie le nombre total d'octets que la mémoire allouée doit contenir.
La valeur renvoyée par la fonction malloc() est ensuite affectée à une autre variante de
pointeur flottant, ptr2.
Les lignes 12 et 13 montrent que les fonctions calloc() et malloc() prévoient en fait
d'allouer deux espaces mémoire de même taille.
L'instruction if-else-if-else des lignes 14-25 vérifie les deux valeurs renvoyées par
les fonctions calloc() et malloc(), puis imprime les valeurs initiales des deux espaces
mémoire alloués si les deux valeurs de retour ne sont pas nulles.
J'ai exécuté plusieurs fois le programme exécutable du Listing 17.3. À chaque fois, la
valeur initiale de l'espace mémoire alloué par la fonction calloc() était toujours 0. Mais
il n'y a pas de telle garantie pour l'espace mémoire alloué par la fonction malloc(). La
sortie montrée ici est l'un des résultats de l'exécution du programme exécutable sur ma
machine.
Vous pouvez voir qu'il y a des "déchets" dans l'espace mémoire alloué par la fonction
malloc(). En d'autres termes, la valeur initiale de la mémoire est imprévisible. (Parfois,
la valeur initiale d'un bloc de mémoire alloué par la fonction malloc() est égale à 0.
Mais c'est le cas pour les blocs de mémoire.
Il n'est pas garanti que la valeur initiale soit toujours égale à zéro chaque fois que la
fonction malloc() est appelée.)

La fonction realloc()
La fonction realloc() vous permet de modifier la taille d'un espace mémoire alloué par
la fonction malloc(), la fonction calloc() ou même la fonction realloc() elle-
même.
La syntaxe de la fonction realloc() est la suivante
#include <stdlib.h>
void *realloc(void *block, size_t size) ;

Ici, block est le pointeur sur le début d'un espace mémoire précédemment alloué. size
E
S

spécifie le nombre total d'octets que vous souhaitez modifier. La fonction realloc()
renvoie un pointeur vide.
La fonction realloc() renvoie un pointeur nul si elle ne parvient pas à réallouer un
E

espace mémoire.
La fonction realloc() est équivalente à la fonction malloc() si le premier argument
passé à realloc() est NULL. En d'autres termes, les deux déclarations suivantes sont
équivalentes :
ptr_flt = realloc(NULL, 10 * sizeof(float)) ;
ptr_flt = malloc(10 * sizeof(float)) ;
Attribution de la 289
mémoire

Vous pouvez également utiliser la fonction realloc() à la place de la fonction


free(). Pour ce faire, passez 0 à realloc() en tant que deuxième argument. Par
exemple, pour libérer un bloc de mémoire pointé par un pointeur ptr, vous pouvez
appeler la fonction free() comme suit :
free(ptr) ;

ou utiliser la fonction realloc() de la manière suivante :


realloc(ptr, 0) ;

Le programme du Listing 17.4 démontre l'utilisation de la fonction realloc() dans la


réallocation de mémoire.

TYPE LISTE 17.4 Utilisation de la fonction realloc()


1 : /* 17L04.c : Utilisation de la fonction
realloc() */ 2 : #include <stdio.h>
17
3 : #include <stdlib.h>
4 : #include <string.h>
5 : /* déclaration de fonction */
6 : void StrCopy(char *str1, char *str2)
; 7 : /* fonction main() */
8 : main()
9 : {
10 : char *str[4] = {"Il y a de la musique dans le soupir
d'un roseau ;", 11 : "Il y a de la musique dans le
jaillissement d'un ruisseau ;",
12 : "Il y a de la musique en toutes choses si les hommes ont des oreilles ;",
13 : "La terre n'est qu'un écho des sphères".
14 : } ;
15 : char *ptr ;
16 : int i ;
17 :
18 : int terminaison = 0 ;
19 :
20 : ptr = malloc((strlen(str[0]) + 1) * sizeof(char))
; 21: if (ptr == NULL){
22 : printf("malloc() failed.\n") ;
23 : terminaison = 1 ;
24 : }
25 : else{
26 : StrCopy(str[0], ptr) ;
27 : printf("%s\n", ptr) ;
28 : for (i=1 ; i<4 ; i++){
29 : ptr = realloc(ptr, (strlen(str[i]) + 1) * sizeof(char))
; 30: if (ptr == NULL){
31 : printf("realloc() failed.\n") ;
32 : terminaison = 1 ;

continue
290 Heure
17

LISTE 17.4 suite


33: i = 4 ; /* interrompre la boucle fro */
34 : }
35 : else{
36 : StrCopy(str[i], ptr) ;
37 : printf("%s\n", ptr) ;
38 : }
39 : }
40 : }
41 : free(ptr) ;
42 : retour de la terminaison ;
43 : }
44 : /* définition de la fonction */
45 : void StrCopy(char *str1, char
*str2) 46 : {
47 : int i ;
48 :
49 : for (i=0 ; str1[i] ; i++)
50 : str2[i] = str1[i] ;
51 : str2[i] = '\0' ;
52 : }

Le résultat suivant est obtenu en exécutant le programme 17L04.exe.


Il y a de la musique dans le soupir d'un roseau ;
SORTIE Il y a de la musique dans le jaillissement
d'un ruisseau ; il y a de la musique dans
toutes les choses si les hommes avaient des
oreilles ; la terre n'est qu'un écho des
sphères.

Le but du programme du Listing 17.4 est d'allouer un bloc d'espace mémoire


ANALYSE
pour contenir une chaîne de caractères. Il y a quatre chaînes de caractères dans
cet exemple, et la fonction
La longueur de chaque chaîne peut varier. J'utilise la fonction realloc() pour ajuster la
taille de la mémoire précédemment allouée afin qu'elle puisse contenir une nouvelle
chaîne.
Comme vous pouvez le voir dans les lignes 10 à 13, il y a quatre chaînes de caractères
contenant un joli poème écrit par Lord Byron (vous pouvez voir que j'aime les poèmes
de Byron). (Ici, j'utilise un tableau de pointeurs, str, pour faire référence aux chaînes de
caractères.
Un espace mémoire est d'abord alloué en appelant la fonction malloc() à la ligne 20. La
taille de l'espace mémoire est déterminée par l'expression
(strlen(str[0])+1)*sizeof(char). Comme indiqué précédemment, la fonction C
strlen() ne comptant pas le caractère nul à la fin d'une chaîne, vous devez vous
souvenir d'allouer de l'espace pour un caractère supplémentaire afin de contenir la taille
totale d'une chaîne. L'expression sizeof(char) est utilisée ici pour des raisons de
Attribution de la 291
mémoire
portabilité, bien que le type de données char soit de 1 octet.
292 Heure
17

L'exercice 4 à la fin de cette leçon vous demande de réécrire le programme de la liste


17.4 et de remplacer les fonctions malloc() et free() par leurs formats équivalents de
la fonction real- loc().
Si la fonction malloc() n'échoue pas, le contenu de la première chaîne pointée par le
pointeur str[0] est copié dans le bloc de mémoire alloué par malloc(). Pour ce faire,
une fonction appelée StrCopy() est appelée à la ligne 26. Les lignes 45 à 52 donnent la
définition de StrCopy().
La boucle for, aux lignes 28 à 39, copie les trois chaînes restantes, une à la fois, dans le
bloc de mémoire indiqué par ptr. À chaque fois, la fonction realloc() est appelée à la
ligne 29 pour réattribuer et ajuster l'espace mémoire précédemment alloué en fonction de
la longueur de la chaîne suivante dont le contenu est sur le point d'être copié dans le bloc
de mémoire.
17
Après avoir copié le contenu d'une chaîne de caractères dans le bloc de mémoire, le
contenu est également imprimé (voir lignes 27 et 37).
Dans cet exemple, un bloc d'espace mémoire est alloué et ajusté en fonction de la
longueur de chacune des quatre chaînes. La fonction realloc(), tout comme la
fonction malloc(), effectue l'allocation et l'ajustement de la mémoire de manière
dynamique.

Résumé
Dans cette leçon, vous avez appris les fonctions et concepts suivants, très importants pour
la gestion de la mémoire en C :

• En C, quatre fonctions peuvent être utilisées pour allouer, réallouer ou libérer un


bloc d'espace mémoire de manière dynamique au moment de l'exécution.
• La fonction malloc() alloue un bloc de mémoire dont la taille est spécifiée par
l'argument transmis à la fonction.
• La fonction free() est utilisée pour libérer un bloc d'espace mémoire
précédemment alloué par la fonction malloc(), calloc() ou realloc().
• La fonction calloc() peut effectuer le même travail que la fonction malloc().
En outre, la fonction calloc() peut initialiser l'espace mémoire alloué à 0.

• La fonction realloc() est utilisée pour réallouer un bloc de mémoire qui a été
alloué par la fonction malloc() ou calloc().
• Si un pointeur nul est transmis à la fonction realloc() comme premier argument,
la fonction agit comme la fonction malloc().
• Si le deuxième argument de la fonction realloc() est fixé à 0, la fonction
realloc() est équivalente à la fonction free() qui libère un bloc de mémoire
allouée.
Attribution de la 293
mémoire

• Vous devez d'abord inclure le fichier d'en-tête stdlib.h avant de pouvoir appeler la fonction
les fonctions malloc(), calloc(), realloc() ou free().
• Vous devez toujours vérifier les valeurs renvoyées par les fonctions malloc(), calloc() ou
realloc(), avant d'utiliser la mémoire allouée par ces fonctions.

Dans la prochaine leçon, vous en apprendrez plus sur les types de données en C.

Q&R
Q Pourquoi faut-il allouer de la mémoire au moment de l'exécution ?
R Très souvent, vous ne connaissez pas la taille exacte des tableaux avant l'exécution de votre
programme.
Vous pouvez peut-être estimer la taille de ces tableaux, mais si vous faites ces
trop grands, vous gaspillez de la mémoire. D'autre part, si ces tableaux sont trop
petits, vous perdrez des données. La meilleure solution consiste à allouer des blocs
de mémoire de manière dynamique et précise pour ces tableaux lorsque leur taille
est déterminée au moment de l'exécution. Il existe quatre fonctions de la
bibliothèque C, malloc(), calloc(), realloc() et free(), que vous pouvez
utiliser pour allouer de la mémoire au moment de l'exécution.
Q Que signifie le fait que la fonction malloc() renvoie un pointeur nul ?
R Si la fonction malloc() renvoie un pointeur nul, cela signifie que la fonction n'a pas
réussi à allouer un bloc de mémoire dont la taille est spécifiée par l'argument passé
à la fonction. Normalement, l'échec de la fonction malloc() est dû au fait qu'il n'y
a plus assez de mémoire à allouer. Vous devez toujours vérifier la valeur renvoyée
par la fonction malloc() pour vous assurer que la fonction a réussi avant d'essayer
d'utiliser le bloc de mémoire alloué par la fonction.
Q Quelles sont les différences entre les fonctions calloc() et malloc() ?
R Fondamentalement, il existe deux différences entre les fonctions calloc() et
malloc(), bien que les deux fonctions fassent le même travail. La première
différence est que la fonction calloc() prend deux arguments, alors que la
fonction malloc() n'en prend qu'un. La deuxième différence est que la fonction
calloc() initialise l'espace mémoire alloué à 0, alors que la fonction malloc()
n'offre aucune garantie de ce type.
Q La fonction free() est-elle nécessaire ?
R Oui. La fonction free() est très utile et vous devez l'utiliser pour libérer les blocs
de mémoire alloués dès que vous n'en avez plus besoin. Comme vous le savez, la
mémoire est une ressource limitée dans un ordinateur. Votre programme ne doit pas
occuper trop d'espace mémoire lorsqu'il alloue des blocs de mémoire. Une façon de
réduire la taille de la mémoire
Votre programme doit utiliser la fonction free() pour libérer à temps la mémoire
allouée inutilisée.
294 Heure
17

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Si le type de données char est de 1 octet, le type de données int de 2 octets et le
type de données float de 4 octets, combien d'octets de mémoire les fonctions
suivantes tentent-elles d'allouer ?
• malloc(100 * sizeof(int))

• calloc(200, sizeof(char)) 17
• realloc(NULL, 50 * sizeof(float))

• realloc(ptr, 0)

2. Étant donné un pointeur int, ptr, qui pointe vers un bloc de mémoire pouvant
contenir 100 entiers, si vous voulez réallouer le bloc de mémoire pour qu'il puisse
contenir jusqu'à 150 entiers, laquelle des deux affirmations suivantes utilisez-vous
?
• ptr = realloc(ptr, 50 * sizeof(int)) ;

• ptr = realloc(ptr, 150 * sizeof(int)) ;

3. Après l'exécution réussie des instructions suivantes, quelle est la taille finale du
bloc de mémoire alloué pointé par le pointeur ptr ?
. . .
ptr = malloc(300 * sizeof(int)) ;
. . .
ptr = realloc(ptr, 500 * sizeof(int)) ;
. . .
ptr = realloc(ptr, 60 * sizeof(int)) ;

4. Quelle est la taille finale du bloc de mémoire alloué pointé par le pointeur ptr, si
les instructions suivantes sont exécutées avec succès ?
. . .
ptr = calloc(100 * sizeof(char)) ;
. . .
free(ptr) ;
ptr = realloc(NULL, 200 * sizeof(char)) ;
. . .
ptr = realloc(ptr, 0) ;
Attribution de la 295
mémoire

Exercices
1. Ecrivez un programme qui demande à l'utilisateur d'entrer le nombre total d'octets
qu'il souhaite allouer, puis initialisez la mémoire allouée avec des nombres entiers
consécutifs, en commençant par 1. Ensuite, initialisez la mémoire allouée avec des
nombres entiers consécutifs, en commençant par 1. Additionnez tous les nombres
entiers contenus dans le bloc de mémoire et imprimez le résultat final à l'écran.
2. Écrivez un programme qui alloue un bloc de mémoire pour contenir 100
éléments du type de données float en appelant la fonction calloc(). Ensuite,
réaffectez le bloc de mémoire afin de contenir 50 éléments supplémentaires du
type de données float.
3. Ecrivez un programme qui demande à l'utilisateur d'entrer le nombre total de
données flottantes. Utilisez ensuite les fonctions calloc() et malloc() pour
allouer deux blocs de mémoire de la même taille spécifiée par le nombre, et
imprimez les valeurs initiales des deux blocs de mémoire.
4. Réécrivez le programme du Listing 17.4. Cette fois, utilisez les deux cas particuliers de la fonction
La fonction realloc() remplace les fonctions malloc() et free().
296 Heure
17

HEURE 18
Utilisation de types de
données et de fonctions
spéciales
C'est tout ce qu'il y a, il n'y en a pas d'autre.

-E. Barrymore
Dans l'heure 4, "Comprendre les types de données et les mots-clés", vous
avez découvert la plupart des types de données, tels que char, int, float et
double. Dans l'Heure 15, "Travailler avec les fonctions", vous avez appris les
bases de l'utilisation des fonctions en C. Dans cette heure, vous en
apprendrez plus sur les types de données et les fonctions à partir des sujets
suivants :

• Le type de données enum


• L'énoncé typedef
• Récursion de fonctions
• Arguments de la ligne de commande
296 L'heure
296

L'enum Type de données


Le langage C propose un type de données supplémentaire, le type de données enum. enum
est l'abréviation de énuméré. Le type de données enum peut être utilisé pour déclarer des
constantes entières nommées. Le type de données enum rend le programme C plus lisible
et plus facile à gérer. (Une autre façon de déclarer une constante nommée est d'utiliser la
directive #define, qui est
(voir plus loin dans ce livre).

Déclaration du type de données enum


La forme générale de la déclaration du type de données enum est la suivante
enum tag_name {enumeration_list} variable_list ;

Ici, tag_name est le nom de l'énumération. variable_list donne une liste de noms de
E

variables qui sont du type de données enum. enumeration_list contient des noms
S

énumérés définis qui sont utilisés pour représenter des constantes entières. (Le nom de
E

la balise et la liste des variables sont tous deux facultatifs).

Par exemple, le texte suivant déclare un type de données enum avec le nom de balise

automobile : enum automobile {sedan, pick_up, sport_utility} ;

Vous pouvez donc définir des variables enum comme suit :


enum automobile domestique, étranger ;

Les deux variables énumérées, domestique et étrangère, sont définies ici.


Bien entendu, il est toujours possible de déclarer et de définir une liste de variables enum
dans un seul état, comme le montre la forme générale de la déclaration enum. Par
conséquent, vous pouvez réécrire la déclaration enum de domestique et d'étranger
comme suit :
enum automobile {sedan, pick_up, sport_utility} national, étranger ;

Attribution de valeurs aux noms d'énumérations


Par défaut, la valeur entière associée au nom le plus à gauche dans le champ de la liste
d'énumération, entouré par les accolades ({ et }), commence par 0, et la valeur de chaque
nom dans le reste de la liste augmente d'une unité de la gauche vers la droite. Par
conséquent, dans l'exemple précédent, sedan, pick_up et sport_utility ont
respectivement les valeurs 0, 1 et 2.
En fait, vous pouvez attribuer des valeurs entières aux noms des énumérations. En
reprenant l'exemple précédent, vous pouvez initialiser les noms de l'énumération de la
manière suivante :
enum automobile {sedan = 60, pick_up = 30, sport_utility = 10} ;
Utilisation de types de données et de 297
fonctions spéciales

Maintenant, la valeur de la berline est de 60, celle du pick-up est de 30 et celle du sport_utility est de
30.
prend la valeur de 10.
Le programme présenté dans le Listing 18.1 imprime les valeurs des noms des enums.

TYPE LISTE 18.1 Définition des types de données enum


1 : /* 18L01.c : Définition des types de
données enum */ 2 : #include <stdio.h>
3 : /* fonction main()
*/ 4 : main()
5 : {
6 : enum language
{human=100, 7 :
animal=50,
8 : ordinateur} ;
9 : enum jours{SUN,
10 : MON,
11 : MAR,
12 : MER,
13 : JEUDI
14 : VEN,
15 : SAT} ;
16 :
17 : printf("human : %d, animal : %d, computer : 18
%d\n", 18: human, animal, computer) ;
19 : printf("SUN : %d\n", SUN) ;
20 : printf("MON : %d\n", MON) ;
21 : printf("TUE : %d\n", TUE) ;
22 : printf("WED : %d\n", WED) ;
23 : printf("THU : %d\n", THU) ;
24 : printf("FRI : %d\n", FRI) ;
25 : printf("SAT : %d\n",
SAT) ; 26 :
27 : retour 0 ;
28 : }

La sortie suivante s'affiche à l'écran après que le fichier exécutable, 18L01.exe, du


programme de la liste 18.1 a été créé et exécuté sur mon ordinateur :
humain : 100, animal : 50, ordinateur : 51
SORTIE SUN : 0
MON : 1
AUT : 2
MER : 3
JEU : 4
VEN : 5
SAT : 6
298 L'heure
298

L'objectif du programme du Listing 18.1 est de vous montrer les valeurs par
ANALYSE
défaut des noms d'enum, ainsi que les valeurs attribuées à certains noms
d'enum par la fonction
programmeur.
Comme vous pouvez le constater, il y a deux déclarations enum, aux lignes 6-8 et 9-15,
respectivement. Notez que les listes de variables dans les deux déclarations enum sont
omises parce qu'elles ne sont pas nécessaires dans le programme.
La première déclaration comporte un nom de balise appelé langage et trois noms
énumérés, humain, animal et ordinateur. En outre, la valeur 100 est attribuée à
human et la valeur 50 est attribuée à animal. Selon la définition de l'énumération, la
valeur par défaut de l'ordinateur est la valeur de l'animal augmentée de 1. Par
conséquent, dans ce cas, la valeur par défaut de l'ordinateur est 51.
La sortie de l'instruction de la ligne 17 montre que les valeurs d'humain, d'animal et de
Les ordinateurs sont en effet 100, 50 et 51.
La deuxième déclaration enum du programme contient sept éléments avec leurs valeurs
par défaut. Ensuite, les lignes 19 à 25 impriment ces valeurs par défaut une par une. Il
n'est pas surprenant de constater que les valeurs représentées par les noms énumérés,
SOLEIL, LUN, MAR, MER, JEU, VRI et SAM, sont respectivement 0, 1, 2, 3, 4, 5 et 6.

Voyons maintenant un autre exemple, présenté dans le Listing 18.2, qui montre
comment utiliser le type de données enum.

TYPE LISTE 18.2 Utilisation du type de données enum


1 : /* 18L02.c : Utilisation du type de
données enum */ 2 : #include <stdio.h>
3 : /* fonction main() */
4 : main()
5 : {
6 : enum unités{penny = 1,
7 : nickel = 5,
8 : pièce de 10 cents = 10,
9 : quart = 25,
10 : dollar = 100}
; 11: int money_units[5] = {
12 : dollar,
13 : quart,
14 : dix cents,
15 : nickel,
16 : penny} ;
17 : char *nom_unité[5] =
{ 18 : " dollar(s)
",
19 : "trimestre(s)",
Utilisation de types de données et de 299
fonctions spéciales

20 : "pièce(s) de 10 cents",
21 : "nickel(s)",
22 : "penny(s)"} ;
23 : int cent, tmp, i ;
24 :
25 : printf("Entrez une valeur monétaire en cents:\N") ;
26 : scanf("%d", &cent) ; /* obtenir la contribution de l'utilisateur */
27 : printf("Which is equivalent to:\n") ;
28 : tmp = 0 ;
29 : for (i=0 ; i<5 ; i++){
30 : tmp = cent / money_units[i] ;
31 : cent -= tmp * money_units[i] ;
32 : si (tmp)
33 : printf("%d %s ", tmp, nom_unité[i]) ;
34 : }
35 : printf("\n") ;
36 : retour 0 ;
37 : }

Pendant l'exécution du fichier exécutable (18L02.exe), je tape 141 (pour 141 cents) et
j'obtiens le résultat suivant à l'écran :

SORTIE
Entrez une valeur monétaire en cents :
141
18
Ce qui équivaut à :
1 dollar(s) 1 quart(s) 1 pièce(s) de 10 cents 1 pièce(s) de 5 cents 1 penny(s)

L'objectif du programme de la liste 18.2 est d'utiliser le type de données enum pour
ANALYSE
représenter la valeur de la somme d'argent saisie par l'utilisateur.
Dans la fonction main(), une déclaration d'énumération avec un nom de balise
d'unités est faite dans les lignes 6-10. Les nombres attribués aux noms énumérés sont
basés sur leurs rapports avec l'unité de cent. Par exemple, un dollar est égal à 100 cents.
Par conséquent, la valeur 100 est attribuée au nom de l'énumération dollar.
Après la déclaration de l'enum, un tableau int, appelé money_units, est déclaré et
initialisé avec les noms énumérés dans la déclaration de l'enum. Conformément à la
définition du type de données enum, la déclaration du tableau money_units dans le
programme est en fait équivalente à la suivante :
int money_units[5] = {
100,
25,
10,
5,
1} ;

Vous voyez maintenant que vous pouvez utiliser des noms énumérés, au lieu de nombres
entiers, pour créer d'autres expressions ou déclarations dans votre programme.
300 L'heure
300

Aux lignes 17 à 22, un tableau de pointeurs, nom_unité, est déclaré et initialisé.


(L'utilisation de tableaux de pointeurs a été introduite dans l'Heure 16, "Application des
pointeurs").
Ensuite, l'instruction de la ligne 15 demande à l'utilisateur de saisir un nombre entier dans
l'unité cent. L'appel à scanf() à la ligne 26 stocke le nombre saisi par l'utilisateur dans
une variable int appelée cent.
La boucle for des lignes 29 à 34 divise le nombre saisi et le représente sous la
forme d'un dollar, d'un quart, d'un dixième, d'un nickel et d'un penny.
Notez que les constantes entières représentées par les noms énumérés sont utilisées aux
lignes 30 et 31, par l'intermédiaire du tableau money_units. Si la valeur d'une unité n'est
pas 0, une chaîne de caractères correspondante pointée par le tableau de pointeurs,
nom_unité, est imprimée à la ligne 33. Par conséquent, lorsque j'entre 141 (dans l'unité
cent), je vois son équivalent dans la sortie : 1 dollar(s) 1 quart(s) 1 dixième(s) 1
nickel(s) 1 penny(s).

Création de définitions typées


Vous pouvez créer vos propres noms pour les types de données à l'aide du mot-clé
typedef en C, et faire de ces noms des synonymes des types de données. Vous pouvez
ensuite utiliser les noms synonymes, au lieu des types de données eux-mêmes, dans vos
programmes. Souvent, les noms synonymes définis par typedef peuvent rendre votre
programme plus lisible.
Par exemple, vous pouvez déclarer TWO_BYTE comme synonyme du type de données int :

typedef int TWO_BYTE ;

Ensuite, vous pouvez commencer à utiliser TWO_BYTE pour déclarer des variables entières comme ceci :
TWO_BYTE i, j ;

ce qui équivaut à
int i, j ;

N'oubliez pas qu'une définition de typedef doit être faite avant que le synonyme créé
dans la définition ne soit utilisé dans les déclarations de votre programme.

Pourquoi utiliser les typedef ?


L'utilisation des définitions typedef présente plusieurs avantages. Tout d'abord, vous
pouvez regrouper des types de données complexes en un seul mot, puis utiliser ce mot
dans les déclarations de variables de votre programme. De cette façon, vous n'avez pas
besoin de taper une déclaration complexe à plusieurs reprises, ce qui permet d'éviter les
Utilisation de types de données et de 301
fonctions
erreurs de frappe. spéciales
302 L'heure
302

Le deuxième avantage est qu'il suffit de mettre à jour une définition de type, ce qui
corrige chaque utilisation de cette définition de type si le type de données est modifié à
l'avenir.
Le typedef est si utile qu'il existe un fichier d'en-tête appelé stddef.h, inclus dans
la norme ANSI C, qui contient une douzaine de définitions de typedef. Par exemple,
size_t est un typedef pour la valeur retournée par l'opérateur sizeof.

Le programme présenté dans le Listing 18.3 est un exemple d'utilisation des définitions typedef.

TYPE LISTE 18.3 Utilisation des définitions typedef


1 : /* 18L03.c : Utilisation de définitions
typedef */ 2 : #include <stdio.h>
3 : #include <stdlib.h>
4 : #include <string.h>
5 :
6 : enum constants{ITEM_NUM = 3,
7 : DELT='a'-'A'} ;
8 : typedef char *STRING[ITEM_NUM]
; 9 : typedef char *PTR_STR ;
10 : typedef char CHAR
;
11 : typedef int
INTEGER ;
18
12 :
13 : void Convert2Upper(PTR_STR str1, PTR_STR str2)
; 14 :
15 : main()
16 : {
17 : STRING str ;
18: STRING moon = {"Quoi que nous
portions", 19 : "nous devenons
belles",
20 : "observation de la lune !"} ;
21 : INTEGER i ;
22 : INTEGER terme = 0 ;
23 :
24 : for (i=0 ; i<ITEM_NUM ; i++){
25 : str[i] = malloc((strlen(moon[i])+1) * sizeof(CHAR))
; 26: if (str[i] == NULL){
27 : printf("malloc() failed.\n") ;
28 : terme = 1 ;
29 : i = ITEM_NUM ; /* interrompre la
boucle for */ 30 : }
31 : Convert2Upper(moon[i], str[i]) ;
32 : printf("%s\n", moon[i]) ;
33 : }
34 : for (i=0 ; i<ITEM_NUM ; i++){
35 : printf("\n%s", str[i]) ;

continue
Utilisation de types de données et de 303
fonctions spéciales

LISTE 18.3 suite


36 : free (str[i]) ;
37 : }
38 : printf("\n") ;
39 : terme de retour ;
40 : }
41 : /* définition de la fonction */
42 : void Convert2Upper(PTR_STR str1, PTR_STR
str2) 43 : {
44 : INTEGER i ;
45 :
46 : for (i=0 ; str1[i] ; i++){
47 : si ((str1[i] >= 'a') &&
48 : (str1[i] <= 'z'))
49 : str2[i] = str1[i] - DELT ;
50 : autre
51 : str2[i] = str1[i]
; 52 : }
53 : str2[i] = '\0' ; /* ajout du caractère
nul */ 54 : }

Le résultat suivant s'affiche à l'écran après l'exécution du fichier exécutable,


18L03.exe, du programme de la liste 18.3 :
Quoi que nous portions
SORTIE nous devenons de
beaux observateurs
de la lune !

QUEL QUE SOIT LE VÊTEMENT QUE NOUS PORTONS


NOUS DEVENONS DE
BEAUX OBSERVATEURS
DE LA LUNE !

L'objectif du programme du Listing 18.3 est de vous montrer comment créer vos
ANALYSE
propres noms pour les types de données tels que char et int. Le programme du
Listing 18.3
convertit tous les caractères d'un haïku japonais en majuscules.
Aux lignes 3 et 4, deux fichiers d'en-tête supplémentaires, stdlib.h et string.h, sont inclus pour la
fonction
malloc(), et strlen() qui sont invoquées plus tard dans le programme.

Une déclaration d'énumération est faite aux lignes 6 et 7 avec deux noms d'énumération,
ITEM_NUM et DELT. La valeur 3 est attribuée à ITEM_NUM car le haïku contient trois
chaînes de caractères. DELT contient la valeur de la différence entre un caractère
minuscule et son équivalent majuscule dans le code ASCII. À la ligne 7, les valeurs de
'a'et 'A' sont utilisées pour calculer la différence. Dans les lignes 8 à 11, je définis les
noms STRING, PTR_STR, CHAR et INTEGER, pour
304 L'heure
304 un pointeur char, un char et une donnée int
un tableau de pointeurs avec trois éléments,
respectivement, afin que je puisse utiliser ces noms comme synonymes de ces types de
données dans le programme.
Utilisation de types de données et de 305
fonctions spéciales

Par exemple, le prototype de la fonction Convert2Upper() à la ligne 13 contient deux


arguments qui sont tous des pointeurs de caractères déclarés avec PTR_STR.
Aux lignes 17-20, deux tableaux de pointeurs, str et moon, sont déclarés avec STRING.
moon est initialisé pour pointer sur les chaînes des haïkus japonais. Aux lignes 21 et 22,
deux vari- ables int, i et term, sont déclarées avec INTEGER.
La boucle for des lignes 24 à 33 alloue dynamiquement suffisamment d'espace
mémoire en fonction de la taille du haïku. La fonction Conver2Upper() est ensuite
appelée à la ligne 31 pour copier les chaînes référencées par moon dans les
emplacements mémoire pointés par str et pour convertir toutes les minuscules en
majuscules.
à leurs homologues en majuscules. La ligne 32 imprime les chaînes de caractères
référencées par moon. La définition de la fonction Conver2Upper() est présentée aux
lignes 42 à 54.
Aux lignes 34 à 37, une autre boucle for imprime le contenu des emplacements
mémoire référencés par str. Il y a au total trois chaînes dont le contenu contient des
caractères majuscules. Après l'affichage d'une chaîne à l'écran, l'espace mémoire alloué à
la chaîne est libéré en appelant la fonction free(). Comme la ligne 35 imprime chaque
chaîne avec un
au début au lieu de la fin, une nouvelle ligne qui suivra la chaîne finale est imprimée à la
ligne 38.
Sur l'écran, vous voyez deux copies du haïku - l'original et celui avec tous les caractères 18
majuscules.

Fonctions récursives
Vous savez déjà qu'en C, une fonction peut être appelée par une autre fonction. Mais une
fonction peut-elle s'appeler elle-même ? La réponse est oui. Une fonction peut s'appeler
elle-même à partir d'une instruction située dans le corps de la fonction elle-même. Une
telle fonction est dite récursive.
La liste 18.4 contient un exemple d'appel d'une fonction récursive pour additionner des
nombres entiers de 1 à 100.

TYPE LISTE 18.4 Appel d'une fonction récursive


1 : /* 18L04.c : Appel d'une fonction
récursive */ 2 : #include <stdio.h>
3 :
4 : enum con{MIN_NUM = 0,
5 : MAX_NUM = 100} ;
6 :
7 : int fRecur(int n)
;
306 L'heure
8 : 306
9 : main()

continue
Utilisation de types de données et de 307
fonctions spéciales

LISTE 18.4 suite


10 : {
11 : int i, sum1, sum2
; 12 :
13 : somme1 = somme2 = 0 ;
14 : for (i=1 ; i<=MAX_NUM ; i++)
15 : sum1 += i ;
16 : printf("The value of sum1 is %d.\n", sum1)
; 17: sum2 = fRecur(MAX_NUM) ;
18 : printf("La valeur retournée par fRecur() est %d.\n",
sum2) ; 19 :
20 : retour 0 ;
21 : }
22 : /* définition de la
fonction */ 23 : int
fRecur(int n)
24 : {
25: if (n == MIN_NUM)
26 : retour 0 ;
27 : return fRecur(n - 1) + n
; 28 : }

Après la création et l'exécution du fichier exécutable 18L04.exe, la sortie suivante


s'affiche sur l'écran de mon ordinateur :
La valeur de sum1 est 5050.
SORTIE La valeur retournée par fRecur() est 5050.

Dans le programme du Listing 18.4, une fonction récursive, fRecur(), est


ANALYSE
déclarée à la ligne 7 et définie aux lignes 23-28.
La définition de la fonction fRecur() montre que la récursion est arrêtée à la ligne 26 si
la variable int entrante, n, est égale à la valeur contenue dans le nom de l'enum
MIN_NUM. Sinon, la fonction fRecur() est appelée par elle-même à plusieurs reprises à la
ligne 27. Notez que chaque fois que la fonction fRecur() est appelée, l'argument entier
passé à la fonction est diminué d'une unité.
Examinons maintenant la fonction main() du programme. La boucle for, illustrée
aux lignes 14 et 15, ajoute des entiers de 1 à la valeur représentée par un autre nom
d'enum, MAX_NUM. Aux lignes 4 et 5, les valeurs MIN_NUM et MAX_NUM sont respectivement
affectées à 0 et 100 dans une déclaration d'enum. L'appel à printf() à la ligne 16
imprime ensuite la somme de l'addition
par la boucle for.
À la ligne 17, la fonction récursive fRecur() est appelée et reçoit un argument entier
commençant à la valeur MAX_NUM. La valeur renvoyée par la fonction fRecur() est
ensuite affectée à une variable int, sum2.
308 L'heure
308

Lorsque la fonction fRecur() est retournée, la valeur de sum2 est imprimée à la ligne
18. La sortie montre que l'exécution de la fonction récursive fRecur() produit le même
résultat que la boucle for à l'intérieur de la fonction main().

Les fonctions récursives sont utiles pour clarifier et simplifier la mise en


œuvre des algorithmes. D'un autre côté, les fonctions récursives peuvent
s'exécuter plus lentement que leurs équivalents itératifs en raison de la
surcharge liée à la répétition des appels de fonction.
Les arguments des fonctions et les variables locales d'un programme sont
généralement stockés temporairement par l'ordinateur, dans un bloc de
mémoire appelé la pile. Chaque appel à une fonction récursive crée une
nouvelle copie des arguments et des variables locales. La nouvelle copie est
alors placée sur la pile. Si vous constatez que votre fonction récursive se
comporte de manière étrange, c'est qu'elle écrase probablement d'autres
données stockées sur la pile.
Bien qu'il soit possible que la fonction récursive soit simplement exécutée
trop de fois et épuise la ressource de la pile, ce type de problème se
produit souvent lorsque vous avez un cas de "récursion incontrôlée".
Dans le Listing 18.4, l'instruction if de la ligne 25 arrête la récursion
lorsque n est égal à MIN_NUM. Dans vos propres fonctions récursives, 18
n'oubliez pas de mettre en place une telle condition pour arrêter la

Réexamen de la fonction main()


Comme vous l'avez appris, chaque programme C doit avoir une et une seule fonction
main(). L'exécution d'un programme commence et se termine par sa fonction main().

Comme pour les autres fonctions en C, vous pouvez passer des arguments à une
fonction main(). Jusqu'à présent, j'ai utilisé le mot-clé void dans la définition de la
fonction main() pour indiquer qu'aucun argument n'était transmis à la fonction.
Maintenant, la question est de savoir ce qu'il faut faire si vous
veulent transmettre des informations à la fonction main().

Arguments de la ligne de commande


Comme chaque programme C commence par sa fonction main(), les informations sont
généralement transmises à la fonction main() par le biais d'arguments de ligne de
commande.
Un argument de ligne de commande est un paramètre qui suit le nom d'un programme
lorsque celui-ci est invoqué à partir de la ligne de commande du système d'exploitation.
Par exemple, pour un programme C, test.c, dont le fichier exécutable s'appelle
test.exe, si vous utilisez un PC, vous pourriez
exécutez le programme à partir d'une invite DOS comme suit :
test argument1 argument2 argument3
Utilisation de types de données et de 309
fonctions spéciales

argument1, argument2 et argument3 sont appelés arguments de la ligne de commande de la fonction


fonction main() dans le programme test.c.
La sous-section suivante vous apprend à recevoir des arguments de ligne de commande.

Réception des arguments de la ligne de commande


La fonction main() comporte deux arguments intégrés qui peuvent être utilisés pour
recevoir des arguments de la ligne de commande. En général, le nom du premier
argument est argc, et il est utilisé pour stocker le nombre d'arguments sur la ligne de
commande. Le deuxième argument est appelé argv et est un pointeur sur un tableau de
pointeurs de caractères. Chaque élément du tableau de pointeurs correspond à un
argument de la ligne de commande qui est traité comme une chaîne de caractères.
Afin d'utiliser argc et argv, déclarez votre fonction main() dans votre programme comme suit :
main(int argc, char *argv[])
{
. . .
}

Continuons à utiliser l'exemple présenté dans la dernière section. Supposons que la fonction main()
définie dans le programme test.c ressemble à ceci :
main(int argc, char *argv[])
{
. . .
}

Si vous exécutez le fichier exécutable du programme à partir d'une invite comme celle-ci :
test argument1 argument2 argument3

la valeur recue par argc est 4 car le nom du programme lui-meme est considere comme
le premier argument de la ligne de commande. En consequence, argv[0] contient une
representation du nom du programme, et argv[1], argv[2], et argv[3] contiennent les
chaines de l'argument1, de l'argument2, et de l'argument3, respectivement.
Le programme du Listing 18.5 est un autre exemple de passage d'arguments de ligne de
commande à la fonction main().

LISTE 18.5 Passage d'arguments de ligne de commande à la fonction main()


TYPE
Fonction
1 : /* 18L05.c : Arguments de la ligne de
commande */ 2 : #include <stdio.h>
3 :
4 : main (int argc, char
*argv[]) 5 : {
310 L'heure
310

6 : int i ;
7 :
8 : printf("La valeur reçue par argc est %d.\n", argc) ;
9 : printf("There are %d command-line arguments passed to
main().\n", 10 : argc) ;
11 : if(argc) {
12 : printf("Le premier argument de la ligne de commande est :
%s\n", argv[0]) ; 13 : printf("Les autres arguments de la ligne de
commande sont : \n") ;
14 : for (i=1 ; i<argc ; i++)
15 : printf("%s\n", argv[i]) ;
16 : }
17 : retour 0 ;
18 : }

Après l'exécution du fichier exécutable 18L05.exe avec plusieurs arguments de ligne


de commande, la sortie suivante s'affiche sur l'écran de mon ordinateur (j'ai saisi
"Hello, world !" dans cet exemple) :
18L05.exe Bonjour, le monde !
SORTIE La valeur reçue par argc est 3.
Il y a 3 arguments de ligne de commande passés à
main(). Le premier argument de la ligne de commande
est : C:\app\18L05.EXE Les autres arguments de la
ligne de commande sont :
18
Bonjour
à tous !

La première ligne de la sortie présentée ci-dessus contient le fichier exécutable


ANALYSE
18L05.exe et les arguments de la ligne de commande "Hello, world !
ordinateur. Le but du programme du Listing 18.5 est de vous montrer comment vérifier le
nombre d'arguments de la ligne de commande et imprimer les chaînes contenant les
arguments entrés par l'utilisateur.
Notez que deux arguments, argc et argv, sont déclarés à la ligne 4 de la fonction
main(). Ensuite, les instructions des lignes 8 et 9 affichent la valeur du nombre total
d'arguments détenus par argc. Si le programme a été exécuté sans arguments de ligne de
commande, argc peut être égal à 0, auquel cas argv est un pointeur nul. La ligne 11
s'assure que argc et argv contiennent des données ; si ce n'est pas le cas, nous
renvoyons 0 et terminons le programme.
La ligne 12 affiche la première chaîne de caractères enregistrée dans l'emplacement
mémoire indiqué par argv[0]. Comme vous pouvez le voir dans la sortie, le contenu de
la première chaîne est le nom du fichier exécutable du programme du Listing 18.5, plus
le chemin d'accès au fichier exécutable.
La boucle for des lignes 14 et 15 affiche le reste des chaînes qui contiennent les
arguments de la ligne de commande saisis par l'utilisateur. Dans cet exemple, je saisis
deux chaînes d'arguments de ligne de commande, "Hello" et "world !", qui sont
Utilisation de types de données et de 311
fonctions spéciales
affichées à l'écran après l'exécution de la boucle for.
312 L'heure
312

argc et argv sont normalement utilisés comme les deux arguments intégrés
dans la fonction main(), mais vous pouvez utiliser d'autres noms pour les
remplacer dans leurs déclarations. Tant que les types de données sont
corrects, votre fonction main() pourra toujours recevoir les arguments de la
ligne de commande.

Résumé
Dans cette leçon, vous avez appris les types de données, les mots-clés et les concepts
importants suivants en C :

• Le type de données enum (c'est-à-dire énuméré) peut être utilisé pour déclarer des
constantes entières nommées.
• Par défaut, le premier nom de l'enum commence par la valeur 0. Chaque nom dans
le reste de la liste augmente d'une unité à partir de la valeur contenue dans le nom
situé à sa gauche.
• Si nécessaire, vous pouvez attribuer des valeurs entières aux noms énumérés.
• Vous pouvez créer vos propres noms pour les types de données à l'aide du mot-
clé typedef. Ces noms peuvent ensuite être utilisés comme synonymes des
types de données.
• En C ANSI, il existe un fichier d'en-tête appelé stddef.h qui contient une douzaine de typedef
définitions.
• Une fonction en C peut s'appeler elle-même. Une telle fonction est dite récursive.
• Vous pouvez utiliser des arguments de ligne de commande pour transmettre des
informations à la fonction main() de votre programme.
• La fonction main() comporte deux arguments intégrés.
• Le premier argument intégré reçoit le nombre d'arguments de ligne de commande
saisis par l'utilisateur. Le deuxième argument intégré est un pointeur sur un
tableau de pointeurs faisant référence aux chaînes d'arguments de la ligne de
commande.

Dans la prochaine leçon, vous apprendrez à rassembler des variables de différents types à l'aide de
structures.

Q&R
Q Que peut-on faire avec le type de données enum ?
R Le type de données enum peut être utilisé pour déclarer des noms représentant des
constantes entières. Vous pouvez utiliser les valeurs par défaut contenues dans les
noms d'enum, ou vous pouvez assigner des valeurs aux noms d'enum et les utiliser
plus tard dans le programme. Le type de données enum rend le programme C plus
lisible et plus facile à maintenir parce que vous pouvez utiliser des mots que vous
Utilisation de types de données et de 313
fonctions spéciales
comprenez comme noms d'enum, et vous n'aurez qu'à aller à un seul endroit pour
mettre à jour les valeurs lorsque cela est nécessaire.
314 L'heure
314

Q Pourquoi faut-il utiliser le mot-clé typedef ?


A En utilisant le mot-clé typedef, vous pouvez définir vos propres noms pour
représenter les types de données en C. Vous pouvez représenter des types de
données complexes en un seul mot et utiliser ce mot dans les déclarations de
variables suivantes. De cette manière, vous évitez les erreurs de frappe lorsque vous
écrivez une déclaration complexe à plusieurs reprises. En outre, si un type de
données est modifié à l'avenir, il suffit de mettre à jour la définition typedef du
type de données, ce qui corrige chaque utilisation de la définition typedef.
Q Une fonction récursive permet-elle d'améliorer les performances d'un programme ?
R Pas vraiment. Normalement, une fonction récursive ne fait que clarifier et simplifier
l'implémentation de certains algorithmes. Une fonction récursive peut ralentir la
vitesse d'un programme en raison de la surcharge liée aux appels répétés de la
fonction.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices sont donnés dans l'annexe B, "Réponses aux questions du quiz et aux
exercices".
18
Quiz
1. Quelles sont les valeurs représentées par les noms d'énumération suivants ?
enum mois { Jan, Feb, Mar, Apr,
Mai, juin, juillet,
août, septembre,
octobre, novembre,
décembre } ;

2. Quelles sont les valeurs représentées par les noms d'énumération suivants ?
enum tag { name1,
nom2 = 10,
nom3, nom4
} ;

3. Parmi les affirmations suivantes, quelles sont celles qui ont des déclarations de variables équivalentes ?
• typedef long int BYTE32 ; BYTE32 x, y, z ;
• typedef char *STRING[16] ; STRING str1, str2, str3 ;
• long int x, y, z ;

• char *str1[16], *str2[16], *str3[16] ;

4. Pouvez-vous passer des arguments de ligne de commande à une fonction main()


Utilisation de types de données et de 315
fonctions spéciales
qui a la définition suivante ?
int main(void)
{
. . .
}
316 L'heure
316

Exercices
1. Ecrivez un programme pour imprimer les valeurs représentées par les noms
énumérés déclarés dans la question 2 de cette heure.
2. Etant donné les déclarations suivantes :
typedef char WORD ;
typedef int SHORT ;
typedef long LONG ;
typedef float FLOAT
; typedef double
DFLOAT ;

écrire un programme pour mesurer la taille des synonymes des types de données.
3. Réécrivez le programme de la liste 18.4. Cette fois, ajoutez des entiers à partir de la valeur de
MIN_NUM au lieu de la valeur de MAX_NUM.
4. Ecrivez un programme qui accepte des arguments de ligne de commande. Si le
nombre d'arguments de la ligne de commande, sans compter le nom de l'exécutable
lui-même, est inférieur à deux, imprimez le format d'utilisation du programme et
demandez à l'utilisateur de saisir à nouveau les arguments de la ligne de
commande. Dans le cas contraire, afficher tous les arguments de ligne de
commande saisis par l'utilisateur.
PARTIE V
Structure, union,
entrées/sorties de fichiers,
etc.
Heure
19 Comprendre les structures
20 Comprendre les syndicats
21 Lire et écrire avec des fichiers
22 Utilisation des fonctions de fichiers spéciaux
23 Compilation des programmes : Le préprocesseur C
24 Quelle est la suite des événements ?
HEURE 19
Comprendre les
structures
L'art de la programmation est l'art d'organiser la complexité.
-W. W. Dijkstra
Dans l'heure 12, "Comprendre les tableaux", vous avez appris à stocker des
données de même type dans des tableaux. Dans cette heure, vous apprendrez
à utiliser des structures pour rassembler des données de types différents. Les
sujets suivants sont abordés dans cette leçon :
• Déclaration et définition des structures
• Référencement des membres d'une structure
• Structures et pointeurs
• Structures et fonctions
• Tableaux de structures
314 Heure
19

Qu'est-ce qu'une structure ?


Comme vous l'avez appris, les tableaux peuvent être utilisés pour rassembler des
groupes de variables de même type. La question qui se pose maintenant est de savoir
comment agréger des données qui ne sont pas de même type.
La réponse est que vous pouvez regrouper des variables de différents types à l'aide
d'un type de données appelé structure. En C, une structure rassemble différents
éléments de données de manière à ce qu'ils puissent être référencés comme une seule
unité.
Il existe plusieurs différences majeures entre un tableau et une structure. Outre le fait
que les éléments de données d'une structure peuvent être de types différents, chaque
élément de données a son propre nom au lieu d'une valeur d'indice. En fait, les éléments
de données d'une structure sont appelés membres de la structure.
Les deux sous-sections suivantes vous apprennent à déclarer des structures et à définir
des variables de structure.

Déclaration des structures


La forme générale de déclaration d'une structure est la suivante
struct struct_tag {
data_type1 variable1 ;
data_type2 variable2 ;
data_type3 variable3 ;
.
.
.
} ;

Ici, struct est le mot-clé utilisé en C pour commencer une déclaration de structure.
struct_tag est le nom de la balise de la structure. variable1, variable2 et variable3
sont les membres de la structure. Leurs types de données sont spécifiés respectivement
par data_type1, data_type2 et data_type3. Comme vous pouvez le constater, les
déclarations des membres doivent être incluses dans les accolades ouvrante et fermante ({
et }) de la déclaration de la structure, et un point-virgule ( ;) doit être inclus à la fin de la
déclaration.
Voici un exemple de déclaration de structure :
struct automobile {
int year ;
char model[8] ;
int engine_power
; float weight ;
} ;
Comprendre les 315
structures

Ici, struct est utilisé pour commencer une déclaration de structure. automobile est le
nom de la balise de la structure. Dans cet exemple, il existe trois types de variables :
char, int et float. Les variables ont leurs propres noms, tels que year, model,
engine_power et weight.

Notez qu'un nom de balise de structure, comme automobile, est le nom d'une structure.
Le compilateur utilise le nom de la balise pour identifier la structure étiquetée par ce nom
de balise.

Définition des variables de structure


Après avoir déclaré une structure, vous pouvez définir les variables de structure. Par
exemple, les variables de structure suivantes sont définies avec le type de données de
structure automobile de la section précédente :
struct automobile sedan, pick_up, sport_utility ;

Ici, trois variables de structure, sedan, pick_up et sport_utility, sont définies par la
structure automobile. Ces trois variables de structure contiennent les quatre membres de
la structure automobile.
Vous pouvez également combiner la déclaration de structure et la définition en une
seule déclaration :
struct automobile {
int year ;
char model[8] ;
int engine_power
; float weight ;
} sedan, pick-up, sport_utility ;

Ici, trois variables de structure, sedan, pick_up et sport_utility, sont définies avec la 19
structure automobile dans une seule déclaration.

Référencement des membres d'une structure à


l'aide de l'opérateur point
Voyons maintenant comment référencer un membre d'une structure. Étant donné la
structure automobile et la variable structurelle berline, par exemple, je peux
accéder à son membre, l'année, et lui affecter un entier de la manière suivante :
sedan.year = 1997 ;

Ici, le nom de la structure et le nom de son membre sont séparés par l'opérateur point
(.) de sorte que le compilateur sait que la valeur entière de 1997 est affectée au
membre appelé année, qui est un membre de la variable de structure appelée berline.
316 Heure
19

De même, l'instruction suivante attribue l'adresse de début du tableau de caractères de


qui est un autre membre de la variable structurelle sedan, à un pointeur char ptr : ptr
= sedan.model ;

Le programme de la liste 19.1 donne un autre exemple de référencement des membres


d'une structure.

TYPE LISTE 19.1 Référencement des membres d'une structure

1 : /* 19L01.c Accès aux membres de la


structure */ 2 : #include <stdio.h>
3 :
4 : main(void)
5 : {
6 : struct computer
{ 7: float cost ;
8 : année int ;
9 : int cpu_speed ;
10 : char cpu_type[16] ;
11: } modèle ;
12 :
13 : printf("The type of the CPU inside your computer?\n")
; 14 : gets(model.cpu_type) ;
15 : printf("The speed(MHz) of the CPU?\n")
; 16 : scanf("%d", &model.cpu_speed) ;
17 : printf("L'année de fabrication de votre
ordinateur?\N") ; 18: scanf("%d",
&model.year) ;
19 : printf("Combien avez-vous payé pour
l'ordinateur ?") ; 20: scanf("%f",
&model.cost) ;
21 :
22 : printf("Voici ce que vous avez
saisi:\N") ; 23 : printf("Année : %d\n",
model.year) ;
24 : printf("Coût : $%6.2f\n", model.cost) ;
25 : printf("CPU type : %s\n", model.cpu_type) ;
26 : printf("CPU speed : %d MHz\n",
model.cpu_speed) ; 27 :
28 : retour 0 ;
29 : }

Après avoir exécuté l'exécutable (19L01.exe) du programme de la liste 19.1 et saisi mes
réponses aux questions, j'obtiens la sortie suivante à l'écran (dans la sortie, les
caractères gras ou les chiffres sont les réponses saisies au clavier) :
Comprendre les 317
structures

Le type d'unité centrale de votre ordinateur ?


SORTIE
Pentium
La vitesse (MHz) de l'unité centrale ?
100
L'année de fabrication de votre ordinateur ?
1996
Combien avez-vous payé pour l'ordinateur ?
1234.56
Voici ce que vous avez saisi :
Année : 1996
Coût : 1234,56
Type de CPU :
Pentium Vitesse du
CPU : 100 MHz

L'objectif du programme de l'illustration 19.1 est de vous montrer comment


ANALYSE
référencer les membres d'une structure. Comme vous pouvez le voir dans le
programme, il existe une structure
appelé modèle qui est défini avec un type d'ordinateur struct dans les lignes 6-11. La
structure possède une variable float, deux variables int et un tableau char.
L'instruction de la ligne 13 demande à l'utilisateur d'entrer le type de CPU (unité centrale
de traitement) utilisé dans son ordinateur. Ensuite, la ligne 14 reçoit la chaîne de
caractères du type de CPU saisie par l'utilisateur et l'enregistre dans le tableau de
caractères appelé cpu_type. Comme cpu_type est un membre de la structure du modèle,
l'expression model.cpu_type est utilisée à la ligne 14 pour référencer le mem- bre de la
structure. Notez que l'opérateur point (.) est utilisé pour séparer les deux noms dans
l'expression.
Les lignes 15 et 16 demandent la vitesse du processeur et stockent la valeur d'un entier
saisi par l'utilisateur dans un autre membre de la structure du modèle, la variable int
cpu_speed. Notez qu'à la ligne 16, l'opérateur d'adresse (&) est préfixé à l'expression
model.cpu_speed à l'intérieur de la fonction scanf() parce que l'argument devrait être
19
un pointeur int.
De même, les lignes 17 et 18 reçoivent la valeur de l'année au cours de laquelle
l'ordinateur de l'utilisateur a été fabriqué, et les lignes 19 et 20 obtiennent le nombre
correspondant au coût de l'ordinateur. Après l'exécution, la variable int year et la
variable float cost de la structure du modèle contiennent les valeurs correspondantes
introduites par l'utilisateur.
Ensuite, les lignes 23 à 26 affichent toutes les valeurs détenues par les membres de la
structure du modèle. La sortie indique que chaque membre de la structure a été accédé et
qu'un nombre ou une chaîne de caractères lui a été attribué correctement.

Initialisation des structures


318 Heure
19 de données appelées initialisateurs. Les
Une structure peut être initialisée par une liste
virgules sont utilisées pour séparer les éléments d'une liste de données.
Comprendre les 319
structures

La liste 19.2 contient un exemple d'initialisation d'une structure avant qu'elle ne soit
mise à jour par l'utilisateur.

TYPE LISTE 19.2 Initialisation d'une structure

1 : /* 19L02.c Initialisation d'une


structure */ 2 : #include <stdio.h>
3 :
4 : main(void)
5 : {
6 : struct employee
{ 7: int id ;
8 : char name[32] ;
9 : } ;
10 : /* initialisation de la
structure */ 11: struct employee
info = {
12 : 1,
13 : "B. Smith"
14 : } ;
15 :
16 : printf("Here is a sample:\n") ;
17 : printf("Nom de l'employé : %s\n",
info.name) ; 18 : printf("Numéro
d'identification de l'employé : %04d\n\n",
info.id) ; 19 :
20 : printf("Quel est votre nom
?") ; 21 : gets(info.name)
;
22 : printf("Quel est votre numéro
d'identification ?") ; 23:
scanf("%d", &info.id) ;
24 :
25 : printf("\NVoici ce que vous avez
saisi:\N") ; 26 : printf("Nom : %s\n",
info.name) ;
27 : printf("ID # : %04d\n",
info.id) ; 28 :
29 : retour 0 ;
30 : }

Lors de l'exécution de l'exécutable 19L02.exe, le contenu initial sauvegardé dans une


structure s'affiche. Ensuite, j'introduis mes réponses aux questions et j'obtiens les
informations mises à jour qui s'affichent à l'écran :
En voici un exemple :
SORTIE Nom de l'employé : B. Smith
Numéro d'identification de
l'employé : 0001

Quel est votre nom ?


T. Zhang
320 Heure
Quel est votre 19
numéro
d'identificati
on ?
1234
Comprendre les 321
structures

Voici ce que vous avez saisi :


Nom : T. Zhang
ID # : 1234

L'objectif du programme du Listing 19.2 est d'initialiser une structure et de


ANALYSE
demander ensuite à l'utilisateur de mettre à jour le contenu de la structure.
Le type de données de structure, appelé employé, est déclaré aux lignes 6 à 9. Ensuite, la
variable info est définie avec le type de données structure et initialisée avec l'entier 1 et
la chaîne de caractères "B. Smith" aux lignes 11-14.
Vous pouvez également combiner la déclaration, la définition et l'initialisation d'une
structure en une seule instruction. En voici un exemple :
struct employee {
int id ;
char name[32] ;
} info =
{ 1,
"B. Smith
} ;

Les instructions des lignes 17 et 18 affichent à l'écran le contenu initial stocké par les
deux membres de la structure info. Ensuite, les lignes 20 à 23 demandent à l'utilisateur
de saisir son nom et son numéro d'identification et de les enregistrer dans les deux
membres de la structure, nom et id, respectivement.
Avant la fin du programme, les contenus mis à jour par les deux membres sont imprimés
par les instructions des lignes 26 et 27.
19
Là encore, l'opérateur point (.) est utilisé dans le programme pour référencer les membres de la structure.

Structures et appels de fonctions


Le langage C permet de transmettre une structure entière à une fonction. En outre, une
fonction peut renvoyer une structure à son appelant.
Pour vous montrer comment passer une structure à une fonction, j'ai réécrit le programme
de l'illustration 19.1 et créé une fonction appelée DataReceive() dans le programme. Le
programme mis à jour est présenté dans l'illustration 19.3.
322 Heure
19

TYPE LISTE 19.3 Transmission d'une structure à une fonction

1 : /* 19L03.c Passage d'une structure à une


fonction */ 2 : #include <stdio.h>
3 :
4 : struct computer
{ 5: float cost ;
6 : année int ;
7 : int cpu_speed ;
8 : char cpu_type[16] ;
9 : } ;
10 : /* créer un synonyme */
11 : typedef struct computer
SC ; 12 : /* déclaration de
fonction */ 13 : SC
DataReceive(SC s) ;
14 :
15 : main(void)
16 : {
17: SC modèle ;
18 :
19 : modèle = DataReceive(modèle) ;
20 : printf("Voici ce que vous avez
saisi:\N") ; 21 : printf("Année : %d\n",
model.year) ;
22 : printf("Coût : $%6.2f\n", model.cost) ;
23 : printf("CPU type : %s\n", model.cpu_type) ;
24 : printf("CPU speed : %d MHz\n",
model.cpu_speed) ; 25 :
26 : retour 0 ;
27 : }
28 : /* définition de la
fonction */ 29 : SC
DataReceive(SC s)
30 : {
31 : printf("Le type de CPU à l'intérieur de votre
ordinateur ?") ; 32 : gets(s.cpu_type) ;
33 : printf("The speed(MHz) of the CPU?\n")
; 34: scanf("%d", &s.cpu_speed) ;
35 : printf("L'année de fabrication de votre
ordinateur?\N") ; 36: scanf("%d",
&s.year) ;
37 : printf("How much you paid for the computer?\n")
; 38: scanf("%f", &s.cost) ;
39 : retour s ;
40 : }

Après avoir exécuté l'exécutable, 19L03.exe, et saisi mes réponses aux questions,
j'obtiens la sortie suivante, qui est la même que celle du programme exécutable de
l'illustration 19.1 :
Comprendre les 323
structures

Le type d'unité centrale de votre ordinateur ?


SORTIE Pentium
La vitesse (MHz) de l'unité centrale ?
100
L'année de fabrication de votre ordinateur ?
1996
Combien avez-vous payé pour l'ordinateur ?
1234.56
Voici ce que vous avez saisi :
Année : 1996
Coût : 1234,56
Type de CPU :
Pentium Vitesse du
CPU : 100 MHz

Le but du programme du Listing 19.3 est de vous montrer comment passer une
ANALYSE
structure à une fonction. La structure de la liste 19.3, avec le nom de balise
computer,
est déclaré aux lignes 4 à 9.
Notez qu'à la ligne 11, le mot-clé typedef est utilisé pour définir un synonyme, SC, de
struct computer. SC est ensuite utilisé dans les déclarations séquentielles. Ici, la
structure et le typedef sont placés en dehors de la fonction main(), de sorte que SC
peut être utilisé dans n'importe quelle fonction du programme.
La fonction DataReceive() est déclarée à la ligne 13, avec la structure de l'ordinateur
comme argument (c'est-à-dire le synonyme SC et le nom de variable s), afin qu'une
copie de la structure puisse être transmise à la fonction.
En outre, la fonction DataReceive() renvoie la copie de la structure à l'appelant après la
mise à jour du contenu de la structure. Pour ce faire, la fonction est précédée du préfixe
SC à la ligne 13 afin d'indiquer le type de données de la valeur renvoyée par la fonction. 19
L'instruction de la ligne 17 définit la variable de structure model avec SC. La structure
du modèle est transmise à la fonction DataReceive() à la ligne 19, puis la valeur
renvoyée par la fonction est également réaffectée au modèle. Notez que si la valeur de
retour de la fonction DataReceive() n'est pas affectée à model, les modifications
apportées à s dans la fonction ne seront pas évidentes dans model.
La définition de la fonction DataReceive() est présentée aux lignes 29 et 40, d'où il
ressort que les nouvelles valeurs de données saisies par l'utilisateur sont enregistrées dans
les membres correspondants de la structure transmise à la fonction. À la fin de la
fonction, la copie de la structure mise à jour est renvoyée à la ligne 39.
Puis, de retour à la fonction main() du programme, les lignes 21-24 impriment les
contenus mis à jour détenus par les membres de la structure. Comme le programme du
Listing 19.3 est fondamentalement le même que celui du Listing 19.1, je vois la même
sortie sur mon écran après avoir exécuté le fichier exécutable 19L03.exe.
324 Heure
19

Référencement de structures avec des pointeurs


Comme vous pouvez passer un pointeur qui fait référence à un tableau dans un appel de
fonction, vous pouvez également passer un pointeur qui fait référence à une structure.
Toutefois, contrairement au passage d'une structure à une fonction, qui envoie une copie
complète de la structure à la fonction, le passage d'un pointeur à une structure n'envoie
que l'adresse de la structure à la fonction. La fonction peut alors utiliser l'adresse pour
accéder directement aux membres de la structure, évitant ainsi les frais généraux liés à la
duplication de la structure. Il est donc plus efficace de passer un pointeur à une structure
que de passer la structure elle-même à une fonction. En conséquence, le programme de
l'illustration 19.3 peut être réécrit pour passer à la fonction DataReceive() un pointeur
qui pointe sur la structure. Le programme réécrit est illustré à la figure 19.4.

LISTE 19.4 Passage d'une fonction avec un pointeur qui


TYPE pointe sur une structure
1 : /* 19L04.c Pointer sur une
structure */ 2 : #include <stdio.h>
3 :
4 : struct computer
{ 5: float cost ;
6 : année int ;
7 : int cpu_speed ;
8 : char cpu_type[16] ;
9 : } ;
10 :
11 : typedef struct computer
SC ; 12 :
13 : void DataReceive(SC *ptr_s)
; 14 :
15 : main(void)
16 : {
17: SC modèle ;
18 :
19 : DataReceive(&model) ;
20 : printf("Voici ce que vous avez
saisi:\N") ; 21 : printf("Année : %d\n",
model.year) ;
22 : printf("Coût : $%6.2f\n", model.cost) ;
23 : printf("CPU type : %s\n", model.cpu_type) ;
24 : printf("CPU speed : %d MHz\n",
model.cpu_speed) ; 25 :
26 : retour 0 ;
27 : }
28 : /* définition de la
fonction */ 29 : void
DataReceive(SC *ptr_s) 30 : {
Comprendre les 325
structures

31 : printf("Le type de CPU à l'intérieur de votre


ordinateur ?") ; 32 : gets((*ptr_s).cpu_type) ;
33 : printf("The speed(MHz) of the CPU?\n")
; 34 : scanf("%d", &(*ptr_s).cpu_speed) ;
35 : printf("L'année de fabrication de votre
ordinateur?\N") ; 36 : scanf("%d",
&(*ptr_s).year) ;
37 : printf("Combien avez-vous payé pour
l'ordinateur ?") ; 38 : scanf("%f",
&(*ptr_s).cost) ;
39 : }

De même, j'obtiens une sortie identique à celle du programme de l'illustration 19.3 après
avoir exécuté l'exécutable (19L04.exe) du programme de l'illustration 19.4 :
Le type d'unité centrale de votre ordinateur ?
SORTIE Pentium
La vitesse (MHz) de l'unité centrale ?
100
L'année de fabrication de votre ordinateur ?
1996
Combien avez-vous payé pour l'ordinateur ?
1234.56
Voici ce que vous avez saisi :
Année : 1996
Coût : 1234,56
Type de CPU :
Pentium Vitesse du
CPU : 100 MHz

Le programme du Listing 19.4 est presque identique à celui du Listing 19.3, sauf
ANALYSE
que l'argument transmis à la fonction DataReceive() est un pointeur défini avec la
fonction
19
SC, c'est-à-dire struct computer. (De plus, la fonction DataReceive() n'a pas besoin
de renvoyer une copie de la structure, car elle peut accéder directement à tous les
membres de la structure originale, et non à une copie, et les modifier via le pointeur qui
lui a été transmis. C'est pourquoi le mot-clé void est préfixé au nom de la fonction à la
ligne 13.
L'instruction de la ligne 17 définit la variable de structure model. Et à la ligne 19,
l'adresse de la structure du modèle est transmise à la fonction DataReceive() en
appliquant l'opérateur d'adresse (&).
Lorsque l'on examine la définition de la fonction DataReceive() aux lignes 29 à 39, on
constate que le pointeur déréférencé *ptr_s est utilisé pour référencer les membres de la
structure du modèle. Par exemple, pour accéder au tableau de caractères de
cpu_type, (*ptr_s) est utilisé dans l'expression (*ptr_s).cpu_type pour indiquer au
compilateur que cpu_type est un membre de la structure pointée par le pointeur ptr_s.
Notez que le pointeur déréférencé *ptr_s doit être entouré de parenthèses (( et )).
326 Heure
En effet, l'ordre de préséance par défaut des19
opérateurs évaluerait l'opérateur . (point)
avant l'opérateur *, ce qui, dans ce cas, n'est pas notre intention.
Comprendre les 327
structures

Un autre exemple est l'expression &(*ptr_s).cpu_speed à la ligne 34, qui s'évalue à


l'adresse du membre cpu_speed de la structure pointée par le pointeur ptr_s. Là encore,
le pointeur déréférencé *ptr_s est entouré de parenthèses (( et )).
La sous-section suivante montre comment utiliser l'opérateur de flèche (->) pour faire
référence à un membre de la structure avec un pointeur.

Référencement d'un membre d'une structure avec ->


Vous pouvez utiliser l'opérateur de flèche -> pour faire référence à un membre de
la structure associé à un pointeur qui pointe sur la structure.
Par exemple, vous pouvez réécrire l'expression (*ptr_s).cpu_type dans le Listing 19.4
avec ceci :
ptr_s ->u_type

ou vous pouvez remplacer l'expression &(*ptr_s).cpu_speed par ceci :


&(ptr_s->cpu_speed)

L'opérateur -> étant plus prioritaire que l'opérateur &, vous pouvez omettre les
parenthèses dans l'expression ci-dessus et l'écrire comme suit :
&ptr_s->cpu_speed

En raison de sa plus grande clarté, l'opérateur -> est plus fréquemment utilisé dans les
programmes qui accèdent aux membres d'une structure via des pointeurs sur des
structures, plutôt que l'opérateur dot. L'exercice 3, plus tard dans cette heure, vous
donnera l'occasion de réécrire l'ensemble du programme du Listing 19.4 en utilisant
l'opérateur ->.

Tableaux de structures
En C, vous pouvez déclarer un tableau de structures en faisant précéder le nom du tableau
du nom de la structure. Par exemple, étant donné une structure dont le nom de balise est
x, la déclaration suivante peut être faite :

struct x array_of_structure[8] ;

déclare un tableau, appelé tableau_de_structure, de la structure x. Le tableau a huit


éléments, chaque élément étant une instance unique de la structure x.
Le programme présenté dans la liste 19.5 montre comment utiliser un tableau de
structures en imprimant deux haïkus japonais et les noms de leurs auteurs.
328 Heure
19

TYPE LISTE 19.5 Utilisation de tableaux de structures

1 : /* 19L05.c Tableaux de structures


*/ 2 : #include <stdio.h>
3 :
4 : struct haiku {
5 : int start_year ;
6 : int fin_année ;
7 : char author[16] ;
8 : char str1[32] ;
9 : char str2[32] ;
10 : char str3[32] ;
11 : } ;
12 :
13 : typedef struct haiku HK ;
14 :
15 : void DataDisplay(HK *ptr_s) ;
16 :
17 : main(void)
18 : {
19 : HK poème[2] = {
20: { 1641,
21 : 1716,
22 : "Sodo",
23 : "Leading me along",
24 : "mon ombre rentre à la maison",
25 : "en regardant la lune".
26 : },
27: { 1729,
28 : 1781,
29 :
30 :
"Chora",
"Un vent de tempête souffle",
19
31 : "de l'herbe",
32 : "la pleine lune grandit".
33 : }
34 : } ;
35 : int i ;
36 :
37 : for (i=0 ; i<2 ; i++)
38 : DataDisplay(&poème[i]) ;
39 :
40 : retour 0 ;
41 : }
42 : /* définition de la
fonction */ 43 : void
DataDisplay(HK *ptr_s) 44 : {
45 : printf("%s\n", ptr_s->str1) ;
46 : printf("%s\n", ptr_s->str2) ;

continue
Comprendre les 329
structures

LISTE 19.5 suite


47 : printf("%s\n", ptr_s->str3) ;
48 : printf("--- %s\n", ptr_s->auteur) ;
49 : printf(" (%d-%d)\n\n", ptr_s->start_year, ptr_s->end_year)
; 50 : }

Après avoir exécuté l'exécutable (19L05.exe) du programme de l'illustration 19.5, je vois


les deux morceaux de haïku japonais s'afficher sur l'écran de mon ordinateur :
Me guider
SORTIE mon ombre rentre à la
maison après avoir regardé
la lune.
--- Sodo
(1641-1716)

Un vent de tempête souffle


Parmi les herbes, la
pleine lune pousse.
--- Chora
(1729-1781)

Dans le Listing 19.5, un type de données structure, avec le nom de balise haiku,
ANALYSE
est déclaré dans les lignes 4-11. Le type de données structure contient deux
variables int et quatre variables char
comme membres. L'instruction de la ligne 13 crée un synonyme, HK, pour le type de
données struct haiku.
Ensuite, dans les lignes 19 à 34, un tableau de deux éléments, poème, est déclaré et
initialisé avec deux morceaux de haïku écrits respectivement par Sodo et Chora. Ce qui
suit est une copie des deux morceaux de haïku de poème :
"Leading me along",
"mon ombre rentre à la
maison", "en regardant la
lune".

et
"Un vent de tempête souffle,
"au milieu des herbes", "la
pleine lune pousse".

L'initialisateur inclut également les noms des auteurs et leurs années de naissance et
de décès (voir lignes 20-22 et lignes 27-29). Notez que le tableau poem, déclaré avec
HK, est bien un tableau de la structure haiku.

La fonction DataDisplay() est appelée deux fois dans une boucle for aux lignes 37 et
38. À chaque fois, l'adresse d'un élément du poème est transmise à la fonction
DataDisplay(). Selon la définition de la fonction aux lignes 43-50, DataDisplay()
330 Heure
19nom de l'auteur et la période à laquelle il a vécu.
imprime les trois chaînes d'un haïku, le
Comprendre les 331
structures

La sortie montre que le contenu stocké dans le tableau poem du haiku


s'affichent correctement à l'écran.

Structures imbriquées
Vous pouvez déclarer un membre de structure qui est lui-même une structure. Par
exemple, étant donné que le type de données de la structure est x, la déclaration suivante :
struct y
{ int
i ;
char ch[8] ;
struct x nested ;
} ;

déclare une structure imbriquée avec le nom de balise y. L'un des membres de la structure y
est une structure avec le nom de variable imbriquée qui est définie par le type de données de
structure x.
La liste 19.6 contient un exemple d'utilisation d'une structure imbriquée pour recevoir et
imprimer des informations sur un employé.

TYPE LISTE 19.6 Utilisation de structures imbriquées

1 : /* 19L06.c Utilisation de structures


imbriquées */ 2 : #include <stdio.h>
3 :
4 : struct département
{ 5: int code ;
6 : char name[32] ;
7 : char position[16] ;
8 : } ;
9 :
19
10 : typedef struct department DPT ;
11 :
12 : struct employee
{ 13: DPT d ;
14 : int id ;
15 : char name[32] ;
16 : } ;
17 :
18 : typedef struct employee EMPLY ;
19 :
20 : void InfoDisplay(EMPLY *ptr)
; 21 : void InfoEnter(EMPLY
*ptr) ; 22 :
23 : main(void)
24 : {
25 : EMPLY info = {

continue
332 Heure
19

LISTE 19.6 suite


26: { 1,
27 : "Marketing",
28 : "Gestionnaire"
29 : },
30 : 1,
31 : "B. Smith"
32 : } ;
33 :
34 : printf("Voici un exemple:\n")
; 35 : InfoDisplay(&info) ;
36 :
37 : InfoEnter(&info) ;
38 :
39 : printf("\NVoici ce que vous avez
saisi:\N") ; 40 : InfoDisplay(&info) ;
41 :
42 : retour 0 ;
43 : }
44 : /* définition de la
fonction */ 45 : void
InfoDisplay(EMPLY *ptr) 46 : {
47 : printf("Nom : %s\n", ptr->nom)
; 48 : printf("ID # : %04d\n", ptr->id)
;
49 : printf("Nom du service : %s\n", ptr-
>d.nom) ; 50 : printf("Code département :
%02d\n", ptr->d.code) ;
51 : printf("Votre position : %s\n", ptr-
>d.position) ; 52 : }
53 : /* définition de la
fonction */ 54 : void
InfoEnter(EMPLY *ptr) 55 : {
56 : printf("\NVeuillez saisir vos
informations:\N") ; 57: printf("Votre nom:\N")
;
58 : gets(ptr->nom) ;
59 : printf("Votre position:\N") ;
60 : gets(ptr->d.position) ;
61 : printf("Dept. name:\n") ;
62 : gets(ptr->d.name) ;
63 : printf("Dept. code:\n") ;
64 : scanf("%d", &(ptr->d.code))
; 65 : printf("Votre numéro d'employé
#:\n") ; 66: scanf("%d",
&(ptr->id)) ;
67 : }

Lorsque le fichier exécutable, 19L06.exe, est lancé, le contenu initial de la structure


imbriquée est d'abord imprimé. Je saisis ensuite mes informations sur l'emploi, qui
apparaissent en gras dans l'édition suivante et sont également affichées à l'écran :
Comprendre les 333
structures

En voici un exemple :
SORTIE Nom : B. Smith
ID # : 0001
Nom du département :
Marketing Code du
département : 01
Votre fonction : Manager

Veuillez saisir vos informations:\n")


; Votre nom :
T. Zhang
Votre position:\N") ;
Ingénieur
Nom du département :
R&D
Code du département :
3
Votre numéro d'identification d'employé :
1234

Voici ce que vous avez saisi :


Nom : T. Zhang
ID # : 1234
Nom du
département : R&D
Code du
département 03
Votre fonction : Ingénieur

Il existe deux types de données de structure dans le Listing 19.6. Le premier,


ANALYSE
appelé depart- ment, est déclaré aux lignes 4 à 8. Le second, employé, est
déclaré aux lignes
12-16, contient un membre du type de données de la structure du département. Par conséquent, l'élément
est un type de données de structure imbriquée.
Deux synonymes, DPT pour le type de données struct department et EMPLY pour le type de
19
données struct employee, sont créés dans deux déclarations typedef respectivement aux
lignes 10 et
18. Dans le programme, il y a deux fonctions, InfoDisplay() et InfoEnter(), dont
les prototypes sont déclarés avec un pointeur sur un EMPLY comme argument (voir
lignes 20 et 21).
Les instructions des lignes 25 à 32 initialisent une structure imbriquée, appelée info et
dont le type de données est EMPLY. Notez que les accolades imbriquées ({ et }) des lignes
26 et 29 contiennent les initialisateurs de la structure d de DPT qui est imbriquée dans la
structure info.
Ensuite, l'instruction de la ligne 35 affiche le contenu initial de la structure
d'information imbriquée en appelant la fonction InfoDisplay(). La ligne 37 appelle
334 Heure
la fonction InfoEnter() 19
pour demander à l'utilisateur de saisir ses informations d'emploi
et de les enregistrer dans la structure info. La fonction InfoDisplay() est appelée à
nouveau à la ligne 40 pour afficher les informations saisies par l'utilisateur et stockées
dans la structure imbriquée.
Les définitions des deux fonctions, InfoDisplay() et InfoEnter(), figurent
respectivement aux lignes 45-52 et 54-67.
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu
Visitez www.DeepL.com/pro pour en savoir plus.

330 Heure
19

Résumé
Dans cette leçon, vous avez appris les concepts très importants suivants sur les structures en C :
• Vous pouvez regrouper des variables de différents types à l'aide d'un type de données appelé
structure.
• Les éléments de données d'une structure sont appelés membres de la structure.
• Le mot-clé struct est utilisé pour commencer une déclaration de structure ou
une définition de variable de structure.
• L'opérateur point (.) est utilisé pour séparer un nom de structure et un nom de
membre lors de la référence au membre de la structure.
• L'opérateur de flèche (->) est couramment utilisé pour référencer un membre d'une
structure à l'aide d'un pointeur sur la structure.
• Une structure peut être transmise à une fonction et une fonction peut renvoyer une
structure à l'appelant.
• Il est plus efficace de passer un pointeur sur une structure lors de l'appel d'une
fonction que de passer la structure entière en tant qu'argument. En outre, si un
pointeur est utilisé, la fonction peut modifier directement le contenu de la structure.
• Les tableaux de structures sont autorisés en C.
• Vous pouvez enfermer une structure dans une autre structure. C'est ce qu'on
appelle une structure imbriquée.
Dans la prochaine leçon, vous apprendrez à utiliser les unions pour collecter des données différentes en C.

Q&R
Q Pourquoi avez-vous besoin de structures ?
A Il est souvent nécessaire de collecter et de regrouper des données pertinentes les
unes par rapport aux autres, mais de types différents. Le type de données struct
offre un moyen pratique de regrouper des éléments de données de types
différents.
Q Comment référencer un membre de la structure ?
R Vous pouvez faire référence à un membre de la structure en faisant précéder le
nom du membre de la structure du nom de la variable de la structure et d'un
opérateur point (.). Si vous accédez à la structure via un pointeur, vous pouvez
utiliser le nom du pointeur, l'opérateur de flèche (->), puis le nom du membre
pour référencer le membre de la structure.
Comprendre les 331
structures

Q Pourquoi est-il plus efficace de passer un pointeur qui fait référence à une
structure à une fonction ?
A Lorsqu'une structure entière est transmise à une fonction, une copie de la structure
est effectuée et enregistrée dans un bloc de stockage temporaire. Une fois que la
copie est modifiée par la fonction, elle doit être renvoyée et réécrite dans la
mémoire qui contient le contenu original de la structure. En revanche, si l'on passe
à une fonction un pointeur qui pointe sur une structure, on transmet simplement
l'adresse de la structure à la fonction au lieu de lui transmettre une copie de la
structure entière. La fonction peut alors accéder à l'emplacement original de la
structure en mémoire et modifier le contenu de la structure sans dupliquer la
structure dans un stockage temporaire. Il est donc plus efficace de transmettre un
pointeur de structure que de transmettre la structure elle-même à une fonction.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Qu'est-ce qui ne va pas dans la déclaration de structure suivante ?
struct automobile {
int year ;
char model[8] ;
int engine_power 19
;
poids flottant ;
}

2. Combien de variables de structure sont définies dans l'instruction suivante ?


struct x {int y ; char z} u, v, w ;

3. Étant donné une déclaration de structure


struct automobile {
int year ;
char model[8]} ;

et deux modèles de voitures, Taurus et Accord, fabriquées en 1997, initialiser un


tableau de deux éléments, car, des structures automobiles.
332 Heure
19

Exercices
1. Etant donné la déclaration et la définition suivantes d'une structure :
struct automobile {
int year ;
char model[10] ;
int engine_power
; double weight
;
} sedan =
{ 1997,
"Nouveau
modèle", 200,
2345.67} ;

écrire un programme permettant d'afficher à l'écran les valeurs initiales détenues par la structure.
2. Réécrivez le programme du Listing 19.2. Cette fois, créez une fonction capable
d'afficher le contenu de la structure du salarié. Ensuite, faites des appels à la
fonction en lui passant la structure.
3. Réécrivez le programme du Listing 19.4. Cette fois, utilisez l'opérateur de flèche
(->) avec les pointeurs sur les structures.
4. Réécrivez le programme du Listing 19.5. Cette fois, ajoutez un tableau de
pointeurs déclaré avec HK. Passez ensuite chaque élément du tableau de
pointeurs à la fonction DataDisplay().
HEURE 20
Comprendre les syndicats
Se réunir est un début ; rester
ensemble est un progrès ;
travailler ensemble est une
réussite.

-T. Roosevelt
Dans la leçon de l'heure précédente, vous avez appris à stocker des données
de différents types dans des structures. Dans cette heure, vous apprendrez
une autre façon de collecter des données de types différents en utilisant les
unions. Cette leçon aborde les sujets suivants :

• Comment déclarer et définir les unions


• Comment initialiser les syndicats
• Les différences entre les syndicats et les structures
• Unions imbriquées avec des structures
• Manipulation du champ de bits avec struct
334 Heure
20

Qu'est-ce qu'un syndicat ?


Une union est un bloc de mémoire utilisé pour contenir des données de types différents.
En C, une union est similaire à une structure, sauf que les données enregistrées dans
l'union sont superposées afin de partager le même emplacement mémoire. En d'autres
termes, ils partagent tous la même adresse de départ, contrairement à une structure où
chaque membre dispose de sa propre zone de mémoire. De plus amples détails sur les
différences entre les unions et les structures sont abordés dans les sections suivantes.
Tout d'abord, examinons la syntaxe des unions.

Déclarer des syndicats


La syntaxe de déclaration d'une union est similaire à celle d'une structure. Voici un
exemple de déclaration d'une union :
union automobile {
int year ;
char model[8] ;
int engine_power ;
float weight ;
} ;

Ici, union est le mot-clé qui spécifie le type de données de l'union. automobile est le
nom de la balise de l'union. Les variables année, modèle, puissance du moteur et poids
sont membres de l'union et sont déclarées entre accolades ({ et }). La déclaration d'union
se termine par un point-virgule ( ;).
Comme un nom de balise de structure, un nom de balise d'union est une étiquette pour
une union, qui est utilisée par le compilateur pour identifier l'union.

Définition des variables de l'Union


Vous pouvez définir des variables d'union après avoir déclaré une union. Par exemple, les
variables d'union suivantes sont définies avec l'union étiquetée avec automobile de la
section précédente :
union automobile berline, pick-up, sport_utility ;

Ici, les trois variables, sedan, pickup et sport_utility, sont définies comme des vari-
ables d'union.
Bien entendu, vous pouvez déclarer une union et définir les variables de l'union dans une
seule déclaration. Par exemple, vous pouvez réécrire la déclaration et la définition de
l'union précédente comme suit :
union automobile {
int year ;
char model[8] ;
int engine_power ;
float weight ;
} sedan, pickup, sport_utility ;
Comprendre les 335
syndicats

Ici, trois variables d'union, sedan, pickup et sport_utility, sont définies par l'union de
automobile, qui compte quatre membres de types de données différents. Si vous déclarez
une union et définissez les variables de l'union dans une seule déclaration, et qu'il n'y a
pas d'autres définitions de variables d'union avec l'union, vous pouvez omettre le nom de
balise de l'union. Par exemple, le nom de balise automobile peut être omis dans la
définition de l'union comme suit :
union {
année ;
char model[8] ;
int engine_power ;
float weight ;
} sedan, pickup, sport_utility ;

Référencement d'une union avec . ou ->


En plus d'être utilisé pour référencer les membres d'une structure, l'opérateur point (.)
peut être utilisé pour référencer les membres d'une union. Par exemple, l'instruction
suivante attribue la valeur 1997 à l'un des membres de l'union sedan :
sedan.year = 1997 ;

Ici, l'opérateur point est utilisé entre le nom de l'union sedan et le nom du membre year.
De plus, si vous définissez un pointeur ptr comme ceci :
union automobile *ptr ;

vous pouvez faire référence à l'un des membres de l'union de la manière suivante :
ptr->année = 1997 ;

Ici, l'opérateur de flèche (->) est utilisé pour référencer l'année du membre de l'union avec le pointeur
ptr.

Le programme de la liste 20.1 donne un autre exemple de la manière de référencer et


d'affecter des valeurs aux membres d'une union.

20
TYPE LISTE 20.1 Référencement des membres d'un syndicat
1 : /* 20L01.c Référencement d'une
union */ 2 : #include <stdio.h>
3 : #include <string.h>
4 :
5 : main(void)
6 : {
7 : menu de l'union {
8 : char name[23] ;

continue
336 Heure
20

LISTE 20.1 suite


9 : double prix ;
10: } plat ;
11 :
12 : printf("Le contenu assigné à l'union séparément:\n") ;
13: /* nom de référence */
14 : strcpy(dish.name, "Poulet aigre-doux") ; 15
: printf("Nom du plat : %s\n", nom.du plat)
;
16 : /* prix de référence
*/ 17: dish.price = 9.95 ;
18 : printf("Prix du plat : %5.2f\n",
plat.prix) ; 19 :
20 : retour 0 ;
21 : }

Après avoir exécuté l'exécutable 20L01.exe du programme de la liste 20.1, j'obtiens les
résultats suivants sur l'écran de mon ordinateur :
Le contenu attribué à l'union séparément :
SORTIE Nom du plat : Plat de poulet aigre-
doux Prix : 9,95

L'objectif du programme du Listing 20.1 est de vous montrer comment


ANALYSE
référencer les membres d'une union à l'aide de l'opérateur dot.
Dans la fonction main(), une union, appelée plat, est d'abord définie avec le type de
données union menu dans les lignes 7 à 10. L'union comporte deux membres, nom et
prix.

Ensuite, l'instruction de la ligne 14 copie la chaîne "Poulet aigre-doux" dans le


tableau de caractères name qui est l'un des membres de l'union. Notez que l'expression
dish.name est utilisée comme premier argument de la fonction strcpy() à la ligne 14.
Lorsque le compilateur voit l'expression, il sait que vous voulez faire référence à
l'emplacement mémoire de name qui est membre de l'union dish.
La fonction strcpy() est une fonction C qui copie le contenu d'une chaîne de
caractères, indiqué par le deuxième argument de la fonction, dans la mémoire indiquée
par le premier argument de la fonction. J'ai inclus le fichier d'en-tête string.h dans le
programme avant d'appeler la fonction strcpy(). (Voir ligne 3.)
La ligne 15 imprime le contenu copié dans le tableau de noms en utilisant une nouvelle
fois l'expression dish.name.
L'instruction de la ligne 17 affecte la valeur 9.95 à la variable double prix, qui est un
autre membre de l'union plat. Notez que l'expression dish.price est utilisée pour faire
référence au membre de l'union. Ensuite, la ligne 18 affiche la valeur affectée à prix en
appelant la fonction printf() et en passant l'expression prix.plat comme argument à
la fonction.
Comprendre les 337
syndicats

D'après les résultats affichés dans la sortie, les deux membres de l'union de paraboles, nom
et le prix, ont été référencés avec succès et des valeurs correspondantes leur ont été attribuées.

Syndicats et structures
Vous remarquerez peut-être que dans le Listing 20.1, j'ai assigné une valeur à un
membre de l'union de paraboles, puis j'ai immédiatement imprimé la valeur
assignée avant de passer au membre suivant de l'union. En d'autres termes, je n'ai pas
affecté de valeurs à tous les membres de l'union avant d'imprimer chaque valeur affectée
à chaque membre de l'union.
Je l'ai fait à dessein pour la raison expliquée dans la section suivante. Continuez donc à
lire. (Dans l'exercice 1 à la fin de cette leçon, vous verrez un résultat différent lorsque
vous réécrirez le programme de l'illustration 20.1 en échangeant l'ordre entre les
instructions des lignes 15 et 17).

Initialisation d'une union


Comme indiqué précédemment dans cette leçon, les éléments de données d'une union
sont superposés au même emplacement mémoire. En d'autres termes, l'emplacement
initial de la mémoire d'une union est partagé par tous les membres de l'union à des
moments différents. La taille d'une union est au moins égale à la taille de la plus grande
donnée de la liste des membres de l'union. Ainsi, elle est suffisamment grande pour
contenir tous les membres de l'union, un à la fois. Par conséquent, il n'est pas judicieux
d'initialiser tous les membres d'une union en même temps, car la valeur du dernier
membre initialisé écrase la valeur du membre précédent. Vous n'initialisez un membre
d'une union que lorsque vous êtes prêt à l'utiliser. La valeur contenue dans une union est
toujours la dernière valeur attribuée à un membre de l'union.
Par exemple, si vous déclarez et définissez une union sur une machine 16 bits (c'est-à-
dire que le type de données int a une longueur de 2 octets) comme ceci :
union u {
char ch
; int x
;
20
} a_union ;

alors l'instruction suivante initialise la variable char ch avec la constante de caractère


'H' :

a_union.ch = 'H' ;

et la valeur contenue dans l'union a_union est la constante de caractère 'H'. Cependant,
si la variable int x est initialisée par l'instruction suivante :
a_union.x = 365 ;
338 Heure
20

alors la valeur contenue dans l'union a_union devient la valeur 365. La figure 20.1
illustre le changement de contenu de l'union au cours des deux initialisations.

FIGURE 20.1 0x1000 0x1001 0x1002


Le contenu de a_union.ch = 'H' 'H'
l'union a_union est
le même que le
Le bloc de mémoire
contenu attribué à de l'union (en octets)
l'un de ses membres.
0x1000 0x1001 0x1002

a_union.x = 365 365

(Supposons que 0x1000 soit l'adresse


de départ de l'union).

Selon la norme ANSI C, une union peut être initialisée en attribuant une valeur au
premier membre de l'union. Par exemple, dans l'instruction suivante :
union u {
char ch
; int x
;
} a_union = {'H'} ;

l'union a_union est dite initialisée car la constante de caractère 'H' est affectée au
premier membre de l'union, ch.
Si le premier membre d'une union est une structure, la structure entière doit être
initialisée avec une liste de valeurs avant que l'union ne soit considérée comme
initialisée.
Voyons ce qui se passe si vous essayez d'attribuer des valeurs à tous les membres d'une
union. Le Listing 20.2 donne un exemple de ce type.

LISTE 20.2 Les membres d'une union partagent le même


TYPE emplacement mémoire

1 : /* 20L02.c : Partage de mémoire dans


les unions */ 2 : #include <stdio.h>
3 :
4 : main(void)
5 : {
6 : employé du syndicat {
7 : int start_year ;
8 : int dpt_code ;
9 : int id_number ;
10: } info ;
Comprendre les 339
syndicats

11 :
12 : /* initialisation de start_year */
13 : info.start_year = 1997 ;
14 : /* initialisation du dpt_code */
15 : info.dpt_code = 8 ;
16 : /* initialisation de l'id */
17 : info.id_number = 1234 ;
18 :
19 : /* afficher le contenu de l'union */
20 : printf("Start Year : %d\n", info.start_year) ;
21 : printf("Dpt. Code : %d\n", info.dpt_code) ;
22 : printf("ID Number : %d\n", info.id_number) ;
23 :
24 : retour 0 ;
25 : }

Après la création et l'exécution de l'exécutable 20L02.exe, la sortie suivante s'affiche à


SORTIE
l'écran :
Année de 1234
démarrage :
Dpt. Code 1234
:
Numéro 1234
d'identifi
cation :

Comme vous pouvez le voir dans le Listing 20.2, une union appelée info possède
ANALYSE trois variables int mem- bers, start_year, dpt_code et id_number. (Voir les
lignes 6 à 10.) Ensuite, ces trois
Les membres de l'union sont affectés à différentes valeurs consécutivement aux lignes
13, 15 et 17. Aux lignes 20 à 22, vous essayez d'imprimer les valeurs attribuées aux trois
membres.
Cependant, le résultat montre que chaque membre de l'union d'information a la même valeur,
1234, qui est l'entier attribué au troisième membre de l'union, id_number. Notez que
numéro_id est le membre auquel 1234 a été attribué en dernier ; l'union d'informations
contient en effet la dernière valeur attribuée à ses membres.

La taille d'une Union


On vous a dit que les membres d'une union partagent tous le même emplacement
mémoire. La taille d'une union est au moins égale à la taille du membre le plus grand de 20
l'union.
Contrairement à une union, tous les membres d'une structure peuvent être initialisés
ensemble sans écrasement. En effet, chaque membre d'une structure dispose de sa propre
mémoire. La taille d'une structure est au moins égale à la somme des tailles de ses
membres, et non à la taille du membre le plus grand, comme c'est le cas avec une union.
La liste 20.3 contient un programme qui mesure la taille d'une union ainsi que la taille d'une
structure. La structure a exactement les mêmes membres que l'union.
340 Heure
20

TYPE LISTE 20.3 Mesure de la taille d'un syndicat


1 : /* 20L03.c La taille d'un
syndicat */ 2 : #include <stdio.h>
3 : #include <string.h>
4 :
5 : main(void)
6 : {
7 : union u {
8 : double x ;
9 : int y ;
10: } a_union ;
11 :
12 : struct s {
13 : double x ;
14 : int y ;
15: } a_struct ;
16 :
17 : printf("The size of double : %d-
byte\n", 18 : sizeof(double)) ;
19: printf("The size of int: %d-byte\n",
20 : sizeof(int)) ;
21 :
22 : printf("The size of a_union : %d-
byte\n", 23 : sizeof(a_union)) ;
24 : printf("The size of a_struct : %d-
byte\n", 25 : sizeof(a_struct)) ;
26 :
27 : retour 0 ;
28 : }

Le compilateur de votre machine peut générer plusieurs messages d'avertissement, du


type "variables locales non référencées". En effet, l'union a_union et la structure
a_struct ne sont pas initialisées dans le programme. Vous pouvez ignorer ces messages
d'avertissement car ils ne s'appliquent pas à ce que nous faisons ici. La sortie suivante
s'affiche sur l'écran de mon ordinateur après la création et l'exécution de l'exécutable
20L03.exe :
Taille du double : 8 octets
SORTIE La taille d'un int :
2 octets
La taille d'une_union : 8
octets La taille
d'une_struct : 10 octets

L'objectif du programme du Listing 20.3 est de montrer la différence entre une


ANALYSE
allocation de mémoire d'union et une allocation de mémoire de structure, bien
que les deux types d'allocation de mémoire d'union et de structure ne soient pas
identiques.
Le syndicat et la structure sont composés des mêmes membres.
Comprendre
Une union, appelée a_union lesaux lignes 7 à 10 ; elle possède deux membres,341
, est définie
syndicats
une variable double x et une variable int y. En outre, une structure, appelée
a_structure et définie aux lignes 12 à 15, se compose également de deux membres,
une variable double x et une variable int y.
342 Heure
20

Les instructions des lignes 17 à 20 mesurent d'abord la taille des types de données
double et int sur la machine hôte. Par exemple, sur ma machine, la taille du type de
données double est de 8 octets et celle du type de données int est de 2 octets.
Les lignes 22 à 25 mesurent ensuite la taille de l'union a_union et de la structure
a_structure, respectivement. La sortie montre que la taille de a_union est de 8 octets
sur ma machine.
La taille de la structure, en revanche, est de 10 octets sur ma machine, car il doit y
avoir suffisamment d'espace mémoire pour le double et l'int dans la structure.

Utilisation des syndicats


Concentrons-nous à présent sur les applications des unions. Fondamentalement, il existe deux
types d'applications des unions, qui sont présentées dans les deux sections suivantes.

Référencement différent d'un même emplacement mémoire


La première application des unions consiste à référencer le même emplacement mémoire
avec différents membres de l'union.
Pour mieux comprendre comment référencer la même mémoire avec différents membres
d'une union, examinons le programme de l'illustration 20.4, qui utilise les deux membres
d'une union pour référencer le même emplacement mémoire. (Vous supposez que le type
de données char a une longueur de 1 octet et que le type de données int a une
longueur de 2 octets).

LISTE 20.4 Référencement d'un même emplacement mémoire avec


TYPE des membres d'union différents

1 : /* 20L04.c : Référencement de la même mémoire de différentes


manières */ 2 : #include <stdio.h>
3 :
4 : union u{
5 : char ch[2] ; 20
6 : int num ;
7 : } ;
8 :
9 : int UnionInitialize(union u val)
; 10 :
11 : main(void)
12 : {
13 : union u val ;
14 : int x ;
15 :

continue
Comprendre les 343
syndicats

LISTE 20.4 suite


16 : x = UnionInitialize(val)
; 17 :
18 : printf("Les deux caractères détenus par
l'union:\n") ; 19: printf("%c\n", x & 0x00FF) ;
20 : printf("%c\n", x >> 8) ;
21 :
22 : retour 0 ;
23 : }
24 : /* définition de la fonction */
25 : int UnionInitialize(union u val)
26 : {
27 : val.ch[0] = 'H' ;
28 : val.ch[1] = 'i'
; 29 :
30 : retour val.num ;
31 : }

La sortie suivante s'affiche sur l'écran de mon ordinateur après l'exécution de l'exécutable
20L04.exe est créé et exécuté :
Les deux caractères détenus par le syndicat :
SORTIE H
i

Comme vous le voyez dans le programme du Listing 20.4, une union appelée
ANALYSE
val est définie à la ligne 13, qui contient deux membres. L'un est un tableau de
caractères ch et l'autre est un tableau de caractères
la variable int num. Si un type de données char a une longueur de 1 octet et un type de
données int a une longueur de 2 octets, le tableau ch et la variable integer num ont la
même longueur de mémoire sur ces machines.
Une fonction nommée UnionInitialize() est appelée et reçoit le nom de l'union val à la ligne
16. La définition de la fonction UnionInitialize() est présentée aux lignes 25 à 31.
La définition de la fonction montre que les deux éléments du tableau de caractères ch
sont initialisés avec deux constantes de caractères, 'H' et 'i' (aux lignes 27 et 28).
Comme le tableau de caractères ch et la variable int num partagent le même
emplacement mémoire, vous pouvez renvoyer la valeur de num qui contient le même
contenu que le tableau ch. (Voir ligne 30.) Ici
vous avez utilisé les deux membres, ch et num, dans l'union val pour référencer le
même emplacement mémoire et le même contenu de l'union.
La valeur renvoyée par la fonction UnionInitialize() est affectée à une variable int x
à la ligne 16 de la fonction main(). Les instructions des lignes 19 et 20 impriment les
deux octets de la variable int num. Chaque octet de num correspond à un caractère qui a
été utilisé pour initialiser le tableau ch parce que num et ch sont tous deux dans la même
union et ont le même contenu que l'union. La ligne 19 affiche l'octet de poids faible de
344 num, obtenu en évaluant Heure
20
Comprendre les 345
syndicats

l'expression x & 0x00FF. À la ligne 20, l'octet de poids fort de num est obtenu en
décalant la variable x vers la droite de 8 bits, c'est-à-dire en utilisant l'opérateur de
décalage vers la droite dans l'expression x >> 8. (L'opérateur binaire (&) et l'opérateur
de décalage (>>) ont été présentés dans l'Heure 8, "Utilisation des opérateurs
conditionnels").
La sortie montre que le contenu de l'union val s'affiche correctement à l'écran.
La figure 20.2 montre l'emplacement des deux constantes de caractères dans la mémoire.

FIGURE 20.2 Faibl


Les emplacements de e 0x1000
mémoire des deux 'H'
constantes de 0x1001
caractères. 'i'
0x1002

Haut

(Supposons que 0x1000 soit l'adresse de départ).

Il existe deux formats pour stocker une quantité de plusieurs octets, comme
la variante int num du Listing 20.4. L'un de ces formats est appelé format
little-endian ; l'autre est le format big-endian.
Pour le format little-endian, les octets de poids fort d'une quantité de
plusieurs octets sont stockés à des adresses mémoire plus élevées et les
octets de poids faible sont stockés à des adresses plus basses. Le format
little-endian est utilisé par les microprocesseurs 80x86 d'Intel. L'unité
centrale de mon ordinateur est un microprocesseur Pentium, qui est l'un
des mem- bres de la famille 80x86. Par conséquent, dans le Listing 20.4, la
constante de caractère 'H', qui est un octet de poids faible, est stockée à
l'adresse inférieure. 'i' est stocké à l'adresse supérieure car il s'agit d'un
octet de poids fort. 20
Le format big-endian est exactement le contraire. C'est-à-dire que les octets
de poids fort sont stockés à des adresses inférieures et les octets de poids

Assouplir les structures


La deuxième application des unions consiste à imbriquer une union dans une structure
afin que cette dernière puisse contenir différents types de valeurs.
Par exemple, supposons que vous souhaitiez écrire un programme qui demande à
l'utilisateur le nom d'une société de câblodistribution ou d'une société d'antenne
parabolique qui fournit des services à l'utilisateur. Supposons que l'utilisateur utilise un
câble ou une antenne parabolique à la maison, mais pas les deux. Dans ce cas, si vous
346 définissez Heure
20
Comprendre les 347
syndicats

deux tableaux de caractères pour stocker respectivement le nom de la compagnie de câble


et le nom de la compagnie d'antenne parabolique, l'un des tableaux sera vide en raison de
l'hypothèse. Dans ce cas, vous pouvez déclarer une union avec les deux tableaux de
caractères comme membres de sorte que l'union puisse contenir soit un nom de
compagnie de câble, soit un nom de compagnie d'antenne parabolique, en fonction de la
saisie de l'utilisateur. Le Listing 20.5 montre comment écrire un programme avec une
telle union.

TYPE LISTE 20.5 Rendre une structure flexible


1 : /* 20L05.c : Utilisation
des unions */ 2 : #include
<stdio.h>
3 : #include <string.h>
4 :
5 : struct survey {
6 : char name[20]
;
7 : char c_d_p ;
8 : âge int ;
9 : int heure_par_semaine ;
10 : union {
11 : char cable_company[16] ;
12 : char dish_company[16] ;
13: } fournisseur ;
14 : } ;
15 :
16 : void DataEnter(struct survey *s)
; 17 : void DataDisplay(struct survey
*s) ; 18 :
19 : main(void)
20 : {
21 : enquête
structurelle tv ;
22 :
23 : DataEnter(&tv) ;
24 : DataDisplay(&tv) ;
25 :
26 : retour 0 ;
27 : }
28 : /* définition de la fonction */
29 : void DataEnter(struct survey
*ptr) 30 : {
31 : char is_yes[4] ;
32 :
33 : printf("Utilisez-vous le câble à la maison ? (Oui ou
Non)\n") ; 34 : gets(is_yes) ;
35 : si ((is_yes[0] == 'Y') ||
36 : (is_yes[0] == 'y')){
37 : printf("Enter the cable company name:\n")
; 38 : gets(ptr->provider.cable_company) ;
39 : ptr->c_d_p = 'c'
348 Heure
; 40: } else { 20
41 : printf("Utilisez-vous une antenne parabolique ? (Oui ou Non)\n") ;
Comprendre les 349
syndicats

42 : gets(is_yes) ;
43 : si ((is_yes[0] == 'Y') ||
44 : (is_yes[0] == 'y')){
45 : printf("Enter the satellite dish company name:\n")
; 46 : gets(ptr->provider.dish_company) ;
47 : ptr->c_d_p = 'd'
; 48: } else {
49 : ptr->c_d_p = 'p'
; 50 : }
51 : }
52 : printf("Veuillez entrer votre
nom:\N") ; 53 : gets(ptr->nom) ;
54 : printf("Votre âge:\N") ;
55 : scanf("%d", &ptr->age) ;
56 : printf("Combien d'heures passez-vous à regarder la télévision
par semaine:\n") ; 57 : scanf("%d", &ptr->hour_per_week) ;
58 : }
59 : /* définition de la fonction */
60 : void DataDisplay(struct survey
*ptr) 61 : {
62 : printf("\NVoici ce que vous avez
saisi:\N") ; 63 :printf("Nom : %s\n", ptr->nom)
;
64 : printf("Age : %d\n", ptr->age) ;
65 : printf("Heure par semaine : %d\n", ptr-
>hour_per_week) ; 66: if (ptr->c_d_p == 'c')
67 : printf("Votre compagnie de câble est
: %s\n", 68 : ptr-
>provider.cable_company) ;
69 : else if (ptr->c_d_p = 'd')
70 : printf("Votre société d'antenne parabolique
est : %s\n", 71 : ptr->provider.dish_company) ;
72 : autre
73 : printf("Vous n'avez pas le câble ou une antenne
satellite.\N") ; 74 : printf("\NMerci et au revoir!\N") ;
75 : }

Lorsque le programme exécutable 20L05.exe est lancé, je saisis mes réponses à l'enquête
et le résultat suivant s'affiche (mes réponses sont en caractères gras dans le résultat) :
Utilisez-vous le câble à la maison ? (Oui ou Non)
20
SORTIE Non
Utilisez-vous une antenne parabolique (oui ou non) ?
Oui
Entrez le nom de l'entreprise de l'antenne parabolique :
Société ABCD
Veuillez saisir votre nom :
Tony Zhang
Votre âge :
30
Combien d'heures passez-vous à regarder la télévision par semaine ?
8
350 Heure
20

Voici ce que vous avez inscrit :


Nom : Tony Zhang
Âge : 30
Heures par semaine : 8
Votre société d'antenne parabolique est : Société ABCD

Merci et au revoir !

ANALYSE Comme vous pouvez le voir dans les lignes 5 à 14, un type de données de structure avec le nom
de balise survey
est déclarée, et dans celle-ci, une union imbriquée appelée provider a deux membres, l'élément
le tableau cable_company et le tableau dish_company. Les deux membres de l'union
sont utilisés pour contenir les noms des sociétés de câble ou d'antenne parabolique, en
fonction des données fournies par l'utilisateur.
Les instructions des lignes 16 et 17 déclarent deux fonctions, DataEnter() et
DataDisplay(), dans lesquelles un pointeur avec la structure survey est transmis à
chaque fonction en tant qu'argument.
Une structure appelée tv est définie à la ligne 21 dans la fonction main(). Ensuite, aux
lignes 23 et 24, les fonctions DataEnter() et DataDisplay() sont toutes deux appelées
avec l'adresse de la structure tv comme argument.
Les lignes 29 à 58 contiennent la définition de la fonction DataEnter(), qui demande à
l'utilisateur de saisir les informations appropriées en fonction des questions de l'enquête.
Selon l'hypothèse que vous avez formulée précédemment, l'utilisateur peut utiliser soit le
câble, soit une antenne parabolique, mais pas les deux. Si l'utilisateur utilise le câble, la
ligne 38 reçoit le nom de la société de câble saisi par l'utilisateur et l'enregistre dans le
fichier
la mémoire référencée par l'un des membres de l'union de fournisseurs,
cable_company.

Si l'utilisateur utilise une antenne parabolique, la ligne 46 stocke le nom de la société


d'antenne parabolique saisi par l'utilisateur dans le même emplacement du syndicat des
fournisseurs. Mais cette fois, le nom d'un autre membre de l'union, dish_company, est
utilisé pour référencer l'emplacement mémoire. Vous voyez maintenant comment
sauvegarder la mémoire en plaçant deux éléments de données exclusifs dans une union.
En fait, le programme prend en charge une autre situation dans laquelle l'utilisateur ne
dispose ni du câble ni d'une antenne parabolique. Dans ce cas, la variable char c_d_p,
membre de la structure, est affectée de la constante de caractère "p".
Les lignes 60 à 75 définissent la fonction DataDisplay() qui imprime à l'écran les
informations saisies par l'utilisateur. La sortie affichée ici est un échantillon que j'ai
réalisé en exécutant le programme exécutable du Listing 20.5 sur ma machine.
Comprendre les 351
syndicats

Définition de champs de bits avec struct


Dans cette section, vous allez revisiter notre vieil ami le mot-clé struct pour déclarer
un très petit objet. Vous utiliserez ensuite cet objet avec des unions.
Comme vous le savez, char est le plus petit type de données en C. Le type de données
char a une longueur d'un octet. Cependant, à l'aide du mot-clé struct, vous pouvez
déclarer un objet plus petit - un champ de bits - qui vous permet d'accéder à un seul bit.
Un bit ne peut contenir que l'une des deux valeurs suivantes : 1 ou 0.
La forme générale de déclaration et de définition des champs de bits est la suivante
struct tag_name {
data_type name1 :
length1 ; data_type name2
: lenght2 ;
. . .
type de données nomN : longueurN ;
} variable_list ;

Ici, le mot-clé struct est utilisé pour commencer la déclaration. tag_name est le nom de
la balise du type de données struct. data_type, qui doit être soit int, unsigned int ou
signed int, spécifie le type de données des champs de bits. name1, name2 et nameN sont
les noms des champs de bits. length1, length2 et lengthN indiquent les longueurs des
champs de bits, spécifiées en bits. La longueur d'un champ de bits ne peut pas dépasser la
longueur du type de données int. variable_list contient les noms des variables du
champ de bits.
Par exemple, l'instruction suivante définit une structure appelée "jumpers" avec trois
membres de champ de bits :
struct bf {
int jumper1 : 1 ;
int jumper2 : 2 ;
int jumper3 : 3 ;
} jumpers ;

Ici, jumper1, jumper2 et jumper3 sont les trois champs de bits avec des longueurs de 1
bit, 2 bits et 3 bits, respectivement. La figure 20.3 illustre les allocations de mémoire des 20
champs de 3 bits.

FIGURE 20.3 1 bit 2 bits 3 bits 10 bits

Les allocations de sauteur1 sauteur inutilisé


cavalier 3
mémoire du cavalier
1, du cavalier 2 et
du cavalier 3 dans
un int. 16 bits.
352 Heure
20

Le programme du Listing 20.6 est un exemple d'utilisation des champs de bits définis
avec struct. En fait, le programme du Listing 20.6 est une version modifiée du
programme du Listing 20.5.

TYPE LISTE 20.6 Application des champs de bits


1 : /* 20L06.c : Application des
champs de bits */ 2 : #include
<stdio.h>
3 : #include <string.h>
4 :
5 : struct bit_field
{ 6: int cable : 1
;
7 : plat int : 1 ;
8 : } ;
9 :
10 : struct survey
{ 11 : char name[20]
;
12 : struct bit_field c_d
; 13: int age ;
14 : int hour_per_week ;
15 : union {
16 : char cable_company[16] ;
17 : char dish_company[16] ;
18: } fournisseur ;
19 : } ;
20 :
21 : void DataEnter(struct survey *s)
; 22 : void DataDisplay(struct survey
*s) ; 23 :
24 : main(void)
25 : {
26 : enquête
structurelle tv ;
27 :
28 : DataEnter(&tv) ;
29 : DataDisplay(&tv) ;
30 :
31 : retour 0 ;
32 : }
33 : /* définition de la fonction */
34 : void DataEnter(struct survey
*ptr) 35 : {
36 : char is_yes[4] ;
37 :
38 : printf("Utilisez-vous le câble à la maison ? (Oui ou
Non)\n") ; 39 : gets(is_yes) ;
40 : si ((is_yes[0] == 'Y') ||
41 : (is_yes[0] == 'y')){
42 : printf("Enter the cable company name:\n")
; 43 : gets(ptr->provider.cable_company) ;
Comprendre les 353
syndicats

44 : ptr->c_d.cable = 1 ;
45 : ptr->c_d.dish = 0
; 46: } else {
47 : printf("Utilisez-vous une antenne parabolique ? (Oui ou
Non)\n") ; 48 : gets(is_yes) ;
49 : si ((is_yes[0] == 'Y') ||
50 : (is_yes[0] == 'y')){
51 : printf("Enter the satellite dish company name:\n")
; 52 : gets(ptr->provider.dish_company) ;
53 : ptr->c_d.cable = 0 ;
54 : ptr->c_d.dish = 1
; 55: } else {
56 : ptr->c_d.cable = 0 ;
57 : ptr->c_d.dish = 0 ;
58 : }
59 : }
60 : printf("Veuillez entrer votre
nom:\N") ; 61 : gets(ptr->nom) ;
62 : printf("Votre âge:\N") ;
63 : scanf("%d", &ptr->age) ;
64 : printf("Combien d'heures passez-vous à regarder la télévision
par semaine:\n") ; 65 : scanf("%d", &ptr->hour_per_week) ;
66 : }
67 : /* définition de la fonction */
68 : void DataDisplay(struct survey
*ptr) 69 : {
70 : printf("\NVoici ce que vous avez
saisi:\N") ; 71 :printf("Nom : %s\n", ptr->nom)
;
72 : printf("Age : %d\n", ptr->age) ;
73 : printf("Heure par semaine : %d\n", ptr-
>hour_per_week) ; 74 : if (ptr->c_d.cable && !ptr-
>c_d.dish)
75 : printf("Votre compagnie de câble est
: %s\n", 76 : ptr-
>provider.cable_company) ;
77 : else if (!ptr->c_d.cable && ptr->c_d.dish)
78 : printf("Votre société d'antenne parabolique
est : %s\n", 79 : ptr->provider.dish_company) ;
80 : autre
81 : printf("Vous n'avez pas le câble ou une antenne
satellite.\N") ; 82 :
83 : }
printf("\NMerci et au revoir!\N") ; 20

Le programme de la liste 20.6 étant fondamentalement identique à celui de la liste 20.5,


j'obtiens le même résultat à l'écran après avoir exécuté l'exécutable 20L06.exe et saisi les
mêmes réponses à l'enquête :
Utilisez-vous le câble à la maison ? (Oui ou Non)
SORTIE Non
Utilisez-vous une antenne parabolique ? (Oui ou Non)
354 Heure
20

Oui
Entrez le nom de l'entreprise de l'antenne parabolique :
Société ABCD
Veuillez saisir votre nom :
Tony Zhang
Votre âge :
30
Combien d'heures passez-vous à regarder la télévision par semaine ?
8

Voici ce que vous avez inscrit :


Nom : Tony Zhang
Âge : 30
Heures par semaine : 8
Votre société d'antenne parabolique est : ABCD

company Merci et au revoir !

[Le but du programme du Listing 20.6 est de vous montrer comment déclarer le bit
et comment les utiliser. Comme vous pouvez le voir dans les lignes 5 à 8, deux champs
de bits, câble et antenne, sont déclarés avec le type de données struct. Chacun des
champs de bits a une longueur de 1 bit. Ensuite, une structure appelée c_d est définie
avec les deux champs de bits à la ligne 12, qui se trouve à l'intérieur d'une autre struc-
de la ligne 10 à la ligne 19.
Les champs de bits cable et dish servent de drapeaux pour indiquer si l'utilisateur
utilise le câble ou une antenne parabolique en fonction des réponses données par
l'utilisateur. Si l'utilisateur a le câble, le champ de bits "cable" est mis à 1 et le champ
de bits "dish" est mis à 0 (voir lignes 44 et 45). En revanche, si l'utilisateur a une
antenne parabolique, "dish" est mis à 1 et "cable" est mis à 0, comme le montrent les
lignes 53 et 54. Si, en revanche, l'utilisateur n'a ni câble ni antenne parabolique, le câble
et l'antenne parabolique sont tous deux mis à 0 dans les lignes 56 et 57.
Vous voyez donc que vous avez utilisé les combinaisons des deux champs de bits, câble
et antenne, pour représenter les trois situations : avoir le câble, avoir une antenne
parabolique ou n'avoir ni câble ni antenne parabolique.
Comme le programme du Listing 20.6 est fondamentalement le même que celui du
Listing 20.5, j'obtiens le même résultat après avoir exécuté le programme exécutable du
Listing 20.6 et entré les mêmes informations que celles que j'ai entrées dans l'exécutable
20L05.exe.

Résumé
Au cours de cette heure, vous avez appris les concepts très importants suivants sur les unions en C
:
• Une union est un bloc de mémoire utilisé pour contenir des données de différents types.
Comprendre les 355
syndicats

• Une union est similaire à une structure, sauf que les éléments de données
enregistrés dans l'union sont superposés afin de partager le même emplacement de
départ dans la mémoire.
• La taille d'un syndicat est au moins égale à la taille du membre le plus
important du syndicat.
• Le mot-clé union est utilisé pour spécifier le type de données de l'union dans une
déclaration d'union ou une définition de variable d'union.
• Pour faire référence à un membre d'un syndicat, vous pouvez utiliser soit un
opérateur point (.) pour séparer le nom du syndicat et le nom du membre du
syndicat, soit un opérateur flèche (->) pour séparer le nom d'un pointeur qui
pointe vers le syndicat et le nom du membre du syndicat.
• La norme ANSI C permet d'initialiser une union en attribuant une valeur au
premier membre de l'union.
• Vous pouvez accéder au même emplacement de mémoire avec différents membres du syndicat.
• Pour rendre une structure flexible, vous pouvez imbriquer une union à
l'intérieur d'une structure afin que celle-ci puisse contenir différents types de
valeurs.
• Vous pouvez définir des champs de bits, qui peuvent être un seul bit ou un nombre
quelconque de bits jusqu'au nombre de bits d'un entier, en utilisant le type de
données struct.
Dans l'heure qui suit, vous apprendrez à lire et à écrire sur des fichiers disque.

Q&R
Q Quelles sont les différences entre un syndicat et une structure ?
R Fondamentalement, la différence entre une union et une structure est que les
membres d'une union sont superposés et qu'ils partagent le même emplacement de
mémoire de départ, alors que les membres d'une structure ont leurs propres
emplacements de mémoire. Comme pour une structure, les membres d'une union
sont référencés à l'aide d'un de leurs noms.
Q Que se passera-t-il si vous initialisez tous les membres d'une union ensemble ?
R La valeur affectée en dernier à un membre de l'union sera la valeur qui restera dans
la mémoire de l'union jusqu'à la prochaine affectation à l'union. En C ANSI, vous
20
pouvez initialiser une union en initialisant son premier membre.
Q Comment référencer un membre d'un syndicat ?

A Si le nom d'un syndicat est utilisé pour référencer les membres du syndicat,
l'opérateur point (.) peut être utilisé entre le nom du syndicat et le nom d'un
membre du syndicat. Si c'est un pointeur qui pointe vers une union, l'opérateur de
flèche (->) peut être utilisé entre le nom du pointeur et le nom d'un membre de
356 Heure
l'union. 20
Comprendre les 357
syndicats

Q Peut-on accéder à un même emplacement mémoire avec différents membres du syndicat ?


R Oui. Comme tous les membres d'une union partagent le même emplacement
mémoire, vous pouvez accéder à l'emplacement mémoire avec différents membres
de l'union. Par exemple, dans le programme de la liste 20.4, deux constantes de
caractères sont affectées à une mémoire d'union par l'un des membres de l'union,
puis les deux caractères enregistrés à l'emplacement mémoire de l'union sont
imprimés avec l'aide d'un autre membre de l'union.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Parmi les deux affirmations suivantes, laquelle correspond à la déclaration d'une
union et laquelle correspond à la définition des variables d'une union ?
union a_union {
int x ;
char y ;
} ;

union a_union x, y
;

2. Quel est le problème avec la déclaration d'union suivante ?


union automobile
{ int year ;
char model[8]
float weight
;
}

3. Dans l'énoncé suivant, quelles sont les valeurs contenues dans les deux membres
de l'union ?
union u {
int date ;
char year
;
} a_union = {1997}
;
358 Heure
20

Exercices
1. Réécrivez le programme de la liste 20.1 en inversant l'ordre entre l'instruction de la
ligne 15 et l'instruction de la ligne 17. Qu'obtenez-vous après avoir exécuté le
programme réécrit ? Pourquoi ?
2. Réécrivez le programme de l'illustration 20.2. Cette fois, imprimez les valeurs
détenues par tous les membres de l'union d'information chaque fois qu'une
valeur est attribuée à l'un des membres.
3. Écrivez un programme qui demande à l'utilisateur d'entrer son nom. Demandez-lui
ensuite s'il est citoyen américain. Si la réponse est oui, demandez à l'utilisateur
d'entrer le nom de l'État dont il est originaire. Dans le cas contraire, demandez à
l'utilisateur d'indiquer le nom de son pays d'origine. (Vous devez utiliser un
syndicat dans votre programme).
4. Modifiez le programme que vous avez écrit dans l'exercice 3. Ajoutez un champ de
bits et utilisez-le comme drapeau. Si l'utilisateur est un citoyen américain, mettez le
champ de bits à 1, sinon mettez-le à 0. Sinon, mettez le champ bit à 0. Imprimez le
nom de l'utilisateur et le nom du pays ou de l'état en vérifiant la valeur du champ
bit.

20
Comprendre les 359
syndicats
HEURE 21
Lire et écrire avec
des fichiers
Je ne peux que supposer qu'un document "Ne pas classer" est classé
dans un dossier "Ne pas classer".

-F. Church
Dans l'Heure 5, "Manipulation de l'entrée et de la sortie standard", vous
avez appris à lire et à écrire des caractères par le biais de l'entrée ou de la
sortie standard. Dans cette leçon, vous apprendrez à lire et à écrire des
données dans des fichiers de disque. Les sujets suivants sont abordés
abordés dans cette leçon :

• Fichiers et flux
• Ouvrir un fichier avec fopen()
• Fermer un fichier avec fclose()
• Les fonctions fgetc() et fputc()
• Les fonctions fgets() et fputs()
• Les fonctions fread() et fwrite()
• La fonction feof()
356 Heure
21

Fichiers et flux
Le langage C fournit un riche ensemble de fonctions de bibliothèque pour effectuer des
opérations d'entrée et de sortie (E/S). Ces fonctions peuvent lire ou écrire n'importe quel
type de données dans des fichiers. Avant d'aller plus loin dans la discussion sur les
fonctions d'E/S en C, commençons par comprendre les définitions des fichiers et des flux
en C.

Qu'est-ce qu'un fichier ?


En C, un fichier peut désigner un fichier disque, un terminal, une imprimante ou un
lecteur de bande. En d'autres termes, un fichier représente un dispositif concret avec
lequel vous souhaitez échanger des informations. Avant de communiquer avec un
fichier, vous devez l'ouvrir. Vous devez ensuite fermer le fichier ouvert lorsque vous
avez fini d'échanger des informations avec lui.

Qu'est-ce qu'un cours d'eau ?


Le flux de données que vous transférez de votre programme vers un fichier, ou vice
versa, est appelé flux, c'est-à-dire une série d'octets. Contrairement à un fichier, qui cible
un périphérique d'E/S spécifique, un flux est indépendant du périphérique. Tous les flux
ont le même comportement, quel que soit le périphérique auquel ils sont associés. Pour
effectuer des opérations d'E/S, vous pouvez lire ou écrire dans n'importe quel type de
fichier en associant simplement un flux au fichier.
Il existe deux formats de flux. Le premier, appelé flux de texte, consiste en une séquence
de caractères (c'est-à-dire de données textuelles). Selon le système, chaque ligne de
caractères d'un flux de texte peut être terminée par un caractère de retour à la ligne ("\n").
Les flux de texte sont utilisés pour les données textuelles, qui ont une apparence
cohérente d'un environnement à l'autre ou d'une machine à l'autre.
Le deuxième format de flux est appelé flux binaire, qui est une série d'octets, par exemple
le contenu d'un fichier de programme exécutable. Les flux binaires sont principalement
utilisés pour les données non textuelles, où le contenu exact des données est conservé
sans tenir compte de l'apparence.

E/S tamponnées
En C, une zone de mémoire utilisée temporairement pour stocker des données avant
qu'elles ne soient envoyées à leur destination s'appelle un tampon. Grâce aux tampons,
le système d'exploitation peut améliorer l'efficacité en réduisant le nombre d'accès aux
périphériques d'E/S (c'est-à-dire les fichiers).
L'accès à un disque ou à un autre périphérique d'E/S est généralement beaucoup plus lent
que l'accès direct à la mémoire. Plusieurs opérations d'E/S peuvent être effectuées sur un
tampon qui représente le fichier d'E/S, au lieu du fichier lui-même. Par exemple, si
plusieurs opérations d'écriture sont envoyées à un tampon, elles sont conservées en
Lire et écrire avec des fichiers 357
mémoire jusqu'à ce qu'il soit temps d'enregistrer, ou de valider, les nouvelles données sur
le périphérique réel.
358 Heure
21

(Ainsi, au lieu d'effectuer plusieurs opérations d'écriture à la suite, chacune d'entre elles
étant destinée au périphérique de disque lent, elles sont toutes effectuées dans la
mémoire tampon et le périphérique de disque n'est accédé qu'une seule fois, lorsque la
mémoire tampon est vidée.
Par défaut, tous les flux d'E/S sont mis en mémoire tampon. Les E/S tamponnées
sont également appelées E/S de haut niveau, tandis que les E/S non tamponnées
(directement vers le périphérique) sont appelées E/S de bas niveau.

Les bases des entrées/sorties de fichiers sur disque


Voyons maintenant comment ouvrir et fermer un fichier de données sur disque et
comment interpréter les messages d'erreur renvoyés par les fonctions d'entrée/sortie.

Pointeurs de FILE
La structure FILE est la structure de contrôle des fichiers définie dans le fichier d'en-tête
stdio.h. Un pointeur de type FILE est appelé pointeur de fichier et fait référence à un
fichier disque. Un pointeur de fichier est utilisé par un flux pour effectuer les opérations
des fonctions d'entrée/sortie. Par exemple, l'exemple suivant définit un pointeur de
fichier appelé fptr :
FILE *fptr ;

La structure FILE contient un membre, appelé indicateur de position de fichier, qui


indique la position dans un fichier où les données seront lues ou écrites. Vous
apprendrez à déplacer l'indicateur de position de fichier dans la prochaine leçon.

Ouverture d'un fichier


La fonction d'E/S en C fopen() ouvre un fichier et associe un flux au fichier ouvert.
Vous devez spécifier la méthode d'ouverture du fichier et le nom du fichier comme
arguments de la fonction fopen().
La syntaxe de la fonction fopen() est la suivante
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode) ;

Ici, nom-de-fichier est un pointeur de caractère qui fait référence à une chaîne de
E
S

caractères contenant un nom de fichier. Le nom du fichier est donné au fichier qui va
être ouvert par la fonction fopen(). mode pointe sur une autre chaîne qui spécifie la
manière d'ouvrir le fichier. La fonction fopen() renvoie un pointeur de type FILE. Si
21
E

une erreur survient au cours de la procédure d'ouverture d'un fichier, la fonction fopen()
renvoie un pointeur nul.
Le paramètre mode est constitué d'une combinaison des caractères r (read), w
(write), b (binary), a (append) et + (update). Lorsque vous utilisez le mode append et
Lire et écrire avec des fichiers 359
que le fichier existe déjà, le contenu du fichier sera préservé et les nouvelles données
que vous écrivez seront
360 Heure
21

ajouté à la fin. Si le fichier n'existe pas encore, il sera créé. À l'inverse de w, qui tente
toujours de créer un nouveau fichier et supprime toutes les données déjà présentes dans le
fichier. L'utilisation du caractère + permet de passer en mode lecture et en mode écriture.
Lorsque vous utilisez r, le fichier doit déjà exister ; si ce n'est pas le cas, l'appel échoue.
La liste suivante montre les possibilités d'ouverture d'un fichier par différentes chaînes de modes :

• "r" ouvre un fichier texte existant pour lecture.


• "w" crée un fichier texte pour l'écriture.
• "a" ouvre un fichier texte existant pour l'ajouter.
• "r+" ouvre un fichier texte existant en lecture ou en écriture.
• "w+" crée un fichier texte pour la lecture ou l'écriture.
• "a+" ouvre ou crée un fichier texte à ajouter.
• "rb" ouvre un fichier binaire existant pour lecture.
• "wb" crée un fichier binaire pour l'écriture.
• "ab" ouvre un fichier binaire existant pour l'ajouter.
• "r+b" ouvre un fichier binaire existant en lecture ou en écriture.
• "w+b" crée un fichier binaire pour la lecture ou l'écriture.
• "a+b" ouvre ou crée un fichier binaire à ajouter.
Notez que vous pouvez voir du code où le mode est donné comme "rb+" au lieu de
"r+b". Ces deux chaînes sont équivalentes. De même, "wb+" est équivalent à "w+b" ;
"ab+" est équivalent à "a+b".

Les instructions suivantes tentent d'ouvrir un fichier appelé


test.txt : FILE *fptr ;
if ((fptr = fopen("test.txt", "r")) == NULL){
printf("Cannot open test.txt file.\n") ;
exit(1) ;
}

Ici, "r" est utilisé pour indiquer qu'un fichier texte est sur le point d'être ouvert en
lecture seule. Si une erreur survient lorsque la fonction fopen() tente d'ouvrir le fichier,
la fonction renvoie un pointeur nul (ce qui se produit si test.txt n'existe pas déjà). (Un
message d'erreur est alors imprimé par la fonction printf() et le programme est
abandonné en appelant la fonction exit() avec une valeur non nulle.

Fermeture d'un dossier


Après la lecture, l'écriture ou l'ajout de nouvelles données dans un fichier disque, vous
devez dissocier le fichier d'un flux spécifié en appelant la fonction fclose().
Lire et écrire avec des fichiers 361

La syntaxe de la fonction fclose() est la suivante


#include <stdio.h>
int fclose(FILE *stream) ;

Ici, stream est un pointeur de fichier associé à un flux vers le fichier ouvert. Si fclose()
E
S

ferme un fichier avec succès, elle renvoie 0. Dans le cas contraire, la fonction renvoie
EOF. Normalement, la fonction fclose() n'échoue que si le disque est retiré avant l'appel
E

de la fonction ou s'il n'y a plus d'espace disponible sur le disque.


Comme toutes les opérations d'E/S de haut niveau sont mises en mémoire tampon, la
fonction fclose() efface les données laissées dans la mémoire tampon pour s'assurer
qu'aucune donnée ne sera perdue avant de dissocier le flux spécifié du fichier ouvert.
Notez qu'un fichier ouvert et associé à un flux doit être fermé après l'opération d'E/S.
Sinon, les données enregistrées dans le fichier peuvent être perdues. Dans le cas
contraire, les données enregistrées dans le fichier peuvent être perdues ; certaines erreurs
imprévisibles peuvent se produire pendant l'exécution de votre programme. En outre, si
vous ne fermez pas un fichier lorsque vous avez terminé, vous risquez d'empêcher
d'autres programmes (ou des utilisateurs de votre programme) d'accéder au fichier
ultérieurement.
Le programme du Listing 21.1 vous montre comment ouvrir et fermer un fichier texte et
comment vérifier la valeur du pointeur de fichier renvoyé par fopen().

TYPE LISTE 21.1 Ouverture et fermeture d'un fichier texte


1 : /* 21L01.c : Ouverture et fermeture
d'un fichier */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL} ;
5 :
6 : main(void)
7 : {
8 : FILE *fptr ;
9 : char filename[]= "haiku.txt"
; 10: int reval = SUCCESS ;
11 :
12 : if ((fptr = fopen(filename, "r")) ==
NULL){ 13 : printf("Cannot open
%s.\n", filename) ; 14: reval = FAIL ;
15 : } else {
16 : printf("La valeur de fptr : 0x%p\n",
fptr) ; 17: printf("Prêt à fermer le
fichier") ;
18 : fclose(fptr) ;
21
19 : }
20 :
21 : retour reval ;
22 : }
362 Heure
21

La sortie suivante s'affiche à l'écran après l'exécution de l'exécutable 21L01.exe du


programme de la liste 21.1 sur mon ordinateur. (Notez que la valeur de fptr est
probablement différente sur votre machine. Ce n'est pas grave).
Valeur de fptr : 0x013E
SORTIE Prêt à clôturer le dossier.

Le but du programme du Listing 21.1 est de vous montrer comment ouvrir un


ANALYSE
fichier texte. D'après l'expression de la ligne 12, vous pouvez voir que la
fonction fopen() essaie d'ouvrir un fichier texte.
pour ouvrir en lecture un fichier texte dont le nom est contenu dans le tableau de caractères
filename. Le tableau filename est défini et initialisé avec le nom haiku.txt à la ligne
9.
Si une erreur se produit lorsque vous essayez d'ouvrir le fichier texte, la fonction fopen()
renvoie un pointeur nul. La ligne 13 imprime alors un message d'avertissement et la ligne
14 affecte la valeur représentée par le nom de l'enum FAIL à la variable int reval.
D'après la déclaration du type de données enum à la ligne 4, vous savez que la valeur de
FAIL est 1.

Si, toutefois, la fonction fopen() ouvre le fichier texte avec succès, l'instruction de la
ligne 16 imprime la valeur contenue dans le pointeur de fichier fptr. La ligne 17 indique
à l'utilisateur que le programme est sur le point de fermer le fichier, et la ligne 18 ferme le
fichier en appelant la fonction fclose().
À la ligne 21, l'instruction return renvoie la valeur de reval qui contient 0 si le
fichier texte a été ouvert avec succès, ou 1 dans le cas contraire.
D'après la sortie affichée sur mon écran, je constate que la valeur détenue par le pointeur de fichier fptr
est la suivante
0x013E après l'ouverture du fichier texte.

Lecture et écriture de fichiers sur disque


Le programme du Listing 21.1 ne fait rien avec le fichier texte haiku.txt, si ce n'est
l'ouvrir et le fermer. En fait, deux haïkus japonais, écrits par Sodo et Chora, sont
enregistrés dans le fichier haiku.txt. Comment pouvez-vous les lire à partir du fichier
?
En C, vous pouvez effectuer des opérations d'E/S de la manière suivante :

• Lire ou écrire un caractère à la fois.


• Lire ou écrire une ligne de texte (c'est-à-dire une ligne de caractères) à la fois.
• Lire ou écrire un bloc de caractères à la fois.
Les trois sections suivantes expliquent les trois façons de lire et d'écrire sur des fichiers de disque.
Lire et écrire avec des fichiers 363
Un personnage à la fois
Parmi les fonctions d'E/S en C, il existe une paire de fonctions, fgetc() et fputc(),
qui peuvent être utilisées pour lire ou écrire sur un fichier disque un caractère à la fois.
364 Heure
21

La syntaxe de la fonction fgetc() est la suivante


#include <stdio.h>
int fgetc(FILE *stream) ;

Ici, stream est le pointeur de fichier associé à un stream. La fonction fgetc() récupère
E
S

le caractère suivant dans le flux spécifié par stream. La fonction renvoie ensuite la valeur
d'un int converti à partir du caractère. EOF est renvoyé si fgetc() rencontre la fin du
E

fichier ou en cas d'erreur.


La syntaxe de la fonction fputc() est la suivante
#include <stdio.h>
int fputc(int c , FILE *stream) ;

Ici, c est une valeur int qui représente un caractère. En fait, la valeur int est convertie
E
S

en unsigned char avant d'être émise. stream est le pointeur de fichier associé à un
stream. La fonction fputc() renvoie le caractère écrit si la fonction est réussie ; sinon,
elle renvoie EOF. Après l'écriture d'un caractère, la fonction fputc() avance le pointeur
E

de fichier associé.
Pour apprendre à utiliser les fonctions fgetc() et fputc(), examinons le Listing 21.2,
qui contient un programme qui ouvre un fichier texte, puis lit et écrit un caractère à la
fois.

TYPE LISTE 21.2 Lecture et écriture d'un caractère à la fois


1 : /* 21L02.c : Lecture et écriture d'un caractère à la fois
*/ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL} ;
5 :
6 : void CharReadWrite(FILE *fin, FILE *fout)
; 7 :
8 : main(void)
9 : {
10 : FICHIER *fptr1, *fptr2 ;
11 : char filename1[]= "outhaiku.txt"
; 12 : char filename2[]= "haiku.txt" ;
13: int reval = SUCCESS ;
14 :
15 : if ((fptr1 = fopen(filename1, "w")) ==
NULL){ 16 : printf("Cannot open
%s.\n", filename1) ; 17: reval = FAIL ; 21
18 : } else if ((fptr2 = fopen(filename2, "r")) ==
NULL){ 19 : printf("Cannot open %s.\n",
filename2) ;

continue
Lire et écrire avec des fichiers 365

LISTE 21.2 suite


20 : reval = FAIL
;
21 : } else {
22 : CharReadWrite(fptr2, fptr1) ;
23 : fclose(fptr1) ;
24 : fclose(fptr2) ;
25 : }
26 :
27 : retour reval ;
28 : }
29 : /* définition de la fonction */
30 : void CharReadWrite(FILE *fin, FILE
*fout) 31 : {
32 : int c ;
33 :
34 : while ((c=fgetc(fin)) != EOF){
35 : fputc(c, fout) ; /* écrire dans un fichier */
36 : putchar(c) ; /* affiche le caractère à l'écran */
37 : }
38 : }

Après avoir exécuté l'exécutable, 21L02.exe, sur ma machine, j'obtiens le résultat suivant :
Me guider
SORTIE mon ombre rentre à la
maison après avoir regardé
la lune.
--- Sodo
(1641-1716)

Un vent de tempête souffle


parmi les herbes, la
pleine lune pousse.
--- Chora
(1729-1781)

L'objectif du programme de la liste 21.2 est de lire un caractère dans un fichier,


ANALYSE
d'écrire le caractère dans un autre fichier et d'afficher le caractère à l'écran.
Avant d'exécuter le programme, vous devez créer un fichier texte appelé haiku.txt
avec le contenu indiqué ci-dessus et l'enregistrer dans le même répertoire que le
programme exécutable.
Dans le Listing 21.2, il y a une fonction appelée CharReadWrite(), qui a deux pointeurs
de fichier comme arguments. (Voir la déclaration de la fonction CharReadWrite() à la
ligne 6).
L'instruction de la ligne 10 définit deux pointeurs de fichier, fptr1 et fptr2, qui sont
utilisés plus loin dans le programme. Les lignes 11 et 12 définissent deux tableaux de
caractères, nomfichier1 et nomfichier2, et initialisent les deux tableaux avec deux
366 Heure
chaînes contenant des noms de fichiers, outhaiku.txt et haiku.txt.
21
Lire et écrire avec des fichiers 367

À la ligne 15, un fichier texte nommé outhaiku.txt est ouvert en écriture.


outhaiku.txt est contenu dans le tableau filename1. Le pointeur de fichier fptr1 est
associé au fichier. Si la fonction fopen() renvoie NULL, ce qui signifie qu'une erreur s'est
produite, un message d'avertissement est imprimé à la ligne 16. De plus, à la ligne 17, la
variable reval est affectée à 1 et est représentée par le nom de l'enum FAIL.
Si le fichier outhaiku.txt est ouvert avec succès, un autre fichier texte, appelé
haiku.txt, est ouvert en lecture à la ligne 18. Le pointeur de fichier fptr2 est associé
au fichier texte ouvert.
Si aucune erreur ne se produit, la fonction CharReadWrite() est appelée à la ligne 22
avec deux pointeurs de fichier, fptr1 et fptr2, transmis à la fonction en tant
qu'arguments. La définition de la fonction CharReadWrite() aux lignes 30 et 38 montre
qu'il existe une boucle while qui appelle sans cesse la fonction fgetc() pour lire le
caractère suivant dans le fichier haiku.txt.
jusqu'à ce que la fonction atteigne la fin du fichier (voir ligne 34).
Dans la boucle while, la fonction fputc() de la ligne 35 écrit chaque caractère lu dans
le fichier haiku.txt dans un autre fichier texte, outhaiku.txt, pointé par fout. En
outre, la fonction putchar() est appelée à la ligne 36 afin d'afficher à l'écran le
caractère renvoyé par la fonction fgetc().
Une fois que la fonction CharReadWrite() a terminé son travail, les deux fichiers
ouverts, qui sont associés à fptr1 et fptr2, sont chacun fermés par un appel à la
fonction fclose(), respectivement aux lignes 23 et 24.
Comme indiqué précédemment, le fichier haiku.txt contient deux morceaux de haïku
japonais écrits par Sodo et Chora. Si le programme de l'illustration 21.2 est exécuté avec
succès, vous verrez les deux morceaux de haïku s'afficher à l'écran et ils seront
également écrits dans le fichier outhaiku.txt. Vous pouvez visualiser outhaiku.txt
dans un éditeur de texte pour confirmer que le contenu de haiku.txt a été correctement
copié dans outhaiku.txt.

Une ligne à la fois


Outre la lecture ou l'écriture d'un caractère à la fois, vous pouvez également lire ou écrire
une ligne de caractères à la fois. Il existe une paire de fonctions d'E/S en C, fgets() et
fputs(), qui vous permettent de le faire.

La syntaxe de la fonction fgets() est la suivante


#include <stdio.h>
char *fgets(char *s, int n, FILE *stream) ; 21
Ici, s fait référence à un tableau de caractères utilisé pour stocker les caractères lus à
E E

partir du fichier ouvert pointé par le pointeur de fichier stream. n spécifie le nombre
maximum d'éléments du tableau. Si elle réussit, la fonction fgets() renvoie le pointeur
S

de caractères s. Si EOF est


368 Heure
21

E Si une erreur est rencontrée, la fonction fgets() renvoie un pointeur nul et laisse le
tableau intact. En cas d'erreur, la fonction renvoie un pointeur nul et le contenu du
E

tableau est inconnu.


La fonction fgets() peut lire jusqu'à n-1 caractères, et peut ajouter un caractère nul
après le dernier caractère lu, jusqu'à ce qu'une nouvelle ligne ou un EOF soit rencontré.
Notez que si une nouvelle ligne est rencontrée pendant l'opération de lecture, la fonction
fgets() inclut la nouvelle ligne dans le tableau. Ceci est différent de ce que fait la
fonction gets(). La fonction gets() remplace simplement le caractère de nouvelle
ligne par un caractère nul. (La fonction gets() a été présentée dans l'Heure 13,
"Manipulation des chaînes de caractères").
La syntaxe de la fonction fputs() est la suivante
#include <stdio.h>
int fputs(const char *s, FILE *stream) ;

Ici, s pointe vers le tableau qui contient les caractères à écrire dans un fichier
E
S

associé au pointeur de fichier stream. Le modificateur const indique que le contenu


du tableau pointé par s ne peut pas être modifié par la fonction fputs(). (Vous avez
appris à connaître le modificateur const dans l'Heure 14, "Comprendre la portée et les
E

classes de stockage"). En cas d'échec, la fonction fputs() renvoie une valeur non
nulle ; sinon, elle renvoie zéro.
Notez que le tableau de caractères doit inclure un caractère null à la fin de la chaîne
comme terminateur de la fonction fputs(). De plus, contrairement à la fonction puts(),
la fonction fputs() n'insère pas de caractère de retour à la ligne dans la chaîne écrite
dans le fichier. (La fonction puts() a été présentée dans l'Heure 13, "Manipulation des
chaînes de caractères").
Vous pouvez modifier le programme du Listing 21.2 pour lire ou écrire une ligne de
caractères à la fois en appelant les fonctions fgets() et fputs(). La version modifiée
est présentée dans le tableau 21.3.

TYPE LISTE 21.3 Lecture et écriture d'une ligne de caractères à la fois


1 : /* 21L03.c : Lecture et écriture d'une ligne à la
fois */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL, MAX_LEN = 81} ;
5 :
6 : void LineReadWrite(FILE *fin, FILE
*fout) ; 7 :
8 : main(void)
9 : {
10 : FICHIER *fptr1, *fptr2 ;
11 : char filename1[]= "outhaiku.txt"
; 12 : char filename2[]= "haiku.txt" ;
Lire et écrire avec des fichiers 369
13: int reval = SUCCESS ;
14 :
370 Heure
21

15 : if ((fptr1 = fopen(filename1, "w")) == NULL){


16 : printf("Cannot open %s for writing.\n", filename1)
; 17: reval = FAIL ;
18 : } else if ((fptr2 = fopen(filename2, "r")) ==
NULL){ 19 : printf("Cannot open %s for
reading.\n", filename2) ; 20: reval = FAIL ;
21 : } else {
22 : LineReadWrite(fptr2, fptr1) ;
23 : fclose(fptr1) ;
24 : fclose(fptr2) ;
25 : }
26 :
27 : retour reval ;
28 : }
29 : /* définition de la fonction */
30 : void LineReadWrite(FILE *fin, FILE
*fout) 31 : {
32 : char buff[MAX_LEN] ;
33 :
34 : while (fgets(buff, MAX_LEN, fin) !=
NULL){ 35: fputs(buff, fout) ;
36 : printf("%s", buff) ;
37 : }
38 : }

Comme le programme de l'illustration 21.3 lit le même fichier texte, haiku.txt, que le
programme de l'illustration 21.2, j'obtiens le même résultat à l'écran :
Me guider
SORTIE mon ombre rentre à la
maison après avoir regardé
la lune.
--- Sodo
(1641-1716)

Un vent de tempête souffle


Parmi les herbes, la pleine
lune pousse.
--- Chora
(1729-1781)

Le programme de la liste 21.3 montre qu'une fonction appelée


ANALYSE
LineReadWrite() a remplacé la fonction CharReadWrite().

La définition de la fonction LineReadWrite() est présentée aux lignes 30 à 38. La fonction


fgets() est appelée à plusieurs reprises dans une boucle while pour lire une ligne de 21
caractères à la fois dans le fichier texte haiku.txt, jusqu'à ce qu'elle atteigne la fin du fichier
texte. À la ligne 34, le nom du tableau
buff et le nombre maximum d'éléments du tableau MAX_LEN sont transmis à la fonction
fgets(), ainsi que le pointeur de fichier fin associé au fichier haiku.txt ouvert.
Lire et écrire avec des fichiers 371

Pendant ce temps, chaque ligne lue par la fonction fgets() est écrite dans un autre
fichier texte ouvert appelé outhaiku.txt qui est associé au pointeur de fichier fout.
Pour ce faire, la fonction fputs() est appelée à la ligne 35.
L'instruction de la ligne 36 imprime le contenu de chaque chaîne à l'écran, de sorte que
vous voyez les deux morceaux de vers japonais après avoir exécuté le programme de
l'illustration 21.3. Vous pouvez également visualiser le fichier outhaiku.txt dans un
éditeur de texte pour vous assurer que le contenu du fichier haiku.txt a été copié dans
le fichier outhaiku.txt.

Dans l'heure précédente, nous avons utilisé la fonction gets() pour lire les
données saisies au clavier. Comme gets() ne connaît pas la taille du
tableau de caractères que vous lui passez, elle lit simplement les données
jusqu'à ce qu'une nouvelle ligne soit rencontrée. C'est en fait assez
dangereux, car l'utilisateur peut très facilement taper plus de caractères que
votre tableau ne peut en contenir. Par conséquent, vos autres variables
seraient écrasées et votre programme se bloquerait ou, au mieux, se
comporterait de manière imprévisible.
Heureusement, fgets() fournit un moyen beaucoup plus sûr de lire les
entrées du clavier, si vous passez stdin comme flux à partir duquel lire.
Le code suivant utilise fgets() pour lire les données provenant de stdin dans une
chaîne de caractères str.
int length = 80
; char str[80]
;
fgets(str, length, stdin) ;
if (str[(length = strlen(str) - 1)] == '\n')
str[length] = '\n' ; /* remplace le '\n' */
autre
while (getchar() != '\n')
; /* rejeter l'entrée jusqu'à la nouvelle ligne */

Comme vous pouvez le voir, cette méthode implique un peu plus de travail que

Un bloc à la fois
Si vous le souhaitez, vous pouvez également lire ou écrire un bloc de données à la fois.
En C, il existe deux fonctions d'E/S, fread() et fwrite(), qui peuvent être utilisées
pour effectuer des opérations d'E/S par bloc. Les fonctions fread() et fwrite() sont
des images miroir l'une de l'autre.
La syntaxe de la fonction fread() est la suivante
E SYNTAX

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t n, FILE *stream) ;

Ici, ptr est un pointeur sur un tableau dans lequel les données sont stockées. size
indique la taille de chaque élément du tableau. n spécifie le nombre d'éléments à lire.
372 Heure
stream est un pointeur de fichier. 21
Lire et écrire avec des fichiers 373
E

qui est associé au fichier ouvert à la lecture. size_t est un type intégral défini dans le
fichier d'en-tête stdio.h. La fonction fread() renvoie le nombre d'éléments
effectivement lus.
Le nombre d'éléments lus par la fonction fread() doit être égal à la valeur spécifiée par
E

le troisième argument de la fonction, à moins qu'une erreur ne se produise ou qu'un EOF


(end-of-file) ne soit rencontré. La fonction fread() renvoie le nombre d'éléments
effectivement lus pendant la tentative, si une erreur se produit ou si un EOF est rencontré.
La syntaxe de la fonction fwrite() est la suivante
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream) ;

Ici, ptr fait référence au tableau qui contient les données à écrire dans un fichier ouvert
E
S

pointé par le pointeur de fichier stream. size indique la taille de chaque élément du
tableau. n spécifie le nombre d'éléments à écrire. La fonction fwrite() renvoie le
E

nombre d'éléments effectivement écrits.


Si aucune erreur ne s'est produite, la valeur retournée par fwrite() doit être égale au
troisième argument de la fonction. La valeur de retour peut être inférieure à la valeur
spécifiée en cas d'erreur.
Il incombe au programmeur de s'assurer que le tableau est suffisamment grand pour
contenir les données de la fonction fread() ou de la fonction fwrite().
En C, une fonction appelée feof() peut être utilisée pour déterminer quand la fin d'un
fichier est rencontrée. Cette fonction est plus utile lorsque vous lisez un fichier binaire
car les valeurs de certains octets peuvent être égales à la valeur de EOF. En d'autres
termes, le caractère utilisé comme marqueur de fin de fichier dans un fichier texte peut
facilement apparaître dans un fichier binaire, mais dans ce cas, il n'est pas destiné à
marquer la fin du fichier. Si vous essayez de déterminer la fin d'un fichier binaire
en vérifiant la valeur renvoyée par fread(), vous risquez de vous retrouver à une
position erronée. L'utilisation de la fonction feof() permet d'éviter les erreurs dans la
détermination de la fin d'un fichier, car elle vérifie si l'indicateur de position du fichier a
effectivement atteint la fin du fichier, indépendamment de la présence éventuelle d'un
caractère EOF.
La syntaxe de la fonction feof() est la suivante
#include <stdio.h>
int feof(FILE *stream) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. La fonction feof()
E
S

renvoie 0 si la fin du fichier n'a pas été atteinte ; sinon, elle renvoie un entier non nul. 21
E
374 Heure
21

Le programme du Listing 21.4 montre comment lire et écrire un bloc de caractères à la


fois en appelant les fonctions fread() et fwrite(). En fait, le programme du Listing
21.4 est une autre version modifiée du programme du Listing 21.2.

TYPE LISTE 21.4 Lecture et écriture d'un bloc de caractères à la fois


1 : /* 21L04.c : Lecture et écriture d'un bloc à la
fois */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL, MAX_LEN = 80} ;
5 :
6 : void BlockReadWrite(FILE *fin, FILE
*fout) ; 7 : int ErrorMsg(char *str) ;
8 :
9 : main(void)
10 : {
11 : FICHIER *fptr1, *fptr2 ;
12 : char filename1[]= "outhaiku.txt"
; 13 : char filename2[]= "haiku.txt" ;
14: int reval = SUCCESS ;
15 :
16 : if ((fptr1 = fopen(filename1, "w")) ==
NULL){ 17: reval = ErrorMsg(filename1) ;
18 : } else if ((fptr2 = fopen(filename2, "r")) ==
NULL){ 19: reval = ErrorMsg(filename2) ;
20 : } else {
21 : BlockReadWrite(fptr2, fptr1) ;
22 : fclose(fptr1) ;
23 : fclose(fptr2) ;
24 : }
25 :
26 : retour reval ;
27 : }
28 : /* définition de la fonction */
29 : void BlockReadWrite(FILE *fin, FILE
*fout) 30 : {
31 : int num ;
32 : char buff[MAX_LEN + 1] ;
33 :
34 : while (!feof(fin)){
35 : num = fread(buff, sizeof(char), MAX_LEN, fin) ;
36 : buff[num * sizeof(char)] = '\0' ; /* ajouter un caractère nul
*/ 37: printf("%s", buff) ;
38 : fwrite(buff, sizeof(char), num, fout)
; 39 : }
40 : }
41 : /* définition de la
fonction */ 42 : int
ErrorMsg(char *str) 43 : {
Lire et écrire avec des fichiers 375

44 : printf("Cannot open %s.\n", str)


; 45: return FAIL ;
46 : }

Là encore, j'obtiens le même résultat à l'écran car le programme du Listing 21.4 lit
également le même fichier texte, haiku.txt :
Me guider
SORTIE mon ombre rentre à la
maison après avoir regardé
la lune.
--- Sodo
(1641-1716)

Un vent de tempête souffle


parmi les herbes, la pleine
lune pousse.
--- Chora
(1729-1781)

ANALYSE L'objectif du programme du Listing 21.4 est de vous montrer comment invoquer la commande
fread() et fwrite() dans votre programme pour effectuer des opérations d'E/S
en bloc. Dans le Listing 21.4, le fichier haiku.txt est lu par la fonction fread(), puis
la fonction fwrite() est utilisée pour écrire le contenu lu dans haiku.txt dans un
autre fichier appelé outhaiku.txt. Vous appelez les deux fonctions d'E/S en C à partir
de votre propre fonction, BlockReadWrite().
La définition de la fonction BlockReadWrite() aux lignes 29 et 40 montre qu'un
tableau de caractères appelé buff est défini avec un nombre d'éléments égal à MAX_LEN
+ 1 à la ligne 32, bien que vous ne lisiez que le nombre MAX_LEN de caractères en
appelant la fonction fread().
à la ligne 35. La raison en est que vous ajoutez un caractère nul à la ligne 36 après le
dernier caractère lu afin de vous assurer que le bloc de caractères sauvegardé dans buff
est traité comme une chaîne et peut être imprimé à l'écran correctement par la fonction
printf() qui est appelée à la ligne 37.

La boucle while, illustrée aux lignes 34 à 39, continue d'appeler la fonction fread()
pour lire un bloc de caractères avec les éléments MAX_LEN, jusqu'à ce que la fonction
feof() de la ligne 34 renvoie 0, ce qui signifie que la fin du fichier texte a été atteinte.
Comme le montrent les lignes 35 et 38, vous utilisez l'opérateur sizof pour mesurer la
taille du type de données char, car les éléments de
le tableau de buff sont tous des caractères.
Si tout se passe bien, vous devriez revoir les vers japonais à l'écran ou dans le fichier 21
outhaiku.txt après avoir exécuté le programme de l'illustration 21.4.
376 Heure
21

Résumé
Dans cette leçon, vous avez appris les concepts et fonctions importants suivants
concernant l'entrée et la sortie de fichiers disque en C :

• En C, un fichier peut désigner un fichier disque, un terminal, une imprimante ou un lecteur de bande.
• Le flux de données que vous transférez de votre programme vers un fichier, ou
vice versa, est appelé flux.
• Un flux est une série d'octets ordonnés.
• Contrairement à un fichier, un flux est indépendant du périphérique.
• Il existe deux formats de flux : le flux de texte et le flux binaire.
• L'indicateur de position du fichier dans la structure FILE indique la position
dans un fichier où les données seront lues ou écrites.
• La fonction fopen() est utilisée pour ouvrir un fichier et associer un flux au
fichier ouvert.
• Vous pouvez spécifier différents modes d'ouverture d'un fichier.
• La fonction fclose() est responsable de la fermeture d'un fichier ouvert et de la
dissociation d'un flux avec le fichier.
• Les fonctions fgetc() et fputc() lisent ou écrivent un caractère à la fois.
• Les fonctions fgets() et fputs() lisent ou écrivent une ligne à la fois.
• Les fonctions fread() et fwrite() lisent ou écrivent un bloc de données à la fois.
• La fonction feof() permet de déterminer si la fin d'un fichier a été atteinte.
• Dans un fichier binaire, la fonction feof() doit être utilisée pour détecter EOF.
Dans la prochaine leçon, vous en apprendrez plus sur les entrées/sorties de fichiers disque en C.

Q&R
Q Quelles sont les différences entre un flux de texte et un flux binaire ?
A Un flux de texte est une séquence de caractères qui peut ne pas avoir de relation
univoque avec les données de l'appareil. Les flux de texte sont normalement
utilisés pour les données textuelles, qui ont une apparence cohérente d'un
environnement à l'autre ou d'une machine à l'autre. C'est pourquoi les données
d'un fichier texte peuvent être interprétées de manière à apparaître correctement à
l'écran. Un flux binaire, quant à lui, est une séquence d'octets qui correspond à
ceux présents sur le périphérique.
Les flux binaires sont principalement utilisés pour les données non textuelles dont
le contenu exact doit être conservé sur l'appareil.
Lire et écrire avec des fichiers 377

Q Pourquoi avez-vous besoin d'un pointeur de fichier ?


A Un pointeur de fichier est utilisé pour associer un flux à un fichier ouvert à des fins
de lecture ou d'écriture. Un pointeur de type FILE est appelé pointeur de fichier.
FILE est une définition de type pour une structure qui contient des
informations générales sur un fichier disque. Un pointeur de fichier joue un rôle
important dans la communication entre les programmes et les fichiers de disque.
Q Que fait la fonction fclose() avant de fermer un fichier ouvert ?
R Comme vous le savez, toutes les opérations d'E/S de haut niveau sont mises en
mémoire tampon. L'une des tâches de la fonction fclose() est d'effacer les
données laissées dans la mémoire tampon afin de valider les opérations d'E/S et de
s'assurer qu'aucune donnée n'est perdue. Par exemple, lorsque vous avez fini
d'écrire plusieurs blocs de caractères dans un fichier texte ouvert, vous appelez la
fonction fclose() pour dissocier un flux spécifique et fermer le fichier texte. La
fonction fclose() efface d'abord tous les caractères restants dans la mémoire
tampon et les écrit dans le fichier texte avant de fermer le fichier. De cette manière,
tous les caractères que vous écrivez dans le fichier seront sauvegardés
correctement.
Q Quelle est la différence entre fgets() et gets() ?
A La principale différence entre les fonctions fgets() et gets() est que la fonction
fgets() inclut un caractère de nouvelle ligne dans le tableau si la nouvelle ligne
est rencontrée au cours de l'opération de lecture, alors que la fonction gets()
remplace simplement le caractère de nouvelle ligne par un caractère nul. En outre,
la fonction fgets() est beaucoup plus sûre que la fonction gets() car elle vous
permet de spécifier la taille du tableau dans lequel la chaîne est stockée.

Atelier
Afin de consolider votre compréhension de cette leçon, nous vous encourageons à répondre
aux questions du quiz et à terminer les exercices proposés dans l'atelier avant de passer à la
leçon suivante. Les réponses et les conseils aux questions et aux exercices sont donnés dans
l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Que font les expressions suivantes ?
fopen("test.bin", "r+b")
fopen("test.txt" "a")
fopen("test.ini", "w+")

2. Qu'est-ce qui ne va pas dans le segment de code suivant ?


FILE *fptr
; int c ; 21
if ((fptr = fopen("test1.txt", "r")) == NULL){
while ((c=fgetc(fptr)) != EOF){
378 Heure
putchar(c) ; 21
}
}
fclose(fptr) ;
Lire et écrire avec des fichiers 379

3. Qu'est-ce qui ne va pas dans le segment de code suivant ?


FILE *fptr ;
int c ;
if ((fptr = fopen("test2.txt", "r")) != NULL){
while ((c=fgetc(fptr)) != EOF){
fputc(c, fptr) ;
}
fclose(fptr) ;
}

4. Qu'est-ce qui ne va pas dans le segment de code suivant ?


FILE *fptr1, *fptr2
; int c ;
if ((fptr1 = fopen("test1.txt", "r")) != NULL){
while ((c=fgetc(fptr1)) != EOF){
putchar(c) ;
}
}
fclose(fptr1) ;
if ((fptr2 = fopen("test2.txt", "w")) != NULL){
while ((c=fgetc(fptr1)) != EOF){
fputc(c, fptr2) ;
}
}
fclose(fptr2) ;

Exercices
1. Ecrivez un programme pour lire le fichier texte haiku.txt et compter le nombre de
caractères dans le fichier. Imprimez également le contenu du fichier et le nombre
total de caractères à l'écran.
2. Écrire un programme qui reçoit une chaîne de caractères saisie par l'utilisateur,
puis enregistre cette chaîne dans un fichier dont le nom est également donné par
l'utilisateur.
3. Étant donné la chaîne de caractères "Les entrées/sorties de fichiers disque
sont amusantes", écrivez un programme pour écrire la chaîne de caractères dans
un fichier appelé test_21.txt en écrivant un caractère à la fois. Pendant ce
temps, imprimez la chaîne à l'écran.
4. Réécrivez l'exercice 3. Cette fois, essayez d'écrire un bloc de caractères (c'est-
à-dire une chaîne) à la fois.
380 Heure
21

HEURE 22
Utilisation des fonctions
de fichiers spéciaux
L'espace disque : la dernière frontière.
-Le frère cadet du capitaine Kirk
Dans la leçon de l'heure dernière, vous avez appris les bases de la lecture et
de l'écriture des fichiers de données sur disque. Dans cette leçon, vous en
apprendrez davantage sur la communication avec les fichiers de données sur
disque. Les principaux sujets abordés dans cette heure sont les suivants
• Accès aléatoire aux fichiers
• Lecture ou écriture de données binaires
• Redirection des flux standards
En outre, les fonctions d'E/S en C suivantes sont présentées dans cette leçon :
• Les fonctions fseek(), ftell() et rewind()
• Les fonctions fscanf() et fprintf()
• La fonction freopen()
374 Heure
374

Accès aléatoire aux fichiers du disque


Jusqu'à présent, vous avez appris à lire ou à écrire des données de manière séquentielle
dans un fichier disque ouvert, ce que l'on appelle l'accès séquentiel. En d'autres termes,
vous commencez par le premier octet et continuez à lire ou à écrire chaque octet successif
dans l'ordre. Dans de nombreux cas, cependant, vous devez accéder à des données par-
ticulières au milieu d'un fichier disque. Une façon de procéder consiste à continuer à lire
les données du fichier jusqu'à ce que la donnée en question soit récupérée. Il est évident
que cette méthode n'est pas efficace, en particulier lorsque le fichier contient de
nombreuses données.
L'accès aléatoire est une autre façon de lire ou d'écrire des données sur des fichiers de
disque. L'accès aléatoire permet d'accéder à des éléments spécifiques du fichier dans un
ordre aléatoire (c'est-à-dire sans lire toutes les données précédentes).
En C, il existe deux fonctions d'E/S, fseek() et ftell(), qui sont conçues pour
gérer l'accès aléatoire.

Les fonctions fseek() et ftell()


Comme nous venons de le voir, vous avez besoin de fonctions qui vous permettent d'accéder à des fichiers
de manière aléatoire. Les fonctions
Les fonctions fseek() et ftell() vous offrent cette possibilité.
Dans la leçon précédente, vous avez appris que l'un des membres de la structure FILE
s'appelle l'indicateur de position du fichier. L'indicateur de position de fichier doit
pointer vers la position souhaitée dans un fichier avant que des données puissent y être
lues ou écrites. Vous pouvez utiliser la fonction fseek() pour déplacer l'indicateur de
position de fichier à l'endroit auquel vous voulez accéder dans un fichier.
La syntaxe de la fonction fseek() est la suivante
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. offset indique le
E
S

nombre d'octets à partir d'une position fixe spécifiée par whence qui peut avoir l'une des
valeurs intégrales suivantes représentées par SEEK_SET, SEEK_CUR et SEEK_END. Si elle
E

réussit, la fonction fseek() renvoie 0 ; sinon, elle renvoie une valeur non nulle.
Vous trouverez les valeurs représentées par SEEK_SET, SEEK_CUR et SEEK_END dans le
fichier d'en-tête stdio.h.
Si SEEK_SET est choisi comme troisième argument de la fonction fseek(), le décalage
est compté à partir du début du fichier et la valeur du décalage doit être supérieure ou
égale à zéro. En revanche, si SEEK_END est utilisé, le décalage commence à la fin du
fichier et la valeur du décalage doit être négative. Lorsque SEEK_CUR est transmis à la
fonction fseek(), le décalage est calculé à partir de la valeur actuelle de l'indicateur de
Utilisation des fonctions de 375
position du fichier. fichiers spéciaux
376 Heure
376

Vous pouvez obtenir la valeur actuelle de l'indicateur de position du fichier en appelant la fonction ftell()
fonction.
22
La syntaxe de la fonction ftell() est la suivante
SYNTAX

#include <stdio.h>
long ftell(FILE *stream) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. La fonction ftell()
E

renvoie la valeur actuelle de l'indicateur de position du fichier.


La valeur renvoyée par la fonction ftell() représente le nombre d'octets entre le début
du fichier et la position actuelle indiquée par l'indicateur de position du fichier.
Si la fonction ftell() échoue, elle renvoie -1L (c'est-à-dire une valeur longue de
moins 1). L'échec de la fonction ftell() peut être dû au fait que le fichier est un
terminal ou un autre type de fichier pour lequel l'indicateur de position du fichier n'a
plus de sens.
Le programme du Listing 22.1 montre comment accéder de manière aléatoire à un fichier disque en utilisant la
fonction
fseek() et ftell().

TYPE LISTE 22.1 Accès aléatoire à un fichier


1 : /* 22L01.c : Accès aléatoire à un
fichier */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL, MAX_LEN = 80} ;
5 :
6 : void PtrSeek(FILE *fptr)
; 7 : long PtrTell(FILE
*fptr) ; 8 : void
DataRead(FILE *fptr) ; 9 :
int ErrorMsg(char *str) ; 10
:
11 : main(void)
12 : {
13 : FILE *fptr ;
14 : char filename[]= "haiku.txt"
; 15: int reval = SUCCESS ;
16 :
17 : if ((fptr = fopen(filename, "r")) ==
NULL){ 18: reval =
ErrorMsg(filename) ;
19 : } else {
20 : PtrSeek(fptr) ;
21 : fclose(fptr) ;
22 : }
23 :

continue
Utilisation des fonctions de 377
fichiers spéciaux

LISTE 22.1 suite


24 : retour reval ;
25 : }
26 : /* définition de la
fonction */ 27 : void
PtrSeek(FILE *fptr) 28 : {
29 : long offset1, offset2, offset3
; 30 :
31 : offset1 = PtrTell(fptr)
; 32 : DataRead(fptr) ;
33 : offset2 = PtrTell(fptr)
; 34 : DataRead(fptr) ;
35 : offset3 = PtrTell(fptr)
; 36 : DataRead(fptr) ;
37 :
38 : printf("\nRe-lisez le haïku:\n") ;
39 : /* relire le troisième vers du haïku */
40 : fseek(fptr, offset3, SEEK_SET) ;
41 : DataRead(fptr) ;
42 : /* relire le deuxième vers du haïku */ 43
: fseek(fptr, offset2, SEEK_SET) ;
44 : DataRead(fptr) ;
45 : /* relire le premier vers du haïku */ 46
: fseek(fptr, offset1, SEEK_SET) ;
47 : DataRead(fptr) ;
48 : }
49 : /* définition de la
fonction */ 50 : long
PtrTell(FILE *fptr) 51 : {
52 : longue réévaluation ;
53 :
54 : reval = ftell(fptr) ;
55 : printf("Le fptr est à %ld\n", reval)
; 56 :
57 : retour reval ;
58 : }
59 : /* définition de la
fonction */ 60 : void
DataRead(FILE *fptr) 61 : {
62 : char buff[MAX_LEN] ;
63 :
64 : fgets(buff, MAX_LEN, fptr)
; 65: printf("---%s", buff) ;
66 : }
67 : /* définition de la
fonction */ 68 : int
ErrorMsg(char *str) 69 : {
70 : printf("Cannot open %s.\n", str)
; 71: return FAIL ;
72 : }
378 Heure
378

L'écran de mon ordinateur affiche la sortie suivante après l'exécution de l'exécutable


22L01.exe du programme de la liste 22.1 :
Le fptr est à 0
22
SORTIE ---Le fptr est à
18 ans.
-- mon ombre retourne à la
maison Le fptr est à 44
--de regarder la lune.

Relisez le haïku :
--de regarder la lune.
---Mon ombre retourne à la maison
-- Me mener par le bout du nez

L'objectif du programme du Listing 22.1 est de déplacer l'indicateur de position


ANALYSE
du fichier afin de lire différents versets du fichier haiku.txt.
Dans la fonction main(), un pointeur de fichier fptr est défini à la ligne 13, et le nom
du fichier haiku.txt est assigné au tableau appelé filename à la ligne 14. Ensuite, à la
ligne 17, vous essayez d'ouvrir le fichier haiku.txt pour la lecture en appelant la
fonction fopen(). En cas de succès, vous invoquez la fonction PtrSeek() avec le
pointeur de fichier fptr comme argument à la ligne 20.
La définition de votre première fonction, PtrSeek(), est présentée aux lignes 27-48.
L'instruction de la ligne 31 obtient la valeur originale du pointeur de fichier fptr en
appelant votre autre fonction, PtrTell(), qui est définie aux lignes 50-58. La fonction
PtrTell() trouve et imprime la valeur de l'indicateur de position de fichier à l'aide de la
fonction ftell(). La valeur originale de l'indicateur de position de fichier contenue
dans fptr est affectée à la variable longue offset1 à la ligne 31.
À la ligne 32, la troisième fonction, DataRead(), est appelée pour lire une ligne de
caractères du fichier ouvert et l'imprimer à l'écran. La ligne 33 récupère la valeur de
l'indicateur de position de fichier fptr juste après l'opération de lecture et l'affecte à
une autre variable longue offset2.
Ensuite, la fonction DataRead() de la ligne 34 lit la deuxième ligne de caractères du
fichier ouvert. La ligne 35 obtient la valeur de l'indicateur de position du fichier qui
pointe sur le premier octet du troisième verset et affecte cette valeur à la troisième
variable longue, offset3. La ligne 36 appelle la fonction DataRead() pour lire le
troisième verset et l'imprimer à l'écran.
Par conséquent, dans la première partie de la sortie, vous pouvez voir les trois valeurs
différentes de l'indicateur de position du fichier à trois positions différentes, ainsi que les
trois vers du haïku écrit par Sodo. Les trois valeurs de l'indicateur de position du fichier
sont enregistrées respectivement par offset1, offset2 et offset3.
Utilisation des fonctions de 379
fichiers spéciaux

Maintenant, en partant de la ligne 40 à la ligne 47, vous lisez le haïku de Sodo à l'envers,
un vers à la fois. En d'autres termes, vous lisez d'abord le troisième vers, puis le
deuxième vers et enfin le premier vers. Pour ce faire, vous appelez d'abord la fonction
fseek() pour déplacer l'indicateur de position de fichier au début du troisième verset en
passant la valeur contenue dans offset3 à la fonction. Ensuite, vous appelez à nouveau
fseek() et passez la valeur de l'offset2 à la fonction afin que l'indicateur de position
du fichier soit positionné sur le premier octet du deuxième couplet. Enfin, vous déplacez
l'indicateur de position du fichier au début du premier couplet en transmettant la valeur
de l'offset1 à la fonction fseek(). Par conséquent, dans la deuxième partie de la sortie,
vous voyez les trois versets du haïku dans l'ordre inverse.

La fonction rewind()
Il peut arriver que vous souhaitiez réinitialiser l'indicateur de position de fichier et le
placer au début d'un fichier. Il existe une fonction C pratique, appelée rewind(), qui
peut être utilisée pour rembobiner l'indicateur de position du fichier.
La syntaxe de la fonction rewind() est la suivante
SYNTAX

#include <stdio.h>
void rewind(FILE *stream) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. Aucune valeur n'est
E

renvoyée par la fonction rewind().


En fait, la déclaration suivante de la fonction rewind() :

rewind(fptr) ;

est équivalent à celui-ci :


fseek(fptr, 0L, SEEK_SET) ;

Le Listing 22.2 contient un exemple qui appelle la fonction rewind() pour déplacer
l'indicateur de position de fichier au début d'un fichier ouvert.

Autres exemples d'E/S de fichiers sur disque


Les sections suivantes présentent plusieurs autres exemples d'E/S de fichiers sur disque,
tels que la lecture et l'écriture de données binaires et la redirection des flux standard.
Trois autres fonctions d'E/S, fscanf(), fprintf() et freopen(), sont également
présentées.

Lecture et écriture de données binaires


Comme vous l'avez appris dans l'Heure 21, "Lire et écrire avec des fichiers", vous
pouvez indiquer au compilateur que vous allez ouvrir un fichier binaire en définissant
un mode approprié lors de l'appel de la commande
380 Heure
380

la fonction fopen(). Par exemple, l'instruction suivante tente d'ouvrir un fichier binaire
existant pour le lire :
22
fptr = fopen("test.bin", "rb") ;

Notez que le mode "rb" est utilisé pour indiquer que le fichier que vous allez ouvrir en
lecture est un fichier binaire.
La liste 22.2 contient un exemple de lecture et d'écriture de données binaires.

TYPE LISTE 22.2 Lecture et écriture de données binaires


1 : /* 22L02.c : Lecture et écriture de données
binaires */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL, MAX_NUM = 3} ;
5 :
6 : void DataWrite(FILE *fout)
; 7 : void DataRead(FILE
*fin) ; 8 : int
ErrorMsg(char *str) ;
9 :
10 : main(void)
11 : {
12 : FILE *fptr ;
13 : char filename[]= "double.bin"
; 14: int reval = SUCCESS ;
15 :
16 : if ((fptr = fopen(filename, "wb+")) ==
NULL){ 17: reval =
ErrorMsg(filename) ;
18 : } else {
19 : DataWrite(fptr) ;
20 : rewind(fptr) ; /* reset fptr
*/ 21 : DataRead(fptr) ;
22 : fclose(fptr) ;
23 : }
24 :
25 : retour reval ;
26 : }
27 : /* définition de la
fonction */ 28 : void
DataWrite(FILE *fout) 29 : {
30 : int i ;
31 : double buff[MAX_NUM] =
{ 32 : 123.45,
33 : 567.89,
34 : 100.11} ;
35 :

continue
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

380 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
22

LISTE 22.2 suite


36 : printf("The size of buff : %d-byte\n",
sizeof(buff)) ; 37: for (i=0 ; i<MAX_NUM ; i++){
38 : printf("%5.2f\n", buff[i]) ;
39 : fwrite(&buff[i], sizeof(double), 1, fout) ;
40 : }
41 : }
42 : /* définition de la
fonction */ 43 : void
DataRead(FILE *fin) 44 : {
45 : int i ;
46 : double x ;
47 :
48 : printf("\NRelecture du fichier binaire:\N") ;
49: for (i=0 ; i<MAX_NUM ; i++){
50 : fread(&x, sizeof(double), (size_t)1, fin)
; 51: printf("%5.2f\n", x) ;
52 : }
53 : }
54 : /* définition de la
fonction */ 55 : int
ErrorMsg(char *str) 56 : {
57 : printf("Cannot open %s.\n", str)
; 58: return FAIL ;
59 : }

Après avoir lancé l'exécutable 22L02.exe, l'écran de mon ordinateur affiche la sortie
suivante :
Taille de la mémoire tampon :
SORTIE 24 octets
123.45
567.89
100.11

Lecture du fichier binaire :


123.45
567.89
100.11

L'objectif du programme de la liste 22.2 est d'écrire trois valeurs du double


ANALYSE
dans un fichier binaire, puis de rembobiner l'indicateur de position du fichier et
de relire les trois valeurs doubles à partir du fichier binaire. Les deux fonctions,
DataWrite() et DataRead(), qui effectuent l'écriture et la lecture, sont déclarées aux
lignes 6 et 7.
Les noms de l'énumération, SUCCESS, FAIL et MAX_NUM, sont définis à la ligne 4 avec des
valeurs de 0, 1 et 3, respectivement.
Utilisation des fonctions de 381
fichiers spéciaux

Dans la fonction main(), l'instruction de la ligne 16 tente de créer et d'ouvrir un fichier


binaire appelé double.bin pour la lecture et l'écriture. Notez que le mode "wb+" est
utilisé dans la fonction fopen() de la ligne 16.
22
Si la fonction fopen() réussit, la fonction DataWrite() est appelée à la ligne 19 pour
écrire trois données doubles, 123.45, 567.89 et 100.11, dans le fichier binaire ouvert,
conformément à la définition de la fonction DataWrite() aux lignes 28 à 41. La
fonction fwrite() de la ligne 39 effectue l'écriture. Comme les trois données doubles
sont sauvegardées dans un tableau nommé buff, vous mesurez et imprimez également la
taille du tableau buff à la ligne 36. Sur ma machine, la taille du tableau buff est de 24
octets car chaque donnée double est de 8 octets.
Juste après l'exécution de la fonction DataWrite(), l'indicateur de position du fichier est
réinitialisé au début du fichier binaire en appelant la fonction rewind() à la ligne 20
parce que vous voulez relire les trois doubles données écrites dans le fichier.
Ensuite, à la ligne 21, la fonction DataRead() lit les trois données doubles dans le
fichier binaire ouvert double.bin. La définition de la fonction DataRead() aux lignes
43-53 montre que la fonction fread() est utilisée pour effectuer l'opération de lecture
(voir ligne 50).
Le résultat de l'exécution du programme de la liste 22.2 montre les trois données
doubles avant l'écriture et après la lecture.

Les fonctions fscanf() et fprintf()


Comme vous l'avez appris, les deux fonctions scanf() et printf() de la bibliothèque
C peuvent être utilisées pour lire ou écrire des données formatées via les E/S standard
(c'est-à-dire stdin et stdout). Parmi les fonctions d'E/S de fichier disque en C, il existe
deux fonctions équivalentes, fscanf() et fprintf(), qui peuvent effectuer les mêmes
tâches que les fonctions scanf() et printf(). En outre, les fonctions fscanf() et
fprintf() permettent au programmeur de spécifier des flux d'E/S autres que stdin et
stdout.

La syntaxe de la fonction fscanf() est la suivante


#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. format, dont
E
S

l'utilisation est la même que dans la fonction scanf(), est un pointeur de caractère
pointant vers une chaîne de caractères contenant les spécificateurs de format. En cas de
E

succès, la fonction fscanf() renvoie le nombre de données lues. Dans le cas contraire,
la fonction renvoie EOF.
382 Heure
22

La syntaxe de la fonction fprintf() est la suivante


#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...) ;

Ici, stream est le pointeur de fichier associé à un fichier ouvert. format, dont l'utilisation
E
S

est la même que dans la fonction printf(), est un pointeur de caractère pointant vers une
chaîne de caractères contenant les spécificateurs de format. En cas de succès, la fonction
E

fprintf() renvoie le nombre d'expressions formatées. Dans le cas contraire, la fonction


renvoie une valeur négative.
Pour en savoir plus sur les fonctions fprintf() et fscanf(), vous pouvez consulter les
explications sur les fonctions printf() et scanf() dans l'Heure 5, "Manipulation des
entrées et sorties standard", et l'Heure 13, "Manipulation des chaînes de caractères".
Le programme de la liste 22.3 montre comment utiliser les fonctions fscanf() et
fprintf() pour lire et écrire des éléments de données typés différemment.

TYPE LISTE 22.3 Utilisation des fonctions fscanf() et fprintf()


1 : /* 22L03.c : Utilisation des fonctions fscanf() et
fprintf() */ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL,
5 : MAX_NUM = 3,
6 : STR_LEN = 23} ;
7 :
8 : void DataWrite(FILE *fout)
; 9 : void DataRead(FILE
*fin) ; 10 : int
ErrorMsg(char *str) ; 11 :
12 : main(void)
13 : {
14 : FILE *fptr ;
15 : char filename[]= "strnum.mix"
; 16: int reval = SUCCESS ;
17 :
18 : if ((fptr = fopen(filename, "w+")) ==
NULL){ 19: reval = ErrorMsg(filename) ;
20 : } else {
21 : DataWrite(fptr) ;
22 : rewind(fptr) ;
23 : DataRead(fptr) ;
24 : fclose(fptr) ;
25 : }
26 :
27 : retour reval ;
28 : }
29 : /* définition de la
fonction */ 30 : void
DataWrite(FILE *fout)
Utilisation des fonctions de 383
fichiers spéciaux

31 : {
32 : int i ;
33 : char cities[MAX_NUM][STR_LEN] = 22
{ 34 : "St.Louis->Houston
:",
35 : "Houston->Dallas :",
36 : "Dallas->Philadelphie :"}
; 37: int miles[MAX_NUM] = {
38 : 845,
39 : 243,
40 : 1459} ;
41 :
42 : printf("Les données
écrites:\N") ; 43: for (i=0 ;
i<MAX_NUM ; i++){
44 : printf("%-23s %d miles\n", cities[i], miles[i])
; 45 : fprintf(fout, "%s %d", cities[i], miles[i]) ;
46 : }
47 : }
48 : /* définition de la
fonction */ 49 : void
DataRead(FILE *fin) 50 : {
51 : int i ;
52 : int miles ;
53 : char cities[STR_LEN] ;
54 :
55 : printf("\NLes données
lues:\N") ; 56: for (i=0 ;
i<MAX_NUM ; i++){
57 : fscanf(fin, "%s%d", cities, &miles) ;
58 : printf("%-23s %d miles\n", cities, miles)
; 59 : }
60 : }
61 : /* définition de la
fonction */ 62 : int
ErrorMsg(char *str) 63 : {
64 : printf("Cannot open %s.\n", str)
; 65: return FAIL ;
66 : }

La sortie suivante s'affiche à l'écran après la création et l'exécution de l'exécutable


22L03.exe sur mon ordinateur :

Les données écrites :


SORTIE Louis->Houston : 845 miles
Houston->Dallas : 243 miles
Dallas->Philadelphie : 1459 miles

Les données se lisent comme suit :


Louis->Houston : 845 miles
Houston->Dallas : 243 miles
Dallas->Philadelphie : 1459 miles
384 Heure
22

L'objectif du programme de la liste 22.3 est d'écrire des données de différents


ANALYSE
types dans un fichier à l'aide de la fonction fprintf() et de lire les données en
retour.
dans le même format en appelant la fonction fscanf(). Les deux fonctions déclarées
aux lignes 8 et 9, DataWrite() et DataRead(), effectuent l'écriture et la lecture.
La déclaration de la fonction main() à la ligne 18 tente de créer et d'ouvrir un fichier
texte appelé strnum.mix pour la lecture et l'écriture en spécifiant le deuxième argument
de la fonction fopen() comme "w+". Si fopen() ne renvoie pas un pointeur nul, la
fonction DataWrite() est appelée à la ligne 21 pour écrire des chaînes de caractères et
des éléments de données int dans le fichier strnum.mix. Notez que la fonction
fprintf() est invoquée à l'intérieur de la fonction DataWrite() à la ligne 45 pour
écrire les données formatées dans le fichier texte.
La définition de la fonction DataWrite() aux lignes 30 à 47 montre qu'il y a deux
tableaux, cities et miles. Le tableau cities contient trois chaînes qui indiquent trois
paires de villes, et le tableau miles contient trois valeurs int représentant les distances
correspondantes entre les villes indiquées dans le tableau cities. Par exemple, 845
dans le tableau miles est la distance (en miles) entre les deux villes exprimées par la
chaîne St.Louis->Houston : dans le tableau cities.
À la ligne 22, la fonction rewind() est appelée pour rembobiner l'indicateur de position
du fichier et le réinitialiser au début du fichier strnum.mix. Ensuite, la fonction
DataRead() de la ligne 23 lit ce qui a été sauvegardé dans strnum.mix à l'aide de la
fonction fscanf(). La définition de la fonction DataRead() est présentée aux lignes
49 à 60.
Cet exemple montre qu'il est pratique d'utiliser les fonctions fprintf() et fscanf().
pour effectuer des opérations d'E/S sur des fichiers de disque formatés.

Redirection des flux standards avec freopen()


Au cours de l'heure 5, vous avez appris à lire ou à écrire sur les E/S standard. On vous a
également expliqué que les fonctions C, telles que getc(), gets(), putc et printf(),
dirigent automatiquement leurs opérations d'E/S vers stdin ou stdout.
Dans cette section, vous allez apprendre à rediriger les flux standard, tels que stdin et
stdout, vers des fichiers disque. Vous allez utiliser une nouvelle fonction C appelée
freopen(), qui permet d'associer un flux standard à un fichier disque.

La syntaxe de la fonction freopen() est la suivante


#include <stdio.h>
FILE *freopen(const char *filename, const char *mode, FILE *stream) ;

Ici, filename est un pointeur de caractères référençant le nom d'un fichier que vous
E
S

souhaitez associer au flux standard représenté par stream. mode est un autre pointeur de
caractères pointant vers
Utilisation des fonctions de 385
fichiers spéciaux

une chaîne de caractères qui définit la manière d'ouvrir un fichier. Les valeurs que peut
prendre mode dans freopen() sont les mêmes que celles de la fonction fopen(). (Les
22
E

définitions de toutes les valeurs de mode sont données dans l'heure 21.)
La fonction freopen() renvoie un pointeur nul en cas d'erreur. Sinon, la fonction
renvoie le flux standard qui a été associé à un fichier disque identifié par le nom de
fichier.

Le Listing 22.4 montre un exemple de redirection de la sortie standard, stdout, à l'aide


de la fonction freopen().

TYPE LISTE 22.4 Redirection du flux standard stdout


1 : /* 22L04.c : Redirection d'un flux standard
*/ 2 : #include <stdio.h>
3 :
4 : enum {SUCCESS, FAIL,
5 : STR_NUM = 4} ;
6 :
7 : void StrPrint(char **str)
; 8 : int ErrorMsg(char
*str) ; 9 :
10 : main(void)
11 : {
12 : char *str[STR_NUM] = {
13 : "Courbe-toi, et tu resteras droit",
14 : "Sois inoccupé, et tu resteras rassasié",
15 : "Soyez usés, et vous resterez neufs",
16 : "--- de Lao Tseu"} ;
17 : char filename[]= "LaoTzu.txt"
; 18: int reval = SUCCESS ;
19 :
20 : StrPrint(str) ;
21 : if (freopen(filename, "w", stdout) ==
NULL){ 22: reval =
ErrorMsg(filename) ;
23 : } else {
24 : StrPrint(str) ;
25 : fclose(stdout) ;
26 : }
27 : retour reval ;
28 : }
29 : /* définition de la
fonction */ 30 : void
StrPrint(char **str) 31 : {
32 : int i ;
33 :

continue
386 Heure
22

LISTE 22.4 suite


34 : for (i=0 ; i<STR_NUM ; i++)
35 : printf("%s\n", str[i]) ;
36 : }
37 : /* définition de la
fonction */ 38 : int
ErrorMsg(char *str) 39 : {
40 : printf("Cannot open %s.\n", str)
; 41: return FAIL ;
42 : }

Après la création et l'exécution de l'exécutable 22L04.exe, la sortie suivante est imprimée


sur l'écran de mon ordinateur :
Courbez-vous, et vous resterez droit.
SORTIE Sois vide, et tu resteras plein.
Soyez usés, et vous resterez neufs.
--- de Lao Tseu

L'objectif du programme de la liste 22.4 est d'enregistrer un paragraphe du Tao Te


ANALYSE
Ching écrit par un philosophe chinois, Lao Tzu, dans un fichier texte,
LaoTzu.txt. Pour
Pour ce faire, vous appelez la fonction printf() au lieu de la fonction fprintf() ou
d'autres fonctions d'E/S sur disque après avoir redirigé le flux par défaut, stdout, de la
fonction printf() pour qu'il pointe vers le fichier texte.
La fonction qui effectue l'écriture s'appelle StrPrint(), qui appelle la fonction C
printf() pour envoyer des chaînes de caractères formatées sur le flux de sortie. (Voir la
définition de la fonction StrPrint() aux lignes 30-36.)
Dans la fonction main(), vous appelez la fonction StrPrint() à la ligne 20 avant de
rediriger stdout vers le fichier LaoTzu.txt. Il n'est pas surprenant de voir que le
paragraphe adopté du Tao Te Ching est imprimé à l'écran parce que la fonction printf()
envoie automatiquement le paragraphe à stdout, qui dirige par défaut vers l'écran.
Ensuite, à la ligne 21, vous redirigez stdout vers le fichier texte LaoTzu.txt en
appelant la fonction freopen(). Ici, le "w" est utilisé comme mode indiquant d'ouvrir le
fichier texte pour l'écriture. Si freopen() réussit, vous appelez alors la fonction
StrPrint() à la ligne 24.
Cependant, cette fois, la fonction StrPrint() écrit le paragraphe dans le fichier texte
ouvert, LaoTzu.txt. La raison en est que stdout est désormais associé au fichier texte,
et non à l'écran, de sorte que les chaînes de caractères envoyées par l'appel printf()
dans StrPrint() sont dirigées vers le fichier texte.
Après l'exécution du programme de la liste 22.4, vous pouvez ouvrir le fichier
LaoTzu.txt dans un éditeur de texte et constater que le paragraphe du Tao Te Ching a
été enregistré dans le fichier.
Utilisation des fonctions de 387
fichiers spéciaux

Comme indiqué précédemment, les flux d'E/S sont mis en mémoire tampon
par défaut. Il peut arriver que vous souhaitiez désactiver la mise en mémoire 22
tampon afin de pouvoir traiter l'entrée immédiatement. En C, deux fonctions,
setbuf() et setvbuf(), peuvent être utilisées pour désactiver la mise en
mémoire tampon, bien que les E/S non mises en mémoire tampon sortent du
cadre de cet ouvrage.
Il existe également un ensemble de fonctions d'E/S de bas niveau, telles que
open(), create(), close(), read(), write(), lseek() et tell(), qui ne
sont pas prises en charge par la norme C ANSI. Vous pouvez encore les voir
dans certains programmes C dépendant de la plate-forme. Pour les utiliser,
vous devez lire le manuel de référence de votre compilateur C pour vous

Résumé
Dans cette leçon, vous avez appris les concepts et fonctions importants suivants
concernant l'entrée et la sortie de fichiers disque en C :
• L'indicateur de position du fichier peut être réinitialisé par la fonction fseek().
• La fonction ftell() peut vous indiquer la valeur de l'indicateur de position actuelle du fichier.
• La fonction rewind() permet de placer l'indicateur de position de fichier au début d'un fichier.
• Après avoir spécifié le mode de la fonction fopen() pour un fichier binaire, vous
pouvez utiliser les fonctions fread() ou fwrite() pour effectuer des opérations
d'E/S sur des données binaires.
• Outre le fait que les fonctions fscanf() et fprintf() peuvent effectuer les mêmes
tâches que les fonctions scanf() et printf(), les fonctions fscanf() et
fprintf() permettent également au programmeur de spécifier les flux d'E/S.
• Vous pouvez rediriger les flux standard, tels que stdin et stdout, vers un
fichier disque à l'aide de la fonction freopen().
Dans la prochaine leçon, vous découvrirez le préprocesseur C.

Q&R
Q Pourquoi l'accès aléatoire à un fichier disque est-il nécessaire ?
A Lorsque vous souhaitez extraire une information d'un fichier volumineux contenant
une grande quantité de données, l'accès aléatoire au fichier est un moyen plus
efficace que l'accès séquentiel au fichier. Les fonctions qui effectuent un accès
aléatoire peuvent placer l'indicateur de position du fichier directement au bon
endroit dans le fichier, et vous pouvez alors simplement commencer à récupérer
l'information requise à partir de là. En C, les fonctions fseek() et ftell() sont
deux fonctions pratiques qui vous aident à effectuer l'opération d'accès aléatoire.
388 Heure
22

Q Comment spécifier le format d'un nouveau fichier disque que vous allez créer en
appelant fopen() ?
R Vous devez ajouter b à l'argument mode de la fonction fopen() pour spécifier que
le fichier que vous allez créer est un fichier binaire. Vous pouvez utiliser "wb"
pour créer un nouveau fichier en écriture et "wb+" pour créer un nouveau fichier en
écriture et en lecture. Si, en revanche, le fichier à créer est un fichier texte, aucun b
n'est nécessaire dans l'argument mode.

Q Quelle est la différence entre les fonctions printf() et fprintf() ?


En principe, les fonctions printf() et fprintf() peuvent effectuer un travail
similaire : envoyer les éléments de données formatés vers les flux de sortie.
Toutefois, la fonction printf() envoie automatiquement les données formatées à
stdout, alors que la fonction fprintf() peut se voir attribuer un pointeur de
fichier associé à un flux de sortie spécifié.
Q Pouvez-vous rediriger un flux standard vers un fichier disque ?
R Oui. À l'aide de la fonction freopen(), vous pouvez rediriger un flux standard et
l'associer à un fichier disque.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Les deux affirmations suivantes sont-elles équivalentes ?
rewind(fptr) ;
fseek(fptr, 0L, SEEK_SET) ;

2. Les deux affirmations suivantes sont-elles équivalentes ?


rewind(fptr) ;
fseek(fptr, 0L, SEEK_CUR) ;

3. Après la déclaration
freopen("test.txt", "r", stdin) ;
est exécutée avec succès, où la fonction scanf() de l'état suivant lit-elle des
données ?
scanf("%s%d", str, &num) ;
4. Étant donné que la taille du type de données double est de 8 octets et que vous
avez quatre éléments de données doubles, si vous écrivez les quatre éléments de
données doubles dans un fichier binaire, combien d'octets les quatre éléments de
Utilisation des fonctions de 389
fichiersdans
données occupent-ils spéciaux
le fichier ?
390 Heure
22

Exercices
1. Supposons que le paragraphe suivant du Tao Te Ching soit enregistré dans un fichier texte 22
appelé
LaoTzu.txt :
Soyez courbés, et vous resterez droits.
Sois vide, et tu resteras plein.
Sois usé, et tu resteras neuf.

Ecrivez un programme qui utilise ftell() pour trouver les positions des trois
chaînes dans le fichier, puis appelez fseek() pour définir l'indicateur de position
du fichier de manière à ce que les trois chaînes soient imprimées dans l'ordre
inverse.
2. Réécrivez le programme que vous avez créé dans l'exercice 1 en appelant la
fonction rewind() pour réinitialiser l'indicateur de position du fichier au début
du fichier LaoTzu.txt.
3. Étant donné une valeur double de 123,45 et une valeur int de 10000, écrivez
un programme pour les enregistrer dans un fichier binaire, appelé data.bin, et
les lire ensuite à partir du fichier binaire. Imprimez également ce que vous
écrivez ou lisez. Quelle sera, selon vous, la taille du fichier binaire ?
4. Lisez le fichier texte strnum.mix, créé par le programme de l'illustration 22.3.
Redirigez le flux d'entrée de manière à pouvoir utiliser la fonction scanf() pour
effectuer l'opération de lecture.
Utilisation des fonctions de 391
fichiers spéciaux
HEURE 23
Compilation des
programmes : Le
préprocesseur C
L'intelligence est la faculté de fabriquer des objets artificiels, notamment
des outils pour fabriquer des outils.

-H. Bergson
Dans l'Heure 2, "Votre premier programme C", vous avez appris à utiliser la
directive de préprocesseur #include pour inclure des fichiers d'en-tête C.
Depuis lors, la directive #include a été utilisée dans tous les programmes de
ce livre. Depuis, la directive #include a été utilisée dans tous les
programmes de ce livre. Dans cette leçon, vous en apprendrez plus sur le
préprocesseur C et sur la définition de macros à l'aide des directives du
préprocesseur. Les sujets suivants sont abordés dans cette heure :

• Ce que le préprocesseur C peut faire


• Définitions et substitutions de macros
• Les directives #define et #undef
• Comment définir des macros de type fonction avec #define
• Les directives #ifdef, #ifndef et #endif
• Les directives #if, #elif et #else
• Comment imbriquer les directives #if et #elif
392 Heure
23

Qu'est-ce que le préprocesseur C ?


Si une constante apparaît à plusieurs endroits dans votre programme, il est judicieux de
lui associer un nom symbolique, puis d'utiliser ce nom symbolique pour remplacer la
constante dans l'ensemble du programme. Il y a deux avantages à faire cela.
Premièrement, votre programme sera plus lisible. Deuxièmement, il est plus facile de
maintenir votre programme. Par exemple, si la valeur de la constante doit être modifiée,
il suffit de trouver l'instruction qui associe la constante au nom symbolique et de
remplacer la constante par la nouvelle. Si vous n'utilisez pas le nom symbolique, vous
devez chercher partout dans votre programme pour remplacer la constante. Cela a l'air
génial, mais peut-on faire cela en C ?
Le langage C dispose d'un programme spécial, le préprocesseur C, qui permet de définir
et d'associer des noms symboliques à des constantes. En fait, le préprocesseur C utilise la
terminologie noms de macro et corps de macro pour se référer aux noms symboliques et
aux constantes. Le préprocesseur C s'exécute avant le compilateur. Au cours du
prétraitement, l'opération consistant à remplacer un nom de macro par le corps de macro
qui lui est associé est appelée macro-substitution ou macro-expansion.
Vous pouvez placer une définition de macro n'importe où dans votre programme.
Toutefois, le nom d'une macro doit être défini avant de pouvoir être utilisé dans votre
programme.
En outre, le préprocesseur C vous permet d'inclure d'autres fichiers source. Par exemple,
vous avez utilisé la directive #include du préprocesseur pour inclure des fichiers d'en-
tête C, tels que stdio.h, stdlib.h et string.h, dans les programmes de ce livre. Le
préprocesseur C vous permet également de compiler différentes sections de votre
programme dans des conditions spécifiques.

Le préprocesseur C et le compilateur
Il est important de rappeler que le préprocesseur C ne fait pas partie du compilateur C.
Le préprocesseur C utilise une syntaxe différente. Toutes les directives du
préprocesseur C commencent par un dièse (#). En d'autres termes, le signe dièse
indique le début d'une directive du préprocesseur et doit être le premier caractère non
spatial de la ligne.
Le préprocesseur C est axé sur les lignes. Chaque instruction de macro se termine par un
caractère de nouvelle ligne, et non par un point-virgule (seules les instructions C se
terminent par des points-virgules). (Seules les instructions C se terminent par des points-
virgules.) L'une des erreurs les plus courantes commises par le programmeur consiste à
placer un point-virgule à la fin d'une macro. Heureusement, de nombreux compilateurs C
peuvent détecter de telles erreurs.
Les sections suivantes décrivent certaines des directives les plus fréquemment utilisées, telles que
Compilation des programmes : Le 393
préprocesseur
#define, #undef, #if, #elifC
, #else, #ifdef, #ifndef et #endif.
394 Heure
23

Les noms de macros, en particulier ceux qui seront remplacés par des
constantes, sont généralement représentés par des lettres majuscules afin
qu'ils puissent être distingués des autres noms de variables dans le
programme.

Les directives #define et #undef


La directive #define est la directive la plus courante du préprocesseur. Elle indique au 23
préprocesseur de remplacer toutes les occurrences d'une chaîne de caractères particulière
(c'est-à-dire le nom de la macro) par une valeur spécifiée (c'est-à-dire le corps de la
macro).
La syntaxe de la directive #define est la suivante
SYNTAX

#define macro_name macro_body

Le nom de la macro est un identifiant qui peut contenir des lettres, des chiffres ou des traits de soulignement.
macro_body peut être une chaîne de caractères ou un élément de données, qui est utilisé pour remplacer chaque
E

macro_name
dans le programme.
Comme indiqué précédemment, l'opération consistant à remplacer les occurrences du nom de
la macro par la valeur spécifiée par le corps de la macro est connue sous le nom de
substitution ou d'expansion de macro.
La valeur du corps de la macro spécifiée par une directive #define peut être n'importe
quelle chaîne de caractères ou n'importe quel nombre. Par exemple, la définition suivante
associe STATE_NAME à la chaîne de caractères "Texas" (y compris les guillemets) :
#define STATE_NAME "Texas"

Ensuite, lors du prétraitement, toutes les occurrences de STATE_NAME seront remplacées par
"Texas".

De même, l'instruction suivante demande au préprocesseur C de remplacer SUM par la chaîne de caractères
(12 + 8) :

#define SUM (12 + 8)

En revanche, vous pouvez utiliser la directive #undef pour supprimer la définition d'un
nom de macro qui a été défini précédemment.
La syntaxe de la directive #undef est la suivante
SYNTAX

#undef macro_name

Ici, le nom de la macro est un identifiant qui a été préalablement défini par un #define
directive.
Compilation des programmes : Le 395
préprocesseur C

Vous pouvez considérer la directive #undef comme une "indéfinition" du nom d'une
macro. Par exemple, le segment de code suivant :
#define STATE_NAME "Texas"
printf("Je déménage de %s.\n", NOM_D'ÉTAT) ;
#undef NOM_D'ÉTAT

définit d'abord le nom de macro STATE_NAME et l'utilise dans la fonction printf() ; puis
il supprime le nom de macro. À partir de ce point du programme, le nom STATE_NAME ne
peut plus être utilisé (à moins, bien sûr, qu'il ne soit redéfini au préalable).

Définition de macros de type fonction avec #define


Vous pouvez spécifier un ou plusieurs arguments à un nom de macro défini par la
directive #define, de sorte que le nom de macro puisse être traité comme une simple
fonction acceptant des arguments.
Par exemple, le nom de macro suivant, MULTIPLY, prend deux arguments :
#define MULTIPLY(val1, val2) ((val1) * (val2))

Lorsque l'affirmation suivante :


résultat = MULTIPLY(2, 3) + 10 ;

est prétraité, le préprocesseur substitue l'expression 2 à val1 et 3 à val2, puis produit


l'équivalent suivant :
résultat = ((2) * (3)) + 10 ;

Le programme du Listing 23.1 est un exemple d'utilisation de la directive #define pour


effectuer une substitution de macro.

TYPE LISTE 23.1 Utilisation de la directive #define


1 : /* 23L01.c : Utilisation
de #define */ 2 : #include
<stdio.h>
3 :
4 : #define METHOD "ABS"
5 : #define ABS(val) ((val) < 0 ? -(val) :
(val)) 6 : #define MAX_LEN 8
7 : #define NEGATIVE_NUM -10
8 :
9 : main(void)
10 : {
11 : char *str =
MÉTHODE ; 12 : int
array[MAX_LEN] ;
13 : int i ;
14 :
15 : printf("The orignal values in array:\n")
396 Heure
; 16: 23
for (i=0 ; i<MAX_LEN ; i++){
Compilation des programmes : Le 397
préprocesseur C

17 : array[i] = (i + 1) * NEGATIVE_NUM ;
18 : printf("array[%d] : %d\n", i,
array[i]) ; 19 : }
20 :
21 : printf("\NApplication de la macro %s:\N",
str) ; 22: for (i=0 ; i<MAX_LEN ; i++){
23 : printf("ABS(%d) : %3d\n", array[i],
ABS(array[i])) ; 24 : }
25 :
26 : retour 0 ; 23
27 : }

La sortie suivante apparaît sur l'écran de mon ordinateur après l'exécution de l'exécutable
23L01.exe du programme de la liste 23.1 :
Les valeurs originales dans le tableau :
SORTIE array[0] : -10
array[1] : -20
array[2] : -30
array[3] : -40
array[4] : -50
array[5] : -60
array[6] : -70
array[7] : -80

Application de la macro APA :


ABS(-10) 10
:
ABS(-20) 20
:
ABS(-30) 30
:
ABS(-40) 40
:
ABS(-50) 50
:
ABS(-60) 60
:
ABS(-70) 70
:
ABS(-80) 80
:

ANALYSE L'objectif du programme de la liste 23.1 est de définir différents noms de macros, y
compris une macro de type fonction, et de les utiliser dans le programme.
Dans les lignes 4 à 7, quatre macros, METHOD, ABS, MAX_LEN et NEGATIVE_NUM sont
définies à l'aide de la directive #define. Parmi ces macros, ABS peut accepter un
argument. La définition de ABS à la ligne 5 vérifie la valeur de l'argument et renvoie la
valeur absolue de l'argument. Notez que l'opérateur conditionnel ? : est utilisé pour
trouver la valeur absolue de l'argument entrant. (L'opérateur ? : a été présenté dans
l'heure 8, "Utilisation des opérateurs conditionnels").
Opérateurs")
398 Heure
Ensuite, dans la fonction main(), le pointeur23char str est défini et assigné à METHOD à
la ligne 11. Comme vous pouvez le voir, METHOD est associé à la chaîne de caractères
"ABS". À la ligne 12, un tableau int appelé array est défini avec le nombre d'éléments
spécifié par MAX_LEN.
Compilation des programmes : Le 399
préprocesseur C

Dans les lignes 16 à 19, chaque élément du tableau est initialisé avec la valeur représentée par l'élément
(i + 1) * NEGATIVE_NUM expression qui produit une série de nombres entiers négatifs.

La boucle for des lignes 22-24 applique la macro fonction ABS à chaque élément du
tableau et obtient la valeur absolue de chaque élément. Toutes les valeurs absolues
sont ensuite affichées à l'écran. La sortie du programme de la liste 23.1 prouve que
chaque macro définie dans le programme fonctionne très bien.

Définitions de macros imbriquées


Une macro définie précédemment peut être utilisée comme valeur dans une autre
instruction #define. Voici un exemple :
#define ONE 1
#define TWO (ONE +
ONE) #define THREE (UN +
DEUX)
résultat = DEUX * TROIS ;

Ici, la macro ONE est définie comme étant équivalente à la valeur 1, et TWO est défini
comme étant équivalent à (ONE + ONE), où ONE a été défini dans la définition de la
macro précédente. De même, TROIS est défini comme équivalent à (UN + DEUX), ONE
et DEUX ayant été définis précédemment.
Par conséquent, l'instruction d'affectation qui suit les définitions des macros est étendue à
l'instruction suivante :
résultat = (1 + 1) * (1 + (1 + 1)) ;

Lorsque vous utilisez la directive #define avec un corps de macro qui est
une expression, vous devez mettre le corps de la macro entre parenthèses.
Par exemple, si la définition de la macro est
#define SUM 12 + 8
puis la déclaration suivante :
résultat = SOMME * 10 ;
devient ceci :
résultat = 12 + 8 * 10 ;
qui attribue 92 au résultat.
Cependant, si vous mettez le corps de la macro entre parenthèses comme ceci :
#define SUM (12 + 8)
l'instruction d'affectation devient alors la suivante :
résultat = (12 + 8) * 10 ;
et produit le résultat 200, ce qui est probablement ce que vous voulez.
400 Heure
23

Compilation du code sous conditions


Vous pouvez sélectionner les parties de votre programme C que vous souhaitez compiler en
utilisant un ensemble de directives de préprocesseur. Cette technique est appelée compilation
conditionnelle. Elle est utile, en particulier lorsque vous testez un nouveau morceau de code ou
que vous déboguez une partie du code.

Les directives #ifdef et #endif


Les directives #ifdef et #endif déterminent si un groupe donné d'instructions doit être 23
inclus dans votre programme.
La forme générale d'utilisation des directives #ifdef et #endif est la suivante
#ifdef macro_name
statement1
statement2
. . .
déclarationN
#endif

Ici, nom_de_macro est toute chaine de caractere qui peut etre definie par une directive
#define. declaration1, declaration2, et declarationN sont des declarations qui sont
incluses dans le programme seulement si nom_de_macro a deja ete defini. Si nom_macro
n'a pas été défini, déclaration1, déclaration2 et tout ce qui va jusqu'à déclarationN
sont ignorés.
Contrairement à une instruction if en C, les instructions sous le contrôle de la directive
#ifdef ne sont pas placées entre accolades ; au lieu de cela, la directive #endif doit
être utilisée pour marquer la fin du bloc #ifdef.
Par exemple, la directive #ifdef dans le segment de code suivant :
. . .
#ifdef DEBUG
printf("The contents of the string pointed to by str : %s\n", str)
; #endif
. . .

indique que si le nom de macro DEBUG est défini, la fonction printf() de l'instruction
suivant la directive #ifdef est incluse dans le programme. Le compilateur compilera
l'instruction de manière à ce que le contenu d'une chaîne de caractères pointée par str
soit imprimé par l'état. Toutefois, si DEBUG n'a pas été défini, l'appel à la fonction
printf() ne figurera pas dans le programme compilé.

La directive #ifndef
La directive #ifndef permet de définir le code à exécuter lorsqu'un nom de macro
particulier n'est pas défini.
Compilation des programmes : Le 401
préprocesseur C

Le format général pour utiliser #ifndef est le même que pour #ifdef :
#ifndef macro_name
statement1
statement2
. . .
déclarationN
#endif

Ici, macro_name, statement1, statement2 et statementN ont la même signification que


dans la forme de #ifdef présentée dans la section précédente. A nouveau, la directive
#endif est nécessaire pour marquer la fin du bloc #ifndef.

La liste 23.2 contient un programme qui montre comment utiliser les commandes #ifdef, #ifndef et
Les directives #endif sont regroupées.

TYPE LISTE 23.2 Utilisation des directives #ifdef, #ifndef et #endif


1 : /* 23L02.c : Utilisation de #ifdef, #ifndef,
et #endif */ 2 : #include <stdio.h>
3 :
4 : #define UPPER_CASE 0
5 : #define NO_ERROR 0
6 :
7 : main(void)
8 : {
9 : #ifdef UPPER_CASE
10 : printf("THIS LINE IS PRINTED OUT,\n") ;
11 : printf("BECAUSE UPPER_CASE IS DEFINED.\n") ;
12 : #endif
13 : #ifndef LOWER_CASE
14 : printf("\NCette ligne est imprimée,\N") ;
15 : printf("because LOWER_CASE is not defined.\n")
; 16 : #endif
17 :
18 : retour NO_ERROR ;
19 : }

La sortie suivante s'affiche à l'écran après la création et l'exécution de l'exécutable


23L02.exe sur mon ordinateur :
CETTE LIGNE EST IMPRIMÉE,
SORTIE CAR L'OPTION UPPER_CASE EST DÉFINIE.

Cette ligne est imprimée,


car LOWER_CASE n'est pas défini.

Le but du programme du Listing 23.2 est d'utiliser les directives #ifdef et #ifndef
ANALYSE
pour contrôler l'affichage d'un message.
402 Heure
23

Deux noms de macro, UPPER_CASE et NO_ERROR, sont définis aux lignes 4 et 5.


La directive #ifdef de la ligne 9 vérifie si le nom de la macro UPPER_CASE a été
défini. Comme le nom de la macro a été défini à la ligne 4, les deux instructions des
lignes 10 et 11 (jusqu'à ce que la directive #endif de la ligne 12 marque la fin du bloc
#ifdef) sont incluses dans le programme compilé.

À la ligne 13, la directive #ifndef indique au préprocesseur d'inclure les deux


instructions des lignes 14 et 15 dans le programme si le nom de la macro LOWER_CASE 23
n'a pas été défini. Comme vous pouvez le constater, LOWER_CASE n'est pas du tout
défini dans le programme. Par conséquent, les deux déclarations des lignes 14 et 15 sont
compilées en tant que partie du programme.
Le résultat de l'exécution du programme du Listing 23.2 montre que les fonctions
printf() des lignes 10, 11, 14 et 15 sont compilées et exécutées en conséquence, sous
le contrôle des directives #ifdef et #ifndef. Vous pouvez essayer de modifier le
programme en changeant la ligne 4 pour qu'elle définisse LOWER_CASE au lieu de
UPPER_CASE. Les directives #ifdef et #ifndef supprimeront alors les quatre appels
printf() du programme.

Les directives #if, #elif et #else


La directive #if spécifie que certaines instructions ne doivent être incluses que si la
valeur représentée par l'expression conditionnelle est différente de zéro. L'expression
conditionnelle peut être une expression arithmétique.
La forme générale d'utilisation de la directive #if est la suivante
#if expression
statement1
statement2
. . .
déclarationN
#endif

Ici, expression est l'expression conditionnelle à évaluer. déclaration1, déclaration2 et


déclarationN représentent le code à inclure si expression est non nulle.
Notez que la directive #endif est incluse à la fin de la définition pour marquer la fin du
bloc #if, comme c'est le cas pour un bloc #ifdef ou #ifndef.
En outre, la directive #else fournit une alternative à choisir. La forme générale
suivante utilise la directive #else pour insérer les instructions 1, 2 et N dans le
programme si l'expression est égale à zéro :
#if expression
statement1
statement2
. . .
Compilation des programmes : Le 403
préprocesseur C

déclarationN
#else
déclaration_1
déclaration_2
. . .
déclaration_N
#endif

Là encore, la directive #endif est utilisée pour marquer la fin du bloc #if.
De plus, une définition de macro peut être utilisée comme partie de l'expression
conditionnelle évaluée par la directive #if. Si la macro est définie, elle a une valeur non
nulle dans l'expression ; sinon, elle a la valeur 0.
Par exemple, regardez la portion de code suivante :
#ifdef DEBUG
printf("The value of the debug version : %d\n", debug)
; #else
printf("The value of the release version : %d\n", release)
; #endif

Si DEBUG a été défini par une directive #define, la valeur de la version de débogage est
imprimée par la fonction printf() dans la déclaration suivante :
printf("La valeur de la version de débogage : %d\n", debug) ;

Sinon, si DEBUG n'a pas été défini, l'instruction suivante est exécutée :
printf("The value of the release version : %d\n", release) ;

Prenons un autre exemple :


#if 1
printf("La ligne est toujours imprimée.\n") ;
#endif

La fonction printf() est toujours exécutée parce que l'expression 1 évaluée par le #if
ne renvoie jamais 0. Dans
l'exemple suivant :
#if MACRO_NAME1 || MACRO_NAME2
printf("MACRO_NAME1 ou MACRO_NAME2 est défini.\n")
; #else
printf("MACRO_NAME1 et MACRO_NAME2 ne sont pas définis.\n")
; #endif

l'opérateur logique || est utilisé avec MACRO_NAME1 et MACRO_NAME2 dans l'expression


évaluée par la directive #if. Si l'un des noms de macro, MACRO_NAME1 ou MACRO_NAME2,
a été défini, l'expression est évaluée à une valeur non nulle ; sinon, elle produit 0.
404 Heure
23

Le préprocesseur C dispose d'une autre directive, #elif, qui signifie "else if". Vous
pouvez utiliser #if et #elif ensemble pour construire une chaîne if-else-if pour
une compilation conditionnelle multiple.
Le programme présenté dans le Listing 23.3 est un exemple d'utilisation des symboles #if, #elif et #else
Les directives de l'Union européenne.

TYPE LISTE 23.3 Utilisation des directives #if, #elif et #else 23


1 : /* 23L03.c : Utilisation de #if, #elif, et
#else */ 2 : #include <stdio.h>
3 :
4 : #define C_LANG 'C'
5 : #define B_LANG 'B'
6 : #define NO_ERROR 0
7 :
8 : main(void)
9 : {
10 : #if C_LANG == 'C' && B_LANG == 'B'
11 : #undef C_LANG
12 : #define C_LANG "Je connais le langage
C.\n" 13:#undef B_LANG
14 : #define B_LANG "Je connais
BASIC.\n" 15 : printf("%s%s", C_LANG,
B_LANG) ;
16 : #elif C_LANG == 'C'
17 : #undef C_LANG
18 : #define C_LANG "Je ne connais que le
langage C.\n" 19:printf("%s", C_LANG) ;
20 : #elif B_LANG == 'B'
21 : #undef B_LANG
22 : #define B_LANG "Je ne connais que
BASIC.\n" 23: printf("%s", B_LANG) ;
24 : #else
25 : printf("I don't know C or BASIC.\n")
; 26 : #endif
27 :
28 : retour NO_ERROR ;
29 : }

Après la création et l'exécution de l'exécutable 23L03.exe, la sortie suivante s'affiche sur


l'écran de mon ordinateur :
Je connais le langage C.
SORTIE Je connais le BASIC.

L'objectif du programme de la liste 23.3 est d'utiliser les symboles #if, #elif et #else
ANALYSE
pour sélectionner les portions de code qui seront compilées.
Compilation des programmes : Le 405
préprocesseur C

Dans la fonction main(), la directive #if de la ligne 10 évalue l'expression


conditionnelle C_LANG == 'C' && B_LANG == 'B'. Si l'expression est non nulle, les états
des lignes 11 à 15 sont sélectionnés pour être compilés.
À la ligne 11, la directive #undef est utilisée pour supprimer le nom de la macro
C_LANG. La ligne 12 redéfinit ensuite C_LANG avec la chaîne de caractères "I know the
C language.\n". De même, la ligne 13 supprime le nom de la macro B_LANG et la ligne
14 redéfinit B_LANG avec une autre chaîne de caractères. L'appel à printf() à la ligne
15 imprime les deux nouvelles chaînes de caractères associées à C_LANG et B_LANG.
La directive #elif de la ligne 16 commence à évaluer l'expression C_LANG == 'C' si
l'expression de la ligne 10 a été évaluée à 0. Si l'expression C_LANG == 'C' est évaluée à
une valeur non nulle, les instructions des lignes 17 à 19 sont compilées.
Si, toutefois, l'expression de la ligne 16 n'est pas évaluée à une valeur non nulle, le message B_LANG
== 'B' est évaluée par une autre directive #elif à la ligne 20. Les instructions des
lignes 21 à 23 sont ignorées et l'instruction de la ligne 25 est compilée finalement si
l'expression B_LANG == 'B' est évaluée à 0.
À la ligne 26, la directive #endif marque la fin du bloc #if qui a commencé à la ligne 10.
Le programme de la liste 23.3 montre que C_LANG et B_LANG ont été proprement définis
aux lignes 4 et 5. Par conséquent, les déclarations des lignes 11 à 15 sont sélectionnées
comme faisant partie du programme et compilées par le compilateur C. Les deux chaînes
de caractères attribuées à C_LANG et B_LANG lors de la redéfinition sont affichées après le
programme dans le Listing
23.3 est exécuté.
Vous pouvez modifier la valeur des macros C_LANG et B_LANG pour expérimenter
d'autres exécutions du programme.

Compilation conditionnelle imbriquée


Selon la norme ANSI C, les directives #if et #elif peuvent être imbriquées sur au
moins huit niveaux.
Par exemple, la directive #if est imbriquée dans le segment de code suivant :
#if MACRO_NAME1
#if MACRO_NAME2
#if MACRO_NAME3
printf("MACRO_NAME1, MACRO_NAME2, et MACRO_NAME3") ;
#else
printf("MACRO_NAME1 et MACRO_NAME2") ;
#endif
#else
printf("MACRO_NAME1\n") ;
406 Heure
23

#endif
#else
printf("No macro name defined.\n") ;
#endif

Ici, la directive #if est imbriquée à trois niveaux. Notez que chaque #else ou #endif
est associé au #if le plus proche.
Voyons maintenant un autre exemple dans le Listing 23.4, dans lequel les directives #if sont
imbriquées. 23
TYPE LISTE 23.4 Emboîtement de la directive #if
1 : /* 23L04.c : Emboîtement
de
#i
f*/ 2 : #include <stdio.h>
3 :
4 : /* définitions de
macros */ 5 : #define
ZERO 0
6 : #define ONE 1
7 : #define TWO (ONE + ONE)
8 : #define THREE (UN + DEUX)
9 : #define
TEST
_1ONE 10 : #define
TEST
_2TWO 11 : #define TEST_3
TROIS
12 : #define MAX_NUM TROIS
13 : #define NO_ERROR ZERO
14 : /* déclaration de fonction */
15 : void StrPrint(char **ptr_s, int max)
; 16 : /* la fonction main() */
17 : main(void)
18 : {
19 : char *str[MAX_NUM] = {"Le choix d'un point de vue",
20 : "est l'acte initial de la
culture",
21 : "--- par O. Gasset"} ;
22 :
23 : #if TEST_1 == 1
24 : #if TEST_2 == 2
25 : #if TEST_3 == 3
26 : StrPrint(str, MAX_NUM) ;
27 : #else
28 : StrPrint(str, MAX_NUM - ONE)
; 29 : #endif
30 : #else
31 : StrPrint(str, MAX_NUM - TWO) ;
32 : #endif
33 : #else
Compilation des programmes : Le 407
34 : préprocesseur
printf("Aucune macro C
TEST n'a été
définie.\n") ; 35 : #endif

continue
408 Heure
23

LISTE 23.4 suite


36 :
37 : retour NO_ERROR ;
38 : }
39 : /* définition de la fonction */
40 : void StrPrint(char **ptr_s, int
max) 41 : {
42 : int i ;
43 :
44 : for (i=0 ; i<max ; i++)
45 : printf("Contenu : %s\n",
46 : ptr_s[i]) ;
47 : }

La sortie suivante s'affiche à l'écran après la création et l'exécution de l'exécutable


23L04.exe sur ma machine :
Contenu : Le choix d'un point de vue
SORTIE Contenu : c'est l'acte initial de la culture.
Le contenu : --- par O. Gasset

Le but du programme du Listing 23.4 est d'imprimer le contenu des chaînes de


ANALYSE
caractères contrôlées par les directives #if imbriquées.
Au début du programme, neuf noms de macros sont définis aux lignes 5 à 13. Le proto-
type d'une fonction, StrPrint(), est donné à la ligne 15. Les lignes 19 à 21 définissent et
initialisent un tableau de pointeurs de caractères appelé str.

Les directives #if des lignes 23 à 25 évaluent les noms de macro TEST_1, TEST_2 et
TEST_3, respectivement. Si les trois noms de macro sont tous évalués à des valeurs non
nulles, alorsStrPrint() est appelé à la ligne 26 pour imprimer le contenu de toutes les
chaînes de caractères pointées par les pointeurs dans le tableau str.
Toutefois, si seuls TEST_1 et TEST_2 sont non nuls, l'instruction de la ligne 28 imprime
le contenu des chaînes MAX_NUM-ONE. De même, si seul TEST_1 est évalué à une valeur
non nulle, la fonction StrPrint() est appelée à la ligne 31 pour imprimer le contenu
des chaînes MAX_NUM-TWO.
Dans le dernier cas, TEST_1, TEST_2 et TEST_3 renvoient tous zéro. L'appel printf()
de la ligne 34 est alors exécuté pour afficher à l'écran le message No TEST macro has
been set.

Comme vous pouvez le constater dans le programme de la liste 23.4, TEST_1, TEST_2 et
TEST_3 sont tous définis avec des constantes non nulles ; le contenu de toutes les chaînes
de caractères référencées par les pointeurs du tableau str est imprimé en tant que sortie
du programme.
Vous pouvez expérimenter les effets de ces directives imbriquées en modifiant les
valeurs des macros TEST_1, TEST_2 et TEST_3.
Compilation des programmes : Le 409
préprocesseur C

Résumé
Dans cette leçon, vous avez appris les concepts et directives importants suivants concernant le
préprocesseur en C :

• Le préprocesseur C s'exécute avant le compilateur. Au cours de la phase de


prétraitement, toutes les occurrences d'un nom de macro sont remplacées par le
corps de la macro qui est associé au nom de la macro.
• Le préprocesseur C permet également d'inclure des fichiers sources
23
supplémentaires dans le programme ou de compiler des sections de code C de
manière conditionnelle.
• Le préprocesseur C ne fait pas partie du compilateur C.
• Une instruction de macro se termine par un caractère de retour à la ligne, et non par un point-virgule.
• La directive #define indique au préprocesseur de remplacer chaque occurrence
d'un nom de macro défini par la directive par un corps de macro associé au nom de
la macro.
• La directive #undef est utilisée pour supprimer la définition d'un nom de
macro qui a été défini précédemment.
• Vous pouvez spécifier un ou plusieurs arguments à un nom de macro défini par l'option #define
directive.
• La directive #ifdef vous permet de spécifier le code à inclure uniquement
lorsqu'un nom de macro particulier est défini.
• La directive #ifndef est une directive miroir de la directive #ifdef. Avec
#ifndef, vous spécifiez le code à inclure lorsqu'un nom de macro particulier
n'est pas défini.
• La directive #endif est utilisée pour marquer la fin d'un #ifdef, d'un #ifndef ou d'un #if
bloc.
• Les directives #if, #elif et #else vous permettent de sélectionner des
portions de code à compiler.
Dans la prochaine leçon, vous trouverez un résumé de ce que vous avez appris et de ce
que vous pouvez faire après avoir étudié ce livre.

Q&R
Q Le préprocesseur C fait-il partie du compilateur C ?
R Non. Le préprocesseur C ne fait pas partie du compilateur C. Avec sa propre
grammaire et syntaxe orientée ligne, le préprocesseur C s'exécute avant le
compilateur afin de gérer les constantes nommées, les macros et l'inclusion de
fichiers.
410 Heure
23

Q Comment supprimer un nom de macro ?


R En utilisant la directive #undef avec un nom de macro, ce nom de macro peut être
supprimé ou "indéfini". Selon la norme ANSI C, un nom de macro doit être
supprimé avant de pouvoir être redéfini.
Q Pourquoi avez-vous besoin de la directive #endif ?
R La directive #endif est utilisée avec les directives #if, #ifdef ou #ifndef parce
que les instructions sous le contrôle d'une directive conditionnelle du
préprocesseur ne sont pas placées entre accolades ({ et }). Par conséquent, la
directive #endif doit être utilisée pour marquer la fin du bloc d'instructions.
Q L'expression conditionnelle qui suit la directive #if peut-elle être une expression
arithmétique ?
R Oui. L'expression conditionnelle évaluée par la directive #if peut être une
expression arithmétique. Si l'expression est évaluée à une valeur non nulle, le code
entre la directive #if et la directive conditionnelle suivante la plus proche est
inclus dans la compilation. Dans le cas contraire, le code est entièrement ignoré et
ne fera pas partie du programme compilé.

Atelier
Afin de consolider votre compréhension de la leçon de cette heure, nous vous
encourageons à répondre aux questions du quiz et à terminer les exercices proposés dans
l'atelier avant de passer à la leçon suivante. Les réponses et les conseils aux questions et
aux exercices figurent à l'annexe B, "Réponses aux questions du quiz et aux exercices".

Quiz
1. Quel est le problème avec la définition de la macro suivante ?
#define ONE 1 ;
2. Quelle est la valeur finale attribuée à result après l'exécution de l'instruction
d'affectation ?
#define ONE 1
#define NINE 9
#define EXPRESSONE + NINE
résultat = EXPRESS * NINE ;

3. Quel message sera affiché par le segment de code suivant ?


#define MACRO_NAME 0
#if MACRO_NAME
printf("Sous #if.\n") ;
#else
printf("Sous #else.\n") ;
#endif
Compilation des programmes : Le 411
préprocesseur C

4. Quel message sera affiché par le segment de code suivant ?


#define MACRO_NAME 0
#ifdef MACRO_NAME
printf("Sous #ifdef.\n") ;
#endif
#ifndef MACRO_NAME
printf("Sous #ifndef.\n") ;
#endif
23
Exercices
1. Dans l'heure 18, "Utilisation de types de données et de fonctions spéciales", vous avez appris à définir les
éléments suivants
enum data. Réécrivez le programme du Listing 18.1 avec la directive #define.
2. Définissez un nom de macro qui peut multiplier deux arguments. Ecrivez un
programme pour calculer la multiplication de 2 et 3 à l'aide de la macro.
Imprimez le résultat de la multiplication.
3. Réécrivez le programme du Listing 23.2 avec les directives #if, #elif et #else.
4. Réécrivez le programme du Listing 23.3 avec des directives #if imbriquées.
HEURE 24
Quelle est la suite des
événements ?
Ce qui compte, ce n'est pas ce que l'on sait, mais ce que l'on peut.

-A. Alekhine
Félicitations ! Vous en êtes au dernier chapitre de ce livre. Il ne vous reste
plus qu'une heure à passer pour achever votre voyage de 24 heures. Dans
cette leçon, vous en apprendrez plus sur le langage C à partir des sujets
suivants :
• Style de programmation
• Programmation modulaire
• Débogage
Cette leçon comprend également une brève révision de ce que vous avez
appris dans ce livre. Avant de commencer à aborder ces sujets, jetons un
coup d'œil au dernier exemple de ce livre.
410 Heure
24

Création d'une liste chaînée


Dans cette section, je vais construire des fonctions capables de créer une liste chaînée et
d'ajouter ou de supprimer des éléments de la liste chaînée créée. J'enregistre ces fonctions
dans un fichier source (c'est-à-dire un module ; voir la section "Programmation
modulaire" de cette leçon). En outre, je vais mettre en place une interface entre le fichier
module et l'utilisateur. En d'autres termes, l'utilisateur peut appeler l'une des fonctions
sauvegardées dans le module via l'interface. L'interface est invoquée
dans la fonction main() qui est enregistrée dans un autre fichier source. Je placerai les
déclarations de données et les prototypes de fonctions dans un fichier d'en-tête séparé.
Une liste chaînée est une chaîne de nœuds (ou d'éléments). Chaque nœud se compose
d'éléments de données et d'un pointeur qui renvoie au nœud suivant de la liste. Le dernier
élément a un pointeur nul pour indiquer la fin de la liste.
Une liste chaînée est une structure de données très puissante et très polyvalente. Quel
que soit le projet de programmation que vous entreprendrez, il est probable que vous
voudrez utiliser une liste chaînée à un moment ou à un autre. Pourquoi les listes
chaînées sont-elles si populaires ? Tout d'abord, elles sont relativement faciles à mettre
en œuvre, contrairement à d'autres structures de données qui peuvent être plus
puissantes mais qui se compliquent rapidement. Deuxièmement, il est facile de
"parcourir" une liste chaînée en suivant simplement la méthode
le long des pointeurs. C'est presque aussi simple que d'utiliser un tableau, mais avec une liste chaînée,
vous disposez de l'avantage suivant
la flexibilité de l'allocation dynamique de la mémoire.
La figure 24.1 présente une liste chaînée de N nœuds.

FIGURE 24.1 Tête


nœud 1 nœud 2 - -- nœud N
Une liste chaînée
avec N nœuds. Null

Comme le montre la figure 24.1, un pointeur de départ pointe sur le premier nœud de la
liste. Le pointeur du dernier (Nième) nœud est un pointeur nul.
La liste chaînée que je vais construire est très simple, dans laquelle chaque élément ne
contient que deux éléments : un nom d'étudiant et un numéro d'identification. La liste
24.1 contient le pro- gramme du module, qui est sauvegardé dans le fichier source
nommé 24L01.c.

TYPE LISTE 24.1 Mise en place de fonctions cohésives dans le programme du module
1 : /* 24L01.c : Un fichier de
module */ 2: #include
"24L02.h"
Que faire maintenant ? 411
3 :
4 : NODE statique *head_ptr =
NULL ; 5 :
412 Heure
24

6 : /**
7: ** main_interface()
8 : **/
9 : void main_interface(int
ch) 10 : {
11 : switch (ch){
12 : cas 'a' :
13 : list_node_add() ;
14 : pause ;
15 : cas 'd' :
16: if (!list_node_delete())
17 : list_node_print() ;
18 : pause ;
19 : cas 'p' :
20 : list_node_print() ;
21 : pause ;
22 :
23 :
défaut :
pause ;
24
24 : }
25 : }
26 : /**
27: ** list_node_create()
28 : **/
29 : NODE *list_node_create(void)
30 : {
31 : NODE *ptr ;
32 :
33 : if ((ptr=(NODE *)malloc(sizeof(NODE))) ==
NULL) 34 : ErrorExit("malloc() failed.\n") ;
35 :
36 : ptr->next_ptr = NULL ; /* mettre le pointeur suivant à
NULL */ 37: ptr->id = 0 ; /* initialisation */
38 : retour ptr ;
39 : }
40 :
41 : /**
42: ** list_node_add()
43 : **/
44 : void list_node_add(void)
45 : {
46 : NODE *new_ptr, *ptr
; 47 :
48 : new_ptr = list_node_create() ;
49: printf("Entrez le nom et l'identifiant de
l'étudiant : ") ; 50 : scanf("%s%ld", new_ptr-
>name, &new_ptr->id) ; 51 :
52 : if (head_ptr == NULL){
53 : head_ptr = new_ptr ; 54
: } else {

continue
Que faire maintenant ? 413

LISTE 24.1 suite


55 : /* trouver le dernier nœud de la
liste */ 56: for (ptr=head_ptr ;
57 : ptr->next_ptr != NULL ;
58 : ptr=ptr->next_ptr)
59 : ; /* ne fait rien ici */
60 : /* lien vers le dernier nœud */
61 : ptr->next_ptr = new_ptr
; 62 : }
63 : }
64 : /**
65: ** list_node_delete()
66 : **/
67 : int list_node_delete(void)
68 : {
69 : NODE *ptr, *ptr_saved
; 70:unsigned long id ;
71 : int deleted = 0 ;
72 : int reval = 0 ;
73 :
74 : if (head_ptr == NULL){
75 : printf("Sorry, nothing to delete.\n")
; 76: reval = 1 ;
77 : } else {
78 : printf("Enter the student ID :
") ; 79: scanf("%ld", &id) ;
80 :
81 : if (head_ptr->id == id){
82 : ptr_saved = head_ptr->next_ptr ;
83 : free(head_ptr) ;
84 : head_ptr = ptr_saved ;
85 : if (head_ptr == NULL){
86 : printf("Tous les nœuds ont été supprimés.\N") ;
87 : reval = 1 ;
88 : }
89 : } else {
90 : pour (ptr=head_ptr ;
91 : ptr->next_ptr != NULL ;
92 : ptr=ptr->next_ptr){
93 : if (ptr->next_ptr->id == id){
94 : ptr_saved = ptr->next_ptr->next_ptr ;
95 : free(ptr->next_ptr) ;
96 : ptr->next_ptr = ptr_saved ;
97 : supprimé = 1 ;
98 : pause ;
99 : }
100 : }
101: if (!deleted){
102 : printf("Can not find the student ID.\n")
; 103 : }
414 Heure
24

104 : }
105 : }
106 : retour reval ;
107 : }
108 : /**
109 : ** list_node_print()
110 : **/
111 : void list_node_print(void)
112 : {
113 : NODE *ptr ;
114 :
115 : if (head_ptr == NULL){
116 : printf("Nothing to display.\n")
; 117: } else {
118 : printf("The content of the linked list:\n") ;
119 : for (ptr = head_ptr ;
120 :
121 :
ptr->next_ptr != NULL ;
ptr = ptr->next_ptr){
24
122 : printf("%s:%d ->
", 123 : ptr->nom,
124 : ptr->id) ;
125 : }
126 : printf("%s:%d ->|",
127 : ptr->nom,
128 : ptr->id) ;
129 : printf("\n") ;
130 : }
131 : }
132 : /**
133 : ** list_node_free()
134 : **/
135 : void list_node_free()
136 : {
137 : NODE *ptr, *ptr_saved
; 138 :
139 : for (ptr=head_ptr ; ptr != NULL ;
){ 140: ptr_saved = ptr->next_ptr ;
141 : free(ptr) ;
142 : ptr = ptr_saved
; 143 : }
144 : free(ptr) ;
145 : }
146 : /**
147 : ** ErrorExit()
148 : **/
149 : void ErrorExit(char
*str) 150 : {
151 : printf("%s\n", str) ;
152 : exit(ERR_FLAG) ;
153 : }
Que faire maintenant ? 415

Il n'y a pas de sortie directe du programme du module de la liste 24.1.


L'objectif du programme de la liste 24.1 est de fournir un programme module qui
ANALYSE
contient toutes les fonctions cohésives pour la création de listes chaînées, l'ajout
de nœuds et l'ajout de nœuds.
réduction. La figure 24.2 illustre les tâches accomplies par les fonctions, telles que
list_node_create(), list_node_add(), et list_node_delete(), du programme.

FIGURE 24.2 head_ptr


Pierre
next_ptr

Utiliser les fonctions 1234


définies dans 24L01.c (a)
(voir les paragraphes
suivants pour les
next_ptr
explications).
head_ptr next_ptr
Pierre Paul
1234 5678

(b) Nul

next_ptr next_ptr
head_ptr next_ptr
Pierre Paul Marie
1234 5678 7777
(c)
Nul

head_ptr next_ptr next_ptr


Pierre Marie
1234 7777
(d) Nul

head_ptr next_ptr
Marie
7777
(e)
Nul

Comme le montre la figure 24.2 (a), le premier nœud de la liste chaînée est créé en
appelant la fonction list_node_create(), et les éléments de données sont ajoutés à l'aide
de la fonction list_node_add(). Le pointeur head_ptr pointe également vers le nœud.
Ici, Peter est le nom de l'étudiant ; 1234 est son numéro d'identification. Comme il n'y a
plus de nœuds liés, le pointeur next_ptr du premier nœud est défini comme nul.
Dans la figure 24.2 (b), un autre nœud est ajouté à la liste chaînée, avec Paul comme
nom d'étudiant et 5678 comme numéro d'identification. Notez que le pointeur next_ptr
du premier nœud est réinitialisé pour pointer sur le deuxième nœud, tandis que le
pointeur next_ptr du deuxième nœud est défini comme étant nul.
416 Heure
24

De même, dans la figure 24.2 (c), le troisième nœud est ajouté à la liste chaînée. Le
pointeur next_ptr du troisième nœud est un pointeur nul. Le pointeur du deuxième
nœud est réinitialisé pour pointer sur le troisième nœud.
Si vous souhaitez supprimer l'un des nœuds, vous pouvez appeler la fonction
list_node_delete(). Comme le montre la figure 24.2 (d), le deuxième nœud est
supprimé, de sorte que le pointeur du premier nœud doit être réinitialisé pour pointer sur
l'ancien troisième nœud qui contient le nom de l'élève, Marie, et son numéro
d'identification, 7777.
Dans la figure 24.2 (e), le premier nœud est supprimé en appliquant à nouveau la
fonction list_node_delete(). Il ne reste plus qu'un seul nœud dans la liste chaînée.
Le pointeur head_ptr doit être réinitialisé pour pointer sur le dernier nœud.
Le fichier d'en-tête, 24L02.h, inclus dans le programme du module 24L01.c, est illustré dans le Listing
24.2. (Le fichier d'en-tête est également inclus dans le programme pilote de la liste 24.3). 24
LISTE 24.2 Placement des déclarations de données et des
TYPE prototypes de fonctions dans le fichier d'en-tête
1 : /* 24L02.h : le fichier d'en-
tête */ 2 : #include <stdio.h>
3 : #include <stdlib.h>
4 :
5 : #ifndef LNK_LIST_H
6 : #define LNK_LIST_H
7 : #define ERR_FLAG 1
8 : #define MAX_LEN 16
9 :
10 : struct lnk_list_struct
11 : {
12 : char name[MAX_LEN]
; 13:unsigned long id
;
14 : struct lnk_list_struct *next_ptr
; 15 : } ;
16 :
17 : typedef struct lnk_list_struct NODE ;
18 :
19 : NODE *list_node_create(void) ;
20 : void list_node_add(void) ;
21 : int list_node_delete(void) ;
22 : void list_node_print(void) ;
23 : void
list_node_free(void) ; 24 :
void ErrorExit(char *) ; 25
: void main_interface(int) ;
26 :
27 : #endif /* pour LNK_LIST_H */
Que faire maintenant ? 417

Il n'y a pas de sortie directe du programme de la liste 24.2.


L'objectif du programme du Listing 24.2 est de déclarer une structure avec le
ANALYSE
nom de balise lnk_list_struct dans les lignes 10-15, et de définir un nouveau
nom de variable, de
la structure NODE, à la ligne 17.
Les prototypes des fonctions définies dans le programme du module du Listing 24.1, tels que
Les fonctions list_node_create(), list_node_add() et
list_node_delete() sont énumérées aux lignes 19 à 25.

Notez que les directives de préprocesseur #ifndef et #endif sont utilisées dans les
lignes 5 et 27. Les déclarations et définitions situées entre les deux directives ne sont
compilées que si le nom de macro LNK_LIST_H n'a pas été défini. De même, la ligne 6
définit le nom de la macro s'il n'a pas été défini. Il est conseillé de placer les directives
#ifndef et #endif dans un fichier d'en-tête afin d'éviter les inclusions croisées lorsque
le fichier d'en-tête est inclus par plus de
un seul fichier source. Dans ce cas, les déclarations et définitions du fichier d'en-tête
24L02.h ne seront pas incluses plus d'une fois.

Le programme module du Listing 24.3 fournit une interface que l'utilisateur peut
utiliser pour appeler les fonctions enregistrées dans le fichier source (24L01.c).

TYPE LISTE 24.3 Appel de fonctions enregistrées dans le fichier du module


1 : /* 24L03.c : Le fichier du pilote */
2 : #include "24L02.h" /* inclure le fichier
d'en-tête */ 3 :
4 : main(void)
5 : {
6 : int ch ;
7 :
8 : printf("Enter a for adding, d for deleting,\n")
; 9 : printf("p pour afficher, et q pour
quitter:\n") ; 10: while ((ch=getchar()) != 'q'){
11 : main_interface(ch) ; /* traiter les données de
l'utilisateur */ 12 : }
13 :
14 : list_node_free() ;
15 : printf("\nBye!\n") ;
16 :
17 : retour 0 ;
18 : }

Je compile les fichiers sources, 24L01.c et 24L03.c, séparément avec Microsoft Visual
C++, puis je lie leurs fichiers objets et les fonctions de la bibliothèque C pour produire
un seul programme exe- cutable appelé 24L03.exe. Le résultat suivant s'affiche après
l'exécution du programme
418 Heure
24

exécutable 24L03.exe et saisissez ou supprimez plusieurs noms d'élèves et leurs


numéros d'identification (les caractères ou chiffres en gras dans la section de sortie
sont ceux que j'ai saisis à partir du clavier) :
Entrez a pour ajouter, d pour
SORTIE supprimer, p pour afficher et q
pour quitter :
a
Saisissez le nom et l'identifiant de
l'élève : Peter 1234 a
Saisissez le nom et l'identifiant de
l'étudiant : Paul 5678 a
Saisissez le nom et l'identifiant de
l'élève : Mary 7777 p
Le contenu de la liste chaînée :
Pierre:1234 -> Paul:5678 -> Marie:7777
->| d
Saisissez l'identifiant de l'étudiant : 1234 24
Le contenu de la liste chaînée :
Paul:5678 -> Mary:7777 ->|
d
Saisissez l'identifiant de l'étudiant : 5678
Le contenu de la liste chaînée :
Mary:7777 ->|
d
Saisissez l'identifiant de
l'étudiant : 7777 Tous les
nœuds ont été supprimés. q

Au revoir !

Le but du programme de la liste 24.3 est de fournir à l'utilisateur une interface


ANALYSE
pour appeler d'autres fonctions. Les fonctions, telles que list_node_create(),
list_node_add(), et list_node_delete(), peuvent être invoquées par l'intermédiaire
de l'interface. De plus, la fonction main() est située dans le programme de la liste 24.3.
Le contenu d'un nœud de liste chaînée peut être imprimé dans le format suivant
nom:id ->

Voici un exemple :
Pierre:1234 -> Paul:5678 -> Marie:7777 ->|

Ici, le signe | est utilisé pour indiquer que le pointeur du dernier nœud est un
pointeur nul. La figure 24.3 montre la relation entre les fichiers 24L01.c, 24L02.h
et 24L03.c.
Que faire maintenant ? 419

FIGURE 24.3 24LO2.h


La relation entre les
24L01.c, 24L02.h, et
24L03.c
des dossiers.
24LO1.c 24LO3.c

Compilateur

24LO1.obj 24LO3.obj
Fonctions
de la
bibliothèqu
eC

Lien

24LO3.exe

Pour apprendre à compiler des fichiers sources distincts et à lier leurs fichiers objets afin
d'obtenir un programme exécutable unique, vous devez consulter la référence technique
de votre fournisseur de compilateur C.

Style de programmation
Dans cette section, j'aimerais souligner brièvement quelques points qui peuvent vous
aider à rédiger des programmes propres qui peuvent être facilement lus, compris et
maintenus.
Tout d'abord, assurez-vous que les noms des variables ou des fonctions de votre
programme décrivent la signification des variables ou les tâches des fonctions de
manière précise et concise.
Mettez des commentaires dans votre code afin que vous ou les autres lecteurs puissiez
avoir des indices sur ce que fait votre code, ou du moins sur ce que le code a l'intention
de faire mais qu'il pourrait faire de manière incorrecte.
Dans la mesure du possible, continuez à utiliser des variables locales et non des variables
globales. Essayez d'éviter de partager les données globales entre les fonctions ; passez
plutôt les données partagées en tant qu'arguments aux fonctions.
420 Heure
24

Il convient d'être prudent dans l'utilisation des opérateurs C qui utilisent les mêmes
symboles, en particulier l'opérateur d'affectation (=) et l'opérateur conditionnel (==), car
toute mauvaise utilisation de ces deux opérateurs peut conduire à un résultat inattendu et
rendre le débogage très difficile.
Évitez d'utiliser l'instruction goto ; utilisez plutôt d'autres instructions de flux de
contrôle chaque fois que cela est nécessaire.
Utilisez des constantes nommées dans votre programme, plutôt que des constantes
numériques, car les constantes nommées peuvent rendre votre programme plus lisible et
vous n'aurez qu'à vous rendre à un seul endroit pour mettre à jour les valeurs des
constantes.
Vous devez mettre des parenthèses autour de chaque expression constante ou argument défini
par une directive du préprocesseur afin d'éviter les effets de bord.
Vous devez également définir une règle raisonnable pour l'espacement et l'indentation
afin de pouvoir suivre cette règle de manière cohérente dans tous les programmes que 24
vous écrivez. Cette règle devrait faciliter la lecture de vos programmes.

Programmation modulaire
Ce n'est pas une bonne pratique de programmation que d'essayer de résoudre un
problème complexe avec une seule fonction. La bonne façon de procéder consiste à
décomposer le problème en plusieurs problèmes plus petits et plus simples qui peuvent
être compris de manière plus détaillée, puis à commencer à définir et à construire des
fonctions de
des fonctions pour résoudre ces problèmes plus petits et plus simples. Gardez à l'esprit
que chacune de vos fonctions ne doit effectuer qu'une seule tâche, mais qu'elle doit être
bien exécutée.
Lorsque votre programme devient de plus en plus volumineux, vous devez envisager de
le diviser en plusieurs fichiers sources, chaque fichier source contenant un petit groupe de
fonctions cohérentes.
Ces fichiers sources sont également appelés modules. Placez les déclarations de données
et les prototypes de fonctions dans des fichiers d'en-tête afin que toute modification
apportée aux déclarations ou aux prototypes soit automatiquement répercutée sur tous
les fichiers sources qui incluent le fichier d'en-tête.
Par exemple, dans la section "Création d'une liste chaînée", toutes les fonctions qui
peuvent être utilisées pour créer une liste chaînée et ajouter ou supprimer un nœud sont
placées dans le même module (24L01.c). Les déclarations de structures de données et
de variables ainsi que les prototypes de fonctions sont sauvegardés dans un en-tête
(24L02.h). La fonction main() et l'interface sont sauvegardées dans un autre module
(24L03.c).
Que faire maintenant ? 421
Vous pouvez utiliser une technique de génie logiciel connue sous le nom de
dissimulation d'informations pour réduire la complexité de la programmation. En termes
simples, la dissimulation d'informations exige qu'un module ne fournisse pas
d'informations à d'autres modules, à moins que cela ne soit absolument nécessaire.
422 Heure
24

Le compilateur C vous permet de compiler et de déboguer différents fichiers sources


séparément. Vous pouvez ainsi vous concentrer sur un fichier source à la fois et
terminer la compilation avant de passer au suivant. Avec la compilation séparée, vous
pouvez compiler uniquement les fichiers source qui ont été modifiés et laisser les
fichiers source qui ont déjà été compilés et débogués sans les attacher.
Si vous souhaitez en savoir plus sur le génie logiciel, vous devriez étudier le livre
classique de Ian Sommerville, Software Engineering, que j'ai placé dans la liste des
livres recommandés à la fin de cette leçon.

Débogage
J'ai mentionné le débogage à plusieurs reprises dans cette leçon. Qu'est-ce qu'un bogue ?
Dans ce contexte, un bogue désigne tout comportement erroné d'un système informatique
ou d'un logiciel. Le débogage consiste à trouver les bogues et à les corriger. Sachez
qu'aucun système informatique ou programme logiciel n'est à l'abri des bogues. Les
programmeurs, comme vous et moi, créent des bogues parce que nous sommes des
êtres humains.
Lorsque vous déboguez votre programme, vous devez apprendre à isoler le
comportement erroné de votre programme. De nombreux compilateurs C fournissent
des débogueurs intégrés que vous pouvez utiliser pour déboguer votre programme. Il
existe également un grand nombre d'outils de débogage conçus par des éditeurs de
logiciels tiers.
Comme on le dit, le débogage exige de la patience, de l'ingéniosité et de l'expérience. Je
vous recommande de lire un bon livre qui vous apprendra toutes les techniques de
débogage ; en fait, j'en recommande un dans la liste des livres de la section suivante.

Ce que vous avez appris


Les sous-sections suivantes vous donnent un bref aperçu des bases du langage C. Il
s'agit d'un résumé qui vous sera utile pour réviser ce que vous avez appris au cours des
heures précédentes. Il s'agit d'un résumé qui vous sera utile pour réviser ce que vous
avez appris au cours des heures précédentes.

C Mots-clés
En C, certains mots ont été réservés. Ces mots réservés, appelés mots-clés C, ont une
signification particulière pour le langage C. Les mots-clés C sont les suivants :
auto int
pause long

cas registre
Que faire maintenant ? 423

char retour

constante court

continuer signé

par défaut taille de

faire statique

double struct
autre commutateur

enum typedef

externe union

float (flotteur) non signé

pour vide 24
goto volatile

si pendant que

Opérateurs
Les opérateurs peuvent vous aider à manipuler des données. Le langage C offre un riche
ensemble d'opérateurs. Le tableau 24.1 contient une liste des opérateurs utilisés en C.

TABLEAU 24.1 Les opérateurs en C


Opérateur Description de l'activité
= Opérateur d'affectation
+= Opérateur d'affectation par addition
-= Opérateur d'affectation de soustraction
*= Opérateur d'affectation de la multiplication
/= Opérateur d'affectation de division
%= Opérateur d'affectation des restes
- Opérateur unaire moins
++ Opérateur d'incrémentation
-- Opérateur de décrémentation
== Egal à
!= N'est pas égal à
> Supérieur à

continue
424 Heure
24

TABLEAU 24.1 suite


Opérateur Description de l'activité
< Inférieur à
>= Supérieur ou égal à
<= Inférieur ou égal à
taille de Opérateur de taille
&& Opérateur logique ET
|| Opérateur logique OR
! Opérateur logique de NEGATION
& Opérateur ET binaire
| Opérateur OR binaire
^ Opérateur binaire OU exclusif (XOR)
~ Opérateur de complément binaire
>> Opérateur de décalage vers la droite
<< Opérateur de décalage vers la gauche
? : Opérateur conditionnel

Constantes
Les constantes sont des éléments dont la valeur ne change pas dans le programme. En C,
il existe plusieurs types de constantes.

Constantes entières
Les constantes entières sont des nombres décimaux. Vous pouvez suffixer une constante
entière par u ou U pour spécifier que la constante est du type de données non signé.
Une constante entière suffixée par l ou L est une constante long int.
Une constante entière est préfixée par un 0 (zéro) pour indiquer que la constante est au
format octal. Si une constante entière est préfixée par 0X ou 0x, la constante est un
nombre hexadécimal.

Constantes de caractères
Une constante de caractère est un caractère entouré de guillemets simples. Par exemple,
"C" est une constante de caractère.

En C, il existe plusieurs constantes de caractères qui représentent certains caractères


spéciaux (voir tableau 24.2).
Que faire maintenant ? 425

TABLEAU 24.2 Caractères spéciaux en C


Caractère Signification
\a Alerte sonore
\b Espace arrière
\f Forme de l'alimentation
\n Nouvelle ligne
\r Retour de chariot
\t Onglet horizontal
\v Onglet vertical
\" Double citation
\' Guillemet simple
\0 Nul 24
\\ Barre oblique inversée
\N Constante octale (ici N est une constante octale)
\xN Constante hexadécimale (ici N est une constante hexadécimale)

Constantes en virgule flottante


Les constantes à virgule flottante sont des nombres décimaux qui peuvent être suffixés
par f, F, l ou L pour spécifier float ou long double. Une constante à virgule flottante
sans suffixe est du type double par défaut. Par exemple, les instructions suivantes
déclarent et initialisent une variable float (flt_num) et une variable double (db_num) :
float flt_num = 1234.56f ;
double db_num = 1234.56 ;

Une virgule flottante peut également être représentée en notation scientifique.

Constantes de chaîne
Une constante de chaîne est une séquence de caractères entourée de guillemets doubles.
Par exemple, "Ceci est une constante de chaîne" est une constante de chaîne.
Notez que les guillemets doubles ne font pas partie du contenu de la chaîne. En outre, le
compilateur C ajoute automatiquement un caractère nul (0) à la fin d'une constante de
chaîne pour indiquer la fin de la chaîne.

Types de données
Les types de données de base fournis par le langage C sont char, int, float et
double. En outre, il existe des types de données array, enum, struct et union que
vous pouvez déclarer et utiliser dans vos programmes C.
426 Heure
24

La forme générale pour définir une liste de variables avec un type de données spécifié est la suivante
type de données liste_de_nom_de_variable ;

Ici, data_type peut être l'un des mots-clés des types de données. variable_name_list
représente une liste de noms de variables séparés par des virgules.

Le type de données "tableau" (Array)


Un tableau est une collection de variables qui sont du même type de données. Voici la
forme générale de déclaration d'un tableau :
data-type array-name[array-size] ;

Ici, data-type est le spécificateur de type qui indique le type de données des éléments
du tableau. array-name est le nom du tableau déclaré. array-size définit le nombre
d'éléments que le tableau peut contenir. Notez que les crochets ([ et ]) sont nécessaires
pour déclarer un tableau. La paire de [ et ] est également appelée l'opérateur d'indice
de tableau.
En outre, le langage C prend en charge les tableaux multidimensionnels.

L'enum Type de données


enum est le nom abrégé de enumerated (énuméré). Le type de données enum est utilisé
pour déclarer des constantes entières nommées. La forme générale de la déclaration du
type de données enum est la suivante
enum tag_name {enumeration_list} variable_list ;

Ici, tag_name est le nom de l'énumération. variable_list donne une liste de noms de
variables qui sont du type de données enum. Le nom de la balise et la liste des
variables sont tous deux facultatifs. La liste des énumérations contient des noms
d'énumérations définis qui sont utilisés pour représenter des constantes entières. Les
noms représentés par variable_list ou enumeration_list sont séparés par des
virgules.

Le type de données struct


En C, une structure rassemble différents éléments de données de manière à ce qu'ils
puissent être référencés en tant qu'unité unique. La forme générale de déclaration d'une
structure est la suivante
struct struct_tag {
data_type1 variable1
; data_type2
variable2 ;
data_type3 variable3
;
.
.
.
Que faire maintenant ? 427
} ;
428 Heure
24

Ici, struct est le mot-clé utilisé en C pour commencer une déclaration de structure.
struct_tag est le nom de la balise de la structure. variable1, variable2 et variable3
sont les membres de la structure. Leurs types de données sont spécifiés respectivement
par data_type1, data_type2 et data_type3. Les déclarations des membres doivent être
placées entre les accolades ouvrante et fermante ({ et }) dans la déclaration de la
structure, et un point-virgule ( ;) doit être inclus à la fin de la déclaration.
Voici un exemple de déclaration de structure :
struct automobile {
int year ;
char model[8] ;
int engine_power ;
float weight ;
} ;

Ici, struct est utilisé pour commencer une déclaration de structure. automobile est le
24
nom de la balise de la structure. Dans l'exemple, il y a trois types de variables : char,
int et float. Les variables ont leurs propres noms, tels que year, model,
engine_power et weight. Elles sont toutes membres de la structure et sont déclarées à
l'aide d'accolades ({ et }).

Le type de données de l'union


Une union est un bloc de mémoire utilisé pour contenir des données de types différents.
En C, une union est similaire à une structure, sauf que les données enregistrées dans
l'union sont superposées afin de partager le même emplacement mémoire. La syntaxe
de déclaration d'une union est similaire à celle d'une structure. La forme générale de
déclaration d'une union est la suivante
union union_tag {
data_type1 variable1 ;
data_type2 variable2 ;
data_type3 variable3 ;
.
.
.
} ;

Ici, union est le mot-clé utilisé en C pour commencer une déclaration d'union.
union_tag est le nom de la balise de l'union. variable1, variable2 et variable3 sont
les membres de l'union. Leurs types de données sont spécifiés respectivement par
data_type1, data_type2 et data_type3. La déclaration d'union se termine par un point-
virgule ( ;).
Voici un exemple de déclaration d'union :
union automobile {
int year ;
char model[8] ;
int engine_power ;
Que faire maintenant ? 429
float weight ;
} ;
430 Heure
24

Ici, union spécifie le type de données de l'union. automobile est le nom de la balise de
l'union. Les variables, telles que l'année, le modèle, la puissance du moteur et le poids,
sont les membres de l'union et sont déclarées entre accolades ({ et }).

Définition de nouveaux noms de types avec typedef


Vous pouvez créer vos propres noms pour les types de données à l'aide du mot-clé
typedef et utiliser ces noms comme synonymes des types de données. Par exemple,
vous pouvez déclarer NUMBER comme synonyme du type de données int :
typedef int NUMBER ;

Ensuite, vous pouvez commencer à utiliser NUMBER pour déclarer des variables entières comme ceci,
NUMÉROS i, j ;

ce qui équivaut à
int i, j ;

N'oubliez pas qu'une définition de typedef doit être faite avant que le synonyme utilisé
dans la définition ne soit utilisé dans les déclarations de votre programme.

Expressions et déclarations
Une expression est une combinaison de constantes ou de variables utilisée pour désigner
des calculs.
Par exemple,
(2 + 3) * 10

est une expression qui additionne d'abord 2 et 3, puis multiplie le résultat de l'addition par
10.

Dans le langage C, une instruction est une instruction complète, terminée par un point-
virgule. Dans de nombreux cas, vous pouvez transformer une expression en instruction
en ajoutant simplement un point-virgule à la fin de l'expression.
Une instruction null est représentée par un point-virgule isolé.
Un groupe d'instructions peut former un bloc d'instructions qui commence par une
accolade ouvrante ({) et se termine par une accolade fermante (}). Un bloc
d'instructions est traité comme une seule instruction par le compilateur C.

Déclarations de flux de contrôle


En C, il existe un ensemble d'instructions de flux de contrôle qui peuvent être divisées en
deux catégories : le bouclage et le branchement conditionnel.
Que faire maintenant ? 431

Les boucles for, while et do-while


La forme générale de l'instruction for est la suivante
for (expression1 ; expression2 ; expression3) {
déclaration1 ;
déclaration2 ;
.
.
.
}

L'instruction for évalue d'abord l'expression1, qui est généralement une expression qui
ini- tialise une ou plusieurs variables. La deuxième expression, expression2, est la partie
conditionnelle qui est évaluée et testée par l'instruction for à chaque itération. Si
l'expression2 est évaluée à une valeur non nulle, les instructions entre accolades, telles
que l'instruction 1 et l'instruction 2, sont exécutées. Si l'expression2 est évaluée 24
à 0 (zéro), le bouclage est arrêté et l'exécution de l'instruction for est terminée. La
troisième expression de l'instruction for, l'expression3, est évaluée après chaque
bouclage avant que l'instruction ne teste à nouveau l'expression2. Cette troisième
expression est souvent utilisée pour incrémenter une variable d'index.
L'instruction for suivante crée une boucle infinie car les trois expressions sont vides :
pour ( ; ; ){
/* bloc de déclaration */
}

La forme générale de l'instruction while est la suivante


while (expression) {
statement1 ;
statement2 ;
.
.
.
}

Ici, l'expression est l'expression conditionnelle de l'instruction while. L'expression est


évaluée en premier. Si elle est évaluée à une valeur non nulle, le bouclage se poursuit,
c'est-à-dire que les états à l'intérieur du bloc d'instructions, tels que l'instruction 1 et
l'instruction 2, sont exécutés.
Après l'exécution, l'expression est à nouveau évaluée. Les instructions sont ensuite
exécutées une nouvelle fois si l'expression est toujours évaluée à une valeur non nulle. Le
processus est répété encore et encore jusqu'à ce que l'expression soit évaluée à zéro.
432 Heure
24

Vous pouvez également rendre une boucle while infinie en mettant 1 (un) dans le
champ d'expression comme ceci :
while (1) {
/* bloc de déclaration */
}

La forme générale de l'instruction "do-while" est la suivante


do {
statement1 ;
statement2 ;
.
.
.
} while (expression) ;

Ici, l'expression est l'expression conditionnelle qui est évaluée afin de déterminer si les
instructions à l'intérieur du bloc d'instructions sont exécutées une fois de plus. Si
l'expression est évaluée à une valeur non nulle, la boucle "do-while" se poursuit ; dans
le cas contraire, la boucle s'arrête. Notez que l'instruction do-while se termine par un
point-virgule, ce qui constitue une distinction importante. Les instructions contrôlées par
l'instruction do-while sont garanties de s'exécuter au moins une fois avant que
l'expression conditionnelle ne soit évaluée.

Branchement conditionnel
Les instructions if, if-else, switch, break, continue et goto entrent dans la
catégorie des branchements conditionnels.
La forme générale de l'instruction if est la suivante
if (expression) {
statement1 ;
statement2 ;
.
.
.
}

Ici, l'expression est l'expression conditionnelle. Si l'expression est différente de zéro,


les instructions à l'intérieur des accolades ({ et }), telles que l'instruction 1 et
l'instruction 2, sont exécutées. Si l'expression est évaluée à 0, les instructions sont
ignorées.
En tant qu'extension de l'instruction if, l'instruction if-else se présente sous la forme suivante :
if (expression) {
statement1 ;
statement2 ;
.
.
.
Que faire maintenant ? 433

}
else {
déclaration_A ;
déclaration_B ;
.
.
.
}

Ici, si l'expression if est évaluée à une valeur non nulle, les instructions contrôlées par
if, y compris les instructions 1 et 2, sont exécutées. Sinon, les instructions, telles que
l'instruction_A et l'instruction_B, à l'intérieur du bloc d'instructions suivant le mot-
clé else sont exécutées.
La forme générale de l'instruction switch est la suivante
switch (expression) { 24
case expression1 :
statement1 ;
case expression2 :
déclaration2 ;
.
.
.
par défaut :
déclaration par défaut ;
}

Ici, l'expression conditionnelle, expression, est évaluée en premier. Si la valeur produite


par expression est égale à l'expression constante expression1, l'exécution commence à
l'instruction instruction1. Si la valeur de l'expression est égale à la valeur de
l'expression2, l'exécution commence à l'instruction2. Si, par contre, la valeur de
expression n'est égale à aucune des valeurs des expressions constantes étiquetées par le
mot-clé case, l'instruction statement-default, suivant le mot-clé default, est exécutée.
Vous pouvez ajouter une instruction break à la fin de la liste d'instructions qui suit
chaque étiquette de cas si vous souhaitez quitter la construction du commutateur après
que les instructions d'un cas sélectionné ont été exécutées.
L'instruction break peut également être utilisée pour sortir d'une boucle infinie.
Il arrive que l'on veuille rester dans une boucle tout en sautant certaines des
instructions qui s'y trouvent. Pour ce faire, vous pouvez utiliser l'instruction
continue.

La forme générale de l'instruction goto est décrite ci-dessous :


nom de l'étiquette
: déclaration1 ;
déclaration2 ;
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

430 Visitez www.DeepL.com/pro


Heure pour en savoir plus.
24

.
.
.
aller au nom de l'étiquette ;

Ici, nom-étiquette est un nom d'étiquette qui indique à l'instruction goto où sauter.
Vous devez placer nom-étiquette à deux endroits : à l'endroit où l'instruction goto va
sauter et à l'endroit qui suit le mot-clé goto. En outre, l'endroit où l'instruction goto
doit sauter peut se situer avant ou après l'instruction. Notez que le nom de l'étiquette doit
être suivi de deux points ( :) à l'endroit où l'instruction goto va sauter. L'utilisation de
goto n'est cependant pas recommandée, car elle rend votre programme difficile à
déboguer.

Pointeurs
Un pointeur est une variable dont la valeur est utilisée pour pointer vers une autre
variable. La forme générale d'une déclaration de pointeur est la suivante
type de données *nom du pointeur ;

Ici, data-type spécifie le type de données vers lequel pointe le pointeur. pointer-name
est le nom de la variable du pointeur, qui peut être n'importe quel nom de variable valide
en C. Lorsque le compilateur voit l'astérisque (*) préfixé au nom de la variable dans la
déclaration, il note que la variable peut être utilisée comme un pointeur.
En général, l'adresse associée au nom d'une variable est appelée la valeur gauche de la
variable. Lorsqu'une valeur est attribuée à une variable, cette valeur est stockée dans
l'emplacement mémoire réservé de la variable en tant que contenu. Le contenu est
également appelé la valeur droite de la variable.
On dit d'un pointeur qu'il est nul lorsqu'on lui a attribué la bonne valeur de 0. Rappelez-
vous qu'un pointeur nul ne peut jamais pointer vers des données valides, vous pouvez
donc l'utiliser pour tester la validité d'un pointeur.
L'opérateur de déréférence (*) est un opérateur unaire qui ne nécessite qu'un seul
opérande. Par exemple, l'expression *nom_ptr produit la valeur pointée par la variable
pointeur nom_ptr, où nom_ptr peut être n'importe quel nom de variable valide en C.
L'opérateur & est appelé opérateur d'adresse car il peut renvoyer l'adresse (c'est-à-dire la
valeur gauche) d'une variable.
Plusieurs pointeurs peuvent pointer sur le même emplacement d'une variable en
mémoire. En C, vous pouvez déplacer la position d'un pointeur en ajoutant ou en
soustrayant des nombres entiers au pointeur.
Notez que pour les pointeurs de différents types de données, les entiers ajoutés ou
soustraits aux pointeurs ont des tailles scalaires différentes.
Que faire maintenant ? 431

Pointer des objets


Vous pouvez accéder à un élément d'un tableau en utilisant un pointeur. Par exemple,
étant donné un tableau, an_array, et un pointeur, ptr_array, si an_array et
ptr_array sont du même type de données, et que ptr_array est assigné avec
l'adresse de début du tableau comme ceci :
ptr_array = an_array

; l'expression
an_array[n]

est équivalente à l'expression


*(ptr_array + n)

Ici, n est un indice du tableau.


24
Dans de nombreux cas, il est utile de déclarer un tableau de pointeurs et d'accéder au
contenu pointé par le tableau en déréférençant chaque pointeur. Par exemple, la
déclaration suivante déclare un tableau de pointeurs int :
int *ptr_int[3] ;

En d'autres termes, la variable ptr_int est un tableau à trois éléments de pointeurs avec la valeur int
type.
Vous pouvez également définir un pointeur de structure et faire référence à un élément
de la structure via le pointeur. Par exemple, la déclaration de structure suivante :
struct computer {
float cost ;
int year ;
int cpu_speed ;
char cpu_type[16]
;
} ;

un pointeur peut être défini comme suit :


struct computer *ptr_s ;

On peut ensuite accéder aux éléments de la structure en déréférençant le pointeur. Par


exemple, pour affecter la valeur 1997 à la variable int year de la structure
informatique, vous pouvez utiliser l'instruction d'affectation suivante :

(*ptr_s).year = 1997 ;

Vous pouvez également utiliser l'opérateur de flèche (->) pour l'affectation, comme ceci :
ptr_s->year = 1997 ;
432 Heure
24

Notez que l'opérateur de flèche (->) est couramment utilisé pour référencer un membre
d'une structure à l'aide d'un pointeur.

Fonctions
Les fonctions sont les éléments constitutifs des programmes C. Outre les fonctions
standard de la bibliothèque C, vous pouvez également utiliser d'autres fonctions créées
par vous-même ou par un autre programmeur dans votre programme C. L'accolade
ouvrante ({) marque le début du corps d'une fonction, tandis que l'accolade fermante (})
marque la fin du corps de la fonction.
Selon la norme ANSI, la déclaration d'une variable ou d'une fonction spécifie
l'interprétation et les attributs d'un ensemble d'identificateurs. La définition, quant à elle,
impose au compilateur C de réserver un espace de stockage pour une variable ou une
fonction nommée par un identi- fier.
En effet, une déclaration de variable est une définition. Il n'en va pas de même pour les
fonctions. Une déclaration de fonction fait référence à une fonction définie ailleurs et
spécifie le type de valeur renvoyée par la fonction. Une définition de fonction définit ce
que fait la fonction, ainsi que le nombre et le type d'arguments transmis à la fonction.
La norme ANSI permet d'ajouter à la déclaration de la fonction le nombre et le type
d'arguments transmis à la fonction. Le nombre et les types d'arguments sont appelés le
prototype de la fonction.
La forme générale d'une déclaration de fonction, y compris son prototype, est la suivante :
data_type_specifier function_name(
data_type_specifier argument_name1,
data_type_specifier argument_name2,
data_type_specifier argument_name3,
.
.
.
spécificateur de type de données nom de l'argumentN,
) ;

Ici, le spécificateur de type de données détermine le type de la valeur renvoyée par


la fonction ou spécifie les types de données des arguments, tels que nom_d'argument1,
nom_d'argument2, nom_d'argument3 et nom_d'argumentN, transmis à la fonction
nommée nom_de_la_fonction.
L'utilisation d'un prototype de fonction a pour but d'aider le compilateur à vérifier si les
types de données des arguments passés à une fonction correspondent à ce que la fonction
attend. Le compilateur émet un message d'erreur si les types de données ne correspondent
pas. Le type de données void est nécessaire dans la déclaration d'une fonction qui ne
prend aucun argument.
Que faire maintenant ? 433

Pour déclarer une fonction qui prend un nombre variable d'arguments, vous devez
spécifier au moins le premier argument et utiliser l'ellipse (...) pour représenter le
reste des arguments passés à la fonction.
Un appel de fonction est une expression qui peut être utilisée comme une déclaration unique ou
à l'intérieur d'autres expressions ou déclarations.
Il est souvent plus efficace de transmettre l'adresse d'un argument, plutôt que sa copie, à
une fonction afin que celle-ci puisse accéder à la valeur originale de l'argument et la
manipuler.
C'est donc une bonne idée de passer le nom d'un pointeur, qui pointe sur un tableau, comme
argument d'une fonction, plutôt que les éléments du tableau eux-mêmes.
Vous pouvez également appeler une fonction via un pointeur qui contient l'adresse de la fonction.

Entrées et sorties (I/O) 24


En C, un fichier désigne un fichier disque, un terminal, une imprimante ou un lecteur de
bande. En d'autres termes, un fichier représente un périphérique concret avec lequel vous
souhaitez échanger des informations. Un flux, en revanche, est une série d'octets
permettant de lire ou d'écrire des données dans un fichier. Contrairement à un fichier, un
flux est indépendant du périphérique. Tous les flux ont le même comportement.
En outre, trois flux de fichiers sont pré-ouverts pour vous :

• stdin - Entrée standard pour la lecture.


• stdout - La sortie standard pour l'écriture.
• stderr - L'erreur standard pour l'écriture des messages d'erreur.
En général, l'entrée standard stdin renvoie au clavier, tandis que la sortie standard
stdout et l'erreur standard stderr renvoient à l'écran. De plus, de nombreux
systèmes d'exploitation permettent de rediriger ces flux de fichiers.
Par défaut, tous les flux d'E/S sont mis en mémoire tampon. Les E/S en mémoire tampon sont également
appelées E/S de haut niveau.
La structure FILE est la structure de contrôle des fichiers définie dans le fichier d'en-tête
stdio.h. Un pointeur de type FILE est appelé pointeur de fichier et fait référence à un
fichier disque. Un pointeur de fichier est utilisé par un flux pour effectuer les opérations
des fonctions d'entrée/sortie. Par exemple, l'exemple suivant définit un pointeur de
fichier appelé fptr :
FILE *fptr ;

La structure FILE comporte un membre, appelé indicateur de position de fichier, qui


indique la position dans un fichier où les données seront lues ou écrites.
Le langage C fournit un riche ensemble de fonctions de bibliothèque pour effectuer des
opérations d'E/S. Ces fonctions peuvent lire ou écrire n'importe quel type de données dans des
434 Heure
fichiers. Ces fonctions peuvent lire ou écrire24
n'importe quel type de données dans des fichiers.
Parmi elles, fopen(), fclose(), fgetc(), fputc(), fgets(), fputs(), fread(), fwrite(),
feof(), fscanf(),
Que faire maintenant ? 435

fprintf(), fseek(), ftell(), rewind() et freopen() ont été présentés dans ce livre.

Le préprocesseur C
Le préprocesseur C ne fait pas partie du compilateur C. Le préprocesseur C s'exécute
avant le compilateur. Au cours de la phase de prétraitement, toutes les occurrences d'un
nom de macro sont remplacées par le corps de la macro qui est associé au nom de la
macro. Notez qu'une instruction de macro se termine par un caractère de retour à la ligne,
et non par un point-virgule.
Le préprocesseur C vous permet également d'inclure des fichiers source supplémentaires
dans le programme ou de compiler des sections de code C de manière conditionnelle.
La directive #define indique au préprocesseur de remplacer chaque occurrence d'un
nom de macro défini par la directive par un corps de macro associé au nom de la macro.
Vous pouvez spécifier un ou plusieurs arguments pour un nom de macro défini par la
directive #define.
La directive #undef est utilisée pour supprimer la définition d'un nom de macro qui a été
défini précédemment.
La directive #ifdef détermine si un groupe donné d'instructions doit être inclus dans le
programme. La directive #ifndef est une directive miroir de la directive #ifdef ; elle
vous permet de définir le code à inclure lorsqu'un nom de macro particulier n'est pas
défini.
Les directives #if, #elif et #else permettent de filtrer les portions de code à compiler.
#endif est utilisé pour marquer la fin d'un bloc #ifdef, #ifndef ou #if, car les états
contrôlés par ces directives du préprocesseur ne sont pas placés entre accolades.

Le chemin à parcourir...
Je pense que vous pouvez commencer à courir dès maintenant après avoir appris à
marcher dans le monde de la programmation en langage C grâce à ce livre. Bien que
vous soyez seul, vous n'êtes pas seul. Vous pouvez relire ce livre chaque fois que vous
en ressentez le besoin. Les livres suivants, que je vous recommande, peuvent également
vous guider dans votre voyage continu dans le monde du C :
Le langage de programmation C
par Brian Kernighan et Dennis Ritchie, publié par Prentice Hall

C Interfaces et implémentations
par David Hanson, publié par Addison-Wesley
436 Heure
24

Programmation C pratique
par Steve Oualline, publié par O'Reilly & Associates, Inc.

Pas de bogues - Produire du code sans erreur en C et C++


par David Thielen, publié par Addison-Wesley

Génie logiciel
par Ian Sommerville, publié par Addison-Wesley

Le mois de l'homme mythique : Essais sur le génie logiciel


par F. P. Brooks, Jr, publié par Addison-Wesley

La dynamique du développement logiciel : La dynamique du développement de


logiciels : "Ne faites pas l'imbécile" et 53 autres règles pour livrer un logiciel de 24
qualité dans les délais impartis
par Jim McCarthy, publié par Microsoft Press ISBN
: 1-55615-823-8

Code Complete : Un manuel pratique de construction de logiciels


par Steve McConnell, publié par Microsoft Press
ISBN : 1-55615-484-4

Résumé
Avant de refermer ce livre, j'aimerais vous remercier pour votre patience et les efforts
que vous avez déployés pour apprendre les bases du langage C au cours des dernières 24
heures. (Je pense que la plupart d'entre vous ont passé plus de 24 heures. C'est tout à fait
normal. Il y a encore beaucoup d'autres
La programmation en C comporte des aspects que vous apprendrez au fur et à mesure de
votre progression. Mais si vous avez déjà maîtrisé les concepts de base, les opérateurs et
les fonctions enseignés dans ce livre, vous pouvez apprendre les nouvelles choses
rapidement). Maintenant, c'est à vous d'appliquer ce que vous avez appris dans ce livre
pour résoudre les problèmes du monde réel. Bonne chance !
Que faire maintenant ? 437
PARTIE VI
Annexes
Heure
A Fichiers d'en-tête standard ANSI
B Réponses aux questions du quiz et aux exercices
ANNEXE A
Fichiers d'en-tête
standard ANSI
Comme vous l'avez appris au cours des dernières 24 heures, la bibliothèque
standard C est accompagnée d'un ensemble de fichiers d'inclusion appelés
fichiers d'en-tête. Ces fichiers d'en-tête contiennent les décla- rations des
fonctions et des macros de la bibliothèque C, ainsi que les types de données
pertinents. Chaque fois qu'une fonction C est invoquée, le(s) fichier(s) d'en-
tête auquel (auxquels) la fonction C est associée doit (doivent) être inclus
dans vos programmes.
Les fichiers d'en-tête standard ANSI sont les suivants :
Fichier Description du dossier
assert.h Contient des fonctions de diagnostic.
ctype.h Contient des fonctions de test et de mise en correspondance des caractères.
errno.h Contient des constantes pour le traitement
des erreurs. float.h Contient des constantes pour les
valeurs à virgule flottante. limits.h Contient des
valeurs dépendantes de l'implémentation.
continue
440 Annexe A

Fichier Description du dossier


locale.h Contient la fonction setlocale(), et est utilisée pour définir
les paramètres linguistiques.
math.h Contient des fonctions mathématiques.
setjmp.h Contient les fonctions setjmp() et longjmp(), et est utilisé
pour contourner la discipline normale d'appel et de retour des
fonctions.
signal.h Contient des fonctions de traitement des signaux.
stdarg.h Contient des fonctions et des macros permettant d'implémenter
des fonctions acceptant un nombre variable d'arguments.
stddef.h Contient les définitions de ptrdiff_t, size_t, NULL, et errno
macros.
stdio.h Contient des fonctions d'entrée et de sortie.
stdlib.h Contient des fonctions utilitaires générales.
string.h Contient des fonctions utilisées pour manipuler les chaînes de caractères.
time.h Contient des fonctions permettant de manipuler le temps.

Si le compilateur C de votre machine n'est pas 100 % conforme à la norme


ANSI, certains des fichiers d'en-tête ANSI peuvent ne pas être disponibles
avec le compilateur.
ANNEXE B
Réponses aux questions
du quiz et aux exercices
Heure 1, "Faire le premier pas"
Quiz
1. Le langage le plus bas mentionné dans cette heure et qu'un ordinateur
peut comprendre directement est le langage machine, c'est-à-dire le
code binaire. En revanche, la langue la plus élevée est la langue
humaine, comme le chinois, l'anglais, le français, etc. La plupart des
langages de programmation de haut niveau, tels que C, Java et Perl,
sont proches du langage humain.
2. Un ordinateur ne peut pas comprendre directement un programme
écrit en C. Vous devez compiler le programme et le traduire en code
binaire pour que l'ordinateur puisse le lire.
3. Oui. C'est la beauté du langage C ; vous pouvez écrire un programme
en C et l'enregistrer dans un fichier de bibliothèque. Plus tard, vous
pouvez invoquer le programme dans un autre programme C en
incluant le fichier de bibliothèque.
442 Annexe B

4. Nous avons besoin de la norme ANSI pour C afin de garantir la portabilité des
programmes écrits en C. La plupart des vendeurs de compilateurs C supportent la
norme ANSI. Si vous écrivez votre programme en suivant les règles établies par la
norme ANSI, vous pouvez porter votre programme sur n'importe quelle machine en
recompilant simplement votre programme avec un compilateur qui prend en charge
ces machines.

Heure 2, "Écrire votre premier programme en C"


Quiz
1. Non. En fait, le préprocesseur C filtrera tous les commentaires que vous mettez
dans votre programme avant que le compilateur ne puisse les voir. Les
commentaires sont écrits pour vous ou pour les autres programmeurs qui regardent
votre programme.
2. Un fichier .obj est créé après la compilation d'un programme par le compilateur
C. Vous avez toujours besoin d'un éditeur de liens pour lier tous les fichiers .obj
et les autres fichiers de bibliothèque afin d'obtenir le fichier exécutable final.
3. Non, la fonction exit() ne renvoie aucune valeur. En revanche, l'instruction
return en renvoie. Dans la fonction main(), si l'instruction return renvoie une
valeur de 0, elle indique au système d'exploitation que le programme s'est terminé
normalement ; dans le cas contraire, une erreur se produit.
4. Un fichier requis par la directive #include et se terminant par l'extension .h est
appelé fichier d'en-tête en C. Plus loin dans ce livre, vous apprendrez qu'un fichier
d'en-tête contient les déclarations de données ou de fonctions.

Exercices
1. Les crochets (< et >) de l'expression #include <stdio.h> demandent au
préprocesseur C de rechercher un fichier d'en-tête dans un répertoire autre que le
répertoire courant. En revanche, l'expression #include "stdio.h" indique au
préprocesseur C qu'il doit d'abord rechercher le fichier d'en-tête stdio.h dans le
répertoire courant, puis le fichier d'en-tête dans un autre répertoire.
2. Voici une solution possible :
/* 02A02.c */
#include <stdio.h>

main()
{
printf ("It's fun to write my own program in C.\n") ;
return 0 ;
}
Réponses aux questions du quiz et aux 443
exercices

SORTIE La sortie du programme est la suivante :


C'est amusant d'écrire mon propre programme en C.

3. Voici une solution possible :


/* 02A03.c */
#include <stdio.h>

main()
{
printf ("Howdy, neighbor!\NThis is my first C program.\N") ;
return 0 ;
}

La sortie du programme est la suivante :


SORTIE
Bonjour, voisin !
C'est mon premier programme en C.

4. Le message d'avertissement que j'obtiens lorsque j'essaie de compiler le


programme est que la fonction main() devrait retourner une valeur entière car,
par défaut, la fonction main() retourne un entier. Comme la fonction exit() ne
renvoie aucune valeur, vous pouvez remplacer exit() par l'instruction return.
5. J'ai reçu deux messages d'erreur (warning) sur ma machine. Le premier est
"printf" non défini ; le second est "main" : fonction "void" renvoyant
B
une valeur. Pour corriger la première erreur, le fichier d'en-tête, stdio.h, doit
être inclus avant que la fonction printf() puisse être appelée à partir de la
fonction main() ; sinon, vous obtiendrez un message d'erreur lors de l'étape
d'édition de liens. Pour corriger la deuxième erreur, vous pouvez supprimer le mot-
clé void du code.

Heure 3, "Apprendre la structure d'un


programme C"
Quiz
1. Oui, 74 et 571 sont des constantes en C.
2. Oui. x = 571 + 1 et x = 12 + y sont tous deux des expressions.
3. 2methods, *start_function et .End_Exe ne sont pas des noms de fonctions valides.
4. Non. 2 + 5 * 2 est équivalent à 2 + 10, ce qui donne 12 ; (2 + 5) * 2 est équivalent à 2 + 10, ce qui
donne 12 ; (2 + 5) * 2 est équivalent à 2 + 10, ce qui donne 12.
prêtée à 7 * 2, ce qui donne 14.
5. Oui, 7 % 2 et 4 % 3 produisent tous deux 1.
444 Annexe B

Exercices
1. Voici une solution possible :
{
x = 3 ;
y = 5 + x ;
}

2. Le nom de la fonction, 3integer_add, est illégal en C.


3. La deuxième instruction à l'intérieur de la fonction nécessite un point-virgule à la
fin de l'instruction.
4. Voici deux solutions possibles :
/* Méthode 1 : une fonction
C */ int MyFunction( int x,
int y)
{
int result ;
result = x * y
; return
result ;
}

ou
/* Méthode 2 : une fonction
C */ int MyFunction( int x,
int y)
{
retour (x * y) ;
}

5. Voici une solution possible :


/* 03A05.c */
#include <stdio.h>

int integer_multiply( int x, int y )


{
int result ;
result = x * y
; return
result ;
}

int main()
{
int sum ;

sum = integer_multiply(3, 5) ;
printf("La multiplication de 3 et 5 est %d\n", sum) ;
return 0 ;
}
Réponses aux questions du quiz et aux 445
exercices

Heure 4, "Comprendre les types de données et


les mots-clés"
Quiz
1. Oui. 134/100 et 17/10 donnent tous deux le même résultat de 1.
2. Oui. Les résultats de 3000 + 1.0 et 3000/1.0 sont des valeurs à virgule flottante.
3. En notation scientifique, nous avons les expressions suivantes :
• 3.5e3
• 3.5e-3
• -3.5e-3

4. Parmi les quatre noms, 7th_calculation et Tom's_method ne sont pas des noms
valides en C.

Exercices
1. Voici une solution possible :
/* 04A01.c */ B
#include <stdio.h>

main()
{
char c1
; char
c2 ;

c1 = "Z" ;
c2 = "z" ;
printf("La valeur numérique de Z : %d.\n",
c1) ; printf("La valeur numérique de z :
%d.\n", c2) ; return 0 ;
}

La sortie du programme est la


SORTIE
suivante : La valeur
numérique de Z : 90. La
valeur numérique de z : 122.

2. Voici une solution possible :


/* 04A02.c */
#include <stdio.h>

main()
{
char c1 ;
446 Annexe B

char c2 ;

c1 = 72 ;
c2 = 104 ;
printf("Le caractère de 72 est : %c\n", c1)
; printf("Le caractère de 104 est : %c\n", c2)
; return 0 ;
}

La sortie du programme est la


SORTIE
suivante : Le caractère de 72
est : H Le caractère de 104
est : h

3. Non. 72368 dépasse la plage de 16 bits du type de données int. Si vous attribuez
une valeur trop grande pour le type de données, la valeur résultante s'enroulera et le
résultat sera incorrect.
4. Voici une solution possible :
/* 04A04.c */
#include <stdio.h>

main()
{
double dbl_num; ;

dbl_num = 123.456 ;
printf("Le format en virgule flottante de 123.456 est :
%f\n", dbl_num) ;
printf("Le format de notation scientifique de 123.456 est :
%e\n", dbl_num) ;

retour 0 ;
}

La sortie du programme sur ma machine est la suivante :


SORTIE
Le format en virgule flottante de 123.456 est le suivant : 123.456000
Le format de notation scientifique de 123,456 est le suivant : 1.234560e+002

5. Voici une solution possible :


/* 04A05.c */
#include <stdio.h>

main()
{
char ch ;

ch = '\n' ;
printf("La valeur numérique de la nouvelle ligne est : %d\n", ch) ;

retour 0 ;
}
Réponses aux questions du quiz et aux 447
exercices

SORTIE La sortie du programme est la suivante :


La valeur numérique de la nouvelle ligne est : 10

Heure 5, "Gestion des entrées et sorties


standard"
Quiz
1. Oui, en faisant précéder le spécificateur de champ minimum du signe moins -.
2. La principale différence entre putc() et putchar() est que putc() demande à
l'utilisateur de spécifier le flux de fichiers. Pour putchar(), l'utilisateur n'a
pas besoin de le faire car la sortie standard (stdout) est utilisée comme flux de
fichiers.
3. La fonction getchar() renvoie une valeur de type int.
4. Dans l'expression %10.3f, 10 est la valeur de la largeur minimale du champ ;
.3 est appelé spécificateur de précision.

Exercices
1. Voici une solution possible :
B
/* 05A01.c */
#include <stdio.h>

main()
{
char c1, c2, c3 ;

c1 = "B" ;
c2 = "y" ;
c3 = "e" ;

/* Méthode I */
printf("%c%c%c\n", c1, c2, c3)
;

/* Méthode II
*/ putchar(c1)
; putchar(c2)
; putchar(c3)
;

retour 0 ;
}

2. Voici une solution possible :


/* 05A02.c */
#include <stdio.h>
448 Annexe B

main()
{
int x ;
double y
;

x = 123 ;
y = 123.456 ;
printf("x : %-3d\n", x) ;
printf("y : %-6.3f\n", y) ;

retour 0 ;
}

La sortie du programme est la suivante :


SORTIE
x : 123
y : 123.456

3. Voici une solution possible :


/* 05A03.c */
#include <stdio.h>

main()
{
int num1, num2, num3 ;

num1 = 15 ;
num2 = 150 ;
num3 = 1500 ;
printf("Le format hexadécimal de 15 est : 0x%04X\n",
num1) ; printf("Le format hexadécimal de 150 est :
0x%04X\n", num2) ; printf("Le format hexadécimal de
1500 est : 0x%04X\n", num3) ;

retour 0 ;
}

La sortie du programme est la suivante :


SORTIE
Le format hexadécimal de 15 est :
0x000F Le format hexadécimal de
150 est : 0x0096 Le format
hexadécimal de 1500 est : 0x05DC

4. Voici une solution possible :


/* 05A04.c */
#include <stdio.h>

main()
{
int ch ;

printf("Enter a character:\n")
; ch = getchar() ;
Réponses aux questions du quiz et aux 449
exercices

putchar(ch) ;

retour 0 ;
}

5. Vous obtiendrez probablement deux messages d'erreur (avertissement), l'un


indiquant que getchar() est indéfini et l'autre indiquant que putchar() est
indéfini. La raison en est que le fichier d'en-tête, stdio.h, est absent du code.

Heure 6, "Manipuler les données"


Quiz
1. L'opérateur = est un opérateur d'affectation qui attribue la valeur de l'opérande du
côté droit de l'opérateur à celui du côté gauche. En revanche, == est l'un des
opérateurs relationnels ; il teste simplement les valeurs de deux opérandes de part et
d'autre et détermine si elles sont égales l'une à l'autre.
2. Dans l'expression x + - y - - z, les premier et troisième signes moins sont des
opérateurs moins unaires ; le deuxième signe moins est un opérateur de
soustraction.
B
3. 15/4 est égal à 3. (float)15/4 est égal à 3.750000.
4. Non. L'expression y *= x + 5 est en fait égale à l'expression y = y * (x + 5).

Exercices
1. Voici une solution possible :
/* 06A01.c */
#include <stdio.h>

main()
{
int x, y ;

x = 1 ;
y = 3 ;
x += y ;
printf("Le résultat de x += y est : %d\n", x) ;

x = 1 ;
y = 3 ;
x += -y ;
printf("Le résultat de x += -y est : %d\n", x) ;

x = 1 ;
y = 3 ;
450 Annexe B

x -= y ;
printf("Le résultat de x -= y est : %d\n", x) ;

x = 1 ;
y = 3 ;
x -= -y ;
printf("Le résultat de x -= -y est : %d\n", x) ;

x = 1 ;
y = 3 ;
x *= y ;
printf("Le résultat de x *= y est : %d\n", x) ;

x = 1 ;
y = 3 ;
x *= -y ;
printf("Le résultat de x *= -y est : %d\n", x) ;

retour 0 ;
}

Le résultat du programme est le


SORTIE
suivant : Le résultat de x += y
est : 4 Le résultat de x +=
-y est : -2
Le résultat de x -= y est :
-2 Le résultat de x -= -y
est : 4 Le résultat de x *=
y est : 3 Le résultat de x
*= -y est : -3

2. La valeur de z est 1 (un), après l'évaluation de l'expression z=x*y==18.


3. Voici une solution possible :
/* 06A03.c */
#include <stdio.h>

main()
{
int x ;

x = 1 ;
printf("x++ produit: %d\n", x++)
; printf("Maintenant x contient :
%d\n", x) ;

retour 0 ;
}

La sortie du programme est la suivante :


SORTIE
x++ produit: 1
Maintenant x
contient : 2
Réponses aux questions du quiz et aux 451
exercices

4. Voici une solution possible :


/* 06A04.c */
#include <stdio.h>

main()
{
int x ;

x = 1 ;
printf("x = x++ produit : %d\n", x =
x++) ;printf("Maintenant x contient :
", x) ;
retour 0 ;
}

J'obtiens 1 et 1 à partir des deux appels à printf() dans ce programme. La raison


en est que, dans l'expression x = x++, la valeur originale de x est d'abord copiée
dans un emplacement temporaire, puis x est incrémenté de 1. Enfin, la valeur
sauvegardée dans l'emplacement temporaire est réaffectée à x. C'est pourquoi la
valeur finale sauvegardée dans x est toujours 1.
5. Le programme utilise incorrectement un opérateur d'affectation =, au lieu d'un
opérateur relationnel "égal à" (==).
B
Heure 7, "Travailler avec des boucles"
Quiz
1. Non.
2. Oui. La boucle do-while imprime le caractère d, dont la valeur numérique est 100.
3. Oui, les deux boucles for itèrent 8 fois.
4. Oui.

Exercices
1. La première boucle for contient une déclaration :
printf("%d + %d = %d\n", i, j, i+j) ;
Mais la deuxième boucle for comporte un point-virgule juste après l'instruction for. Il
s'agit d'une instruction null - un point-virgule seul, c'est-à-dire une instruction qui ne fait
rien.
2. Voici une solution possible :
/* 07A02.c */
#include <stdio.h>

main()
{
452 Annexe B

int i, j ;

for (i=0, j=1 ; i<8 ; i++, j++)


printf("%d + %d = %d\n", i, j, i+j) ;

printf("\n") ;
pour (i=0, j=1 ; i<8 ; i++, j++) ;
printf("%d + %d = %d\n", i, j, i+j) ;

retour 0 ;
}

3. Voici une solution possible :


/* 07A03.c */
#include <stdio.h>

main()
{
int c ;

printf("Entrez un caractère:\n(entrez K pour


quitter)\n") ; c = ' ' ;

while( c != 'K' )
{ c =
getc(stdin) ;
putchar(c) ;
}
printf("\NSortie de la boucle for. Bye!\N") ;

retour 0 ;
}

4. Voici une solution possible :


/* 07A04.c : Utiliser une
boucle for */ #include
<stdio.h>

main()
{
int i ;

i = 65 ;
for (i=65 ; i<72 ; i++){
printf("La valeur numérique de %c est %d.\n", i, i) ;
}

retour 0 ;
}
Réponses aux questions du quiz et aux 453
exercices

5. Voici une solution possible :


/* 07A05.c */
#include <stdio.h>

main()
{
int i, j ;

i = 1 ;
while (i<=3) { /* boucle extérieure */
printf("The start of iteration %d of the outer loop.\n", i)
; j = 1 ;
do{ /* boucle intérieure */
printf(" Itération %d de la boucle
intérieure.\n", j) ; j++ ;
} while (j<=4) ;
i++ ;
printf("Fin de l'itération %d de la boucle extérieure.\n", i) ;
}

retour 0 ;
}

B
Heure 8, "Utiliser les opérateurs conditionnels"
Quiz
1. L'expression (x=1)&&(y=10) renvoie 1 ; (x=1)&(y=10) renvoie 0.
2. Dans l'expression !y ? x == z : y, !y produit 0, et la valeur du troisième
opérande y est donc considérée comme la valeur de l'expression. En d'autres
termes, l'expression est évaluée à 1.
3. 1100111111000110 et 0011000000111001.
4. L'expression (x%2==0)||(x%3==0) donne 1, et l'expression (x%2==0)&&(x%3==0)
est évaluée à 0.
5. Oui. 8 >> 3 est équivalent à 8/23. 1 << 3 est équivalent à 23.

Exercices
1. ~x donne 0x1000 car ~0xEFFF est équivalent à ~0111111111111111 (en binaire),
ce qui donne 1000000000000000 (en binaire), (c'est-à-dire 0x1000 en format
hexadécimal). De même, ~y vaut 0xEFFF car ~0x1000 est équivalent à
~1000000000000000 (en binaire), ce qui donne 0111111111111111 (en binaire)
(soit 0xEFFF en hexadécimal).
454 Annexe B

2. Voici une solution possible :


/* 08A02.c */
#include <stdio.h>

int main()
{
int x, y ;

x = 0xEFFF
; y =
0x1000 ;

printf("!x donne : %d (i.e., %u)\n", !x, !x) ;


printf("!y produit : %d (i.e., %u)\n", !y, !y) ;

retour 0 ;
}

La sortie du programme est la suivante :


!x donne : 0 (c'est-à-dire 0)
!y donne : 0 (c'est-à-dire 0)

3. Voici une solution possible :


/* 08A03.c */
#include <stdio.h>

int main()
{
int x, y ;

x = 123 ;
y = 4 ;
printf("x << y donne : %d\n", x << y) ;
printf("x >> y donne : %d\n", x >> y) ;

retour 0 ;
}

[ic:output]La sortie du programme est la suivante :


x << y donne : 1968
x >> y donne : 7

4. Voici une solution possible :


/* 08A04.c */
#include <stdio.h>

int main()
{
printf("0xFFFF ^ 0x8888 yields : 0x%X\n",
Réponses aux questions du quiz et aux 455
exercices

0xFFFF ^ 0x8888) ;
printf("0xABCD & 0x4567 yields : 0x%X\n",
0xABCD & 0x4567) ;
printf("0xDCBA | 0x1234 yields : 0x%X\n",
0xDCBA | 0x1234) ;

retour 0 ;
}

La sortie du programme est :


SORTIE 0xFFFF ^ 0x8888 donne : 0x7777
0xABCD & 0x4567 donne : 0xFFFF
^ 0x8888 donne : 0x7777 0xABCD
& 0x4567 donne : 0x145 0xDCBA
| 0x1234 donne : 0xDEBE

5. Voici une solution possible :


/* 08A05.c */
#include <stdio.h>

main()
{
int x ;

printf("Enter a character:\n(enter q to exit)\n") ;


for ( x=' ' ; x != 'q' ? 1 : 0 ; ) {
x = getc(stdin) ;
B
putchar(x) ;
}
printf("\NSortie de la boucle for. Bye!\N") ;

retour 0 ;
}

Heure 9, "Travailler avec des modificateurs de


données et des fonctions mathématiques"
Quiz
1. Non. x contient un nombre négatif qui n'est pas le même que le nombre contenu
dans la variable y (unsigned int).
2. Vous pouvez utiliser le modificateur long, qui augmente la plage de valeurs que
l'int peut contenir. Si vous stockez un nombre positif, vous pouvez également
utiliser le modificateur unsigned pour stocker une valeur plus importante.
3. %lu.
4. Le fichier d'en-tête est math.h.
456 Annexe B

Exercices
1. Voici une solution possible :
/* 09A01 */
#include <stdio.h>

main()
{
int x ;
unsigned int y ;

x = 0xAB78
; y =
0xAB78 ;

printf("La valeur décimale de x est %d.\n", x)


; printf("La valeur décimale de y est %u.\n",
y) ;

retour 0 ;
}

La sortie du programme sur ma machine est la suivante :


SORTIE
La valeur décimale de x est -
21640. La valeur décimale de y est
43896.

2. Voici une solution possible :


/* 09A02 */
#include <stdio.h>

main()
{
printf("The size of short int is %d.\n",
sizeof(short int)) ;
printf("The size of long int is %d.\n",
sizeof(long int)) ;
printf("The size of long double is %d.\n",
sizeof(long double)) ;

retour 0 ;
}

3. Voici une solution possible :


/* 09A03 */
#include <stdio.h>

main()
{
int x, y ;
long int result

; x = 7000 ;
Réponses aux questions du quiz et aux 457
exercices

y = 12000 ;
résultat = x * y ;
printf("x * y == %lu.\n", result) ;

retour 0 ;
}

La sortie du programme sur ma machine est la suivante :


SORTIE
x * y == 84000000.
4. Voici une solution possible :
/* 09A04 */
#include <stdio.h>

main()
{
int x ;

x = -23456 ;
printf("La valeur hexagonale de x est 0x%X.\n", x) ;

retour 0 ;
}

La sortie du programme sur ma machine est la suivante :


B
SORTIE
La valeur hexagonale de x est 0xA460.

5. Voici une solution possible :


/* 09A05.c */
#include <stdio.h>
#include <math.h>

main()
{
double x ;

x = 30.0 ; /* 45 degrés */
x *= 3.141593 / 180.0; /* convertir en radians
*/ printf("The sine of 30 is: %f.\n", sin(x)) ;
printf("The tangent of 30 is : %f.\n", tan(x))
;

retour 0 ;
}

6. Voici une solution possible (notez que j'ai utilisé le type casting (dou- ble)
dans l'instruction d'affectation x=(double)0x19A1 ;):
/* 09A06.c */
#include <stdio.h>
#include <math.h>
458 Annexe B

main()
{
double x ;

x = (double)0x19A1 ;
printf("La racine carrée de x est : %2.0f\n", sqrt(x)) ;

retour 0 ;
}

Heure 10, "Contrôler le déroulement du programme"


Quiz
1. Non.
2. Le résultat final enregistré dans x est 2, après l'exécution de trois cas, '-',
'*' et '/'.
3. Le résultat final enregistré dans x est 2. Cette fois, seul le cas de l'opérateur = '-
' est exe- cuté en raison de l'instruction break.

4. Le résultat sauvegardé par x est 27.

Exercices
1. Voici une solution possible :
/* 10A01.c Utiliser l'instruction
if */ #include <stdio.h>

main()
{
int i ;

printf("Integers that can be divided by both 2 and 3\n") ;


printf("(within the range of 0 to 100):\n") ;
for (i=0 ; i<=100 ;
i++) if (i%6 == 0)
printf(" %d\n", i) ;

retour 0 ;
}

2. Voici une solution possible :


/* 10A02.c Utiliser l'instruction
if */ #include <stdio.h>
Réponses aux questions du quiz et aux 459
exercices

main()
{
int i ;

printf("Integers that can be divided by both 2 and 3\n") ;


printf("(within the range of 0 to 100):\n") ;
for (i=0 ; i<=100 ;
i++) if (i%2 == 0)
si (i%3 == 0)
printf(" %d\n", i) ;

retour 0 ;
}

3. Voici une solution possible :


/* 10A03.c */
#include <stdio.h>

main()
{
lettre int ;

printf("Veuillez entrer une


lettre:\N") ; letter =
switch (letter){
getchar() ;
B
case 'A' :
printf("La valeur numérique de A est : %d\n",
'A') ; break ;
cas 'B' :
printf("La valeur numérique de B est : %d\n",
'B') ; break ;
cas 'C' :
printf("La valeur numérique de C est : %d\n",
'C') ; break ;
par défaut :
pause ;
}

retour 0 ;
}

4. Voici une solution possible :


/* 10A04.c */
#include <stdio.h>

main()
{
int c ;
460 Annexe B

printf("Enter a character:\n(enter q to exit)\n")


; while ((c = getc(stdin)) != 'q') {

/* pas de déclaration à l'intérieur de la boucle while */


}
printf("\nBye!\n") ;

retour 0 ;
}

5. Voici une solution possible :


/* 10A05.c */
#include <stdio.h>

main()
{
int i, sum ;

somme = 0 ;
for (i=1 ; i<8 ; i++){
si ((i%2 == 0) && (i%3 == 0))
continue
; sum += i
;
}
printf("The sum is : %d\n", sum) ;
return 0 ;
}

Heure 11, "Comprendre les pointeurs"


Quiz
1. En utilisant l'opérateur d'adresse, &. En d'autres termes, l'expression &ch donne la
valeur gauche (l'adresse) de la variable de caractère ch.
2. Les réponses sont les suivantes :
• Opérateur de déréférence
• Opérateur de multiplication
• Opérateur de multiplication
• Les premier et troisième astérisques sont des opérateurs de déréférence ; le
deuxième astérisque est un opérateur de multiplication.
3. ptr_int donne la valeur de l'adresse 0x1A38 ; *ptr_int donne la valeur de 10.
4. x contient maintenant la valeur 456.
Réponses aux questions du quiz et aux 461
exercices

Exercices
1. Voici une solution possible :
/* 11A01.c */
#include <stdio.h>

main()
{
int x, y, z ;

x = 512 ;
y = 1024 ;
z = 2048 ;

printf("Les valeurs gauches de x, y et z


sont:\N") ; printf("0x%p, 0x%p, 0x%p\N", &x,
&y, &z) ; printf("Les valeurs droites de x, y
et z sont:\N") ; printf("%d, % d , %d\N", x, y,
z) ;

retour 0 ;
}

2. Voici une solution possible :


/* 11A02.c */ B
#include <stdio.h>

main()
{
int *ptr_int
; char
*ptr_ch ;

ptr_int = 0 ; /* pointeur nul


*/ ptr_ch = 0 ; /* pointeur
nul */

printf("La valeur gauche de ptr_int est :


0x%p\n", ptr_int) ;
printf("La bonne valeur de ptr_int est : %d\n",
*ptr_int) ;
printf("La valeur gauche de ptr_ch est : 0x%p\n",
ptr_ch) ;
printf("La bonne valeur de ptr_ch est : %d\n",
*ptr_ch) ;

retour 0 ;
}

3. Voici une solution possible :


/* 11A03.c */
#include <stdio.h>
462 Annexe B

main()
{
char ch ;
char *ptr_ch ;

ch = 'A' ;
printf("La bonne valeur de ch est : %c\n",
ch) ;
ptr_ch = &ch ;
*ptr_ch = 'B' ; /* décimale 66 */
/* prouve que ch a été mis à jour */
printf("La valeur gauche de ch est :
0x%p\n",
&ch) ;
printf("La valeur droite de ptr_ch est :
0x%p\n", ptr_ch) ;
printf("La bonne valeur de ch est : %c\n",
ch) ;

retour 0 ;
}

4. Voici une solution possible :


/* 11A04.c */
#include <stdio.h>

main()
{
int x, y ;
int *ptr_x, *ptr_y ;

x = 5 ;
y = 6 ;
ptr_x = &x
; ptr_y =
&y ;

*ptr_x *= *ptr_y ;

printf("Le résultat est : %d\n",


*ptr_x) ;

retour 0 ;
}

Heure 12, "Comprendre les tableaux"


Quiz
1. Elle déclare un tableau d'entiers appelé tableau_int avec quatre éléments.
L'instruction initialise également le tableau avec quatre entiers, 12, 23, 9 et 56.
Réponses aux questions du quiz et aux 463
exercices

2. Comme il n'y a que trois éléments dans le tableau int data, et que le dernier
élément est data[2], la troisième instruction est illégale. Elle risque d'écraser des
données valides dans l'emplacement mémoire de data[3].
3. Le premier tableau, tableau1, est un tableau bidimensionnel, le deuxième,
tableau2, est unidimensionnel, le troisième, tableau3, est tridimensionnel et le
dernier, tableau4, est un tableau bidimensionnel.
4. Dans une déclaration de tableau multidimensionnel, seule la taille de la dimension
la plus à gauche peut être omise. Cette déclaration est donc erronée. La déclaration
correcte se présente comme suit :
char list_ch[][2] = {
'A', 'a',
B", "b",
C", "c",
D", "d",
'E', 'e'} ;

Exercices
1. Voici une solution possible :
/* 12A01.c */
#include <stdio.h> B
main()
{
int i ;
char array_ch[5] = {'A', 'B', 'C', 'D', 'E'} ;

for (i=0 ; i<5 ; i++)


printf("%c ", array_ch[i])
;

retour 0 ;
}

2. Voici une solution possible :


/* 12A02.c */
#include <stdio.h>

main()
{
int i ;
char array_ch[5] ;

for (i=0 ; i<5 ; i++)


array_ch[i] = 'a' + i
;
for (i=0 ; i<5 ; i++)
printf("%c ", array_ch[i])
;

retour 0 ;
464 Annexe B
}
Réponses aux questions du quiz et aux 465
exercices

3. Voici une solution possible :


/* 12A03.c */
#include <stdio.h>

main()
{
int i, size ;
char list_ch[][2] = {
'1', 'a',
'2', 'b',
'3', 'c',
'4', 'd',
'5', 'e',
'6', 'f'} ;

/* méthode I */
size = &list_ch[5][1] - &list_ch[0][0] + 1
; size *= sizeof(char) ;
printf("Method I : The total bytes are %d.\n", size) ;

/* méthode II */
size = sizeof(list_ch) ;
printf("Method II : The total bytes are %d.\n", size) ;

for (i=0 ; i<6 ; i++)


printf("%c %c\n",
list_ch[i][0], list_ch[i][1]) ;

retour 0 ;
}

4. Voici une solution possible :


/* 12A04.c */
#include <stdio.h>

main()
{
char array_ch[11] = {'I', ' ',
l", "i", "k", "e", ' ',
'C', '!', '\0'} ;
int i ;
/* array_ch[i] dans le test
logique */ for (i=0 ; array_ch[i]
; i++)
printf("%c", array_ch[i]) ;

retour 0 ;
}
466 Annexe B

5. Voici une solution possible :


/* 12A05.c */
#include <stdio.h>

main()
{
double list_data[6] = {
1.12345,
2.12345,
3.12345,
4.12345,
5.12345} ;
taille int ;

/* Méthode I */
size = sizeof(double) * 6 ;
printf("Method I : The size is %d.\n", size) ;

/* Méthode II */
size = sizeof(list_data) ;
printf("Method II : The size is %d.\n", size) ;

retour 0 ;
} B

Heure 13, "Manipuler les chaînes de caractères"


Quiz
1. Les deux déclarations suivantes sont légales :
• char str2[] = "Une chaîne de caractères" ;
• char str3 = "A" ;

2. Les deux déclarations suivantes sont illégales :


• ptr_ch = 'x' ;
• *ptr_ch = "Ceci est le Quiz 2." ;

3. Non. La fonction puts() ajoute un caractère de nouvelle ligne pour remplacer le


caractère nul à la fin d'un tableau de caractères.
4. Le spécificateur de format %s est utilisé pour la lecture d'une chaîne de caractères ; le %f est utilisé pour
une valeur flottante.
nombre.
Réponses aux questions du quiz et aux 467
exercices

Exercices
1. Voici une solution possible :
/* 13A01.c : Copier une chaîne de
caractères dans une autre */ #include
<stdio.h>
#include <string.h>

main()
{
int i ;
char str1[] = "Ceci est l'exercice
1" ; char str2[20] ;

/* Méthode I */
strcpy(str2, str1) ;

/* confirmer la copie */
printf("from Method I : %s\n",
str2) ;

/* Méthode II */
for (i=0 ; str1[i] ; i++)
str2[i] = str1[i] ;
str2[i] = '\0' ;
/* confirmer la copie */
printf("from Method II : %s\n", str2)
;

retour 0 ;
}

2. Voici une solution possible :


/* 13A02.c : Mesure une chaîne de
caractères */ #include <stdio.h>
#include <string.h>

main()
{
int i, str_length ;
char str[] = "Ceci est l'exercice 2" ;

/* Méthode I
*/ str_length
= 0 ;
for (i=0 ; str[i] ; i++)
str_length++ ;
printf("La longueur de la chaîne est %d.\n", str_length) ;

/* Méthode II */
printf("La longueur de la chaîne
est %d.\n", strlen(str)) ;
468 Annexe B
retour 0 ;
}
Réponses aux questions du quiz et aux 469
exercices

3. Voici une solution possible :


/* 13A03.c : Utiliser gets() et puts()
*/ #include <stdio.h>

main()
{
char str[80] ;
int i, delt ;

printf("Enter a string less than 80 characters:\n")


; gets( str ) ;
delt = 'a' - 'A'
; i = 0 ;
while (str[i]){
if ((str[i] >= 'A') && (str[i] <= 'Z'))
str[i] += delt ; /* conversion en minuscules */
++i ;
}
printf("La chaîne saisie est (en minuscules):\n") ;
puts( str ) ;

retour 0 ;
}

4. Voici une solution possible :


B
/* 13A04.c : Utiliser scanf()
*/ #include <stdio.h>

main()
{
int x, y, sum ;

printf("Enter two integers:\n")


; scanf("%d%d", &x, &y) ;
somme = x + y ;
printf("La somme est %d\n", somme) ;

retour 0 ;
}

Heure 14, "Comprendre les classes d'étendue et


de stockage"
Quiz
1. Les variables int x et float y, déclarées en dehors de la fonction myFunction(),
sont des variables globales. Les variables int, i et j, et la variable float y,
470 Annexe B

déclarées à l'intérieur de la fonction, sont des variables locales. De même, les deux
variables int, x et y, déclarées dans un bloc à l'intérieur de maFonction(), sont des
variables locales dont la portée est limitée au bloc.
2. Pour deux variables partageant le même nom, le compilateur peut déterminer
laquelle utiliser en vérifiant leur portée. La dernière variable déclarée devient
visible en remplaçant la variable qui porte le même nom mais qui est déclarée dans
le bloc extérieur. Toutefois, si deux variables portant le même nom sont déclarées
dans le même bloc, le compilateur émettra un message d'erreur.
3. La variable int i déclarée en dehors de la fonction myFunction() a la même
classe de stockage statique que la variable int x. La variable float y a une
classe de stockage extern.
Dans la fonction myFunction(), les deux variables entières, i et j, ont la classe de
stockage auto. La variable float z a une classe de stockage extern, et la
variable long s a une classe de stockage register. index est une variable integer
avec une classe de stockage static. Le contenu du tableau de caractères str ne
peut pas être modifié en raison du spécificateur const.
4. Non, ce n'est pas légal. Vous ne pouvez pas modifier le contenu d'un tableau spécifié par la méthode
const
spécificateur.

Exercices
1. Les réponses sont les suivantes :
• { int x ; }
• { const char ch ; }
• { static float y ; }
• enregistrer int z ;
• char *ptr_str = 0 ;

2. Voici une solution possible :


/* 14A02.c */
#include <stdio.h>

int x = 1234; /* portée du


programme */ float y = 1.234567f ; /*
portée du programme */

void function_1(int x, double y)


{
printf("From function_1:\n x=%d, y=%f\n", x, y) ;
}

main()
Réponses aux questions du quiz et aux 471
exercices

{
int x = 4321 ; /* champ d'application du bloc 1*/

fonction_1(x, y) ;
printf("Within the main block:\n x=%d, y=%f\n", x, y) ;
/* un bloc imbriqué */
{
float y = 7.654321f ; /* block scope 2 */
function_1(x, y) ;
printf("Within the nested block:\n x=%d, y=%f\n", x, y) ;
}
retour 0 ;
}

3. Voici ce que j'ai obtenu en exécutant le programme C donné dans cet exercice :
x=0,
y=0
x=0,
y=1
x=0,
y=2
x=0,
y=3
x=0, y=4
B
Étant donné que x est stocké temporairement avec la portée du bloc et que y est
stocké en permanence, x est mis à 0 chaque fois que l'exécution du
programme entre dans la boucle for, mais la valeur enregistrée dans y est
conservée.
4. Voici une solution possible :
/* 14A04.c : Utiliser le spécificateur
statique */ #include <stdio.h>
/* la fonction add_two */
int add_two(int x, int
y)
{
static int counter = 1 ;
static int sum = 0 ;

printf("This is the function call of %d,\n", counter++) ;


printf("the previous value of sum is %d,\n", sum) ;
sum = x + y ;
return sum ;
}
/* la fonction
principale */ main()
{
int i, j ;

for (i=0, j=5 ; i<5 ; i++, j--)


printf("l'addition de %d et %d est %d.\n\n", i,
j, add_two(i, j)) ;
472 Annexe B
retour 0 ;
}
Réponses aux questions du quiz et aux 473
exercices

Heure 15, "Travailler avec des fonctions"


Quiz
1. Les réponses sont les suivantes :
• int function_1(int x, int y) ; est une déclaration de fonction
avec un nombre fixe d'arguments.
• void function_2(char *str) ; est une déclaration de fonction avec un
nombre fixe d'arguments.
• char *asctime(const struct tm *timeptr) ; est une déclaration de
fonction avec un nombre fixe d'arguments.
• int function_3(void) ; est une déclaration de fonction sans arguments.
• void function_5(void) ; est une déclaration de fonction sans arguments.
• char function_4(char c, ...) ; est une déclaration de fonction avec
un nombre variable d'arguments.
2. La deuxième expression est une définition de fonction, c'est-à-dire,
int function_2(int x, int y) {return x+y;}
3. Le type de données int est le type de données par défaut renvoyé par une
fonction si un spécificateur de type est omis.
4. La troisième, char function_3(...) ;, est illégale.

Exercices
1. Voici une solution possible :
/* 15A01.c : */
#include <stdio.h>
#include <time.h>

void GetDateTime(void) ;

main()
{
printf("Before the GetDateTime() function is called.\n") ;
GetDateTime() ;
printf("Après l'appel de la fonction GetDateTime().\n")
; return 0 ;
}
/* Définition de GetDateTime() */
void GetDateTime(void)
{
time_t now
; int i ;
474 Annexe B

char *str ;

printf("Within GetDateTime().\n") ;
time(&now) ;
str = asctime(localtime(&now)) ;
printf("La date et l'heure actuelles
sont : ") ; for (i=0 ; str[i] ; i++)
printf("%c", str[i]) ;
}

2. Voici une solution possible :


/* 15A02.c */
#include <stdio.h>

int MultiTwo(int x, int y) ;

main ()
{
printf("Le résultat retourné par MultiTwo() est : %d\n",
MultiTwo(32, 10)) ;
retour 0 ;
}

/* définition de la
fonction */ int
B
MultiTwo(int x, int y)
{
retourner x * y ;
}

3. Voici une solution possible :


/* 15A03.c */
#include <stdio.h>
#include <stdarg.h>

int MultiInt(int x, ...)

; main ()
{
int d1 = 1
; int d2 =
2 ; int d3
= 3 ; int
d4 = 4 ;

printf("Given an argument : %d\n", d1) ;


printf("Le résultat retourné par MultiInt() est : %d\n\n",
MultiInt(1, d1)) ;
printf("Étant donné un argument : %d, %d, %d, et %d\n", d1, d2,
d3, d4) ; printf("Le résultat retourné par MultiInt() est :
%d\n\n",
MultiInt(4, d1, d2, d3, d4)) ;

return 0 ;
Réponses aux questions du quiz et aux 475
exercices

/* définition de MultiInt() */
int MultiInt(int x, ...)
{
va_list arglist
; int i ;
int result = 1 ;

printf("The number of arguments is : %d\n",


x) ; va_start (arglist, x) ;
pour (i=0 ; i<x ; i++)
result *= va_arg(arglist, int)
; va_end (arglist) ;
retourner le résultat ;
}

4. Sur ma machine, va_arg() récupère les arguments de gauche à droite. Voici une
solution possible :
/* 15A04.c */
#include <stdio.h>
#include <stdarg.h>

double AddDouble(int x, ...)

; main ()
{
double d1 = 1,5
; double d2 =
2,5 ; double d3
= 3,5 ; double
d4 = 4,5 ;

printf("Given an argument : %2.1f\n", d1) ;


printf("Le résultat retourné par AddDouble() est : %2.1f\n\n",
AddDouble(1, d1)) ;
printf("Given arguments : %2.1f and %2.1f\n", d1, d2) ;
printf("The result returned by AddDouble() is : %2.1f\n\n",
AddDouble(2, d1, d2)) ;
printf("Given arguments : %2.1f, %2.1f and %2.1f\n", d1, d2, d3)
; printf("The result returned by AddDouble() is : %2.1f\n\n",
AddDouble(3, d1, d2, d3)) ;
printf("Arguments donnés : %2.1f, %2.1f, %2.1f, et %2.1f\n", d1, d2, d3,
d4)
; printf("Le résultat retourné par AddDouble() est : %2.1f\n",
AddDouble(4, d1, d2, d3, d4)) ;
retour 0 ;

/* définition de AddDouble() */
476 Annexe B

double AddDouble(int x, ...)


{
va_list arglist
; int i ;
argument double, résultat = 0.0 ;

printf("The number of arguments is : %d\n", x)


; va_start (arglist, x) ;
for (i=0 ; i<x ; i++){
argument = va_arg(arglist, double) ;
printf("Argument passé à cette fonction : %f\n", argument)
; result += argument ;
}

va_end (arglist) ;

retourner le résultat ;
}

Heure 16, "Appliquer les pointeurs"


Quiz B
1. J'obtiens les réponses suivantes de ma machine :
• 4 octets
• 4 octets
• 4 octets
• 12 octets
• 12 octets
• 12 octets
2. Comme 0x100A - 0x1006 donne 4, et qu'un int prend 2 octets, ptr1 et ptr2
sont distants de deux entiers. La réponse est donc 2.
3. 0x0230 et 0x0260.
4. Les réponses sont les suivantes :
• *(ptr + 3) recherche 'A'.
• ptr - ch donne 1.
• *(ptr - 1) recherche 'a'.
• *ptr = 'F' remplace 'b' par 'F'.
Réponses aux questions du quiz et aux 477
exercices

Exercices
1. Voici une solution possible :
/* 16A01.c */
#include <stdio.h>

void StrPrint(char *str)


; main()
{
char string[] = "I like C

!"; StrPrint(string) ;

retour 0 ;
}

void StrPrint(char *str)


{
printf("%s\n", str) ;
}

2. Voici une solution possible :


/* 16A02.c */
#include <stdio.h>

void StrPrint(char *str)


; main()
{
char string[] = "I like C
!"; char *ptr ;
int i ;

ptr = string ;
for (i=0 ; ptr[i] ; i++){
si (ptr[i] ==
'i')
ptr[i] = 'o' ;
si (ptr[i] ==
'k')
ptr[i] = 'v' ;
}
StrPrint(ptr) ;

retour 0 ;
}

void StrPrint(char *str)


{
printf("%s\n", str) ;
}
478 Annexe B

3. Voici une solution possible :


/* 16A03.c */
#include <stdio.h>

void StrPrint(char str[][15], int max) ;


main()
{
char str[2][15] = {
"You know what,",
"C is powerful." }
;

StrPrint(str, 2) ;

retour 0 ;
}

void StrPrint(char str[][15], int max)


{
int i ;

for (i=0 ; i<max ; i++)


printf("%s\n", str[i]) ;
}

4. Voici une solution possible : B


* 16A04.c */
#include <stdio.h>

/* déclarations de fonctions */
void StrPrint1(char **str1, int size) ;
void StrPrint2(char *str2) ;

/* fonction main() */
main()
{
char *str[7] = {
"Dimanche",
"Lundi",
"Mardi",
"Mercredi",
"Jeudi",
"Vendredi",
"Saturday"} ;
int i, size ;

taille = 7 ;
StrPrint1(str,
taille) ; for (i=0 ;
i<size ; i++)
StrPrint2(str[i]) ;
Réponses aux questions du quiz et aux 479
exercices

retour 0 ;
}

/* définition de la fonction */
void StrPrint1(char **str1, int size)
{
int i ;

for (i=0 ; i<size ; i++)


printf("%s\n", str1[i]) ;
}

/* définition de la
fonction */ void
StrPrint2(char *str2)
{
printf("%s\n", str2) ;
}

Heure 17, "Allocation de la mémoire"


Quiz
1. Les réponses sont les suivantes :
• 200 octets
• 200 octets
• 200 octets
• 0 octets
2. La déclaration est la suivante
ptr = realloc(ptr, 150 * sizeof(int)) ;
3. La taille finale est de 120 octets, à condition que le type de données int ait une longueur d'un octet.
4. La taille finale est de 0. En d'autres termes, tous les blocs de mémoire alloués ont
été libérés par la dernière instruction.

Exercices
1. Voici une solution possible :
/* 17A01.c */
#include <stdio.h>
#include <stdlib.h>

/* fonction main() */
main()
{
480 Annexe B

int *ptr_int ;
int i, sum ;
int max = 0 ;
int terminaison = 0 ;

printf("Enter the total number of integers:\n")


; scanf("%d", &max) ;
/* appeler malloc() */
ptr_int = malloc(max * sizeof(int))
; if (ptr_int == NULL){
printf("La fonction malloc() a
échoué.\n") ; terminaison = 1 ;
}
else{
for (i=0 ; i<max ;
i++) ptr_int[i] =
i + 1 ;
}
somme = 0 ;
for (i=0 ; i<max ;
i++) sum +=
ptr_int[i] ;
printf("The sum is %d.\n", sum) ;
free(ptr_int) ;

la terminaison du retour ;
} B
2. Voici une solution possible :
/* 17A02.c */
#include <stdio.h>
#include <stdlib.h>

/* fonction main() */
main()
{
float *ptr_flt ;
int terminaison = 0 ;

/* appeler calloc() */
ptr_flt = calloc(100, sizeof(float))
; if (ptr_flt == NULL){
printf("La fonction calloc() a
échoué.\n") ; terminaison = 1 ;
}
else{
ptr_flt = realloc(ptr_flt, 150 * sizeof(float))
; if (ptr_flt == NULL){
printf("realloc() function failed.\n") ;
terminaison = 1 ;
}
Réponses aux questions du quiz et aux 481
exercices

autre
free(ptr_flt) ;
}
printf("Done!\n") ;

la terminaison du retour ;
}

3. Voici une solution possible :


/* 17A03.c */
#include <stdio.h>
#include <stdlib.h>

/* fonction main() */
main()
{
float *ptr1, *ptr2
; int i ;
int termination = 1 ;
int max = 0 ;

printf("Enter the total number:\n")


; scanf("%d", &max) ;

ptr1 = malloc(max * sizeof(float))


; ptr2 = calloc(max, sizeof(float))
;

if (ptr1 == NULL)
printf("malloc() failed.\n")
;
else if (ptr2 == NULL)
printf("calloc() failed.\n") ;
else{
pour (i=0 ; i<max ; i++)
printf("ptr1[%d]=%5.2f, ptr2[%d]=%5.2f\n",
i, *(ptr1 + i), i, *(ptr2 + i)) ;
free(ptr1) ;
free(ptr2) ;
terminaison = 0 ;
}
printf ("\nBye!\n") ;

la terminaison du retour ;
}

4. Voici une solution possible :


/* 17A04.c : Utiliser la fonction realloc() */
#include <stdio.h>
#include <stdlib.h>
482 Annexe B

#include <string.h>
/* déclaration de fonction */
void StrCopy(char *str1, char *str2) ;
/* fonction main() */
main()
{
char *str[4] = {"Il y a de la musique dans le soupir d'un
roseau ;", "Il y a de la musique dans le
jaillissement d'un ruisseau ;", "Il y a de
la musique en toutes choses si les hommes
avaient des oreilles ;", "La terre n'est
qu'un écho des sphères.\n"
} ;
char *ptr
; int i ;

int terminaison = 0 ;

ptr = realloc(NULL, strlen((str[0]) + 1) * sizeof(char))


; if (ptr == NULL){
printf("realloc() failed.\n") ;
terminaison = 1 ;
}
else{
StrCopy(str[0], ptr)
; printf("%s\n",
ptr) ; for (i=1 ; B
i<4 ; i++){
ptr = realloc(ptr, (strlen(str[i]) + 1) * sizeof(char))
; if (ptr == NULL){
printf("realloc() failed.\n") ;
terminaison = 1 ;
i = 4 ; /* interrompt la boucle for */
}
else{
StrCopy(str[i], ptr) ;
printf("%s\n", ptr) ;
}
}
}
realloc(ptr, 0) ;
return termination ;
}
/* définition de la fonction */
void StrCopy(char *str1, char *str2)
{
int i ;

for (i=0 ; str1[i] ; i++)


str2[i] = str1[i] ;
str2[i] = '\0' ;
}
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu

480 Visitez www.DeepL.com/pro


Annexe B pour en savoir plus.

Heure 18, "Utiliser des types de


données et des fonctions spéciales"
Quiz
1. Les noms énumérés Janv, Fév, Mars, Avril, Mai, Juin, Juillet, Août, Sep, Oct, Nov, et
Dec représentent les valeurs de 0 à 11, respectivement.
2. Les valeurs 0, 10, 11 et 12 sont représentées par les noms énumérés name1,
name2, name3 et name4, respectivement.
3. Les déclarations typedef long int BYTE32 ; et BYTE32 x, y, z ; sont
équivalentes à long int x, y, z ;.
typedef char *STRING[16] ; et STRING str1, str2, str3 ; sont équivalents à
char *str1[16], *str2[16], *str3[16] ;
4. Le mot-clé void dans la fonction main() indique qu'aucun argument n'est
transmis à la fonction.

Exercices
1. Voici une solution possible :
/* 18A01.c */
#include <stdio.h>

main(void)
{
enum tag {nom1,
nom2 = 10,
nom3, nom4
} ;

printf("La valeur représentée par nom1 est : %d\n",


nom1) ;
printf("La valeur représentée par nom2 est : %d\n",
nom2) ;
printf("La valeur représentée par nom3 est : %d\n",
nom3) ;
printf("La valeur représentée par le nom4 est : %d\n",
nom4) ;

retour 0 ;
}

2. Voici une solution possible :


/* 18A02.c */
#include <stdio.h>
Réponses aux questions du quiz et aux 481
exercices

main(void)
{
typedef char WORD ;
typedef int SHORT ;
typedef long LONG ;
typedef float FLOAT
; typedef double
DFLOAT ;

printf("La taille de WORD est de : %d-byte\n",


sizeof(WORD)) ; printf("La taille de SHORT est : %d-
byte\n", sizeof(SHORT)) ; printf("La taille de LONG est :
%d-byte\n", sizeof(LONG)) ; printf("La taille de FLOAT est
: %d-byte\n", sizeof(FLOAT)) ; printf("La taille de DFLOAT
est : %d-byte\n", sizeof(DFLOAT)) ;

retour 0 ;
}

3. Voici une solution possible :


/* 18A03.c */
#include <stdio.h>

enum con{MIN_NUM = 0,
MAX_NUM = 100} ;

int fRecur(int n)
B
; main()
{
int i, sum1, sum2 ;

somme1 = somme2 = 0 ;
for (i=1 ; i<=MAX_NUM ;
i++) sum1 += i ;
printf("The value of sum1 is %d.\n", sum1) ;
sum2 = fRecur(MIN_NUM) ;
printf("La valeur retournée par fRecur() est %d.\n", sum2) ;

retour 0 ;
}
int fRecur(int n)
{
si (n >
MAX_NUM)
retour 0 ;
return fRecur(n + 1) + n ;
}

4. Voici une solution possible :


/* 18A04.c : Arguments de la ligne de
commande */ #include <stdio.h>

main (int argc, char *argv[])


482 Annexe B

{
int i ;

if (argc < 2){


printf("L'utilisation de ce programme est:\N") ;
printf("18A04.EXE argument1 argument2 [...argumentN]\N")
;
}
else {
printf("Les arguments de la ligne de
commande sont:\N") ; for (i=1 ; i<argc ;
i++)
printf("%s ", argv[i])
; printf("\n") ;
}

retour 0 ;
}

Heure 19, "Comprendre les structures"


Quiz
1. Le point-virgule ( ;) doit être inclus à la fin de la déclaration de la structure.
2. u, v et w sont trois variables de structure.
3. Vous pouvez initialiser le tableau de la structure automobile comme suit :
struct automobile {
int year ;
char model[8]} car[2] = {
{1997, "Taurus"},
{1997, "Accord"}} ;

Exercices
1. Voici une solution possible :
/* 19A01.c */
#include <stdio.h>

main(void)
{
struct automobile {
int year ;
char model[10] ;
int engine_power ;
double weight ;
} sedan =
{ 1997,
"Nouveau modèle",
Réponses aux questions du quiz et aux 483
exercices

200,
2345.67} ;

printf("year : %d\n", sedan.year)


; printf("model : %s\n",
sedan.model) ;
printf("engine_power : %d\n", sedan.engine_power) ;
printf("weight : %6.2f\n", sedan.weight) ;

retour 0 ;
}

2. Voici une solution possible :


/* 19A02.c */
#include <stdio.h>

struct employee {
int id ;
char name[32] ;
} ;

void Display(struct employee s) ;

main(void)
{
/* initialisation de la structure
B
*/ struct employee info = {
0001,
"B. Smith
} ;

printf("Voici un échantillon:\n") ;
Display(info) ;

printf("Quel est votre nom


?") ; gets(info.name) ;
printf("Quel est votre numéro
d'identification ?") ; scanf("%d",
&info.id) ;

printf("\NVoici ce que vous avez saisi:\N")


; Display(info) ;

retour 0 ;
}

/* définition de la fonction */
void Display(struct employee s)
{
printf("Nom de l'employé : %s\n", s.name)
; printf("Numéro d'identification de
l'employé : %04d\n\n", s.id) ;
}
484 Annexe B

3. Voici une solution possible :


/* 19A03.c Utiliser l'opérateur
-> */ #include <stdio.h>

struct computer {
float cost ;
int year ;
int cpu_speed ;
char cpu_type[16] ;
} ;

typedef struct computer SC

; void DataReceive(SC

*ptr_s) ; main(void)
{
Modèle SC ;

DataReceive(&model) ;
printf("Voici ce que vous avez saisi:\N") ;
printf("Année : %d\N", modèle.année) ;
printf("Coût : %6.2f\N", modèle.coût) ;
printf("Type d'unité centrale : %s\N",
modèle.type d'unité centrale) ;
printf("Vitesse du processeur : %d MHz", model.cpu_speed) ;

retour 0 ;
}

void DataReceive(SC *ptr_s)


{
printf("Le type de CPU dans votre ordinateur ?") ;
gets(ptr_s->cpu_type) ;
printf("The speed(MHz) of the CPU?\n")
; scanf("%d", &(ptr_s->cpu_speed))
;
printf("L'année de fabrication de votre
ordinateur ?") ; scanf("%d", &(ptr_s-
>année)) ;
printf("Combien avez-vous payé pour l'ordinateur ?")
; scanf("%f", &(ptr_s->cost)) ;
}

4. Voici une solution possible :


/* 19L04.c Tableaux de structures */
#include <stdio.h>

struct haiku {
int start_year
; int end_year
;
Réponses aux questions du quiz et aux 485
exercices

char author[16]
; char str1[32]
; char str2[32]
; char str3[32]
;
} ;

typedef struct haiku HK ;

void DataDisplay(HK *ptr_s)

main(void)
{
HK poème[2] = {
{ 1641,
1716,
"Sodo",
"Leading me along",
"mon ombre rentre à la
maison", "en regardant la
lune".
},
{ 1729,
1781,
"Chora",
"Un vent de tempête souffle,
"au milieu des herbes", "la B
pleine lune pousse".
}
} ;

/* définir un tableau de pointeurs avec


HK */ HK *ptr_poem[2] = {&poem[0],
&poem[1]} ; int i ;

for (i=0 ; i<2 ; i++)


DataDisplay(ptr_poem[i]) ;

retour 0 ;
}

void DataDisplay(HK *ptr_s)


{
printf("%s\n", ptr_s->str1) ;
printf("%s\n", ptr_s->str2) ;
printf("%s\n", ptr_s->str3) ;
printf("--- %s\n", ptr_s->auteur)
;
printf(" (%d-%d)\n\n", ptr_s->start_year, ptr_s->end_year) ;
}
486 Annexe B

Heure 20, "Comprendre les syndicats"


Quiz
1. La première instruction est la déclaration d'une union avec le nom de balise
_union. La deuxième instruction définit deux variables d'union, x et y, avec le
type de données a_union.
2. Le point-virgule ( ;) manque à deux endroits : à la fin de la déclaration de char
model[8] et à la fin de la déclaration de l'union.
3. Les deux membres de l'union ont la même valeur, 1997.

Exercices
1. Voici la version modifiée. Le contenu du tableau name est partiellement écrasé
par la valeur assignée à la variable double price.
/* 20A01.c */
#include <stdio.h>
#include <string.h>

main(void)
{
union menu {
char name[23]
; double
price ;
} dish ;

printf("Le contenu assigné à l'union séparément:\n") ;


/* accès au nom */
strcpy(dish.name, "Poulet aigre-doux") ;
/* accès au prix */
dish.price = 9.95 ;
printf("Nom du plat : %s\n", nom.du plat) ;
printf("Prix du plat : %5.2f\n", prix.du
plat) ;

retour 0 ;
}

2. Voici une solution possible :


/* 20A02.c */
#include <stdio.h>

union employee {
int start_year ;
int dpt_code ;
int id_number ;
} ;

void DataDisplay(union employee u) ;


Réponses aux questions du quiz et aux 487
exercices

main(void)
{
informations sur les employés du syndicat ;

/* initialisation de l'année de
début */ info.start_year = 1997
; DataDisplay(info) ;

/* initialisation du code
dpt */ info.dpt_code = 8
; DataDisplay(info) ;

/* initialisation de
l'identifiant */
info.id_number = 1234
; DataDisplay(info) ;

retour 0 ;
}

/* définition de la fonction */
void DataDisplay(union employee u)
{
printf("Start Year : %d\n", u.start_year)
;printf("Dpt. Code : ", u.dpt_code) ; B
printf("ID Number : ", u.id_number) ;
}

La sortie du programme est la suivante


SORTIE
Année de 1997
démarrage :
Dpt. Code : 1997
Numéro 1997
d'identific
ation :
Année de 8
démarrage :
Dpt. Code : 8
Numéro 8
d'identific
3. Voici une solution possible :
/* 20A03.c */
#include <stdio.h>
#include <string.h>

struct survey {
char name[20] ;
union {
char state[32] ;
char country[32] ;
} place ;
} ;
488 Annexe B

void DataEnter(struct survey *s)


; void DataDisplay(struct survey
*s) ;

main(void)
{
enquête struct citoyenne ;

DataEnter(&citoyen) ;
DataDisplay(&citoyen) ;

retour 0 ;
}

/* définition de DataDisplay() */
void DataDisplay(struct survey
*ptr)
{
printf("\NVoici ce que vous avez saisi : \N") ;
printf("Votre nom est %s.\N", ptr->nom) ;
printf("Vous êtes de %s.\N", ptr->lieu.état) ;
printf("\NMerci!\N") ;
}

/* définition de DataEnter() */
void DataEnter(struct survey *ptr)
{
char is_yes[4] ;

printf("Veuillez entrer votre


nom:\N") ; gets(ptr->nom) ;

printf("Êtes-vous citoyen américain ? (Yes or


No)\n") ; gets(is_yes) ;
si ((is_yes[0] == 'Y') ||
(is_yes[0] == 'y')){
printf("Enter the name of the state:\n")
; gets(ptr->place.state) ;
} else {
printf("Entrez le nom de votre pays:\N") ;
gets(ptr->place.country) ;
}
}

Voici le résultat du programme sur ma machine :


SORTIE
Veuillez saisir votre nom :
Tony
Êtes-vous citoyen américain ? (Oui ou Non)
Oui
Saisissez le nom de l'État :
Texas
Réponses aux questions du quiz et aux 489
exercices

Voici ce que vous avez


saisi : Votre nom est
Tony.
Vous êtes du
Texas.

Merci de votre
attention !

4. Voici une solution possible :


/* 20A04.c */
#include <stdio.h>
#include <string.h>

struct bit_field
{ int yes : 1
;
} ;

struct survey {
struct bit_field flag
; char name[20] ;
union {
char state[32] ;
char country[32] ;
} place ;
} ;

void DataEnter(struct survey *s) ;


B
void DataDisplay(struct survey *s) ;

main(void)
{
enquête struct citoyenne ;

DataEnter(&citoyen)
;
DataDisplay(&citoyen) ;

retour 0 ;
}

/* définition de la fonction */
void DataEnter(struct survey *ptr)
{
char is_yes[4] ;

printf("Veuillez entrer votre


nom:\N") ; gets(ptr->nom) ;

printf("Êtes-vous citoyen américain ? (Yes or


No)\n") ; gets(is_yes) ;
si ((is_yes[0] == 'Y') ||
(is_yes[0] == 'y')){
490 Annexe B
printf("Enter the name of the state:\n")
; gets(ptr->place.state) ;
Réponses aux questions du quiz et aux 491
exercices

ptr->flag.yes = 1 ;
} else {
printf("Entrez le nom de votre pays:\N") ;
gets(ptr->place.country) ;
ptr->flag.yes = 0 ;
}
}
/* définition de la fonction */
void DataDisplay(struct survey *ptr)
{
printf("\NVoici ce que vous avez saisi:\N") ;
printf("Nom : %s\N", ptr->nom) ;
si (ptr->flag.yes)
printf("L'état est : %s\n",
ptr->place.state) ;
autre
printf("Votre pays est :
%s\n", ptr->place.country)
;
printf("\nMerci et au revoir!\n") ;
}

Heure 21, "Lire et écrire avec des fichiers"


Quiz
1. La première expression tente d'ouvrir un fichier binaire existant appelé test.bin
pour la lecture et l'écriture. La deuxième expression tente d'ouvrir un fichier texte
existant appelé test.txt pour l'ajouter. La dernière expression tente de créer un
fichier texte, appelé test.ini, pour la lecture et l'écriture.
2. La fonction fopen() renvoie un pointeur nul lorsqu'une erreur se produit au cours
de la procédure d'ouverture d'un fichier. Il n'est pas légal d'effectuer une lecture ou
une écriture avec un pointeur de fichier nul. Par conséquent, le code est erroné car
il appelle fgetc() alors que fopen() renvoie un pointeur nul.
3. Le mode est défini en lecture seule, mais le code tente d'écrire un caractère dans
le fichier ouvert en appelant la fonction fputc().
4. Le code lit toujours un fichier texte en utilisant le pointeur de fichier fptr1, même
si le pointeur de fichier fptr1 a été fermé.

Exercices
1. Voici une solution possible :
/* 21A01.c */
#include <stdio.h>
492 Annexe B

enum {SUCCESS, FAIL} ;

int CharRead(FILE *fin)

; main(void)
{
FILE *fptr ;
char filename[]= "haiku.txt"
; int reval = SUCCESS ;

if ((fptr = fopen(filename, "r")) == NULL){


printf("Cannot open %s.\n", filename) ;
reval = FAIL ;
} else {
printf("\NLe nombre total de caractères est %d.\N",
CharRead(fptr)) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de CharRead() */
int CharRead(FILE *fin)
{
B
int c, num ;

num = 0 ;
while ((c=fgetc(fin)) != EOF){
putchar(c) ;
++num ;
}

retour num ;
}

2. Voici une solution possible :


/* 21A02.c */
#include <stdio.h>
#include <string.h>

enum {SUCCESS, FAIL, MAX_LEN = 80} ;

void LineWrite(FILE *fout, char *str) ;

main(void)
{
FILE *fptr ;
char str[MAX_LEN+1] ;
char filename[32] ;
int reval = SUCCESS ;
Réponses aux questions du quiz et aux 493
exercices

printf("Veuillez saisir le nom du


fichier:\N") ; gets(filename) ;

printf("Enter a string:\n")
; gets(str) ;

if ((fptr = fopen(filename, "w")) == NULL){


printf("Cannot open %s for writing.\n", filename)
; reval = FAIL ;
} else {
LineWrite(fptr, str) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de LineWrite() */
void LineWrite(FILE *fout, char *str)
{
fputs(str, fout) ;
printf("Done!\n") ;
}

3. Voici une solution possible :


/* 21A03.c */
#include <stdio.h>

enum {SUCCESS, FAIL} ;

void CharWrite(FILE *fout, char *str) ;

main(void)
{
FILE *fptr ;
char filename[]= "test_21.txt" ;
char str[]= "Disk file I/O is fun."
; int reval = SUCCESS ;

if ((fptr = fopen(filename, "w")) == NULL){


printf("Cannot open %s.\n", filename) ;
reval = FAIL ;
} else {
CharWrite(fptr, str) ;
fclose(fptr) ;
}

retour reval ;
}
494 Annexe B

/* définition de la fonction */
void CharWrite(FILE *fout, char *str)
{
int i, c ;

i = 0 ;
while ((c=str[i]) != '\0'){
putchar(c) ;
fputc(c, fout)
; i++ ;
}
}

4. Voici une solution possible :


/* 21A04.c */
#include <stdio.h>
#include <string.h>

enum {SUCCESS, FAIL} ;

void BlkWrite(FILE *fout, char *str)

; main(void)
{
FILE *fptr ;
B
char filename[]= "test_21.txt" ;
char str[]= "Les entrées/sorties de
fichiers sur disque sont délicates" ;
int reval = SUCCESS ;

if ((fptr = fopen(filename, "w")) == NULL){


printf("Cannot open %s.\n", filename) ;
reval = FAIL ;
} else {
BlkWrite(fptr, str) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de la fonction */
void BlkWrite(FILE *fout, char *str)
{
int num ;

num = strlen(str) ;
fwrite(str, sizeof(char), num, fout) ;
printf("%s\n", str) ;
}
Réponses aux questions du quiz et aux 495
exercices

Heure 22, "Utiliser les fonctions de fichiers spéciaux"


Quiz
1. Oui, les deux déclarations sont équivalentes.
2. Non. Les deux déclarations ne sont pas équivalentes, sauf si l'indicateur de position
actuelle du fichier se trouve effectivement au début du fichier.
3. La fonction scanf() lit dans le fichier test.txt, au lieu du flux d'entrée par
défaut, parce que la fonction freopen() a redirigé le flux d'entrée et l'a associé au
fichier test.txt.
4. L'ensemble des quatre données doubles occupera 32 octets dans le fichier binaire,
si la taille du type de données doubles est de huit octets.

Exercices
1. Voici une solution possible :
/* 22A01.c */
#include <stdio.h>

enum {SUCCESS, FAIL, MAX_LEN = 80} ;

void PtrSeek(FILE *fptr)


; long PtrTell(FILE
*fptr) ; void
DataRead(FILE *fptr) ;
int ErrorMsg(char *str)
;

main(void)
{
FILE *fptr ;
char filename[]= "LaoTzu.txt"
; int reval = SUCCESS ;

if ((fptr = fopen(filename, "r")) == NULL){


reval = ErrorMsg(filename) ;
} else {
PtrSeek(fptr) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de la fonction
*/ void PtrSeek(FILE
*fptr)
{
long offset1, offset2, offset3 ;
496 Annexe B

offset1 = PtrTell(fptr)
; DataRead(fptr) ;
offset2 = PtrTell(fptr)
; DataRead(fptr) ;
offset3 = PtrTell(fptr)
; DataRead(fptr) ;
printf("\nRe-lisez le paragraphe:\n") ;

/* relire la troisième phrase */


fseek(fptr, offset3, SEEK_SET) ;
DataRead(fptr) ;

/* relire la deuxième phrase */ fseek(fptr,


offset2, SEEK_SET) ; DataRead(fptr) ;

/* relire la première phrase */


fseek(fptr, offset1, SEEK_SET) ;
DataRead(fptr) ;
}

/* définition de la
fonction */ long
PtrTell(FILE *fptr)
B
{
longue revalorisation ;

reval = ftell(fptr) ;
printf("Le fptr est à %ld\n", reval) ;

retour reval ;
}

/* définition de la
fonction */ void
DataRead(FILE *fptr)
{
char buff[MAX_LEN] ;

fgets(buff, MAX_LEN, fptr)


; printf("%s", buff) ;
}

/* définition de la
fonction */ int
ErrorMsg(char *str)
{
printf("Cannot open %s.\n", str) ;

retourner FAIL ;
}
Réponses aux questions du quiz et aux 497
exercices

2. Voici une solution possible :


/* 22A02.c */
#include <stdio.h>

enum {SUCCESS, FAIL, MAX_LEN = 80} ;

void PtrSeek(FILE *fptr)


; long PtrTell(FILE
*fptr) ; void
DataRead(FILE *fptr) ;
int ErrorMsg(char *str)
;

main(void)
{
FILE *fptr ;
char filename[]= "LaoTzu.txt"
; int reval = SUCCESS ;

if ((fptr = fopen(filename, "r")) == NULL){


reval = ErrorMsg(filename) ;
} else {
PtrSeek(fptr) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de la fonction
*/ void PtrSeek(FILE
*fptr)
{
long offset1, offset2, offset3 ;

offset1 = PtrTell(fptr)
; DataRead(fptr) ;
offset2 = PtrTell(fptr)
; DataRead(fptr) ;
offset3 = PtrTell(fptr)
; DataRead(fptr) ;
printf("\nRe-lisez le paragraphe:\n") ;
/* relire la troisième phrase */
fseek(fptr, offset3, SEEK_SET) ;
DataRead(fptr) ;
/* relire la deuxième phrase */
fseek(fptr, offset2, SEEK_SET) ;
DataRead(fptr) ;
/* relire la première phrase */
rewind(fptr) ; /* rembobine l'indicateur de position du
fichier */ DataRead(fptr) ;
}
498 Annexe B

/* définition de la
fonction */ long
PtrTell(FILE *fptr)
{
longue revalorisation ;

reval = ftell(fptr) ;
printf("Le fptr est à %ld\n", reval) ;

retour reval ;
}

/* définition de la
fonction */ void
DataRead(FILE *fptr)
{
char buff[MAX_LEN] ;

fgets(buff, MAX_LEN, fptr)


; printf("%s", buff) ;
}

/* définition de la
fonction */ int
ErrorMsg(char *str)
{
printf("Cannot open %s.\n", str)
B
; return FAIL ;
}

3. Sur ma machine, le fichier binaire data.bin fait 10 octets. Voici une solution
possible :
/* 22A03.c */
#include <stdio.h>

enum {SUCCESS, FAIL} ;

void DataWrite(FILE *fout)


; void DataRead(FILE
*fin) ; int ErrorMsg(char
*str) ;

main(void)
{
FILE *fptr ;
char filename[]= "data.bin"
; int reval = SUCCESS ;

if ((fptr = fopen(filename, "wb+")) == NULL){


reval = ErrorMsg(filename) ;
} else {
DataWrite(fptr) ;
rewind(fptr) ;
DataRead(fptr) ;
Réponses aux questions du quiz et aux 499
exercices

fclose(fptr) ;
}

retour reval ;
}

/* définition de la
fonction */ void
DataWrite(FILE *fout)
{
double dnum ;
int inum ;

dnum = 123.45 ;
inum = 10000 ;

printf("%5.2f\n", dnum) ;
fwrite(&dnum, sizeof(double), 1, fout)
; printf("%d\n", inum) ;
fwrite(&inum, sizeof(int), 1, fout) ;
}

/* définition de la fonction
*/ void DataRead(FILE
*fin)
{
double x
; int y
;

printf("\NRelecture du fichier binaire:\N") ;


fread(&x, sizeof(double), (size_t)1, fin) ;
printf("%5.2f\N", x) ;
fread(&y, sizeof(int), (size_t)1, fin)
; printf("%d\n", y) ;
}

/* définition de la
fonction */ int
ErrorMsg(char *str)
{
printf("Cannot open %s.\n", str) ;
return FAIL ;
}

4. Voici une solution possible :


/* 22A04.c */
#include <stdio.h>

enum {SUCCESS,
FAIL, MAX_NUM
= 3,
STR_LEN = 23} ;
500 Annexe B
void DataRead(FILE *fin)
; int ErrorMsg(char
*str) ;
Réponses aux questions du quiz et aux 501
exercices

main(void)
{
FILE *fptr ;
char filename[]= "strnum.mix"
; int reval = SUCCESS ;

if ((fptr = freopen(filename, "r", stdin)) == NULL){


reval = ErrorMsg(filename) ;
} else {
DataRead(fptr) ;
fclose(fptr) ;
}

retour reval ;
}

/* définition de la
fonction */ void
DataRead(FILE *fin)
{
int i ;
int miles ;
char cities[STR_LEN] ;

printf("Les données
lues:\N") ; for (i=0 ; B
i<MAX_NUM ; i++){
scanf("%s%d", cities, &miles) ;
printf("%-23s %d\n", cities, miles)
;
}
}

/* définition de la
fonction */ int
ErrorMsg(char *str)
{
printf("Cannot open %s.\n", str) ;
return FAIL ;
}

Heure 23, "Compiler des programmes : Le


préprocesseur C"
Quiz
1. Le point-virgule ( ;) ne doit pas être inclus à la fin de la définition de la
macro car une définition de macro se termine par une nouvelle ligne et non
par un point-virgule.
2. La valeur 82 est attribuée à result en raison de l'expression d'affectation result =
502 Annexe B
1 + 9 * 9.
Réponses aux questions du quiz et aux 503
exercices

3. Le message de Sous #else. est imprimé.


4. Le message de Sous #ifdef. est imprimé.

Exercices
1. Voici une solution possible :
/* 23A01.c */
#include <stdio.h>
/* fonction main() */
main()
{
#define humain 100
#define animal 50
#define ordinate 51
ur
#define SUN 0
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6

printf("human : %d, animal : %d, computer : %d\n",


human, animal, computer) ;
printf("SUN : %d\n", SUN) ;
printf("MON : %d\n", MON) ;
printf("TUE : %d\n", TUE) ;
printf("WED : %d\n", WED) ;
printf("THU : %d\n", THU) ;
printf("FRI : %d\n", FRI) ;
printf("SAT : %d\n", SAT) ;

retour 0 ;
}

2. Voici une solution possible :


/* 23A02.c */
#include <stdio.h>

#define MULTIPLY(val1, val2) ((val1) *


(val2)) #define NO_ERROR 0

main(void)
{
int résultat ;

résultat = MULTIPLY(2, 3) ;
504 Annexe B

printf("MULTIPLY(2, 3) produit une valeur de %d.\n", result) ;

retourner NO_ERROR ;
}

3. Voici une solution possible :


/* 23A03.c */
#include <stdio.h>

#define UPPER_CASE 0
#define NO_ERROR 0

main(void)
{
#if UPPER_CASE
printf("THIS LINE IS PRINTED OUT,\n") ;
printf("BECAUSE UPPER_CASE IS DEFINED.\n") ;
#elif LOWER_CASE
printf("Cette ligne est imprimée,\n") ;
printf("parce que LOWER_CASE est défini.\n") ;
#else
printf("Cette ligne est imprimée,\n") ;
printf("parce que ni UPPER_CASE ni LOWER_CASE ne sont définis.\n") ;
#endif
B
retourner NO_ERROR ;
}

4. Voici une solution possible :


/* 23A04.c: */
#include <stdio.h>

#define C_LANG 'C'


#define B_LANG 'B'
#define NO_ERROR 0

main(void)
{
#if C_LANG == 'C'
#if B_LANG == 'B'
#undef C_LANG
#define C_LANG "Je connais le langage
C." #undef B_LANG
#define B_LANG "Aussi, je connais
BASIC.\n" printf("%s%s", C_LANG,
B_LANG) ;

#else
#undef C_LANG
#define C_LANG "Je ne connais que le langage
C.\n" printf("%s", C_LANG) ;
Réponses aux questions du quiz et aux 505
exercices

#endif
#elif B_LANG == 'B'
#undef B_LANG
#define B_LANG "Je ne connais que
BASIC.\n" printf("%s", B_LANG) ;
#else
printf("I don't know C or BASIC.\n")
; #endif

retourner NO_ERROR ;
}
506 Annexe B

INDEX
33, 59
Symboles ~ (opérateur de
complément binaire),
+= (opérateur 131
d'affectation | (opérateur OR binaire),
d'addition), 93 131
& (esperluette) ^ (opération XOR par
opérateur d'adresse, bit), 131
177-179 {} (accolades), 45,
48
opérateur bitwise
déclaration de type
AND, 131
"if", 156
<> (crochets d'angle), 32
déclaration if-else, 159
-> (opérateur de flèche),
[ ] (entre parenthèses),
unions, 335, 351
190
= (opérateur
*/ (marque de
d'affectation), 92
commentaire final), 29
* (astérisque)
: ? (opérateur
opérateur de déférence,
conditionnel), 135-136
182 détermination du
-- (opérateur de
sens, 182
décrémentation), 96-
opérateur de
98
multiplication, 182
/= (opérateur de
pointeurs, 180
division), 93
. (opérateur point),
unions, 335-337, 351
"(doubles guillemets), 32-
l d'incrémentation), 96-
= ' 98
= o << (opérateur de
p décalage à gauche),
( é 133-135
o r < (opérateur moins que), 98
p a <= (opérateur inférieur ou
é - égal), 98
r && (opérateur logique
a t ET), 124-126
t e ! (opérateur logique
e u de NEGATION),
u r 128-129
r ) || (opérateur logique
, OR), 126-127
é *= (opérateur de
g 9 multiplication), 93
a 8
l >=
(o
à pé
) ra
, te
ur
9 su
8 pé
\ (caractère ri
d'échappement), 59 eu
Spécification du format r
%%, 79 ou
> ég
al
( ),
p 98
l +
u +
s
(
g o
r p
a é
n r
d a
t
q e
u u
e r
504 != (opérateur différent de)

!= (pas égal à l'opéra- fonction ftell(), intégré


teur), 98 374-378 fonctions main(),
\0 (caractère nul), 198 fichiers séquentiels sur 306
/* (marque de disque, 374 nom, 308
commentaire l'accès remplacer, 308
d'ouverture), 29 éléments de tableau, ligne de commande, 305
// (marque de index, 190 réception, 306-308
commentaire tableaux, via les passage aux
d'ouverture), 30 pointeurs, 264- fonctions, 47-48, 305
() (entre parenthèses) 266 variable, traitement,
instruction if, 156 opérateur d'affectation 252-254
placer autour des d'addition (+=), 93 argv arguments, 306
expressions, 419 variables d'adresse. opérateurs
% (opérateur de reste), Voir pointeurs d'affectation
43 opérateur d'adresse arithmétique, 92-95
%= (opérateur (&), 177-179, 323 affectation de
d'affectation du reste), adresses l'addition (+=), 93
93 valeur de gauche, 176 affectation de
>> (opérateur de mémoire, 343 division (/=), 93
décalage vers la algorithmes, mise en affectation de la
droite), 133-135 œuvre, 305 multiplication (*=),
; (points-virgules), 28 aligner la sortie, 83-84 93 affectation du
(guillemets simples), 59 allouer de la mémoire reste (%=), 93
-= (opérateur fonction calloc(), affectation de la
d'affectation de 286-288 soustraction (-=), 93
soustraction), 93 fonction malloc(), expressions
- (opérateur de 280-283 arithmétiques,
soustraction), 96 American National directive #if, 406
- (opérateur unaire Standards Institute opérateurs
moins), 95 (Institut national arithmétiques, 43-44
19L02.exe exécutable, américain de type de données de
318 normalisation). Voir tableau, 424 références
ANSI d'éléments de tableau,
crochets d'angle (<>), 191
32 ANSI (American opérateur d'indice de
A National Standards tableau ([ ]), 190
Institute), 15 tableaux, 190
accès Norme C, 15-16 accès via des
aléatoire fichiers d'en-tête, 439 pointeurs, 264-266
exemple de application de caractère, 190, 210-211
code, 375- spécificateurs statiques, affichage, 196-198
378 230-231 initialisation, 208-209
les fichiers disques, argc arguments, 306 déclaration, 190
374-377, listes d'arguments,
387 47 arguments
fonction fseek(), argc, 306
374-378 argv, 306
livres 505

éléments, 190 chaînes de caractères les entiers vers les structures, 315
accès, 190 vers les pointeurs, les valeurs intermédiaires pour les types de
initialisation, 191- 210-211 données enum, 296, 300 les valeurs
192 pour les types de données enum, 299
entiers, 190 aux pointeurs, 180-181 opérateur
multidimensionnels d'affectation (=), 92
déclaration, 199 astérisques (*)
affichage, 200-201 opérateur de déférence, 182 détermination du
initialisation, 199- sens, 182
201 multidimensionnel opérateur de multiplication, 182
non dimensionné pointeurs, 180
déclaration, 201 mot-clé auto, 56
initialisation, 202- spécificateur auto, variables, 229
203 éviter
liste, 325-326 duplication, structures, 322
passage à des instructions goto, 168
fonctions, 266-267,
270-272
pointeurs
déclarer, 272 B
référencer avec,
195-196 \b (caractère de retour en arrière), 60
chaînes de barre oblique inverse. Voir caractère
caractères, 272- d'échappement
274 tailles format big-endian, 343
calcul, 192-194 code binaire, 14, 24 fichiers binaires
spécifier, 267 lecture, 378-381
des structures, 324-327 l'écriture, 378-381
non dimensionnées format binaire, 13 nombres binaires
calcul, 201 par conversion des nombres décimaux en, 129-130
rapport aux négatifs, 142
structures, 314
caractère non
dimensionné, 209
opérateurs de flèche (->),
324, 335, 351
Codes de caractères
ASCII, 57
fonction asctime(),
250-251
fichier d'en-tête assert.h,
439 évaluation des mem-
bres de la structure, 315
attribuer
les constantes de
caractères vers les
pointeurs, 210 les
504 != (opérateur différent de)
op ch inaire (&), 131
é a complément binaire
r m (~), 131
a ps OU binaire (|), 131
t de XOR binaire (^), 131
e bit décalage vers la
u s gauche (<<), 133-135
r exemple de code, 348- décalage vers la droite
s 350 (>>),
b déclaration, 347 133-135
i définition, 347 bits, 14, 58, 142
n O opérateur ET binaire
a p (&), 131
i é opérateur de
r r complément binaire
e a (~), 131 opérateur de
s t OU binaire (|), 131
, e opérateur XOR bitwise
m u (^), 131
u r champ d'application
l s du bloc (variables),
t d 224-225
i e variables locales, 225
p m imbriqué, 225-226
li a comparaison de
c n l'étendue du
a i programme, 227-229
t p Fonction
i u BlockReadWrite(),
o l 369
n a blocs
( ti commentaires, 31
* o déclaration, 45-46
n corps, fonctions, 48-
)
d 49 livres (lecture
,
e recommandée), 434-
1
b 435
8
2 it
flu s
x ,
bi 1
na 3
ire 0
s, A
35 N
6, D
37
0 b
compilation 507
conditionnelle

Compilateur C++ de Directive #endif, 397-402, 406, 416


Borland, 21-24
C exemple de code, 398-399
courir, 23 syntaxe, 397
spécificateur de format
démarrage, 21
%c (caractère), 60-62,
programmation
74, 79
ascendante, 255
Extension du nom de
accolades ({}), 45, 48
fichier .c, 28 C
déclaration de type "if",
avantages, 12-14
156
l'histoire, 12
déclaration if-else, 159
portabilité, 13
instruction switch,
programmes
165 crochets ([ ]),
maintien, 296
déclaration des
lisibilité,
tableaux, 190
amélioration, 296,
mot clé break, 56
300
les déclarations de
programme structuré,
rupture, 155,
169
164-165
Compilateur C,
boucles infinies,
comparaison des
rupture, 166-167
préprocesseurs C, 392-
liste, 164-165
393, 405
emplacement, 164
Préprocesseur C, 392
instructions de
# (signe dièse), 392
commutation, sortie,
Comparaison des
164
compilateurs C, 392-
lignes de code, 28 E/S
393, 405
en mémoire tampon,
Directive #define, 393,
433 434
tampons, 356 exemple de
rinçage, 356
code, 394-
E/S de haut niveau, 357
396
E/S de bas niveau, 357,
définir des
387
macros de type
fonctions setbuf(), 387
fonction,
fonctions setvbuf(), 387
394-396
bogues. Voir débogage
expressions, 396
des arguments intégrés
définitions de
fonctions main(), 306
macros imbriquées,
nom, 308
396
remplacer, 308
syntaxe, 393
octets, 14, 58, 192-194 Directive #elif, 401-
402,
434
Directive
#else, 399-
402, 434
506 Compilateur C++ de
Borland c ion
dire
c o conditionnelle
ti d imbriquée, 402-
v e 404
e , syntaxe, 399
# directive
if 4 #ifdef, 397-
, 0 399, 434
4 1 Directive
3 - #ifndef, 397-
4 4 399, 416
e 0 macro-corps, 392, 396
x 2 noms de macros, 392-
p d 393 substitution de
r é macros, 392-396
e f caractères de nouvelle
s i ligne, 392 directive
s n #undef,
i i 393-394, 406, 434
o t Langage de
n i programmation C, 15
s o C++, 14
a n calcul de la taille du
ri s tableau, 192-194,
t 201
h d fonctions de l'heure, de
m e la date et de l'heure du
é s calendrier, 249 appel
ti et les fonctions, 49-51,
m 245-247
q
a pas d'arguments, 249
u
c récursif, 303-304
e
r fonction calloc(), 286-288
s
o liste, 287 fonctions
,
s malloc(),
4
, comparées, 292
0
mot-clé majuscule, 56,
6
4 162 sensibilité à la
e
0 casse, noms de fichiers,
x
0 32
e
m c
p o
l m
e p
i
d l
e a
t
compilation 507
conditionnelle

opérateur de casting, impression, 60- l'écriture


101-102 unité centrale 62 lecture à partir du flux de sortie standard, 217
de traitement (CPU), à partir d'un flux fonction puts(), 215-217
13 d'entrée standard, vers le flux de sortie standard, 215-216
modification des valeurs 215-217 fonction Fonction CharReadWrite(), 362-363
des variables via des gets(), vérification des arguments de la ligne de
pointeurs, 183-184 215-217 commande, 307
type de données char, 47, classes, stockage, 229 fermeture de fichiers,
57 fonction fclose(), 358-360, 371
mot-clé char, 56 marque de commentaire final (*/), 29
tableaux de caractères. code, 14. Voir aussi les listes
Voir aussi chaînes de binaire, 14
caractères lignes de rupture, 28
initialisation, 208-209 commentaires, 29-31, 418
constantes de chaîne, commentaires, 31
initialisation, 208-211 imbriqués, 31
non dimensionné, 209 performance, 30
codes de caractères indentation, 28, 419 économie
(ACII), 57 Borland C++, 23
constantes de Visual C++, 19 fichier de code source, 34
caractères, 58-59, espacement, 419
422-423 spaghetti, 169
Pointeurs, assignation, syntaxe, vérification, 36
210 Constantes de espacement, 29 écriture
chaîne, comparées, 209- Borland C++, 21
212 Visual C++, 18 codes, exécution en cas
type de données de d'état, 156
caractère. Voir aussi style de codage, 418-419 collecte de variables,
char type de données groupes, 314
spécification de format Combinaison de déclarations et de définitions,
de caractères (%c), 60- 315
62, 74, 79
chaînes de caractères,
196,
210-211
variables de caractères,
58 caractères
tableaux, 190, 196-198
conversion de valeurs
numériques en, 61
nul, 198. Voir aussi
chaînes de
caractères
valeurs
numériques
convertir, 61
la présentation, 63-64
506 Compilateur C++ de
Borland a nce du programme,
a
r i 30
g r langages compilés, 14-
u e 15
m s compilateurs, 13
e , accès, 17
n Borland C++, 21-24
t 2 C, comparaison du
s 9 préprocesseur C, 392-
- 393, 405
d 3 choix, 17 erreurs
e 1 de conformité,
, 280-281
l Microsoft, 18-21
a 4 optimiseurs,
1 désactivation, 235
l 8 erreur de
i conformité, 280-
g c 281
n o les instructions de
e m branchement
m conditionnel, 155,
d e 428-430
e n pause, 155, 164-165
t continuer, 155, 167-168
c e goto, 155, 168-169
o r si, 155-158
m if-else, 155, 158-159
m l étiquettes, 162
a e commutateur, 155,
n 161-164 compilation
d c conditionnelle, 397
e o Directive #elif, 401-402
, d Directive #else, 399-402
e
3 ,
0
5 3
liste, 306-307 1
numéro, vérification, imbriqués, 31
307 p
réception, 306-308 e
co r
m f
m o
e r
n m
t a
définiss 509
ant

Directive corps de la macro, 392 numérique, versus nommée, 419


#endif, 397- nommée, contre chaîne de caractères, 209-212, 423
402, 406 numérique, 419 contenu, mémoire, 177
exemple de nombres entiers, continuer le mot-clé, 56
code, 398- déclaration, 296 poursuivre la déclaration, 155,
399 167-168
syntaxe, 397 liste, 167
#if directive boucles, 167, 171
expres- sions flux de contrôle, 426 instructions de
arithmétiques, branchement conditionnel, 155,
406 428-430
exemple de pause, 164-165
code, 401- continuer, 167-168
402 goto, 168-169, 419
définitions des si, 156-158
macros, 400 if-else, 158-159
compilation interrupteur, 161-164 instructions
conditionnelle de bouclage, 155, 427-428
imbriquée, 402- Fonction Convert2Upper(), 303
404 la conversion
syntaxe, 399 types de données, cast opera- tor, 101-
directive 102
#ifdef, 397-399 dates, fonction asctime(), 250-251
Directive nombres
#ifndef, 397- décimal à binaire, 129-130
399 de la décimale à l'hexadécimale,
expressions 129-130
conditionnelles, temps, fonction asctime(), 250-251
instructions de en majuscules, 302-303
commutation, copier des chaînes de caractères, 213-215
161-162 fonction cos(), 149-150 registre CPU
opérateur conditionnel (: (unité centrale de traitement), 233
?), 135-136 CPU (unités centrales de traitement), 13
conditions, évaluation
(instruction if), 156
consolidation des types
de données, 300
mot-clé const, 56
modificateur const
(vari- ables), 234-235
constantes, 42
caractère, 58-59,
209-212, 422-423
EOF (fin de fichier),
73
virgule flottante, 423
entier, 422
508 compilation
conditionnelle c 381-384
créer
déclarations, 299 t pile, écrasement, 305
exp i éléments de données,
ress o séparation, 317
ions n modificateurs de données
, longue, 145-147
299 f court, 145
no p signé, 142-143
ms r non signé, 143-145
pou i structures de données,
r n listes chaînées, 410
les t avantages, 410
typ f création, 410-418
es ( types de données, 423.
de ) Voir aussi variables
don , tableau, 424
née char, 47, 57
s, 3 consolider, 300 convertir,
300 8 cast opera- tor, 101-102
- 1 créer, 426
302 - définition, listes de
3 variables, 424
fichier d'en-tête ctype.h,
439 8 double, 67
4
,

D 3
8
%d 8
(ent F
ier) o
spé n
cific c
ate t
ur i
de o
for n
mat
, f
63- s
64, c
79 a
don n
nées f
formaté (
f )
o ,
n
définiss 509
ant

enum, 296, 424 81, 129-130 décisions, illimitées (instructions switch), 161
déclaration, 296 déclaration. Voir également définition ;
définition des fonctions de prototypage
variables, 296 tableaux, 190
flotteur, 47, 64 [ ] (parenthèses), 190 des pointeurs, 272
int, 46, 62 des structures,
noms, 300-302 324-327
pointeurs, champs de bits, 347-350 création de
déplacement, déclarations, 299
260-262 Définitions combinées, 315
taille, changement, 145- comparés, 244, 432 types de données enum,
147 tailles, mesure, 296 fonctions, 244-249
122-123 prototypes, 245 spécification des types de
struct, 424-425 retour, 244
champs de bits, 347- fonction getchar(), 248
350 variables globales, 229
structures, 314 fonctions main(), 306 membres, structures, 314
syndicat, 425-426 tableaux multidimensionnels, 199 tableaux
void, déclarations de multidimensionnels non dimensionnés, 201
fonctions, 248-249 con- stants d'entiers nommés, 296
Fonction DataDisplay(), structures imbriquées, 327
326, 346 pointeurs, 180-181,
Fonction DataEnter(), 274-275
346 structures, 314-315
Fonction DataRead(), synonymes, 300
377, 381, 384 unions, 334 tableaux de caractères non
Fonction dimensionnés, 209
DataReceive(), 321 variables, 48, 177, 244 nombres à virgule
Fonction DataWrite(), flottante, 64-65
381, 384 nombres entiers, 62-63
fonctions de date et
d'heure, 249
dates,
conversion, 250-
251
heure d'été, 249
débogage, 37
insectes, 420
vérification de la
syntaxe, 36
messages d'erreur, 36
conversion des
nombres décimaux
en nombres
binaires, 129-130
conversion en
hexagone, 79-
508 compilation
conditionnelleur rateur de déférence
o
p s (*), 182
é pa #define directive, 296,
r r 393, 434
a dé exemple de code, 394-
t fa 396 définition de
e ut macros de type
u t fonction, 394-396
r y expressions, 396
p définitions de macros
d e imbriquées, 396
e s syntaxe, 393
définir. Voir aussi
d d déclaration
é e champs de bits,
c 347-350 types de
r d données, listes de
é o variables, 424
m n déclarations
e n combinant, 315
n é comparée, 244, 432
t e #define directive, 296,
a s 393, 434
t exemple de
i e code, 394-
o n 396
n u définir des
m macros de type
( , fonction,
- 394-396
2 expressions, 396
-
9 macros de type
)
8 fonction, 394-396
,
définitions de
9
e macros imbriquées,
6
n 396
-
t syntaxe, 393
9
i fonctions, 244-247
8
e macros, directives #if,
m
r 400
ot-
clé s variables de structure, 315
de ,
fa
ult 2
, 9
56 6
va op
le é
dos 511
sier

synonymes, 321 désactivation de la mise affichage


listes de variables, types en mémoire tampon, 387 tableaux de caractères, 196-198
de données enum, 296 tableaux multidimensionnels, 200-201
variables, 244 division, troncature, 100 opérateur
types de données d'affectation de division (/=), 93
enum, 296 unions, faire un mot-clé, 56
334-335 boucles do-while, 107-109 opérateur
pointeurs point (.), 315-317 unions, 335-337, 351
déréférencés, 323 type de données double, 67 mot-clé
directives (préprocesseur) double, 56 guillemets doubles ("), 32-33,
#define, 296, 393 59
exemple de duplication, éviter (structures), 322
code, 394-396 durées (variables), 229
définir des
macros de type
fonction,
394-396 E
expressions, 396
définitions de %e (notation scientifique) spécificateur
macros imbriquées, de format, 67, 79 éléments (tableaux),
396 190-192
syntaxe, 393 Directive #elif, 401-402,
#elif, 401-402 434
#else, 399-402 Directive #else, 434
#endif, 397-402, 406 exemple de code, 401-402
exemple de syntaxe, 399
code, 398- mot-clé else, 56
399 fin de fichier (EOF), 73
syntaxe, 397 Directive #endif, 397-402,
#if 406, 416
expres- sions exemple de code, 398-399
arithmétiques, syntaxe, 397 sorties de fin,
406 caractères nuls, 198-199
exemple de
code, 401-
402
définitions des
macros, 400
compilation
conditionnelle
imbriquée, 402-
404
syntaxe, 399
#ifdef, 397-399
#ifndef, 397-399
#include, 31
#undef, 393-394, 406
510 définiss
ant s xécution
type de données enum,
296, 424 d'erreu codes, instruction if,
déclarations, 296 r, 36 156 programmes, 305
déclaration, 296 erreurs Les déclarations, le
définition, liste, 297 confor commutateur, 164
définit mité L'épuisement des
ion (com ressources de la pile, 305
des pilate fonction exit(), 34
variab urs), quitter les instructions de
les, 280- commutation, 164
296 281 exposants, 67
valeur point
s eurs
entièr ,
es, non
assign initi
ation, alis
296, és,
300 262
liste, 298-299 caractè
valeurs, 298-299 re
listes de variables, 296 d'échap
enum pement
mot-clé, (\), 59
56 type évaluat
de ion des
données conditi
énumér ons,
ées. instruct
Voir ion if,
type de 156
données Extensi
enum on du
EOF nom
(end-of- de
file) fichie
con- r .exe,
stant, 29
73 fichiers exécutables, 24,
opérateu 34-
r égal à 3
(==), 5
98 ,
fichier
d'en-tête 3
errno.h, 1
439 8
message E
dos 511
sier

expressions, 42-43, 426. 371 pointeurs de fichiers, 357, 371 indicateurs


Voir aussi opérateurs lecture des données de position de fichiers.
arithmétique, 406 saisies au clavier, 366 Voir structure FILE
conditionnel, interrupteur syntaxe, 363 champ d'application des fichiers (variables),
déclaration, 161-162 232 flux de fichiers, 433
création, 299 Structure FILE, 357
directive #define, 396 indicateurs de position des fichiers, 357
dans les boucles for, fonction fseek(), 374
113-115 corps de la fonction ftell(), 375 fonction rewind(),
macro, 396 placer des 378
parenthèses autour, extensions de noms de fichiers, 28-29
419 fichiers, 433
valeurs de retour binaire
dans, 47 lecture, 378-381
mot-clé extern, 56, 234 l'écriture, 378-381
défini, 356
exécutable, 24, 34-35 fonction fclose()
fermeture de dossiers, 358-360,
F 371
exemple de code, 359-360
%f (virgule flottante) syntaxe, 359
spécificateur for- mat, fonction feof(), 367-369 fonction fgetc()
65-67, 79 exemple de code, 361-363
\f (caractère de saut de lecture de fichiers, 360-363
page), 60 syntaxe, 361 fonction fgets()
Fonction fclose() exemple de code, 364-366
exemple de code, 359- gets(), comparaison des fonctions, 364-
360 366,
dossiers, fermeture, 358- 371
360, lecture de fichiers, 363-366
371
syntaxe, 359
fonction feof(), 367
exemple de code, 368-
369
syntaxe, 367
Fonction fgetc()
exemple de code, 361-
363
fichiers, lecture, 360-363
syntaxe, 361
Fonction fgets()
exemple de code, 364-
366
fichiers, lecture, 363-
366 comparaison de la
fonction gets(), 364-366,
510 définiss
ant fonction fopen(), 357, ion fputc(), 360-
l
e 381 363
c e fonction fputs(),
t x 364-366
u e fonction fread(), 381
r m exemple de code,
e p 368-369
l lecture de
d e fichiers,
e 366-369
s d syntaxe, 366
e fonction fseek(),
d 374-378
o c fonction ftell(), 374-378
n o fonction fwrite(), 381
n d exemple de code,
é e 368-369
e , syntaxe, 367
s écriture de
3 fichiers,
s 5 366-369
a 9 en-tête, 32
i - ANSI, 439
s 3 stddef.h, 301
i 6 stdio.h, 32
e 0 stdlib.h, 302
s formatage, 388 string.h, 302
modes, 357-358, 388 nommage, sensibilité à
a ouverture de fichiers, la casse, 32
u 358
s
c y
l n
a t
v a
i x
e e
r ,
,
3
3 5
6 7
6
syntaxe, 363 f
pointeurs de fichiers, 357, o
371 n
Structure du fichier, 357, c
374-375, 378 t
fonctions 513

objet, 34 accès spécificateurs de format Exemple de code


aléatoire, 374- %%, 79 de la fonction
378, 387 %c (caractère), 60-62, fscanf(), 382-
fonction rewind(), 378 79 384
exemple de code, %d (entier), 63-64, 79 comparaison de la
379-384 %e (notation fonction scanf(),
syntaxe, 378 scientifique), 67, 79 381
accès séquentiel, 374 %f (virgule syntaxe, 381
code source, 34 flottante), 65-67, formatage des fichiers,
comparaison des flux, 79 fopen(), 388
356 fonction fprintf() formulaires, déclaration
type de données float, 47, ajouter h à, 147-148 de commutation, 161
64 ajouter l à, 147-148 Fonction fprintf()
Le mot clé float, 56 le %G (utilise %f ou exemple de code, 382-
fichier d'en-tête float.h, %E), 79 384 spécificateurs de
439 le type de données %i (entier), 79 format
flottantes. Voir type de largeur minimale du ajouter h à, 147-148
données float champ, 81-83 ajouter l à, 147-148
constantes en virgule %n, 79 comparaison de la
flottante, 423 %o (octal non signé), fonction printf(), 381,
spécificateur de format 79 388
en virgule flottante (%f), %p, 79 syntaxe, 382
65-67, 79 nombres en spécificateurs de Fonction fputc()
virgule flottante, 64 précision, 84-85 exemple de code, 361-363
Calculs, 152 fonction printf(), 78-79 fichiers, rédaction, 360-363
déclaration, 64-65 ajouter h à, 147-148 syntaxe, 361
tampons de rinçage, ajouter l à, 147-148 Fonction fputs()
356 %p, 179 exemple de code, 364-366
fonction fopen(), 381 %s, 217 fichiers, écriture, 363-
exemple de code, 359- %s (chaîne), 366 comparaison de la
360 79 fonction puts(), 364
fichiers, ouverture, fonction scanf(), 217 syntaxe, 364
357-358 formatage de %u (entier non signé), Fonction
nouveaux fichiers, 388 79 fread()
modes, 357-358, 388 %X (hexadécimal non fichiers binaires, 381
syntaxe, 357 signé), 79 exemple de code, 368-369
pour le mot-clé, 56 données formatées fichiers, lecture, 366-369
boucles for, 109-112 Exemple de code syntaxe, 366
expressions complexes de la fonction fRecur() fonction
dans, 113-115 fprintf(), 382- récursive, 304
infini, 166 384 fonction free(), 283-286
déclarations comparaison de la fonction freopen()
nulles, 112-113 fonction printf(), exemple de code, 385-386
caractère de saut de page 381, modes, 385-386
(\f), 60 388 flux, redirection,
syntaxe, 382 384-388
syntaxe, 384
512 dos
sier

Fonction fscanf() données définition, 245-247, 432


exemple de code, 382- void, 248- fin, 48
384 comparaison de la 249 exit(), 34 fclose()
fonction scanf(), 381 fermeture de dossiers, 358-360, 371
syntaxe, 381 exemple de code, 359-360
Fonction syntaxe, 359
fseek() feof(), 367
exemple de code, 375- exemple de code, 368-369
378 syntaxe, 367 fgetc()
accès aléatoire, 374-378 exemple de code, 361-363
syntaxe, 374 lecture de fichiers, 360-363
Fonction ftell() syntaxe, 361 fgets()
exemple de code, 375- exemple de code, 364-366
378 gets(), comparaison des fonctions, 364-366,
accès aléatoire, 374-378 371
syntaxe, 375 lecture de fichiers, 363-366
portée de la fonction lecture des données saisies au clavier,
(vari- ables), 226- 366
227 syntaxe, 363
fonctions, 46, 432-433. fopen(), 381 exemple de code, 359-360
Voir également les formatage de nouveaux fichiers, 388
déclarations, les modes, 357-358, 388
tableaux, le passage, ouverture de fichiers, 357-358
266-267, 270-272 syntaxe, 357
asctime(), 250-251
début, 48
BlockReadWrite(), 369
corps, 48-49
appel, 49-51, 245-249
calloc(), 286-288
CharReadWrite(),
362-363
complexité, 49
Convert2Upper(), 303
cos(), 149-150
DataDisplay(), 326, 346
DataEnter(), 346
DataRead(), 377, 381,
384
DataReceive(), 321
DataWrite(), 381, 384
date et heure, 249
déclarer, 244-247, 432
prototypes, 245
spécification des
types de retour, 244
type de
fonctions 513
fprintf() p ure de
e u fichiers,
x t 360-363
e c fputs()
m ( exemple de
p ) code, 364-
l e 366
e x comparaison de la fonction
e puts(), 364
d m syntaxe, 364
e p écriture de
l fichiers,
c e 363-366
o fread()
d d fichiers
e e binaires, 381
, exemple de
c code, 368-369
3 o lecture de
8 d fichiers,
2 e 366-369
- , syntaxe, 366
3 free(), 283-286
8 3 freopen()
4 6 exemple de
compar 1 code, 385-
aison - 386
de la 3 modes, 385-386
foncti 6 redirection des
on 3 flux, 384-388
printf( s syntaxe, 384
), y fscanf()
381, n exemple de
388 t code, 382-
s a 384
y x comparaison de la fonction
n e scanf(), 381
t , syntaxe, 381
a
x 3
e 6
, 1

3 é
8 c
2 r
i
f t
514 fonctions

fseek() printf(), 78-79, 217, 386 strcpy(), 213-215, 336


exemple de spécificateurs de
code, 375- format, 78-79
378 comparaison de la
accès aléatoire, fonction fprintf(),
374-378 381,
syntaxe, 374 388
ftell() le prototypage, 245-
exemple de 247,
code, 375- 432
378 nombre fixe
accès aléatoire, d'arguments,
374-378 251 pas
syntaxe, 375 d'arguments,
fwrite() 248-249
fichiers nombre variable
binaires, 381 d'arguments, 251-
exemple de 252
code, 368-369 PtrSeek(), 377
syntaxe, 367 PtrTell(), 377
écriture de putc(), 75-76
fichiers, putchar(), 77-78
366-369 puts()
getc(), 72-74 comparaison de la
getchar(), 74-75, fonction fputs(),
248-249 364
gets() syntaxe, 215
comparaison de la realloc(), 288-291
fonction fgets(), récursif, 303-305
364-366, appel, 303-304
371 fRecur(), 304
syntaxe, 215 course à pied, 305
InfoDisplay(), 329 rewind(), 378
InfoEnter(), 329 exemple de
LineReadWrite(), 365 code, 379-384
localtime(), 250 syntaxe, 378
longjmp(), 440 scanf(), 217-219
E/S de bas niveau, 387 comparaison de
main(), 29, 33, 305-306 la fonction
malloc(), 280-283, 292 fscanf(), 381
nommer, 47, 418 syntaxe, 217
passer des arguments setbuf(), 387
à, 47-48 setjmp(), 440
des pointeurs vers setlocale(), 440
déclaration, 274-275 setvbuf(), 387
passage, 268-270 sin(), 149-150
pow(), 150-152 sqrt(), 150-152
entrée, 515
spécific utilisateur
strlen(), 212-213
St ateur de
rP format
rin %g, 79
t, fonction
38 getc(),
6 72-74
str fonction getchar(), 74-75,
uc 248-249
tur gets(), 215-217,
es, 364-366, 371
pa variables globales, 227,
ss 233
ag déclaration, 229
e, contre local, 418
31 déclaration goto, 56, 155,
9- 168-169, 419
32 éviter, 168
1 étiquettes, emplacement,
tan(), 149-150 169
time(), 250 co
types, 46-47 de
décl spagh
ara etti,
tio 169
ns opéra
de teur
var plus
iab grand
les, que
48 (>),
va_en 98
d(), les
252 variables
Foncti de la
on structure,
fwrite( 314 les
) variables
fichiers binaires, 381 de la
exemple de code, 368- structure,
369 314 les
fichiers, écriture, 366-369 variables
syntaxe, 367 de la
structure,
314
groupes, 314
G
514 fonctions

écrire la sortie, 75 C, 296, 300


H fonction printf(),
h 78-79
ajout aux spécificateurs fonction putc(),
de format fprintf, 147- 75-76
148 ajout aux fonction putchar(),
spécificateurs de format 77-78
printf, 147-148 IDE (environnement de
exigences en matière de développement intégré)
matériel, 16 Borland C++, 21
fichiers d'en-tête, 32 Visual C++, 18
ANSI, 439 identifiants, 44
directive #if, 434
stddef.h, 301
expressions
stdio.h, 32
arithmétiques, 406
fonction gets(), 215
exemple de code, 401-
fonction puts(), 215
402
stdlib.h, 302
définitions de macros,
string.h, 302
400 compilation
nombres hexagonaux,
conditionnelle
conversion des
imbriquée, 402-404
nombres décimaux en
syntaxe, 399
nombres hexagonaux,
déclaration "si", 56, 155-
79-81, 129-130
158
E/S de haut niveau, 357,
appareils dentaires, 156
433 langages de
codes, exécution, 156
programmation de haut
conditions, évaluation,
niveau, 12-14
156
histoire de C, 12
liste, 157
nidification, 160-161
déclaration if-else, 155,
158-159
I Directive #ifdef, 397, 434
exemple de code, 398-399
Format %i (entier) spec-
syntaxe, 397
ifier, 79
directive
E/S, 433-434
#ifndef, 397-399,
mis en mémoire tampon,
416
433
exemple de code, 398-
haut niveau, 433
399
ruisseaux, 72
syntaxe, 398
saisie de
caractères illégaux
l'utilisateur, 72
(identi- fiers), 44
fonction getc(),
la mise en œuvre
72-74
d'algo- rithmes, 305
fonction getchar(),
Amélioration de la
74-75
lisibilité, programmes
entrée, 515
a utilisateur
e boucles infinies, 166-
dir
ect ti 167 fonction
ive o InfoDisplay(), 329
#in n Fonction InfoEnter(), 329
clu ( dissimulation de
de, d l'information
31 a (programmation
au n modulaire), 419
gm s initialisateurs,
ent le 317
ati c initialisation
on o éléments de
de d tableau, 191
la e) tableaux de
po , caractères, 208-211
rta 2 éléments, 192
bil 8, tableaux
ité 4 multidimensionn
de 1 els, 199-201
s 9 tableaux
pr in multidimensionn
og de els non
ra x dimensionnés,
m (ta 202-203
me ble cordes, 208-211
s, au structures, 317-319
23 x), syndicats, 337-339,
4 19 351 exemple de
op 0 code de partage
ér op de la mémoire,
ate ér 338-339
ur ate structures, 338
d'i ur tableaux de
nc d'i caractères non
ré nd dimensionnés,
me ire 209
nt cti données d'entrée,
ati on. lecture, 217-219
on Vo entrée/sortie. Voir
ir E/S entrée,
(+
op utilisateur, 72
+),
ér fonction getc(), 72-74
96-
ate fonction getchar(),
98
ur 74-75
in
de
d
déf
e
ére
n
nc
t
516 type de
données int

type de données int, 46, const, 56 étiquettes


62 continuer, 56 instructions de
mot-clé int, 56 par défaut, 56 branchement
constantes entières, 422 faire, 56 conditionnel, 162
spécificateurs de format double, 56 emplacement, état goto,
entier autre, 56 169
%d, 63-64, 79 enum, 56 langues. Voir langages de
%i, 79 externe, 56 programmation
valeurs entières flotteur, 56 valeurs de gauche,
par défaut, 296 pour, 56 176
types de données goto, 56 défini, 187
enum, si, 56 l'obtention, 178
assignation, 296, 300 int, 56 opérandes de gauche,
entiers liste des, 420-421 92 sortie justifiée par
ajouter, 304 long, 56 la gauche, 83-84
tableaux, 190 registre, 56, 233 opérateur de décalage
déclaration, 62-63 réservé, 45 à gauche (<<), 133-
négatifs, 95 retour, 56 135
pointeurs, 259 court, 56 caractères légaux (identi-
structures, affectation, signé, 56 fiers), 44
315 taille, 56 Longueur des cordes,
environnement de statique, 56 mesure, 212-213
développement intégré. struct, 56, , 348-350 opérateur moins que (<),
Voir IDE langages de interrupteur, 56 98 opérateur moins que
programmation typedef, 57, 300-301, ou égal à (<=), 98
interprétés, 15 321, 426 niveaux (langages de
interprètes. Voir syndicat, 57 programmation), 12
compilateurs non signé, 57 bibliothèques, 14
itération. Voir boucles vide, 57, 305 fichier d'en-tête limits.h,
volatile, 57 439 numéros de ligne, 28
tandis que, 57 fonction
LineReadWrite(), 365
J-K lignes (code)
rupture, 28
les instructions de saut. L l'indentation, 28
Voir instructions de listes chaînées, 410
branchement l avantages, 410
conditionnel ajout aux spécificateurs création, 410
justifier. Voir alignement de format fprintf, 147- ajout de nœuds, 414
148 ajout aux appel de fonctions
"K & R", 15 spécificateurs de dans un fichier de
claviers, saisie, 366 format printf, 147-148 module,
mots-clés, 56 416-418
auto, 56 fichier d'en-tête,
pause, 56 415-416 exemple de
cas, 56, 162 programme de
char, 56 module, 410-415
listes 517

linkers, 34-36 déclaration et attribution fonction gets(), 216


listes de valeurs de pointeurs, Les spécificateurs de
les tableaux par le biais 180 directive #define, for- mat %hd, %lu et
de pointeurs, 264-265 394-395 %ld,
l'ajout d'entiers avec définition des types de 147-148
une fonction, 49 données enum, 297 nombres hexagonaux,
aligner la sortie, 83 exemple de boucle "do- conversion en, 80
opérateurs d'affectation while", 108 Directive #if, 401
arithmétique, 94 Directive #elif, 401 Déclaration "si" dans la
tableaux de Directive #else, 401 prise de décision, 157
structures, 325- directive #endif, 398 déclaration if-else, 159
326 arrêt de la sortie au Directive #ifdef, 398
fichiers binaires, caractère nul, 198 Directive #ifndef, 398
lecture/écriture, types de Opérateurs
379-380 données enum, d'incrémentation et de
champs de bits, 348-349 298-299 décrémentation, 97
opérateurs bitwise, 132 fonction fclose, Boucles infinies,
instruction break, 359-360 rupture, 166
164-165 fonction feof(), 368-369 initialisation des
calcul d'une addition et fonction fgetc(), tableaux, 191
impression des résultats 361-362 initialisation des
à l'écran, 50 calcul de la fonction fgets(), chaînes de caractères,
taille du tableau, 193 364-365 210-211
appeler des fonctions spécificateur de initialisation des
après qu'elles aient été format en virgule structures, 318
déclarées et définies, flottante, 65-66 initialisation de
245 fonction fopen(), tableaux non
appel de fonctions 359-360 dimensionnés,
récursives, 303-304 boucles for, 110, 114- 202
fonction calloc(), 287 115 fonction fprintf(), Spécification du format
opérateur cast, 101 382-383 des nombres entiers,
changement des fonction fputc(), 63
valeurs des variables 361-362 listes chaînées
via les pointeurs, 183 fonction fputs(), appeler des fonctions
arguments de la ligne 364-365 dans le fichier du
de commande, 306- fonction fread(), module,
307 opérateur 368-369 416-418
conditionnel, 135 La fonction freopen() fichier d'en-tête,
continuer l'instruction, permet de redi- recter 415 programme
167 convertir les les flux, de module, 410-
valeurs numériques en 385-386 413
caractères, 61 Fonction opérateur logique ET,
copier des chaînes de fscanf(), 382- 125
caractères, 213 383 opération de négation
fonction fwrite(), logique ( !), 128
368-369 opérateur logique
OR, 126
516 type de
f données int a
o l
n l
c o
t c
i (
o )
n ,
s
2
m 8
a 1
l -
l 2
o 8
c 2
(
)

e
t

f
r
e
e
(
)
,

2
8
3
-
2
8
5

f
o
n
c
t
i
o
n

m
emplacements de 519
mémoire

mesure de la impression de tableaux traitement des arguments variables,


longueur des chaînes bidimensionnels, 200 253-254
de caractères, 212 variables fonction put(), 216 accès aléatoire aux
spécification de la d'impression, fichiers, 375-376
largeur minimale des différents niveaux fonction realloc(), 289-290
champs, 82 de portée, 225 les tableaux référencés avec des
pointeurs mobiles, pointeurs, 195 les membres de structure
différents types de référencés, 316
données, les opérateurs relationnels, 99 la relation
260-261 entre la portée du programme et celle du
Pointeurs multiples, bloc, 227
185 Com- pilation Fonction scanf() avec diverses
conditionnelle spécifications de format, 218
imbriquée, 403-404 opérateurs de décalage, 134
Instructions if modificateurs de données courtes et
imbriquées, 160 longues, 146 modificateurs de données
boucles signées et non signées, 144
imbriquées, 116 programme C simple, 28 taille de
structures l'opérateur,
imbriquées, 327- 122-123
329 spécificateur statique, 230 soustraction
obtenir des valeurs de de pointeurs, 263
gauche, 178 instruction switch, 162 fonctions
les tableaux sont trigonométriques, 149
transmis aux définitions typiques, 301-302
fonctions, 266-267 syndicats
Les fonctions de mesure de la taille, 339-340
passage avec des partage de la mémoire, 338-339
pointeurs, 322-323 Le nidification dans les structures, 344-
passage de tableaux 345
multidimen- sionnels à
des fonctions, 270-271
passer des pointeurs
aux fonctions, 268-
269 passer des
structures aux
fonctions, 320
pointer aux
fonctions, 274-275
pow90 et sqrt(), 151
spécificateurs de
précision, 84-85
impression d'un tableau
de caractères, 196
impression de
caractères, 60
518 listes
réfé c ur sur des chaînes
ren ( de caractères, 272-
ce ) 273
me , void dans les déclarations
nt, de fonction, 248-249
335 7 Exemple de boucle
- 3 while, 106
336 écriture de la sortie,
réfé f fonction putc(), 76
ren o listes
ce n lié, 410
me c avantages, 410
nt t création, 410-418
des i variables, types de
em o données enum, 296
pla n format little-endian, 343
ce portée locale. Voir champ
me g d'application du bloc
nts e heure locale, 249
de t variables locales, 225, 418
mé c fichier d'en-tête locale.h, 440
moi h fonction localtime(), 250
re, a emplacements (mémoire),
341 r pointeurs multiples, 185-186
- ( opérateurs logiques, 124
342 ) ET (&&), 124-126
saisie de l'utilisateur , NÉGATION ( !),
e 128-129
x 7 OU (||), 126-127
e 4 modificateur de
m - données longues,
p 7 145-147
l 5 mot-clé long, 56
e util fonctions longjmp(), 440
d is
e ati
l on
a d'
f un
o ta
n bl
c ea
ti u,
o d'
n un
g po
e in
t te
emplacements de 519
mémoire

boucles, 105, 427-428 #undef, 393-394, fonctions main(), 29, 33,


continuer la déclaration, 406 305
171 va_arg(), 252 arguments, 305-306
flux de contrôle, 155 va_start(), 252 déclaration, 306 maintien des programmes C,
faire pendant, 107-109 296
pour, 109-112 fonction malloc(), 280-283
expressions fonction calloc(), comparée, 292
complexes, 113- listes, 281-285 manipulation indirecte de
115 variables, 176
déclarations mantisses, 67
nulles, 112- fichier d'en-tête math.h, 440 fonctions
113 mathématiques, 148
infini, 166-167 cos(), 149-150
imbrication, 116- pow(), 150-152
117 saut, sin(), 149-150
instruction sqrt(), 150-152
continue, 167 tan(), 149-150
pendant, 106- mesure
107 taille
E/S de bas niveau, données, 122-123
357, 387 structures, 339-341
syndicats, 339-341
longueur des cordes, 212-213
membres, structures, 314
M évaluer, 315
déclaration, 314
macro-corps, 392, 396 référencement, 315-319
noms de macros, 392-393 mémoire
macro substitution, 392 adresses, 343 allouées, libération, 283-286
exemple de code, 394- allouant
396 fonction calloc(), 286-288
Directive #define, 393 fonction malloc(), 280-283
macros
Directive #define, 393
exemple de code,
394-396
définir des
macros de type
fonction,
394-396
expressions, 396
définitions de
macros imbriquées,
396
syntaxe, 393
Directive #if,
400 Directive
518 listes
tampons, 356 85-186 la
rinçage, 356 3 réallocation, la
E/S de haut niveau, 8 fonction realloc(),
357 7 288-291
E/S de bas niveau, l pile, 305
357, e syndicats, 334, 425-
387 s 426 opérateur de
f flèche (->), 335,
o e 351
n m déclaration, 334
c p définition des
t l variables, 334-335
i a opérateur point
o c (.), 335-337,
n e 351
m initialisation, 337-339,
s e 351
e n exemple de
t t code de
b s partage de
u , mémoire, 338-
f 339
( l emboîtement,
) e 343-346
, s référencement,
335-337, 351
3 p référencement des
8 o emplacements de
7 i mémoire, 341-343,
f n 352
o t taille, 339-341
n e comparaison des
c u structures, 351
t r noms de balises, 334
i s variables, 176-177
o emplacements de
n m mémoire, temporaires,
u 229
s l
e t
t i
v p
b l
u e
f s
( ,
)
, 1
performance, 521
commentaires

Compilateur Microsoft, 345


18-21
N dans les structures, 343-346
courir, 19 caractère de nouvelle ligne (\n), 33
Spécification du format nibbles, 14
démarrage, 18 %n, 79
spécification de la nœuds (listes chaînées), ajout, 414
\n (caractère de nouvelle
largeur minimale du ligne), 33 constantes
champ, 81-83 modes nommées, par rapport
fonction fopen(), aux constantes
357-358, 388 numériques, 419
fonction freopen(), constantes entières
385-386 nommées, déclaration,
modificateurs 296
(variables), 234-235. noms
Voir aussi
types de données, 300-
modificateurs de
302
données
noms de macros, 392-
programmation
393
modulaire, 419-420
étiquette, syndicats, 334
modules, 419
dénomination
opérateur de module
arguments intégrés, 308
(%), 43
fonctions, 47, 418
en mouvement
identifiants, 44
indicateurs de position
variables, 68, 418
de fichier fonction
les nombres négatifs, 95,
fseek(), 374 fonction
142
rewind(), 378
champ d'application du
pointeurs, 260-262
bloc imbriqué (vari-
tableaux
ables), 225-226
multidimensionnels
Commentaires
déclaration, 199
imbriqués, 31
affichage, 200-201
Compilation
initialisation, 199-201
conditionnelle imbriquée,
tableaux
402-404
multidimensionnels non
définitions de macros
dimensionnés, 201-203
imbriquées (directives
pointeurs
#define), 396 structures
multiples, 185-
imbriquées
186
déclaration, 327
opérateur de
listes, 327-329
multiplication (*=), 93
nidification
opérateur de
déclarations de type
multiplication (*), 182
"if", 160-161
boucles, 116-117
syndicats
exemple de
code, 344-
520 Compilateur
Microsoft e res)
pas égal
à x convertir, 61
l'opérat a la présentation, 63-64
eur g
(!=), 98 o
caractères nuls, 198-199 n
pointeurs nuls, 183 e O
déclarati ,
ons %o (octal non signé)
nulles, 7 spécificateur de for-
112-113 9 mat, 79
nombres - fichiers d'objets,
binai 8 34 objets
re, 1 champs de bits
négat , exemple de
ives, code, 348-
142 1 350
déci 2 déclaration, 347
male 9 définition, 347
s - pointer vers, 431-432
con 1 obtenir des valeurs de
ve 3 gauche, lister, 178
rsi 0 ouverture de fichiers
on virgul (fonction fopen()), 357-
en e 360, 381
bi flott
na ante,
ire calc
, uls,
12 152
9- négatifs, 95
13 notat
0 ion
c scientifi
o que, 67
n constan
v tes
e numéri
r ques,
s ver-
i sus
o nommé
n es, 419
valeurs
e numéri
n ques
(de
h caractè
performance, 521
commentaires

marques de conditionnel (: l'écriture, 75


commentaires ?), 135-136 fonction printf(),
d'ouverture, 29-30 décrément (-), 96-98 78-79
opérandes, 92 déférence (*), 182 fonction putc(),
opérateurs point (.), 315-317 75-76
adresse de (&), syndicats, 335-337, fonction putchar(),
177-179, 323 351 77-78
arithmétique, 43-44 augmentation (++), 96- sorties, caractères nuls,
arithmétique, 92-95 98 198-199
affectation de logique, 124 Ecraser les données de la
l'addition (+=), 93 ET (&&), 124-126 pile, 305
affectation de NÉGATION ( !),
division (/=), 93 128-129
affectation de la OU (||), 126-127
multiplication la préséance, 98-99 P
(*=), 93 relationnel, 98-100 égal
affectation du reste à (==), 98 supérieur Spécification du format
(%=), 93 affectation à (>), 98 supérieur %p, 79, 179
de la soustraction (- ou égal à (>=), 98 paramètres, arguments
=), 93 inférieur à (<), 98 de ligne de commande,
indice de tableau ([ ]), inférieur ou égal à 305 parenthèses
190 (<=), 98 instruction if, 156
flèche (->), 324, 335, pas égal à (!=), 98 placer autour des
351 taille de, 122-123 expressions, 419
affectation (=), 92 tableau de, 421-422 passer
binaire, multiplication unaire, opéra- teurs de arguments
(*), 182 déférence (*), 182 aux fonctions, 47-48
manipulation des optimiseurs, aux fonctions
bits, 130 bitwise désactivation, 235 principales (), 305
AND (&), 131 sortie tableaux, vers les
complément binaire alignement, 83-84 fonctions, 266-
(tilde), 131 spécificateurs de format, 267
bitwise OR (|), 131 79 fonctions, avec
bitwise XOR (^), %c, 60-62 pointeurs, 322-323
131 %d, 63-64 tableaux
décalage à %E, 67 multidimensionnels,
gauche (<<), %f, 65-67 aux fonctions, 270-
133-135 spécification de la 272
décalage vers largeur minimale du pointeurs, 268-270,
la droite (>), champ, 81-83 322 structures, vers les
133-135 spécification de la fonctions, 319-321
moulage, 101-102 précision, 84-85 performance,
mises en garde, 419 commentaires, 30
522 Perl

Perl, 14 opérateur de ascendante, 255


pointeurs, 176, 430 prédécrémentation (-- C, 169
tableaux ), 96 de haut en bas, 255
accès, 264-266 opérateur de pré-
déclarer, 272 incrémentation (++),
référencer avec, 96
195-196 priorité (des opéra-
chaînes de teurs), 43, 98-99
caractères, 272-274 spécificateurs de
constantes de précision, 84-85
caractères, affectation, préprocesseur. Voir
210 chaînes de préprocesseur C
caractères, affectation, les directives du
210-211 préprocesseur.
déclaration, 179-181 Voir directives
déréférencé, 323 fonction printf(), 78-79,
pointeurs de fichiers, 386
357, 371 vers des spécificateurs de
fonctions, déclaration, format, 78-79 ajouter
274-275 h à, 147-148 ajouter
nombres entiers, 259 L à, 147-148
déplacement, 260-262 %c, 60-62
multiples, 185-186 %d, 63-64
null, 183 %e, 67
passer, 268-270, 322 %f, 65-67
pointer des objets, %p, 179
431-432 %s, 217
taille, 260 comparaison de la
structures, fonction fprintf(), 381,
référencement, 322- 388
324 caractères
soustraction, 263-264 d'imprimerie, 60-
types, 180 non 62
initialisés, erreurs, traitement des arguments
262 variables, 252-254
valeurs, portabilité du
attribution, 180- programme,
181 augmentation, 234
valeurs variables, portée du programme
changement, 183-184 (vari- ables), 227, 233
portabilité, 13, 36, 234 Comparaison de
opérateur de post- l'étendue des blocs,
décrémentation (--), 96 227-229
opérateur de post- variables globales, 227
incrémentation (++), programmation
96 modulaire, 419-
fonction pow(), 150-152 420 structurée
fonction rewind() 523
les langages de cti 396
programmation ve caractères de
C++, 14 s retour à la ligne,
compilés, 14 macro-corps, 392, 392
haut niveau, 12-14 396 exécution, 305
interprétées, 15 n noms de fichiers,
niveaux, 12 o extension .c, 28
Perl, 14 m performances,
style de programmation, s com- ments, 30
418- en cours d'exécution
419 d Borland C++, 23
programmes e Visual C++, 20
Préprocesseur C, 392 fonctions de
# m prototypage, 245-247,
(si a 432
gne c nombre fixe
diè r d'arguments, 251
se), o pas d'arguments, 248-
39 s 249 nombre variable
2 , d'arguments, 251-252
Co Fonction PrtSeek(), 377
mp 3 Fonction PtrTell(), 377
ara 9 fonction putc(), 75-76
iso 2
n -
du 3
co 9
mp 3
ilat m
eur a
C, c
39 r
2- o
39 s
3, u
40 b
5 s
dir ti
e t
ct u
iv ti
es o
. n
V ,
oi 3
r 9
di 2
re -
522 Perl

fonction putchar(), 77-78 384 entrée


fonction puts(), 215-217 fonction getc(), 72-74
fonction fputs() fonction getchar(), 74-75
comparaison, 364 fonction scanf(), 217-219
exigences, fichier d'en- saisie au clavier, fonction fgets(), 366
tête stdio.h, 215 cordes, 217
syntaxe, 215 les nombres réels. Voir la fonction realloc()
pour les nombres en virgule flottante,
288-291
réaffectation de la mémoire, 288-291
Q-R lectures recommandées, 434-435
fonctions récursives, 303-305
qualificatifs. Voir appel, 303-304
modificateurs fRecur(), 304
(variables) Fonction fre- open(), 384-388 Référencement
tableaux, avec pointeurs, 195-196
\r (caractère de retour), emplacements de mémoire, unions, 341-343,
60 accès aléatoire 352 membres de la structure, 315-319
exemple de code, 375- structures
378 avec des opérateurs de flèches, 324
fichiers de disque, 374- avec des pointeurs, 322-
377, 387 fonction 324
fseek(), unions, 335, 351 opérateur de flèche (->),
374-378 335, 351
fonction ftell(), 374-378 exemple de code, 335-337
lisibilité du code,
amélioration, 296, 300,
419
lecture
personnages, 215-217
fichiers, 360
binaire, 378-
381 fonction
fgetc(), 360-363
fonction fgets(),
363-366, 371
fonction fread(),
366-369, 381
accès aléatoire,
374-377, 387
accès séquentiel,
374
données formatées
fonction fprintf(),
381-384, 388
Fonction
fscanf(), 381-
fonction rewind() 523
o 3 8 supérieur à (>), 98
p 3 supérieur ou égal à
é (>=), 98
r o inférieur à (<), 98
a p inférieur ou égal à
t é (<=), 98
e r pas égal à (!=), 98
u a libération de la mémoire
r t allouée, 283-286
e opérateur d'affectation
p u du reste (%=), 93
o r opérateur du reste (%),
i s 43
n remplacer les
t r arguments intégrés,
e 308 exigences
( l fichier d'en-tête
. a stdio.h, 215 exigences
) t du système, 16-17
, i mots réservés. Voir
o mots-clés
3 n réinitialisation des
3 n indicateurs de position
5 e des fichiers, 378
- l ressources, 305, 434-435
3 s caractère de retour (\r),
3 , 60 mot-clé de retour, 34,
7 56 types de retour,
, 9 spécification, 244
8 valeurs de retour, 47
3 - temps de retour, 250
5 1 réutilisation, 14
1 0 fonction rewind(), 378
régions (variables), 229 0 exemple de code, 379-384
mot-clé de registre, 56, é syntaxe, 378
233 g
r a
e l
g
i à
s
t (
r =
e =
s )
, ,

2 9
524 valeur
correcte

valeur de droite points-virgules ( ;), 28 régions spatiales (vari-


(variables), 177, 187 séparation des éléments ables), 229
opérandes de droite, 92 de données, 317 spécificateurs, classes de stockage
sortie justifiée par la accès séquentiel, fichiers auto, 229-231
droite, 83-84 de disque, 374 externe, 234
opérateur de décalage fonction setbuf(), 387 registre, 233
vers la droite (>>), 133- fonction setjmp(), 440 statique, 230
135 fichier d'en-tête setjmp.h, spécifiant
Ritchie, Dennis, 12 440 fonction setlocale(), tailles, tableaux, 267
arrondi (division), 100 440 pointeurs null, 183 types de retour, 244
en cours d'exécution fonction setvbuf(), 387 fonction sqrt(), 150-152
compilateurs opérateurs de décalage, pile, 305
Borland C++, 23 133-135 modificateur flux d'entrée standard
Visual C++, 19 de données court, 145 pour la lecture des
programmes mot-clé court, 56 caractères, 215-217
Borland C++, 23 bits de signe, 142 lecture de chaînes de
Visual C++, 20 fichier d'en-tête caractères, 217
fonctions récursives, signal.h, 440 fichier d'en-tête
305 modificateur de d'entrée-sortie
données signées, 142- standard, 32
143 le flux de sortie standard
mot-clé signé, 56 écrit des caractères à
S fonction sin(), 149-150 partir de, 217
guillemets simples ('), écrire des caractères
%s (format de chaîne 59 taille sur, 215-216
spécifié), 79 pointeurs, 260 normes, 15-16
fonction printf(), 217 structures, 339-341 compilateurs
fonction scanf(), 217 syndicats, 339 débutants
code d'épargne mesure, 339-341 Borland C++, 21
Borland C++, 23 comparaison de la Visual C++, 18
Visual C++, 19 taille des structures, blocs de déclaration, 45-
fonction scanf(), 217-219 339-341 46
spécificateurs de taille du mot-clé, 56 déclarations, 45, 426.
format, 217 opérateur sizeof, 122- Voir aussi fonctions ;
comparaison de la 123 tailles, tableaux boucles break, 155, 164-
fonction fscanf(), 381 calcul, 192-194 165
syntaxe, 217 spécifier, 267 Branchements
notation scientifique, 67 tableaux non conditionnels, 155
spécificateur de format dimensionnés, 201 continuer, 155, 167-168
de notation scientifique sauter des boucles, 167 flux de contrôle, 426
(%E), 67, 79 champs exigences logicielles, goto, 155, 168-169
d'application (variables) 16-17 si, 155-158
bloc, 224-225 génie logiciel, 420 code les appareils
fichier, 232 source. Voir code orthopédiques,
fonction, 226-227 espacement (dans le 156 l'évaluation
bloc imbriqué, 225-226 code), 419 code spaghetti, des condi- tions,
programme, 227, 233 169 156
opérateur de 525
soustraction

l'exécution des appareils, 356 fonction freopen(), 384-388


codes, 156 comparaison de fichiers, exemple de code, 385-386
nidification, 160-161 356 modes, 385-386
if-else, 155, 158-159 syntaxe, 384
bouclage. Voir E/S de haut niveau, 357
boucles null, 112- E/S de bas niveau, 357
113 fonction setbuf(), 387
retour, 34 fonction setvbuf(), 387
commutateur, 155, 161- stderr, 72
164 stdin, 72
mot-clé statique, 56 stdout, 72
spécificateur statique, texte, 356, 370
230-231 spécificateurs constantes de chaîne, 208, 423 tableaux de
statiques, application, caractères, initialisation, 208-211 constantes de
231 caractères, comparées, 209-212
fichier d'en-tête spécificateur de format de chaîne (%s), 79
stdarg.h, 440 fichier fichier d'en-tête string.h, 302, 440
d'en-tête stddef.h, 301, cordes, 208
440 copie, 213-215
stderr file stream, 433 initialisation, 208-211 longueurs, mesure,
stderr stream, 72 212-213
stdin file stream, 433 pointeurs, tableaux, 272-274
stdin stream, 72 lecture, 217
fichier stdio.h, 32 fonction strlen(), 212-213
fichier d'en-tête stdio.h, Fonction StrPrint(), 386 Type de données
440 fichier d'en-tête struct, 424-425
stdlib.h, 302, 440 champs de bits
flux de fichiers stdout, exemple de code, 348-350
433 flux stdout, 72 déclaration, 347
stockage, mémoire, 343 mot-clé struct, 56, 347 membres de la
classes de stockage, 229 structure, référencement, 315-319
spécificateur
automatique, 229-231
spécificateur externe,
234
modificateurs, 234-235
spécificateur de registre,
233
spécificateur statique,
230 stockage des
variables, registres, 233
fonction strcpy(),
213-215, 336
ruisseaux, 72, 433
binaire, 356, 370
défini, 356
indépendance des
524 valeur
correcte , e structures, 324-
pr
o 327
g 2 comparés, 314
r 5 opérateurs de flèches,
a 5 référencés, 324
m C, 169 types de données, 314
m p déclaration, 314-315
a r définitions, 315
t o duplication, éviter,
i g 322
o r 357, 374-375,
n a 378
m fonctions,
s m passage, 319-321
t a initialisation, 317-319
r t entiers, assignation, 315
u i membres, 314-315
c o imbriqués, déclaration, 327
t n unions imbriquées, 343-
u 346 pointeurs,
r d référencement, 322-324
é e taille, 339-341
e s noms de balises, 314
c comparaison des syndicats, 351
p e initialisation du syndicat, 338
r n variables, 314-315
o d style
g a (programmation),
r n 418-419
a t soustraction d'entiers,
m e pointeurs, 259, 263-264
m , opérateur d'affectation
a de soustraction (-=), 93
t 2 opérateur de
i 5 soustraction (-), 96
o 5
n structures
tableaux
a t
s a
c b
e l
n e
d a
a u
n x
t
e d
Spécification du format %x (hexadécimal non 527
signé)

déclaration d'aiguillage, fonction time(), 250 transistors, 13 fonctions de


56, 155, fichier d'en-tête time.h, trigonométrie, 149-150
161-164 440 programmation dépannage. Voir aussi
appareils descendante, 255 débogage
orthopédiques, 165 troncature (division), 100 désactivation
mot-clé case, 162 des optimiseurs, 235
expressions mot-clé typedef, 57, 300,
conditionnelles, 161- 321, 426
162 avantages, 300-301
sortie des états de liste, 301-302
rupture, 164 mise à jour, 301
forme, 161
liste, 162 exécution
de la déclaration,
164 U
décisions illimitées,
161 %u (entier non signé) spécificateur de
interrupteu format, 79 opérateur de déférence unaire
rs, 13 (*), 182
synonymes opérateur unaire moins (-), 95
noms des types de Directive #undef, 393-394,
données, 300 406, 434
déclaration, 300 erreurs de pointeurs non initialisés, 262
définition, 321 type de données union, 425-426 mot-clé
exigences du union, 57
système, 16-17 syndicats, 334
opérateur de flèche (->), 335, 351
déclaration, 334 opérateur point
(.), 335-337, 351
T initialisation, 337-339,
351
\t (caractère de exemple de code de partage de
tabulation), 60 noms mémoire, 338-339
de balises structures, 338
structures, 314
syndicats, 334
fonction tan(), 149-150
régions temporelles
(variables), 229
emplacements de
mémoire temporaire,
229
éditeurs de texte,
exigences, 17
flux de texte, 356, 370
temps, conversion, 250-
251
526 déclaration de
commutation 3 334 variables,
nidification
e - définition, 334-335
x 3 Systèmes UNIX,
e 4 exigences, 17
m 6 modificateur de
p le référencement, 335- données non signées,
l 337, 143-145
e 351 spécificateur de format
réfé hexadécimal non signé
d re (%x), 79
e nc spécificateur de format
em d'entier non signé
c ent (%u), 79
o de mot-clé unsigned, 57
d s spécificateur de
e em format octal non signé
, pla (%o), 79 tableaux non
ce dimensionnés
3 me tableaux de caractères, 209
4 nts déclaration, 201
4 de initialisation, 202-203
- mé tailles, calcul, 201
3 m mise à jour des
4 oir définitions, 301
5 e, majuscules, conversion
d 34 en, 302-303
a 1- saisie de l'utilisateur, 72
n 34 fonction getc(), 72-74
s 3, fonction getchar(),
352 74-75
l taille,
e 339-
s 341
comp
s araiso
t n des
r struct
u ures,
c 351
t n
u o
r m
e s
s de
, ba
lis
3 es
4 ,
Spécification du format %x (hexadécimal non 527
signé)

nom, 68, 418 pointeurs, 176, 430-432


V spécificateur de registre, 233
valeurs registres, stockage, 233
valeur correcte, 177 champ d'application
constantes, 42
bloc, 225
types de
champ d'application du bloc, 224
données enum,
fichier, 232
298-299
fonction, 226-227 portée du bloc
entiers, par défaut, 296
imbriqué, 225-226
gauche, 187
pointeur, programme, 227 comparaison de l'étendue
attribution, 180- du programme et du bloc, 227-229
régions spatiales, 229
181
spécificateur statique, 230-231
droit, 187
classes de stockage, 229
variables, 42, 183-184
structures, 314-315
arguments variables,
régions temporelles, 229
traitement, 252-254
valeurs, 42, 183-184
variables, 42. Voir aussi
modificateurs volatils, 235
types de données
macro va_arg(), 252
assignation de valeurs
fonction va_end(), 252
de retour, 47
macro va_start(), 252 affichage de la valeur de
spécificateur d'auto, 229
l'indi- cateur de position du fichier, 375
caractère, 58
Visual C++, 17
modificateurs
compilateur, 18-21
const, 234-235
courir, 19
déclaration, 48, 177,
début, 18
244 nombres à
IDE, 18
virgule flottante,
Type de données void, déclarations de
64-65
fonctions, 248-249
nombres
mot-clé void, 57, 305
entiers, 62-63
mot-clé volatile, 57 modificateur volatile (vari-
définition,
ables), 235
unions, 334-335
spécificateur externe,
234
mondial, 227, 233
déclaration, 229
contre local, 418
groupes, collecte, 314
manipulation indirecte,
176
valeur de gauche,
176 listes,
définition des
types de données,
424
locale, 225, 418
526 déclaration de
commutation e rs le flux de sortie
W-Z standard, 215-216
s code
pendant le mot-clé, 57 o Borland C++, 21
boucles while, 106-107, r Visual C++, 18
166 t fichiers, 360
e i binaire, 378-
s e 381 fonction
p fputc(), 360-363
a s fonction fputs(),
c t 363-366
e a fonction fwrite(),
m n 366-369, 381
e d accès aléatoire,
n a 374-377, 387
t r accès séquentiel,
, d 374
, sortie, 75
2 fonction printf(),
9 2 78-79
1 fonction putc(),
é 7 75-76
c f fonction putchar(),
r o 77-78
i n Spécification du format
t c %x (hexadeci- mal non
u t signé), 79
r i
e o
personnages n
à
p
p u
a t
r s
t (
i )
r ,
d 2
u 1
5
f -
l 2
u 1
x 7
v
d e

Vous aimerez peut-être aussi