Académique Documents
Professionnel Documents
Culture Documents
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
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
Atelier.....................................................................................................................69
Quiz...................................................................................................................69
Exercices ...........................................................................................................70
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
Atelier ...................................................................................................................238
Quiz .................................................................................................................238
Exercices .........................................................................................................239
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
Pointeurs..........................................................................................................430
Fonctions .........................................................................................................432
Entrées et sorties (I/O) ....................................................................................433
Le préprocesseur C ..........................................................................................434
La route à suivre... ................................................................................................434
Résumé .................................................................................................................435
-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.
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) ;
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
Les avertissements vous mettent en garde contre les pièges de la programmation que vous devez éviter.
Voici un avertissement typique :
Les astuces sont des conseils sur la manière d'améliorer l'écriture de vos programmes
C. Voici un exemple de conseil :
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.
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.
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 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)...
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
FIGURE 1.2
Le
Portage de programmes program
écrits en C sur différents me C
types d'ordinateurs.
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
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.
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.
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.
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.
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++.
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
FIGURE 1.10
Compilation d'un
programme C à l'aide
de l'IDE de Borland.
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
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.
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
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.
*/
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.
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
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.
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.
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.
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
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.
Résumé
Dans cette leçon, vous avez appris les concepts et déclarations suivants concernant le langage C :
• 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.
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
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.
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
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
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
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.
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.
{ 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.
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() ;
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 :
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.
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
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.
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
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
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 ;
}
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.
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.
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
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 ;
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 ;
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.
Outre le caractère de retour à la ligne, les autres caractères spéciaux du langage C sont les
suivants :
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.
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
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.
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 ;
Vous en apprendrez plus sur le type de données "integer" plus loin dans le livre.
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.
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 ;
float a, b, c ;
a = 10.38 ;
b = -32.7 ;
c = 12,0f ;
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
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 :
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.
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.
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.
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 ;
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
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
Vous en apprendrez plus sur stdin et stdout dans les sections suivantes.
Entrée syntaxique
SYNTAX
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.
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).
Entrée syntaxique
SYNTAX
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
Les lignes 11 et 12 envoient deux caractères (respectivement conservés par ch1 et ch2) à l'écran.
5
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.
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.
Entrée syntaxique
SYNTAX
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 : }
Entrée syntaxique
SYNTAX
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().
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.
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().
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.
L'exemple de la liste 5.6 montre comment utiliser le spécificateur de largeur de champ minimale.
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.
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
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
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.
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 ;
x -= y ; équivaut à x = x - y ; x
*= y ; équivaut à x = x * y ; x
/= y ; équivaut à x = x / y ; x
%= y ; équivaut à x = x % y ;
z = 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) ;
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é.
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.
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 : }
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
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
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) ;
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
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.
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
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).
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
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".
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 !
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
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.
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.
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.
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
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 :
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++) ;
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
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).
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
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
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.
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++ ;
}
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) ;
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
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.
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
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
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.
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.
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)
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.
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.
non nul 0 1
0 non nul 1
0 0 0
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 : }
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)
0 1
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.
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
TABLEAU suite
8.4
Hexagone Binaire Décimale
B 1011 11
C 1100 12
D 1101 13
E 1110 14
F 1111 15
1110 → 1 * + 1 * + 1 * + 0 * → → 14 (décimal) En
23 22 21 20 23
De même, vous pouvez convertir le reste des nombres décimaux du tableau 8.4 en leurs
contreparties binaires, ou vice versa.
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
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.
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
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)
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.
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
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.
continue
138 Heure
8
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.
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
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 ?
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
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).
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 : }
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.
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 ;
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.
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.
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
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
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.
#include <math.h>
double pow(double x, double y) ;
#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.
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
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
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 ;
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
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) ;
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.
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
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
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 ;
}
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
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.
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.
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 : }
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
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.
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.
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
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
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 ;
case '/' : x /=
y ; default :
break
;
}
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.
• 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
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 ;
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 :
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
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.
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
sur un entier */
Le programme du Listing 11.2 montre comment déclarer des pointeurs et leur attribuer
des valeurs.
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'
.
.
.
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.
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.
TYPE LISTE 11.3 Modification des valeurs des variables à l'aide de pointeurs
continue
Comprendre les 185
pointeurs
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.
Le programme de la liste 11.4 montre un autre exemple de pointer sur la même chose
avec plusieurs 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.
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
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.
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.
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
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.
vous pouvez alors calculer le nombre total d'octets du tableau à l'aide de l'expression suivante :
sizeof(data-type) * Array-Size
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.
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
;
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
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.
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] ;
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 ;
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.
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.
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
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
• 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 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 :
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 !
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
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." ;
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" ;
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.
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 : }
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.
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) ;
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.
17 : retour 0 ;
18 : }
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.
Ici, le contenu de la chaîne src est copié dans le tableau référencé par dest. La
E
S
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.
continue
218 Heure
13
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.
Ici, les caractères lus à partir du flux d'entrée standard sont stockés dans le tableau
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
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.
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
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.
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
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" ;
2. Etant donné une variable de pointeur char ptr_ch, les déclarations suivantes sont-elles légales ?
• *ptr_ch = 'a' ;
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
.
.
.
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.
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
.
.
.
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().
continue
228 Heure
14
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
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.
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
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.
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
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.
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 ;
}
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.
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 ;
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 :
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
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 ;
. . .
}
. . .
}
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 ;
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
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
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
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
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.
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.
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
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
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().
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
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) ;
#include <stdarg.h>
void va_end(va_list ap) ;
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 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
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
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) ;
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 :
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.
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 ;
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).
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 ;
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.
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.
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
Pointeurs et fonctions
Avant de commencer à parler du passage de pointeurs à des fonctions, voyons d'abord
comment passer des tableaux à des fonctions.
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.
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
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
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
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
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.
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
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
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
• *(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
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) ;
continue
282 Heure
17
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().
continue
284 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.
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
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
continue
290 Heure
17
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 :
• 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)) ;
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 :
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
Par exemple, le texte suivant déclare un type de données enum avec le nom de balise
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.
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.
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", ¢) ; /* 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
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.
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.
continue
Utilisation de types de données et de 303
fonctions spéciales
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
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.
continue
Utilisation de types de données et de 307
fonctions spéciales
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().
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().
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().
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 : }
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
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 ;
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
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.
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.
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
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
La liste 19.2 contient un exemple d'initialisation d'une structure avant qu'elle ne soit
mise à jour par l'utilisateur.
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.
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 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
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
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] ;
continue
Comprendre les 329
structures
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
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é.
continue
332 Heure
19
En voici un exemple :
SORTIE Nom : B. Smith
ID # : 0001
Nom du département :
Marketing Code du
département : 01
Votre fonction : Manager
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 ;
}
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 :
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.
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 ;
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.
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
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
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).
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.
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.
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 : }
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.
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.
continue
Comprendre les 343
syndicats
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.
Haut
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
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
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.
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.
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.
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
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
[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
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
;
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.
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.
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 ;
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 :
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.
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
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.
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
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.
continue
Lire et écrire avec des fichiers 365
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)
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
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
Ici, s pointe vers le tableau qui contient les caractères à écrire dans un fichier
E
S
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.
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)
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
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
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
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)
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
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+")
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
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
continue
Utilisation des fonctions de 377
fichiers spéciaux
Relisez le haïku :
--de regarder la lune.
---Mon ombre retourne à la maison
-- Me mener par le bout du nez
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
rewind(fptr) ;
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.
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.
continue
Abonnez-vous à DeepL Pro pour traduire des fichiers plus volumineu
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
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
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
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 : }
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.
continue
386 Heure
22
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.
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) ;
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 :
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.
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) :
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).
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
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.
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
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
La liste 23.2 contient un programme qui montre comment utiliser les commandes #ifdef, #ifndef et
Les directives #endif sont regroupées.
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
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) ;
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
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.
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
#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
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 :
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
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 ;
-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
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
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
(b) Nul
next_ptr next_ptr
head_ptr next_ptr
Pierre Paul Marie
1234 5678 7777
(c)
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
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).
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
Au revoir !
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
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
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.
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é
faire statique
double struct
autre commutateur
enum typedef
externe union
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.
continue
424 Heure
24
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.
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.
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.
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.
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 }).
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 }).
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.
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 */
}
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 */
}
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 ;
.
.
.
}
}
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 ;
}
.
.
.
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
; l'expression
an_array[n]
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]
;
} ;
(*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,
) ;
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.
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.
Génie logiciel
par Ian Sommerville, publié par Addison-Wesley
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
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.
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
main()
{
printf ("Howdy, neighbor!\NThis is my first C program.\N") ;
return 0 ;
}
Exercices
1. Voici une solution possible :
{
x = 3 ;
y = 5 + x ;
}
ou
/* Méthode 2 : une fonction
C */ int MyFunction( int x,
int y)
{
retour (x * y) ;
}
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
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 ;
}
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 ;
}
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 ;
}
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
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 ;
}
main()
{
int x ;
double y
;
x = 123 ;
y = 123.456 ;
printf("x : %-3d\n", x) ;
printf("y : %-6.3f\n", y) ;
retour 0 ;
}
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 ;
}
main()
{
int ch ;
printf("Enter a character:\n")
; ch = getchar() ;
Réponses aux questions du quiz et aux 449
exercices
putchar(ch) ;
retour 0 ;
}
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 ;
}
main()
{
int x ;
x = 1 ;
printf("x++ produit: %d\n", x++)
; printf("Maintenant x contient :
%d\n", x) ;
retour 0 ;
}
main()
{
int x ;
x = 1 ;
printf("x = x++ produit : %d\n", x =
x++) ;printf("Maintenant x contient :
", x) ;
retour 0 ;
}
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 ;
printf("\n") ;
pour (i=0, j=1 ; i<8 ; i++, j++) ;
printf("%d + %d = %d\n", i, j, i+j) ;
retour 0 ;
}
main()
{
int c ;
while( c != 'K' )
{ c =
getc(stdin) ;
putchar(c) ;
}
printf("\NSortie de la boucle for. Bye!\N") ;
retour 0 ;
}
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
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
int main()
{
int x, y ;
x = 0xEFFF
; y =
0x1000 ;
retour 0 ;
}
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 ;
}
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 ;
}
main()
{
int x ;
retour 0 ;
}
Exercices
1. Voici une solution possible :
/* 09A01 */
#include <stdio.h>
main()
{
int x ;
unsigned int y ;
x = 0xAB78
; y =
0xAB78 ;
retour 0 ;
}
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 ;
}
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 ;
}
main()
{
int x ;
x = -23456 ;
printf("La valeur hexagonale de x est 0x%X.\n", x) ;
retour 0 ;
}
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 ;
}
Exercices
1. Voici une solution possible :
/* 10A01.c Utiliser l'instruction
if */ #include <stdio.h>
main()
{
int i ;
retour 0 ;
}
main()
{
int i ;
retour 0 ;
}
main()
{
lettre int ;
retour 0 ;
}
main()
{
int c ;
460 Annexe B
retour 0 ;
}
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 ;
}
Exercices
1. Voici une solution possible :
/* 11A01.c */
#include <stdio.h>
main()
{
int x, y, z ;
x = 512 ;
y = 1024 ;
z = 2048 ;
retour 0 ;
}
main()
{
int *ptr_int
; char
*ptr_ch ;
retour 0 ;
}
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 ;
}
main()
{
int x, y ;
int *ptr_x, *ptr_y ;
x = 5 ;
y = 6 ;
ptr_x = &x
; ptr_y =
&y ;
*ptr_x *= *ptr_y ;
retour 0 ;
}
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'} ;
retour 0 ;
}
main()
{
int i ;
char array_ch[5] ;
retour 0 ;
464 Annexe B
}
Réponses aux questions du quiz et aux 465
exercices
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) ;
retour 0 ;
}
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
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
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 ;
}
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
main()
{
char str[80] ;
int i, delt ;
retour 0 ;
}
main()
{
int x, y, sum ;
retour 0 ;
}
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 ;
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 ;
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]) ;
}
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 ;
}
; main ()
{
int d1 = 1
; int d2 =
2 ; int d3
= 3 ; int
d4 = 4 ;
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 ;
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>
; main ()
{
double d1 = 1,5
; double d2 =
2,5 ; double d3
= 3,5 ; double
d4 = 4,5 ;
/* définition de AddDouble() */
476 Annexe B
va_end (arglist) ;
retourner le résultat ;
}
Exercices
1. Voici une solution possible :
/* 16A01.c */
#include <stdio.h>
!"; StrPrint(string) ;
retour 0 ;
}
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 ;
}
StrPrint(str, 2) ;
retour 0 ;
}
/* 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 ;
/* définition de la
fonction */ void
StrPrint2(char *str2)
{
printf("%s\n", str2) ;
}
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 ;
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 ;
}
/* fonction main() */
main()
{
float *ptr1, *ptr2
; int i ;
int termination = 1 ;
int max = 0 ;
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 ;
}
#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 ;
Exercices
1. Voici une solution possible :
/* 18A01.c */
#include <stdio.h>
main(void)
{
enum tag {nom1,
nom2 = 10,
nom3, nom4
} ;
retour 0 ;
}
main(void)
{
typedef char WORD ;
typedef int SHORT ;
typedef long LONG ;
typedef float FLOAT
; typedef double
DFLOAT ;
retour 0 ;
}
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 ;
}
{
int i ;
retour 0 ;
}
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} ;
retour 0 ;
}
struct employee {
int id ;
char name[32] ;
} ;
main(void)
{
/* initialisation de la structure
B
*/ struct employee info = {
0001,
"B. Smith
} ;
printf("Voici un échantillon:\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
struct computer {
float cost ;
int year ;
int cpu_speed ;
char cpu_type[16] ;
} ;
; 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 ;
}
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]
;
} ;
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".
}
} ;
retour 0 ;
}
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 ;
retour 0 ;
}
union employee {
int start_year ;
int dpt_code ;
int id_number ;
} ;
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) ;
}
struct survey {
char name[20] ;
union {
char state[32] ;
char country[32] ;
} place ;
} ;
488 Annexe B
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] ;
Merci de votre
attention !
struct bit_field
{ int yes : 1
;
} ;
struct survey {
struct bit_field flag
; char name[20] ;
union {
char state[32] ;
char country[32] ;
} place ;
} ;
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] ;
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") ;
}
Exercices
1. Voici une solution possible :
/* 21A01.c */
#include <stdio.h>
492 Annexe B
; main(void)
{
FILE *fptr ;
char filename[]= "haiku.txt"
; int reval = SUCCESS ;
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 ;
}
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("Enter a string:\n")
; gets(str) ;
retour reval ;
}
/* définition de LineWrite() */
void LineWrite(FILE *fout, char *str)
{
fputs(str, fout) ;
printf("Done!\n") ;
}
main(void)
{
FILE *fptr ;
char filename[]= "test_21.txt" ;
char str[]= "Disk file I/O is fun."
; int reval = SUCCESS ;
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++ ;
}
}
; 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 ;
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
Exercices
1. Voici une solution possible :
/* 22A01.c */
#include <stdio.h>
main(void)
{
FILE *fptr ;
char filename[]= "LaoTzu.txt"
; int reval = SUCCESS ;
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") ;
/* 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] ;
/* 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
main(void)
{
FILE *fptr ;
char filename[]= "LaoTzu.txt"
; int reval = SUCCESS ;
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] ;
/* 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>
main(void)
{
FILE *fptr ;
char filename[]= "data.bin"
; int reval = SUCCESS ;
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
;
/* définition de la
fonction */ int
ErrorMsg(char *str)
{
printf("Cannot open %s.\n", str) ;
return FAIL ;
}
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 ;
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 ;
}
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
retour 0 ;
}
main(void)
{
int résultat ;
résultat = MULTIPLY(2, 3) ;
504 Annexe B
retourner NO_ERROR ;
}
#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 ;
}
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)
é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
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
3 é
8 c
2 r
i
f t
514 fonctions
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
2 9
524 valeur
correcte