248
INSTITUT DE TECHNOLOGIE DUT Génie Electrique et Informatique Industrielle PROGRAMMATION EN C/C++ Polycopié de cours C.ALEXANDRE – C.PAUTOT

PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

Embed Size (px)

Citation preview

Page 1: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

INSTITUT DE TECHNOLOGIE

DUT Génie Electrique et Informatique Industrielle

PROGRAMMATION EN C/C++

Polycopié de cours

C.ALEXANDRE – C.PAUTOT

Page 2: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION
Page 3: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

i

1. INTRODUCTION ....................................................................................................................................... 1

1.1 L’informatique ............................................................................................................ 1 1.2 Le système de traitement de l’information ................................................................. 1 1.3 Le codage de l’information......................................................................................... 2 1.4 L’ordinateur ................................................................................................................ 5 1.5 Le système d’exploitation ........................................................................................... 6 1.6 Les applications logicielles ......................................................................................... 9 1.7 Le système de fichiers .............................................................................................. 10 1.8 Les répertoires (syntaxe Unix) ................................................................................. 10 1.9 Partitionnement et montage ...................................................................................... 11 1.10 Informations associées aux fichiers .......................................................................... 13 1.11 Protection des fichiers (Unix) ................................................................................... 14 1.12 Tableau comparatif des systèmes de fichiers ........................................................... 16 1.13 Les fichiers textes ..................................................................................................... 16

2. LANGAGES DE PROGRAMMATION .................................................................................................. 19

2.1 Définitions ................................................................................................................ 19 2.2 Méthodologie pour l’écriture d’un programme ........................................................ 20 2.3 Le langage C ............................................................................................................. 21

3. BASES DU LANGAGE C ......................................................................................................................... 23

3.1 Les variables ............................................................................................................. 23 3.2 L’instruction d’affectation ........................................................................................ 24 3.3 Les types entier ......................................................................................................... 27 3.4 Les types flottants (réels) .......................................................................................... 29 3.5 Les conversions de type ............................................................................................ 30 3.6 Les types char ........................................................................................................... 32 3.7 Communiquer avec le programme : les entrées-sorties standard ............................. 33 3.8 L’instruction printf ................................................................................................... 37 3.9 L’instruction scanf .................................................................................................... 40 3.10 Structure de choix : l’instruction if ........................................................................... 42 3.11 Structure de choix : les conditions en C ................................................................... 45 3.12 Structure de choix : l’instruction switch ................................................................... 49 3.13 Structure de répétition conditionnelle : l’instruction do... while .............................. 51 3.14 Structure de répétition conditionnelle : l’instruction while... ................................... 52 3.15 Structure de répétition inconditionnelle : l’instruction for... .................................... 53 3.16 Algorithmes élémentaires ......................................................................................... 56

4. LES FONCTIONS ..................................................................................................................................... 59

4.1 Introduction .............................................................................................................. 59 4.2 Premier exemple ....................................................................................................... 60 4.3 Fonction sans résultat ou sans paramètres ................................................................ 64 4.4 L’instruction return .............................................................................................. 66 4.5 Variables globales et locales..................................................................................... 67 4.6 Variable statique ....................................................................................................... 70 4.7 La récursivité ............................................................................................................ 71 4.8 Passage des paramètres par valeur. .......................................................................... 72 4.9 Les pointeurs ............................................................................................................ 74 4.10 Passage de pointeurs comme paramètres d’une fonction ......................................... 83

5. LES TABLEAUX ....................................................................................................................................... 85

Page 4: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

ii

5.1 Tableaux à une dimension........................................................................................ 85 5.2 Remarques importantes sur les tableaux .................................................................. 90 5.3 Les tableaux à deux dimensions............................................................................... 93 5.4 Passage d’un tableau comme paramètre d’une fonction .......................................... 97 5.5 Relations entre tableaux et pointeurs ..................................................................... 100 5.6 Allocation dynamique de la mémoire .................................................................... 104

6. LES CHAINES DE CARACTERES ...................................................................................................... 107

6.1 Déclaration ............................................................................................................. 107 6.2 Lire ou écrire des chaînes ....................................................................................... 110 6.3 Connaître la longueur d’une chaîne ....................................................................... 117 6.4 Copier une chaîne dans une autre chaîne ............................................................... 118 6.5 Comparer deux chaînes .......................................................................................... 119 6.6 Concaténer deux chaînes ........................................................................................ 120 6.7 Rechercher un caractère dans une chaîne .............................................................. 121 6.8 Rechercher une chaîne dans une autre chaîne ........................................................ 121 6.9 Fonctions diverses .................................................................................................. 123 6.10 Le passage d’une chaîne comme paramètre d’une fonction. ................................. 124 6.11 Les tableaux de chaînes de caractères. ................................................................... 126

7. LES PARAMETRES DE LA FONCTION MAIN. ............... ................................................................ 129

8. LES STRUCTURES ................................................................................................................................ 133

8.1 Définition ............................................................................................................... 133 8.2 Transmission d’une structure en paramètre d’une fonction ................................... 137 8.3 La définition de types nouveaux ............................................................................ 140

9. LES FICHIERS ........................................................................................................................................ 143

9.1 Introduction ............................................................................................................ 143 9.2 Ouverture et fermeture d’un flux ........................................................................... 145 9.3 Buffers associés aux flux ....................................................................................... 149 9.4 Lecture et écriture dans un flux .............................................................................. 150

9.4.1 Les lectures et écritures par caractère ............................................................ 150 9.4.2 Les lectures et écritures par ligne ................................................................... 151 9.4.3 Les lectures et écritures formatées ................................................................. 153 9.4.4 Les lectures et écritures binaires .................................................................... 155

9.5 Positionnement dans un flux .................................................................................. 158 9.6 Utilisation d’un fichier de configuration ................................................................ 160

10. DIVERS ................................................................................................................................................ 163

10.1 Exécution de commandes....................................................................................... 163 10.2 Les opérateurs binaires ........................................................................................... 164 10.3 Les énumérations ................................................................................................... 165 10.4 Les opérateurs d’incrémentation et de décrémentation .......................................... 166 10.5 L’opérateur virgule ................................................................................................ 168 10.6 L’opérateur conditionnel ? ..................................................................................... 168 10.7 Les macros avec paramètres .................................................................................. 169 10.8 Ce que vous ne verrez pas ...................................................................................... 172 10.9 Définition de macro à l’invocation du compilateur ............................................... 172 10.10 Compilation conditionnelle ................................................................................ 173

11. EDITION DE LIEN ............................................................................................................................ 179

Page 5: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

iii

11.1 Les pointeurs de fonction ....................................................................................... 179 11.2 Notion de processus ................................................................................................ 182 11.3 Les zones mémoires d’un processus ....................................................................... 183 11.4 Projets multi-fichiers : édition de liens ................................................................... 186

12. LES LIBRAIRIES ............................................................................................................................... 195

12.1 Les bibliothèques statiques (archive) ..................................................................... 195 12.2 Les bibliothèques dynamiques (partagées) ............................................................. 196 12.3 Avantages et inconvénients des bibliothèques dynamiques ................................... 200 12.4 La bibliothèque standard du C ................................................................................ 202

12.4.1 Les entrées-sorties <stdio.h> .......................................................................... 202 12.4.2 Les fonctions mathématiques <math.h> ......................................................... 203 12.4.3 Les manipulations de caractères <ctype.h> .................................................... 204 12.4.4 Les manipulations de chaînes <string.h> ....................................................... 204 12.4.5 Manipulations de l’heure <time.h> ................................................................ 205 12.4.6 Diverses fonctions utilitaires <stdlib.h> ......................................................... 205

13. INTRODUCTION A LA PROGRAMMATION EN C++ ............ ................................................... 207

13.1 Généralités .............................................................................................................. 207 13.2 Intérêt de la conception objet.................................................................................. 207 13.3 Un exemple de programmation classique ............................................................... 208 13.4 Les classes .............................................................................................................. 213

13.4.1 Définition ........................................................................................................ 213 13.4.2 Syntaxe ........................................................................................................... 213 13.4.3 Constructeur ................................................................................................... 214 13.4.4 Destructeur...................................................................................................... 214 13.4.5 Restriction d’accès .......................................................................................... 214 13.4.6 Les fonctions (méthodes) de la classe ............................................................ 215 13.4.7 Organisation en fichiers source et header ....................................................... 216

13.5 Création d’un objet ................................................................................................. 217 13.5.1 Au moyen d’une déclaration .......................................................................... 217 13.5.2 Avec l’opérateur new...................................................................................... 217

13.6 Manipulation des objets .......................................................................................... 219 13.6.1 Accès à une variable ....................................................................................... 219 13.6.2 Accès à une fonction....................................................................................... 219

13.7 Surcharge des fonctions et des opérateurs .............................................................. 221 13.8 Passage par référence.............................................................................................. 224 13.9 Héritage et composition .......................................................................................... 225

13.9.1 Introduction .................................................................................................... 225 13.9.2 La composition ............................................................................................... 226 13.9.3 L’héritage........................................................................................................ 226

13.9.3.1 Principe de l’héritage .............................................................................. 226 13.9.3.2 Restriction d’accès .................................................................................. 228 13.9.3.3 Substitution des membres hérités ........................................................... 229 13.9.3.4 Gestion des constructeurs ....................................................................... 230 13.9.3.5 Fonctions virtuelles ................................................................................ 232

13.10 Les flux d’entrée sortie ....................................................................................... 235 13.10.1 Généralités .................................................................................................. 235 13.10.2 Opérateur d'insertion et d’extraction de flux .............................................. 235

13.10.2.1 Extraction de flux >> ............................................................................. 235 13.10.2.2 Insertion de flux >> ................................................................................ 235

Page 6: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

iv

13.10.3 Modification des formatages ...................................................................... 236 13.10.3.1 Liste des drapeaux .................................................................................. 236 13.10.3.2 Fonctions permettant de modifier les drapeaux ..................................... 237 13.10.3.3 Formatage de la sortie ............................................................................ 237

13.10.4 Manipulateurs non paramétriques .............................................................. 238 13.10.5 Entrée et sortie non formatées .................................................................... 239

13.10.5.1 La fonction get() ................................................................................ 239 13.10.5.2 La fonction getline() ...................................................................... 240 13.10.5.3 La fonction read() .............................................................................. 240

13.10.6 Fonctions de sortie non formatées ............................................................. 241 13.10.6.1 La fonction put ..................................................................................... 241 13.10.6.2 La fonction write ................................................................................ 241

13.10.7 Les fonctions de manipulations évoluées................................................... 241 13.11 Bibliographie ...................................................................................................... 242

Page 7: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

1

1. Introduction

1.1 L’informatique

C’est la manipulation de l’information à l’aide d’un ordinateur.

Ordinateur : machine électronique programmable destinée au traitement de l’information

numérique.

Information : texte, son, image, données binaires (produites par un système électronique,

utilisées par un système électronique), …

1.2 Le système de traitement de l’information

ordinateur

Système d’exploitation

Dispositif électroniquematériel (hardware)

Logiciels (software)

Applications Logicielles

Interface de programmation

Interface matériel/logiciel

Interface de programmation, l’Application Programming Interface (API) : exemple, l’API

Win32 pour Windows.

Interface matériel/logiciel : exemple, le Basic Input Output System (BIOS). Jusqu’à MS-

DOS, le BIOS sert d’interface entre l’ordinateur et le système d’exploitation. Cette méthode

n’est plus utilisée aujourd’hui, l’interface est intégrée au système d’exploitation (voir HAL

sous NT).

Page 8: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

2

1.3 Le codage de l’information

Un bit (contraction de Binary digIT) est un chiffre pouvant prendre la valeur 0 ou 1 (base 2).

Dans un ordinateur, un bit est représenté par deux niveaux de tension électrique. Un nombre

binaire est une suite de bits (comme un nombre décimal est une suite de chiffres compris entre

0 et 9).

En décimal (base 10) :

Le nombre 259 = 2*102 + 5*101 + 9*100 = 2*100 + 5*10 + 9*1

En binaire (base 2) :

Le nombre 10101 = 1*24 + 0*23 + 1*22 + 0*21 + 1*20 = 1*16 + 1*4 + 1*1

Un octet (ou byte) est une suite de 8 bits :

L’octet 10000001 = 1*27 + 1*20 = 129

La valeur d’un octet est comprise entre 0 et 255. Pour raccourcir l’écriture, on utilise la

notation hexadécimale (base 16).

Un chiffre en base 16 peut prendre 16 valeurs allant de 0 à 15. Il peut être codé avec 4 bits.

Comme les chiffres s’arrêtent à 9 en décimal, on utilise les lettre a, b, c, d ,e et f pour

représenter les derniers états.

Page 9: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

3

décimal binaire hexadécimal

0 0000 0

1 0001 1

2 0010 2

3 0011 3

4 0100 4

5 0101 5

6 0110 6

7 0111 7

8 1000 8

9 1001 9

10 1010 a

11 1011 b

12 1100 c

13 1101 d

14 1110 e

15 1111 f

L’octet 10000001 = 81 en hexa

Notation pour les bases :

(10000001)b = (81)h = (129)d

101011002 = ac16 = 17210

On utilise couramment les multiples suivants :

multiple valeur

Kilo 210 = 1024

Méga 220 = 1048576 = 1024 kilo

Giga 230 = 1073741824 = 1024 méga

Tera 240 = 1099511627776 = 1024 giga

Page 10: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

4

Exemples :

1 kilobit = 1024 bits

128 mégaoctet = 128*1024*1024 octets

Attention : en informatique

1 kilo ≠≠≠≠ mille

1 méga ≠≠≠≠ 1 million

1 giga ≠≠≠≠ 1 milliard

Changement de base :

• Décimal vers binaire

47 2

1 23 2

1 11 2

1 5 2

1 2 2

0 1 2

1 0

4710 = 1011112

• Binaire vers décimal

1 x 25 + 0 x 24 + 1 x 23 + 1 x 22 + 1 x 21 + 1 x 20 = 4710

Page 11: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

5

• Hexadécimal vers binaire

ab8516 = 1010 1011 1000 01012

• Binaire vers hexadécimal

1001 0000 1010 11112 = 90af16

Pour passer d’hexadécimal en décimal (et vice versa), vous pouvez passer par l’intermédiaire

du binaire ou faire le calcul directement.

Exercice 1.1 :

1) Quel est le nombre décimal le plus grand que l’on peut coder avec 4 bits, 8 bits, 16 bits, 32

bits.

2) Convertissez 11011012 en décimal.

3) Convertissez 1910 et 4510 et 6310 en binaire.

4) Convertissez 11001010010101112 en hexadécimal.

5) Convertissez 10A416 et CF8E16 et 974216 en binaire et en décimal.

1.4 L’ordinateur

Un ordinateur :

• Traite l’information grâce à un programme qu’il mémorise,

• Communique et archive des informations.

Il est constitué de trois éléments :

Page 12: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

6

1. La mémoire centrale (vive) qui permet de stocker les programmes pendant le temps

nécessaire à leur exécution ainsi que les informations temporaires manipulées par ces

programmes. La mise hors tension de l’ordinateur efface le contenu de la mémoire vive.

2. L’unité centrale qui exécute les instructions contenues dans le programme qui se trouve en

mémoire vive.

3. Les périphériques qui échangent des informations avec l’unité centrale. On en trouve de

deux sortes :

• Les périphériques de communication : clavier, écran, souris, imprimante, modem, carte

son, carte réseau, …

• Les périphériques d’archivage : disque dur, disquette, CD-ROM, bande magnétique, ...

ils assurent le stockage permanent des données et des programmes.

Exemple : un PC.

Processeur Intel pentium IV, fréquence 3 GHz.

Mémoire vive 512 Mo.

Disque dur 200 Go.

Lecteur de DVD -ROM + graveur.

Lecture de disquette 3,5 pouces.

Ecran 17 pouces LCD.

Carte son.

Carte réseau.

Clavier, souris.

1.5 Le système d’exploitation

C’est un ensemble de programmes qui servent :

• à gérer les ressources de l’ordinateur et notamment à assurer leur partage harmonieux entre

les différents programmes.

• à présenter à l’utilisateur et aux programmes une interface plus facile à utiliser que la

machine physique. Cette interface est celle d’une machine virtuelle qui cache la complexité

du matériel. Elle sert :

� d’interface entre l’ordinateur et les applications logicielles (API).

� d’interface entre l’ordinateur et l’utilisateur (IHM).

Page 13: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

7

L’utilisateur d’un ordinateur effectue généralement les tâches suivantes :

1. il développe un programme,

2. ou bien il utilise un programme pour générer ou manipuler de l’information.

Pour faciliter ces opérations, le système d’exploitation peut utiliser deux sortes d’interface

avec l’utilisateur :

1. L’interface graphique (mode graphique). Elle utilise la souris, les icônes et les menus

déroulants. Très conviviale elle ne permet pas l’automatisation des traitements.

Exemples : MAC OS, Windows, …

2. L’interpréteur de commandes textuel (mode ligne de commande ou console ou terminal).

L’utilisateur manipule ici les données à l’aide de commandes tapées au clavier. On

l’appelle un shell sous unix ou command.com sous MS-DOS. L’interpréteur de

commande est peu intuitif car l’utilisateur doit connaître les commandes pour pouvoir

l’utiliser mais il est en revanche plus puissant car programmable.

Le système d’exploitation comprend 4 parties essentielles :

1. La gestion des programmes.

2. Les entrées/sorties.

3. La gestion de la mémoire.

4. Le système de fichiers.

Caractéristiques :

⇒ La mémoire virtuelle. La taille des programmes et des données manipulées en mémoire

vive dépasse généralement la quantité de mémoire physiquement disponible dans

l’ordinateur. Pour augmenter la quantité de mémoire disponible, le système d’exploitation

va garder en mémoire vive les parties actives du programme et des données et stocker le

reste sur le disque dur. Un mécanisme de pagination permet de charger en mémoire

quand il le faut les parties de programme à exécuter. Le nombre de bits codant l’adresse

maximale de la mémoire utilisable par un programme caractérise le système

d’exploitation (16, 32 ou 64 bits). La MMU est chargé de traduire les adresses

virtuelles en adresses physiques.

Page 14: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

8

⇒ La gestion des programmes. Le système monotâche ne peut exécuter qu’un seul

programme à la fois même si plusieurs programmes peuvent être présents à la fois en

mémoire (permutation). Un système multitâches préemptif fait tourner plusieurs

programmes en même temps grâce à un planificateur (scheduler) qui attribue à chacun

des programmes un petit laps de temps (slice time) pour s’exécuter. Tous les programmes

se trouvent dans une file d’attente et attendent leur tour. Les programmes les plus

prioritaires reviennent plus souvent dans la queue et donc s’exécutent plus rapidement.

Un système multitâches coopératif est un système monotâche (pas de scheduler) car ce

sont les programmes qui sont conçus pour s’arrêter de temps en temps pour passer à un

autre programme.

⇒ La gestion des utilisateurs. Un système multi-utilisateurs permet à plusieurs utilisateurs

d’accéder simultanément à l’ordinateur pour exécuter des taches différentes. Le système

doit distinguer les différents utilisateurs en les dotant d’un nom et d’un mot de passe ainsi

que d’un espace disque (et d’un espace mémoire) réservé. C’est le compte utilisateur. Le

système est forcément multitâches préemptif. Un système monotâche est forcément

mono-utilisateur.

Caractéristiques des principaux systèmes :

MS-DOS Mono-tache Mono-utilisateur 16 (20) bits

MAC OS 9 Mono-tache Mono-utilisateur 32 bits

Unix multi-tâches Multi-Utilisateurs 32 bits ou 64 bits

VMS multi-tâches Multi-Utilisateurs 32 bits

Windows 95 et 98 multi-tâches Mono-utilisateur 32 bits + 16 bits

Windows NT

multi-tâches Mono-utilisateur

Multi-Utilisateurs

32 bits

Windows 2000

Windows XP

Vista

multi-tâches Multi-Utilisateurs 32 bits ou 64 bits

Page 15: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

9

1.6 Les applications logicielles

Ce sont des programmes. Exemples :

• Bureautique : traitement de texte, tableur, gestion de base de données, …

• Gestion et comptabilité : facturation, paye, stocks, …

• Jeux vidéo,

• Navigation Internet,

• Prévisions météorologiques,

• Conception assistée par ordinateur (CAO),

• Gestion d’une chaîne de fabrication,

• Simulateur de vol,

• …

Un ordinateur est capable de mettre en mémoire un programme (résidant généralement sur le

disque dur), puis de l’exécuter.

Un programme est constitué d’instructions qui spécifient :

• Les opérations élémentaires que va exécuter l’ordinateur,

• La manière dont elles s’enchaînent.

programmedonnées résultats

Les données d’entrée du programme peuvent être fournies manuellement ou lues sur un

disque dur (base de données) ou bien sur des capteurs.

Les résultats fournis par le programme peuvent être lus directement par l’utilisateur (sous

forme de textes ou de graphiques) ou bien stockés sur disque.

Si le temps de réaction du programme entre un changement sur une entrée (capteur) et le

résultat en sortie (actuateur) doit être garanti, alors on dit que le programme s’exécute en

temps réel.

Page 16: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

10

1.7 Le système de fichiers

On ne peut pas maintenir toutes les informations utiles d’un ordinateur en mémoire vive. Il

faut les sauvegarder sur un support qui stocke l’information même lorsqu’il est hors tension

(un disque dur par exemple). Le système de fichiers est l’ensemble des mécanismes destiné à

manipuler de l’information sur ce support.

Un fichier est un objet qui peut contenir des programmes, des données ou tout autre type

d’information. Le système d’exploitation fournit des opérations spéciales, les appels système,

pour les créer, les détruire, les écrire ou les modifier.

Le système de fichiers est, avec le bureau, la partie la plus visible du système d’exploitation.

La plupart des programmes lisent ou écrivent au moins un fichier et les utilisateurs

manipulent beaucoup de fichiers. L’utilisateur attache une grande importance à l’interface du

système de fichiers, c’est-à-dire aux fichiers, à la manière de les nommer et de les protéger,

aux opérations permises sur les fichiers, …

1.8 Les répertoires (syntaxe Unix)

Le système de fichiers range les fichiers dans des répertoires (directories) ou dossiers. Un

répertoire contient un certain nombre d’entrées, une par fichier ou par répertoire. Etant donné

la très grande quantité de fichiers à gérer sur un ordinateur (plusieurs centaines de milliers), il

faut un système de classement performant. Une organisation hiérarchique constituée d’une

arborescence de répertoires permet d’avoir autant de répertoires qu’il est nécessaire afin de

regrouper les fichiers logiquement. Un répertoire peut contenir soit des fichiers, soit

d’autres répertoires.

Page 17: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

11

Le premier répertoire dans la hiérarchie (ici /) s’appelle le répertoire racine (root).

L’utilisateur peut définir le répertoire dans lequel il veut travailler, le répertoire de travail

(working directory) ou répertoire courant (il y a une valeur par défaut spécifiée par le

système d’exploitation). Il existe deux méthodes pour spécifier l’emplacement (chemin

d’accès ou path) d’un fichier :

• Le chemin d’accès absolu. C’est le chemin spécifié à partir du répertoire racine.

Ex : /usr/local/bin/nom_de_fichier.

Le / (slash) représente soit le répertoire racine s’il est au début du chemin d’accès, soit un

séparateur qui indique un changement de niveau.

• le chemin d’accès relatif. C’est le chemin spécifié à partir du répertoire courant.

Exemple : le répertoire courant est /usr/local/bin. On veut accéder à un fichier se trouvant

dans /usr/bin

../../bin/ nom_de_fichier

.. désigne le répertoire père (répertoire juste au-dessus dans l’arborescence).

. désigne le répertoire dans lequel vous êtes.

Exercice 1.2 : le répertoire courant est /usr/local. Donnez le chemin d’accès absolu et relatif

des répertoires man, Yves et etc.

1.9 Partitionnement et montage

Il est possible de diviser un disque en plusieurs morceaux (des partitions) en effectuant un

partitionnement. Cela permet, par exemple, d’installer plusieurs systèmes d’exploitation (OS

= operating system) sur un disque.

Sur les OS Microsoft, on affecte aux partitions une lettre appelée lettre de lecteur. Chaque

partition sera appelée C, D, E, … Les lettres A et B sont généralement affectées aux lecteurs

amovibles.

Page 18: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

12

Un chemin d’accès absolu sera nommé : C:\users\dut_info. Le répertoire racine est précédé

de la lettre du lecteur suivie de :. Le slash (/) d’Unix est remplacé par un backslash (\). Le

chemin d’accès relatif sous Windows est le même que sous Unix, mais avec un \ à la place

d’un /.

Sous Unix, il n’y a pas de lettre de lecteur pour identifier un lecteur physique (disque dur ou

disquette) ou bien une partition. Tout est monté sous le répertoire racine /.

Partition 1

Partition 0 /

usr etc tmp home

lib bin includedut tpb

montage

Page 19: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

13

L’opération de montage sert à indiquer au système à quel niveau de l’arborescence se placent

les différents disques et partitions. Dans l’exemple précédent, la partition utilisateurs

(partition 1) est rattachée (montée) au répertoire home de la partition racine (partition 0). Un

lecteur de DVD-ROM sera par exemple monté sous le répertoire mount (/mnt/cdrom ).

Sous Unix, il faut ajouter à la notion de répertoire de travail (working directory) celle de

répertoire privé (home directory). Le répertoire privé est le répertoire dans lequel

l’utilisateur se retrouvera lors de sa connexion au système. C’est l’emplacement où il

enregistrera ses fichiers par défaut. Cette notion n’a pas de sens avec Windows 98 ni même

avec Windows NT (NT ne créé pas automatiquement un répertoire par utilisateur), mais existe

sous XP et Vista.

1.10 Informations associées aux fichiers

L’entrée du répertoire contient des informations associées au fichier telles que :

• Le nom du fichier. La FAT-16 (MS-DOS) ne permet que des noms de 8 caractères suivis

d’une extension de trois caractères (format 8+3).

Exemple : essai.txt

L’extension indique le type du fichier. Seuls les .bat, .exe et .com sont exécutables. La

FAT-32 et NTFS (XP, Vista) autorisent les noms longs.

Unix permet des noms longs de 255 caractères avec différentiation des majuscules et des

minuscules. Il n’y a pas d’extension, le . est un caractère comme un autre. Un attribut

décide de la possibilité pour un programme de s’exécuter ou non.

• La date et l’heure de la dernière modification.

• La taille du fichier en octets.

• Les attributs de protection (sous Unix).

• Les numéros de bloc contenant les données du fichier. Ces informations ne sont pas

visibles par l’utilisateur.

Page 20: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

14

Exemple d’informations retournées par une commande dir sous Windows NT :

Le volume dans le lecteur D s'appelle MainNT Le numéro de série du volume est EB2A-9B11 Répertoire de D:\users\dut_info 09/10/00 16:36 <DIR> . 09/10/00 16:36 <DIR> .. 09/10/00 16:35 <DIR> essai 09/10/00 08:53 21 fichier_dos.tx t 09/10/00 08:53 20 fichier_unix.t xt 09/10/00 16:36 531 result.txt 6 fichier(s) 41 octets 8 242 147 328 octets libre s

Nous sommes sur le disque D dans le répertoire \users\dut_info. <DIR> indique la présence

d’un répertoire. Le . représente le répertoire courant. Le .. représente le répertoire père.

1.11 Protection des fichiers (Unix)

La protection des fichiers est inexistante sous Windows 98 (il n’y a en général qu’un

utilisateur sur le PC), correcte sous Windows XP et Vista (avec NTFS mais pas avec la FAT-

32 utilisée par défaut) et bonne sous Unix.

Sous Unix. Chaque utilisateur fait partie d’un groupe de travail. Les protections d’un fichier

concernent :

Le propriétaire du fichier Users (u)

Le groupe d’utilisateurs Group (g)

Les autres utilisateurs Others (o)

Les opérations concernées par les permissions rwx sont :

La lecture read (r)

L’écriture write (w)

L’exécution execute (x)

Page 21: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

15

Ces permissions rwx ont une signification particulière lorsqu’il s’agit d’un répertoire :

• r : ce droit permet uniquement de lire les noms des fichiers du répertoire.

• w : cette permission autorise la création et la destruction de fichiers dans ce répertoire.

• x : cette permission indique que l’on pourra passer par ce répertoire (avec une commande

cd).

Exemple du contenu d’un répertoire sous Unix :

drwxr-xr-x 4 dut dut 4096 jui 12 15:30 Desktop/

- rw-r--r-- 1 dut dut 18 oct 9 09:26 essai.txtdrwx------ 2 dut dut 4096 oct 9 09:48 nsmail/- rw-r--r-- 1 dut dut 0 oct 9 14:03 result.txtdrwx------ 2 dut dut 4096 oct 9 09:48 tmp/- rwxr--r-- 1 dut dut 0 oct 9 13:53 toto*

drwxr-xr-x 4 dut dut 4096 jui 12 15:30 Desktop/

- rw-r--r-- 1 dut dut 18 oct 9 09:26 essai.txtdrwx------ 2 dut dut 4096 oct 9 09:48 nsmail/- rw-r--r-- 1 dut dut 0 oct 9 14:03 result.txtdrwx------ 2 dut dut 4096 oct 9 09:48 tmp/- rwxr--r-- 1 dut dut 0 oct 9 13:53 toto*

drwxr-xr-x 4 dut dut 4096 jui 12 15:30 Desktop/

- rw-r--r-- 1 dut dut 18 oct 9 09:26 essai.txtdrwx------ 2 dut dut 4096 oct 9 09:48 nsmail/- rw-r--r-- 1 dut dut 0 oct 9 14:03 result.txtdrwx------ 2 dut dut 4096 oct 9 09:48 tmp/- rwxr--r-- 1 dut dut 0 oct 9 13:53 toto*

Permissions (rwx)user : group : others

d : directoryl : lien symbolique

user group taille

Date et heure de la dernièremodification

(pas d’année si année en cours)

Nom

Exercice 1.3 : donnez tout les renseignements que vous pourrez sur essai.txt , toto et

tmp .

Tout fichier dont le nom commence par un . est dit caché. Cela signifie simplement deux

choses :

1. Le fichier ne sera pas vu lors d’une commande de listage de fichier classique de type ls .

2. Ce fichier ne sera pas détruit par une commande de destruction de type rm si vous utilisez

un joker comme *.

Page 22: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

16

1.12 Tableau comparatif des systèmes de fichiers

Système de fichiers

FAT 16 FAT 32 NTFS EXT2FS

OS MS-DOS, Windows 95

Windows 98, XP, Vista

Windows NT, 2000, XP, Vista

linux

Format des noms 8.3 (255 avec VFAT)

8.3 (255 avec VFAT)

255 255

protection non non oui oui

Sensible à la casse non non Non [1] oui

Mécanismes de correction

non non oui oui

Taille max cluster 32 ko 4 ko 4 ko 4 ko

Taille max partition

(216 – 10)*32 ko = 2 Go

(228 – 10)*8 ko = 2 To

(264 * 4ko)

2 To pour un disque « de base »

4 To

[1] NTFS affiche la différence entre majuscule et minuscule, mais vous ne pouvez avoir dans

le même répertoire deux fichiers toto.txt et TOTO.txt.

1.13 Les fichiers textes

Le codage d’un caractère dans un fichier (on parle alors de fichier texte) doit utiliser un code

pour faire correspondre un octet (en général) à un caractère. Le code le plus connu et le plus

utilisé est le code ASCII (American Standard Code for Information Interchange). Mais il

existe aussi le code EBCDIC (Extended Binary Coded Decimal Interchange Code), code

propriétaire IBM utilisé pour ses gros ordinateurs (mainframes).

Le code ASCII est un jeu normalisé de 128 caractères codés sur 7 bits, devenu un standard

quasi universel. Il comporte tous les caractères alphanumériques non accentués et est lisible

par pratiquement n'importe quelle machine. Ce sont les 8 premières lignes du tableau suivant.

Page 23: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

17

Les 32 premiers codes sont utilisés comme caractères de contrôle pour représenter, par

exemple, une fin de ligne ou une tabulation.

Page 24: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

18

Le code ASCII ne contient pas de caractères accentués et il a été complété par le code ISO-

8859-1 (ou Latin 1). Ce n’est hélas pas le seul. Les 128 premiers caractères correspondent au

code ASCII, les 128 suivants aux caractères accentués et caractères spéciaux (voir les 8

dernières lignes du tableau).

Unicode est un jeu de caractères codé sur 16 bits (contre 7 ou 8 bits pour les standards

anciens) qui permet le codage des caractères utilisés par toutes les langues du monde au sein

d'une table unique. 16 bits permettent de coder 65 536 (2 puissance 16) caractères différents

ce qui couvre largement les besoins en la matière. Unicode est supporté par tous les sytèmes

d’exploitations « modernes ». Les 256 premiers caractères d'Unicode correspondent au jeu

ISO Latin 1.

Un fichier texte comporte un caractère spécial EOL (End Of Line) pour signaler la fin d’une

ligne. Ce caractère est différent suivant que le fichier est créé sous Unix ou bien sous

Windows.

Fin de ligne Unix 0x0A (line feed)

Fin de ligne Windows 0x0D 0x0A (carriage return, line feed)

C’est une des principales sources d’incompatibilité aux transferts de fichiers entre les deux

mondes.

Exercice 1.4 :

1) Sous Unix, donnez le fichier texte correspondant aux octets suivants : 43 0A 4F 0A 55 0A

43 0A 4F 0A 55 0A.

2) Sous Windows, donnez les octets correspondant au fichier précédent.

Page 25: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

19

2. Langages de programmation

2.1 Définitions

Langage machine

Langage assembleur

Langage évolué

ordinateur

hommecompilateur

assembleur

• Langage machine. L’ordinateur ne sait exécuter qu’un nombre limité d’opérations

élémentaires codées en binaire. C’est le langage machine. Les instructions sont

généralement codées avec un ou plusieurs octets.

• Langage assembleur (ou d’assemblage). Quand le programmeur veut écrire en langage

machine, au lieu d’écrire directement les instructions en binaire, il utilise un langage un

peu plus parlant quoique strictement équivalent, le langage assembleur. Celui-ci traduit les

codes binaires par des mnémoniques. Chaque microprocesseur a son propre langage

assembleur. Les langages machine et assembleur possèdent pratiquement les mêmes

instructions.

ADD A, B ≡ additionner (code 0101) les valeurs A (adresse mémoire 010010) et B

(adresse mémoire 010011) ≡ 0101010010010011.

• Langage évolué. C’est un langage général utilisable sur n’importe quel ordinateur. Il y en a

plusieurs : fortran, pascal, basic, C/C++, ADA, java, … Exemple :

Y = A*X + 2*B + C

à partir des variables A, B, C et X, cette instruction calcule l’expression mathématique et

range le résultat dans Y. Pour calculer le même genre d’instruction en assembleur, il

faudrait beaucoup d’instructions élémentaires (d’autant qu’il n’y généralement pas de

multiplication native dans un microprocesseur).

Page 26: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

20

Dans tous les langages évolués, on trouvera les notions suivantes :

� La variable : c’est un nom donné à un emplacement de la mémoire vive destiné à

contenir une information. La nature de cette information (entier, caractère, adresse, …)

est appelée son type.

� L’affectation : elle permet de calculer la valeur d’une expression et de la ranger dans

une variable.

� Le test conditionnel : il permet de faire un choix du genre « si le cours m’intéresse

alors j’écouterai le professeur, sinon je penserai à autre chose ».

� La répétition : elle permet d’exécuter une action jusqu’à satisfaire une condition du

genre « tant que je ne serai pas suffisamment bon en C, je travaillerai le cours de

programmation ».

2.2 Méthodologie pour l’écriture d’un programme

problème

analyse

programmation

Compilation/assemblage

Edition de liens

tests

programme exécutable

algorithme

programme en langage évolué

programme en langage machine

Page 27: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

21

L’analyse du problème commence en général par une réécriture du problème sous une forme

textuelle précise ou bien mathématique. On passe ensuite à l’écriture de l’algorithme.

Définition : un algorithme est une suite finie de règles à appliquer dans un ordre déterminé à

un nombre fini de données pour arriver, en un nombre fini d'étapes, à un certain résultat, et

cela indépendamment des données. Un algorithme est donc une suite finie d'instructions qui

sert à réaliser un travail, un peu à la manière d’une recette de cuisine.

En règle générale, un algorithme doit tenir sur une page. Si tel n’est pas le cas, il faut le

diviser en plusieurs parties indépendantes à l’aide de fonctions de façon à ce que chaque

partie tienne sur une page. On peut l’écrire de deux manières :

• A l’aide d’un langage algorithmique (ou un langage de programmation comme le C),

• A l’aide d’un ordinogramme.

Dans ce cours, nous commencerons l’écriture des algorithmes avec un langage algorithmique

simplifié (voir sa définition au §3.16), puis nous passerons progressivement au langage C dès

que nous aurons vu sa syntaxe. Nous n’utiliserons les ordinogrammes qu’à titre d’exemple

dans les deux exercices suivants.

Exercice 2.1 : calcul de la somme des N premiers nombres entiers. On dispose d’une fonction

LireEntier() qui permet de lire la valeur de N au clavier.

Exercice 2.2 : calcul de la moyenne de N notes. On dispose de la fonction LireEntier() qui

permet de lire la valeur de N ainsi que la valeur d’une note au clavier.

2.3 Le langage C

Le langage C a été inventé vers 1972 pour réécrire Unix dans un langage évolué et le porter

sur d’autres machines que le PDP 7 pour laquelle il avait été écrit à l’origine en assembleur.

Le langage C a été normalisé ANSI en 1988 (le C pré-ANSI est appelé le C Kernighan et

Ritchie ou K&R ou encore compatible).

Page 28: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

22

C’est un langage qui, par ses origines, est assez proche du matériel et donc bien adapté à

l’électronique. C’est un langage :

Evolué : il dispose des structures de contrôle d’un langage évolué ainsi que de la récursivité.

Impératif : il faut déclarer les variables (et leur type) et les fonctions avant de pouvoir les

utiliser.

Modulaire : le programme peut être découpé en modules indépendants qui seront compilés

séparément. C’est l’édition de liens qui regroupe les morceaux et crée l’exécutable.

Compilé : par opposition avec un langage interprété qui n’est pas traduit en langage machine.

Lors de l’exécution d’un programme interprété, un programme spécialisé, l’interpréteur, se

charge d’exécuter les instructions du langage sur une machine donné. Le langage compilé est

beaucoup plus rapide que le langage interprété.

Efficace : comme il est assez proche de l’ordinateur, le compilateur peut assez facilement

optimiser le code machine pour qu’il soit le plus rapide possible. Il est peu probable qu’un

programmeur humain écrive en assembleur un programme plus rapide qu’en C (sauf pour un

microprocesseur spécialisé de type DSP).

Très permissif : contrairement à Pascal ou à Fortran, le C permet d’écrire des absurdités que

le compilateur ne verra pas, notamment dans les opérations arithmétiques. Tout est permis, le

programmeur est supposé savoir ce qu’il fait ! ! !

Nous allons maintenant voir comment on définit en langage C :

� les variables,

� les affectations,

� les entrées-sorties (clavier, écran),

� les structures de choix,

� les structures de répétition conditionnelle.

Avec ces éléments, vous serez à même d’écrire des programmes élémentaires.

Page 29: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

23

3. Bases du langage C

3.1 Les variables

Une variable est un nom qui sert à repérer un emplacement en mémoire, dont on peut faire

évoluer la valeur au fil du déroulement du programme. Les noms de variables sont sensibles à

la casse. Les caractères qui les composent doivent être choisis parmi les 26 lettres majuscules

et minuscules de l’alphabet, les chiffres de 0 à 9 et l’underscore _. Le premier caractère du

nom ne doit pas être un chiffre. Il ne doit pas y avoir d’espace ni de caractères accentués dans

le nom. Le compilateur traite les noms de variable jusqu’à 32 caractères.

Noms corrects : A, A1, n_1, racine_carree

Noms incorrects : 1a, nombre 1, racine_carrée, nombre-1

Une variable peut contenir plusieurs types de données : nombre entier, nombre réel, caractère,

⇒ Il faut donc spécifier le type de la variable lors de sa déclaration.

A chaque type de variable correspond un nombre d’octets destinés à stocker un nombre limité

de valeurs différentes. Exemple :

Un entier non signé (>0) est stocké à l’aide de 4 octets et donc sa valeur est comprise entre 0

et 232-1 (soit 4 294 967 295).

Exemple de types :

1) int = nombre entier,

2) float = nombre réel,

3) char = caractère.

Exemples de déclaration :

int n, p; float valeur, X1, X2; char reponse;

Page 30: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

24

Vous devez écrire vos instructions de déclaration avant les instructions d’exécution.

Le compilateur réserve un emplacement mémoire pour la variable, mais cet emplacement

n’est pas initialisé. Vous ne pouvez prévoir quelle valeur sera stockée dans la variable si vous

ne l’initialisez pas vous-même. Deux méthodes :

� Au moment de la déclaration.

int n = 0, p = 100;

� Dans le programme avant sa première utilisation.

int n; … n = 0;

3.2 L’instruction d’affectation

L’instruction d’affectation a pour rôle :

1) de calculer la valeur de l’expression figurant à droite du signe =,

2) de ranger le résultat dans la variable se trouvant à gauche du signe =.

Exemple :

int n, p; n = 10; p = 2*n – 3;

instructions n p commentaire

déclaration - - les variables ne sont pas initialisées

n = 10 ; 10 - affectation d’une constante

p = 2*n – 3 ; 10 17 calcul de l’expression puis affectation

C’est après l’exécution de l’instruction que la variable à gauche du signe = change de valeur.

Attention à ne pas confondre l’affectation avec l’égalité mathématique.

Page 31: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

25

Exemple 1 : a = b;

Mathématiquement, cette expression signifie que a est égal à b pendant toute la durée du

problème.

En informatique, cette expression signifie que a prend la valeur de b au moment de

l’exécution de l’instruction.

⇒ en informatique, a = b; n’est pas équivalent à b = a; (alors que c’est la même

chose en mathématique)

Exemple 2 :

instructions a b commentaire

déclaration - - les variables ne sont pas initialisées

a = 5 ; 5 - affectation d’une constante

b = a + 1; 5 6 l’action de cette instruction est purement instantanée (au moment de son exécution)

a = 2; 2 6 le changement de la valeur de a n’affecte plus b (qui ne passe pas à 3)

Exemple 3 : a = a + 1;

Mathématiquement, cette expression n’a pas de sens.

En informatique, cette expression signifie que la nouvelle valeur de a (après exécution de

l’instruction) est égale à l’ancienne valeur de a (avant exécution de l’instruction) + 1 .

C’est une incrémentation.

Page 32: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

26

Exemple 4 : a + 5 = 3;

Mathématiquement, cette expression a un sens. C’est une banale équation.

En informatique, cette expression est fausse. On ne peut attribuer une valeur qu’à une

variable et pas à une expression.

Exercice 3.1 : remplir les tableaux suivants.

instructions a b commentaire

déclaration

a = 5 ;

b = a + 4;

a = a + 1;

b = a - 4;

instructions n1 n2 commentaire

déclaration

n1 = 5 ;

n2 = 7 ;

n1 = n2;

n2 = n1;

instructions n1 n2 commentaire

déclaration

n1 = 5 ;

n2 = 7 ;

n2 = n1;

n1 = n2;

Page 33: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

27

Exercice 3.2 : soit trois variables entières. Compléter le programme suivant pour permuter

leurs valeurs, de telle sorte que a → b, b → c et c → a.

main() { int a = 1, b = 2, c = 3, tmp; /* à compléter */ printf("a = %d, b = %d, c = %d\n ", a, b, c); }

3.3 Les types entier

Les types entiers permettent de représenter une partie des nombres entiers naturels et relatifs :

type nombre de bits intervalle

unsigned short int 16 0 à (216-1 = 65535)

short int 16 (-215=-32768) à (215-1 = 32767)

unsigned long int 32 0 à (232-1= 4 294 967 295)

long int 32 (-231=-2 147 483 648) à (231-1 = 2 147 483 647)

int 32* (-231=-2 147 483 648) à (231-1 = 2 147 483 647)

* : le type int dépend du compilateur utilisé qui est généralement lié au système

d’exploitation (ou au compilateur). Sur un système 32 bits, il est généralement codé sur 32

bits. Il est préférable de spécifier short ou long sinon le comportement de vos programmes

changera avec la machine cible (absence de portabilité).

Les nombres signés utilisent le codage en complément à 2. Les constantes de type int

s’écrive comme en mathématique :

short int n, o, p; n = 10; o = +15; p = -2542;

Page 34: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

28

Les opérateurs mathématiques sont de deux types : les opérateurs unaires qui ne portent que

sur un terme et les opérateurs binaires qui portent sur deux termes.

symbole opération

+, - addition, soustraction

* multiplication

/ division entière

% reste de la division entière (modulo)

- opérateur unaire de négation (ex : -b; )

Lorsque plusieurs opérateurs apparaissent dans une même expression, le compilateur respecte

des règles de priorités qui sont celles de l’algèbre traditionnelle.

priorité symbole commentaires

max - (négation)

moy *, /, % en cas d’égalité, le calcul s’effectue de gauche à droite

min +, - en cas d’égalité, le calcul s’effectue de gauche à droite

En utilisant des parenthèses, vous pouvez outrepasser ces règles de priorité en forçant le

calcul préalable de l’expression qu’elles contiennent.

Exercice 3.3 : quelles sont les valeurs des expressions suivantes ?

main() { int n = 8, p = 13, q = 29, result; result = n + p / q; printf("resultat = %d\n ", result); result = n + q / p; printf("resultat = %d\n ", result);

Page 35: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

29

result = (n + q) / p; printf("resultat = %d\n ", result); result = n + p % q; printf("resultat = %d\n ", result); result = n + q % p; printf("resultat = %d\n ", result); result = (n + q) % p; printf("resultat = %d\n ", result); result = n + p / n + p; printf("resultat = %d\n ", result); result = (n + p) / (n + p); printf("resultat = %d\n ", result); }

3.4 Les types flottants (réels)

Les types flottants permettent de représenter, de manière approchée, une partie des nombres

réels. La valeur d’un réel ne peut être ni trop grande, ni trop petite, ni trop précise. Le codage

en binaire est de la forme :

signe exposant (signé) mantisse (non signée)

type nombre de bits format valeur max (valeur min*)

précision max

float 32 1 + 8 + 23 2128 ≈ 10+38 2-23 ≈ 10-7

double 64 1 + 11 + 52 21024 ≈ 10+308 2-52 ≈ 10-15

long double 80 1 + 15 + 64 216384 ≈ 10+4932 2-64 ≈ 10-19

* : la valeur min se détermine à partir de la valeur max (ex : max = 10+38 ⇒ min = 10-38)

Les flottants s’écrivent en C sous la forme : mantisse + exposant (avec un point décimal et

pas une virgule) tels que :

+4.25E+4 ou encore -58.0e-25

Page 36: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

30

C’est la notation scientifique traditionnelle comme sur une calculatrice. Le E signifie 10

puissance. Les constantes de type flottant s’écrivent sous la forme :

float n, o, p; n = 12.43; o = -0.38e-33; p = -4.0;

On retrouve les mêmes opérateurs binaires (portant sur 2 termes) que pour les entiers (sauf le

%) ainsi que l’opérateur unaire de négation. Les règles de priorité restent les mêmes.

Attention : l’expression 5/2 sera calculée en entier (résultat = 2) même si le résultat est rangé

dans un flottant. Il faut utiliser 5./2. pour obtenir 2,5.

main() { float p; p = 5/2; printf("resultat = %f\n",p); p = 5.0/2.0; printf("resultat = %f\n",p); }

3.5 Les conversions de type

En C, vous pouvez mélanger dans une expression des variables de types différents sans

effectuer aucune conversion contrairement au langage Pascal où la conversion est obligatoire

(le langage est dit fortement typé). La conversion en C est implicite (elle est effectuée

automatiquement par le compilateur) alors qu’elle est explicite en Pascal, en Fortran ou en

Ada.

Le compilateur C réalise automatiquement la conversion de type en respectant :

1) la règle de priorité des opérateurs,

2) la règle « du type le plus petit (en nombre d’octets) vers le type le plus grand » (c’est la

règle la moins dégradante pour les données). En général, tous les types plus petits qu’un

int sont systématiquement convertis en int avant toute autre conversion.

Page 37: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

31

Exemple :

int n, p; float x, y;

Dans l’expression y = n + x , n est d’abord converti en flottant puis l’addition est

effectuée en flottant et le résultat est rangé dans le flottant y .

Dans l’expression y = n*p + x , n*p est calculé en entier, le résultat est converti en

flottant puis additionné à x en flottant et le résultat est rangé dans y .

Exercice 3.4 : soient les instructions suivantes.

int n, p; float x; n = 10; p = 7; x = 2.5;

Donnez le type et la valeur des expressions suivantes :

x + n % p; x + n / p; (x + n) / p; 5. * n; (n + 1) / n; (n + 1.0) / n;

Il est à noter que la conversion d’int vers float est non dégradante (on ne perd rien dans la

conversion) alors que la conversion float → int est dégradante (on prend la partie entière

du réel que l’on met dans l’entier, la partie fractionnaire est perdue).

Il est possible de convertir explicitement une expression grâce à un cast en mettant

l’expression entre parenthèse et en la précédant de (type). Par exemple :

x + (float)n/p;

Page 38: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

32

Exercice 3.5 : quels résultats donne le programme suivant ?

main() {

int n=15, p=4; float x;

x = n/p; printf("resultat = %f\n",x); x = (float)n/p; printf("resultat = %f\n",x); x = (float)(n/p); printf("resultat = %f\n",x); }

Une règle élémentaire de prudence consiste à ne jamais mélanger des types signés avec des

types non signés car les conversions n’ont généralement pas de sens. Si vous introduisez une

variable non signée dans une expression signée, utilisez un cast (exemple typique, un indice

de boucle).

main() {

long int n=-15, p; /* signé */ unsigned long int x; /* non signé */

... p = n + (long int)x; ... }

3.6 Les types char

Les types char permettent de coder des caractères en utilisant le code ASCII. Mais ils peuvent

aussi être utilisés comme des petits entiers signés ou non signés ou bien directement en

binaire si le programme travaille sur des octets.

type nombre de bits intervalle

unsigned char 8 0 à (28-1 = 255)

char 8 (-27=-128) à (27-1 = 127)

Page 39: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

33

Les constantes de type char s’écrivent sous la forme :

char n, o, p; n = ‘s’; /* un caractère normal */ o = 100; /* un petit entier */ p = ‘\n’; /* le caractère spécial line feed */ p = 0x0A ; /* toujours un line feed */

Les caractères imprimables sont simplement écrits entre quotes et les caractères spéciaux

utilisent l’antislash (backslash).

Tout ce qui s’applique aux types entiers s’applique aux types char (opérateurs, priorités,

conversions). C’est une des grandes qualités du C (mais c’est aussi un défaut) de pouvoir

effectuer des opérations arithmétiques avec des variables de type char . Les possibilités de

confondre caractères et petits entiers sont nombreuses.

Exemple :

main() { char n; n = 'A'; printf("Caractere = %c\n",n); printf("Code ASCII en hexadecimal = %x\n",n); printf("Code ASCII en decimal = %d\n",n); }

Exercice 3.6 : soient trois variables c1, c2 et c3 de type char. Ecrivez un programme

permettant de permuter le contenu de ces variables.

3.7 Communiquer avec le programme : les entrées-sorties standard

La manière la plus simple de lire un caractère à la fois sur l’entrée standard (qui est

normalement le clavier) est d’utiliser la fonction getchar :

Page 40: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

34

int getchar( void)

type de la variable d’entrée

type de la variable de sortie

Cette fonction n’accepte en entrée aucune variable (entrée de type void qui veut dire vide) et

retourne un entier (type int ) dont la valeur est égale au code ASCII du caractère tapé au

clavier suivi d’un « retour chariot ».

Pour la sortie d’un caractère, on utilise la fonction putchar :

int putchar(int)

Cette fonction accepte un entier en entrée (celui retourné par getchar() par exemple) et

retourne un entier (type int ). putchar(c) envoie le caractère c sur la sortie standard

(l’écran par défaut) et retourne le caractère écrit ou -1 en cas d’erreur.

Exemple 1 : ce programme accepte un caractère en entrée puis le restitue en sortie.

#include <stdio.h> /* déclaration de fonctions getchar et putchar */ main() { int c; c=getchar(); putchar(c); }

Exemple 2 : ce programme accepte un caractère en entrée puis le restitue en sortie et

recommence tant que vous ne tapez pas Ctrl-d pour sortir de la boucle (Ctrl-z sous Windows).

#include <stdio.h> main() {

Page 41: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

35

int c; c=getchar(); while (c != EOF) { putchar(c); c=getchar(); } }

Sous Unix, on peut rediriger la sortie standard (StdOut) d’un programme (l’écran par défaut)

vers un fichier grâce à la commande :

prog > sortie.txt

Mais il est aussi possible de remplacer le clavier comme entrée standard (StdIn) par un fichier

en utilisant la commande :

prog < entree.txt

De plus, il existe encore une autre sortie, la sortie des erreurs (StdErr, par exemple les

messages d’erreurs du compilateur), qui peut être redirigée vers un fichier grâce à :

prog 2> error.txt

ou bien vers le même endroit que la sortie standard en tapant :

prog 2>&1

C’est grâce aux entrées-sorties standards que le pipe fonctionne. Quand on tape :

prog1 | prog2

on redirige la sortie standard de prog1 vers l’entrée standard de prog2 (le pipe « branche »

StdOut1 sur StdIn2).

Page 42: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

36

Reprenons l’exemple 2. Si vous créez avec un éditeur un petit fichier texte (entree.txt )

comportant des lettres majuscules et minuscules ainsi que des chiffres puis que vous tapez la

commande suivante :

exemple2 < entree.txt > sortie.txt

Vous obtenez un fichier sortie.txt identique à entree.txt . Ainsi, la fonction

getchar() peut accepter un flot de texte issu d’un fichier plutôt qu’un seul caractère au

clavier et la fonction putchar peut renvoyer un caractère dans un fichier.

Exemple 3 : le programme suivant accepte un caractère en entrée puis le restitue en sortie

mais converti en minuscule et recommence tant que vous ne tapez pas Ctrl-d pour sortir de la

boucle (en manuel au clavier) ou tant que le programme n’a pas atteint la fin du fichier EOF

(pour une entrée via un fichier). La fonction tolower est définie dans ctype.h . Elle

convertit les lettres majuscules en minuscules et retourne les autres caractères tels quels.

#include <stdio.h> #include <ctype.h> main() { int c, cm; c=getchar(); while (c != EOF) { cm = tolower(c) ;

putchar(cm); c=getchar(); } }

Cet exemple fonctionne aussi bien au clavier qu’avec le fichier entree.txt .

Il existe en C des instructions plus sophistiquées pour afficher du texte à l’écran ou bien pour

lire du texte à partir du clavier : printf et scanf .

Page 43: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

37

3.8 L’instruction printf

Nous avons déjà vu à plusieurs reprises des exemples simples de cette instruction :

int n=10, p=25; printf("nombre : %d, valeur %d ", n, p);

On a entre double quotes ("") soit du texte, soit un ou plusieurs codes de format (%d) qui

seront remplacés par la valeur d’une ou de plusieurs variables lors de l’affichage. On trouve

ensuite les variables à afficher séparées par une virgule. Il doit y avoir autant de codes de

format que de variables à afficher. Il existe de nombreux codes de formats (%d correspond à

un entier) pour tous les types de variables. Ils commencent tous par un %. Tout ce qui n’est

pas un code de format mais qui se trouve entre doubles quotes est affiché tel quel.

On peut omettre les codes de format pour afficher seulement du texte :

printf("bonjour");

Il est possible, quoique non recommandé, de remplacer une variable par une expression :

printf("la somme de %d et de %d est %d", n, p, n+p) ;

Le code de format %c permet d’afficher un caractère :

int n=10; char c=’c’; printf("nombre : %d, caractère %c ", n, c);

Le code de format %e permet d’afficher un nombre flottant en notation exponentielle et le

code %f permet d’afficher un nombre flottant en notation décimale.

float x=1.23456e4; printf("notation exp : %e, notation déc %f ", x, x) ;

Le formatage des données permet de contrôler le nombre de chiffres affichés à l’écran. Dans

ce texte, le caractère ^ sert à matérialiser un espace lors de l’affichage. En insérant un nombre

dans le code de format après le %, on précise le gabarit d’affichage, c’est-à-dire le nombre

minimal de caractères à utiliser pour afficher la valeur du nombre. Si le nombre peut

s’afficher avec moins de caractères, printf le fera précéder d’un nombre suffisant

d’espaces. Par contre, si le nombre nécessite plus de caractères pour s’afficher que vous en

avez spécifiés, printf utilisera le nombre de caractères nécessaire. Exemples :

Page 44: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

38

printf("%3d", n);

valeur affichage

n = 20 ^20

n = 3 ^^3

n = -5 ^-5

n = 2358 2358

n = -5200 -5200

printf("%10f", x);

valeur affichage

x = 1.2345 ^^1.234500

x = 12.345 ^12.345000

x = 1.2345E5 123450.00000

Par défaut, les flottants sont affichés avec 6 chiffres à droite du point décimal. Dans l’exemple

précédent, %10f signifie « en utilisant 10 caractères (y compris le .) avec au moins 6 chiffres

après le point ». Il est possible de modifier ce nombre est écrivant %A.Bf qui signifie « au

minimum A caractères dont au moins B après le point décimal ». Exemples :

printf("%10.3f ", x);

valeur affichage

x = 1.2345 ^^^^^1.234

x = 1.2345E3 ^^1234.500

x = 1.2345E7 12345000.000

Page 45: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

39

printf("%12.4e", x);

valeur affichage

x = 1.2345 ^^1.2345e+00

x = 123.456789E8 ^^1.2345e+10

Chaque instruction printf affiche ses informations à la suite de celles qui ont déjà été

affichées par un printf précédent, ce qui revient à dire qu’il n’y a pas de passage à la ligne

entre deux printf . Exemple :

int n=10; char c=’c’; printf("nombre : %d, ", n); printf("caractère %c", c);

Les deux printf précédent affichent exactement la même chose que le printf suivant :

printf("nombre : %d, caractère %c", n, c);

Le changement de ligne est réalisé avec le caractère non imprimable \n placé au milieu ou à

la fin d’un printf :

printf("nombre : %d\ncaractère %c", n, c);

Il est aussi possible d’afficher une tabulation avec \t comme dans :

printf("\n\tTerminé\n\n");

Exercice 3.7 : soient les déclarations :

int qte=50; char cat=’B’; char art=’S’;

Ecrivez le programme permettant d’afficher les variables de la manière suivante :

50 articles S, de la catégorie B

Quel sera le résultat si art est déclarée de la manière suivante :

char art=’\n’;

Page 46: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

40

Exercice 3.8 : écrivez le programme qui calcule le prix TTC d’un nombre donné d’articles

d’un prix unitaire égal à 10.51 euros, compte tenu d’un taux de TVA de 19,6 %. Les résultats

seront affichés de la manière suivante :

nombre d'articles : 15

prix unitaire HT : 10.51

prix total TTC : 188.55

3.9 L’instruction scanf

La lecture au clavier d’un entier que l’on range dans la variable n s’écrit en langage C :

scanf("%d", &n);

Lorsque le programme exécute cette instruction, il attend que vous tapiez une valeur au

clavier suivie d’un retour chariot (↵). Le format (entre " ") est similaire à celui de l’instruction

printf , mais on voit que la variable est spécifiée par &n (et pas seulement n). Le & qui

précède n signifie « adresse de n ». Par contre, le gabarit, comme %3d, est interdit. Exemples

d’entrées entières :

12 ↵ 0 ↵ -1 ↵ +1234 ↵

La lecture d’un nombre flottant s’effectue avec l’instruction suivante :

scanf("%f", &x);

scanf("%e", &x);

Les codes %e et %f jouent strictement le même rôle. Exemples d’entrées flottantes :

-12 ↵ 0.5 ↵ -1. ↵ +1.2e-4 ↵ 32e45 ↵

Attention : il ne faut pas placer de texte avant ou après le code de format ni même d’espace.

L’instruction suivante n’est pas légale :

scanf("Entrez une valeur : %f", &x);

Page 47: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

41

Il faut écrire :

printf("Entrez une valeur : ");

scanf("%f", &x);

Si vous souhaitez lire deux valeurs numériques sur la même ligne, vous pouvez utiliser

l’instruction (sans aucun espace entre les " ) :

scanf("%d%d", &n, &p);

Les deux valeurs doivent être saisies au clavier séparées par au moins un espace.

12^-15 ↵

L’entrée suivante est identique à la précédente car les codes de format %d ou %f sautent tous

les blancs qui précèdent une valeur.

^^^12^^^-15 ↵

Exercice 3.9 : modifiez le programme de l’exercice 3.8 afin de pouvoir saisir le nombre

d’articles et le prix unitaire.

Attention : lorsque l’utilisateur fournit trop peu de données, scanf continue d’attendre les

données suivantes. Exemple :

scanf("%d%d", &n, &p);

L’entrée 12^-15 ↵ donnera le même résultat que 12 ↵ suivi de -15 ↵. Supposons qu’à

cette même instruction, vous fournissiez :

12^-15^1254 ↵

Il y a maintenant trop de données. scanf va mettre bien 12 dans n, -15 dans p mais il va

garder 1254 en mémoire dans le buffer clavier pour la prochaine lecture avec scanf . En

cas de besoin, vous pouvez purger le buffer clavier à l’aide de l’instruction :

fflush(stdin);

Le code de format %c permet de lire un caractère. Ainsi, l’instruction suivante :

Page 48: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

42

char c1 ; ... scanf("%c", &c1);

lit un caractère au clavier et le range dans c1 . Si vous souhaitez lire plusieurs caractères à la

suite, il suffit de les taper successivement et de les faire suivre d’un retour chariot. Exemple :

scanf("%c%c%c", &c1, &c2, &c3) ;

Avec l’entrée :

abc ↵

a est rangé dans c1 , b dans c2 et c dans c3 . L’espace n’est en aucun cas un séparateur

comme pour les nombres. C’est un caractère comme un autre. Si vous tapez :

a^bc ↵

a va dans c1 , un espace va dans c2 , b va dans c3 et c est disponible pour la lecture suivante.

En règle générale, évitez de lire plusieurs valeurs (numériques ou caractères) avec une

même instruction scanf.

Exercice 3.10 : modifiez le programme de l’exercice 3.7 afin de pouvoir saisir le nom de

l’article, sa catégorie ainsi que la quantité.

3.10 Structure de choix : l’instruction if

L’instruction if permet de réaliser des choix dans un programme comme le montre l’exemple

suivant :

main() { int n, p;

printf("Donnez deux nombres entiers : "); scanf("%d%d", &n, &p);

Page 49: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

43

if (n < p) printf("croissant\n"); else printf("non croissant\n"); printf("au revoir\n"); }

Sa compréhension est assez intuitive. Si n < p , alors on exécute l’instruction :

printf("croissant\n");

sinon on exécute l’instruction :

printf("non croissant\n");

Dans tous les cas, on exécute la dernière instruction du programme :

printf("au revoir\n");

Le compilateur se moque de la présentation des instructions. Ainsi, la forme :

if (n < p) printf("croissant\n"); else printf("non croissant\n");

est strictement équivalente à :

if (n < p) printf("croissant\n"); else printf("non croissant\n");

Mais il est fortement conseillé d’utiliser la première forme pour une meilleure lisibilité du

programme et donc une maintenance plus facile.

Dans l’exemple précédent, chacune des deux parties du choix se limite à une instruction

(printf ). Il est possible d’en placer plusieurs à condition de les placer dans un bloc entre

{}. Voyons une nouvelle version de notre exemple :

main() { int n, p, maxi;

printf("Donnez deux nombres entiers : "); scanf("%d%d", &n, &p);

Page 50: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

44

if (n < p) { maxi = p; printf("croissant\n"); } else { maxi = n; printf("non croissant\n"); } printf("Le plus grand des deux nombres est : %d\n" , maxi); }

L’instruction if correspond maintenant à :

if (n < p) bloc_instructions_1 else bloc_instructions_2

ce qui équivaut dans notre exemple à : si n < p alors maxi prend la valeur de p et printf

sinon maxi prend la valeur de n et printf .

Les blocs d’instructions peuvent contenir n’importe quelles instructions du C y compris

d’autres instructions if . Un bloc peut ne contenir qu’une seule instruction :

if (n < p) { printf("croissant\n"); }

ou bien une instruction vide (parfaitement légale en C) :

if (n < p) { ; }

ou bien aucune instruction :

if (n < p) { }

Il est possible d’avoir un bloc dans le if et une instruction dans le else :

if (n < p) { maxi = p; printf("croissant\n"); } else printf("non croissant\n");

ou le contraire.

Page 51: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

45

if (n < p) printf("croissant\n"); else { maxi = n; printf("non croissant\n"); }

La branche else de l’instruction if n’est pas obligatoire. Ainsi, l’enchaînement :

if (n < p) printf("croissant\n"); printf("au revoir\n");

est tout à fait acceptable. Si n < p, le programme exécute le printf . Dans tous les cas, le

dernier printf est exécuté. Cette remarque est aussi valable avec un bloc d’instructions :

if (n < p) { maxi = p; printf("croissant\n"); } printf("au revoir\n");

D’une manière générale, l’instruction if se présente de la manière suivante (La branche

else est optionnelle) :

if (condition) instruction_1 [else instruction_2]

où instruction_1 et instruction_2 sont :

• une instruction simple (terminée par un ;),

• une instruction structurée (comme un autre if ),

• un bloc d’instructions entre {}.

Voyons maintenant la condition plus en détail.

3.11 Structure de choix : les conditions en C

Les opérateurs de comparaisons ont deux significations suivant qu’ils s’appliquent à deux

types numériques (float ou int) ou à deux types caractères (char) :

Page 52: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

46

opérateur signification « numérique » signification « caractère »

== égal identique

< inférieur de code ASCII inférieur

> supérieur de code supérieur

<= inférieur ou égal de code inférieur ou égal

>= supérieur ou égal de code supérieur ou égal

!= non égal différent

Les comparaisons sur les caractères utilisent le code ASCII (sur un octet) comme un petit

entier. Dans ce code, les lettres sont rangées dans leur ordre naturel, à savoir a...z, A...Z, 0...9

de façon à ce que les conditions suivantes soient toujours vraies :

‘a’ < ‘b’, ‘A’ < ‘B’, ‘0’ <’1’

Les opérateurs de comparaison sont moins prioritaires que les opérateurs arithmétiques. La

condition fausse est égale à 0 et la condition vraie est différente de 0. Voici quelques

exemples de comparaison :

int n, p; char c1, c2;

n != p vrai si la valeur de n est différente de celle de p

n + 3 == p vrai si la valeur de n+3 est égale à celle de p

n + 3 < 2 * p vrai si la valeur de n+3 est inférieure à celle de 2*p

n * p + 2 * n < 5 vrai si la valeur de n*p+2*n est inférieure à 5

c1 <= c2 vrai si le caractère dans c1 est avant (ou au même endroit que) le caractère dans c2 dans la table ASCII

c1 == ‘e’ vrai si le caractère dans c1 est le caractère e

c1 < ‘g’ vrai si le caractère dans c1 est avant le caractère g dans la table ASCII

n = 0 toujours faux

n = -5 toujours vrai

Page 53: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

47

Attention aux tests d’égalité avec des nombres flottants. Il ne faut pas oublier que l’égalité

entre deux réels est plus difficile à réaliser qu’entre deux entiers. Deux flottants égaux sont

deux nombres dont tous les bits sont identiques (y compris 15 chiffres après la virgule).

Exercice 3.11 : écrivez un programme qui lit deux nombres flottants puis qui demande à

l’utilisateur s’il souhaite les afficher dans l’ordre croissant ou décroissant. Dans les deux cas,

la réponse de l’utilisateur sera fournie sous forme d’un caractère seul : la lettre O sera

interprétée comme une réponse positive à la question tandis que tout autre caractère sera

interprété comme une réponse négative.

Il est possible de combiner un nombre quelconque de conditions simples à l’aide des

opérateurs logiques :

opérateur signification

&& et

|| ou

! non

Quelques exemples :

(a<b)&&(c<d) vrai si les deux conditions a<b et c<d sont vraies

(a<b)||(c<d) vrai si l’une au moins des deux conditions a<b et c<d est vraie

!(a<b) vrai si la condition a<b est fausse

((a<b)&&(c<d))||(i==5) vrai si les deux conditions a<b et c<d sont vraies ou si i=5

Les opérateurs && et || sont les moins prioritaires de ceux que nous avons vus. Dans les deux

premières expressions, les parenthèses ne sont pas indispensables (mais conseillées pour la

lisibilité). Dans la troisième elles sont obligatoires. Dans la quatrième expression, certaines

parenthèses pourraient être supprimées.

Page 54: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

48

Exercice 3.12 : modifiez le programme de l’exercice 3.11 de façon à ce que les lettres O et o

soient prises comme une réponse positive.

La structure suivante permet la réalisation de choix multiples :

if (condition1) { bloc1

} else if (condition2) {

bloc2 } else if (condition3) {

bloc3 }

Il faut comprendre :

• si la condition1 est vraie alors le programme exécute le bloc1,

• sinon si la condition2 est vraie alors le programme exécute le bloc2,

• sinon si la condition3 est vraie alors le programme exécute le bloc3.

L’exemple suivant est très utilisé pour tester plusieurs réponses de l’utilisateur à une question :

if (reponse == 1) { /* faire ceci */

} else if (reponse == 2) {

/* faire cela */ } else if (reponse == 3) {

/* ou encore cela */ }

D’une manière plus générale, il est possible d’imbriquer les instructions if :

if (condition1) { if (condition2) {

bloc1 /* si condition1 et condition2 vraies */ } else {

bloc2 /* si condition1 vraie et condition2 fausse * / }

} else {

bloc3 /* si condition1 fausse */ }

Page 55: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

49

Un strict respect de la présentation du programme (indentation, passage à la ligne, ...) est

indispensable afin de préserver sa lisibilité.

Exercice 3.13 : écrivez un programme qui, à partir d’un montant lu en donnée, détermine un

montant net obtenu par l’application d’une remise de :

• 10 % si montant est compris entre 1000 et 5000 € (bornes inclues),

• 20 % si montant est supérieur à 5000 €.

3.12 Structure de choix : l’instruction switch

Nous avons vu précédemment qu’il était possible d’utiliser des instructions if pour réaliser

un choix multiple (on parle d’un aiguillage). Il existe en C une instruction spécialisée pour

cela, le switch. Le programme suivant en donne un exemple.

main() { int n;

printf("Donnez un nombre entier : "); scanf("%d", &n);

switch (n) { case 0 : printf("nul\n"); break ; case 1 : printf("un\n"); break ; case 2 : printf("deux\n"); break ; } printf("au revoir\n"); }

Ce programme évalue la valeur de l’expression entière se trouvant entre parenthèses après le

switch (ici, n). Ensuite, il recherche la branche case qui correspond à cette valeur et

exécute la liste d’instructions se trouvant dans cette branche ainsi que toutes les autres

branches case se trouvant en dessous. Dans notre exemple, l’instruction break est

obligatoire afin de sortir du switch après traitement de la branche correspondant à la valeur

de n.

Page 56: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

50

Exercice 3.14 : dans le programme précédent, supprimez le break de la branche case 0.

On entre la valeur 0. Que se passe-t-il ?

Il est possible de définir une branche par défaut pour traiter le cas où la valeur de n serait

différente des valeurs testées dans les instructions case . C’est l’étiquette default .

Exemple :

main() { int n;

printf("Donnez un nombre entier positif : "); scanf("%d", &n);

switch (n) { case 0 : printf("nul\n"); break ; case 1 : printf("un\n"); break ; case 2 : printf("deux\n"); break ; default : printf("trop grand\n"); } printf("au revoir\n"); }

Si n est différent de 0, 1 ou 2, c’est la branche default qui est utilisée. Les instructions

après un case ne sont pas obligatoires pas plus que le break . Vous pouvez écrire :

switch (n) { case 0 : printf("nul\n"); break ; case 1 : case 2 : printf("deux\n"); default : printf("grand\n"); }

La syntaxe générale du switch est :

switch (expression) { case constante_1 : [suite_instructions_1] case constante_2 : [suite_instructions_2] case constante_3 : [suite_instructions_3]

[default : suite_instructions_4] }

Page 57: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

51

Où ce qui est entre crochets est facultatif et :

• expression est une expression entière quelconque (int ou char),

• constante est une expression constante d’un type entier quelconque (int ou char),

• suite_instructions est une suite d’instructions quelconques.

Ainsi le switch peut être utilisé pour traiter des caractères :

switch (n) { case ‘a’ : ... case ‘X’ : ...

}

3.13 Structure de répétition conditionnelle : l’instruction do... while

Les structures de répétition (on parle aussi de boucles) permettent d’exécuter à plusieurs

reprises une suite d’instructions. Dans une structure de répétition conditionnelle, la poursuite

de la répétition dépend d’une condition. L’instruction do ... while() permet d’effectuer

une répétition dans un programme comme le montre l’exemple suivant :

main() { int n; do {

printf("Donnez un nombre entier : "); scanf("%d", &n); printf("voici son carré : %d\n", n*n);

} while (n != 0);

printf("Fin du programme\n "); }

Elle signifie « exécuter le bloc d’instructions entre {} tant que n est différent de 0 ». La

condition entre parenthèse après le while est donc une condition de poursuite et non une

condition d’arrêt. D’une façon générale, l’instruction se présente de la manière suivante :

do instruction while (condition) ;

Attention au ; à la fin du while.

Page 58: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

52

Comme pour le if , l’instruction peut être une instruction simple, une instruction structurée

ou bien un bloc d’instructions. Cette instruction est toujours exécutée au moins une fois

(puisque le test de la condition se trouve après l’instruction). Il est possible de faire une

boucle infinie en écrivant :

do instruction while (1) ;

En effet, en C, la condition vraie vaut ≠ 0 et la condition fausse vaut 0. C’est la raison pour

laquelle on définit souvent un type booléen (qui n’existe pas en C) comme un char qui peut

prendre la valeur 0 ou 1 ainsi que des valeurs TRUE et FALSE comme suit :

#define BOOL char /* les lignes commençant par # s ont */ #define TRUE 1 /* destinées au preprocesseur et pas au */ #define FALSE 0 /* compilateur */

main() { int n; BOOL toto = TRUE ; do {

printf("Donnez un nombre entier : "); scanf("%d", &n); printf("voici son carré : %d\n", n*n);

} while (toto == TRUE);

printf("Fin du programme\n "); }

Dans ce programme, le préprocesseur va remplacer chaque occurrence de BOOL par char, de

TRUE par 1 et de FALSE par 0. Le programme obtenu après remplacement sera ensuite

compilé.

3.14 Structure de répétition conditionnelle : l’instruction while...

La décision de poursuite de la répétition dans l’instruction do... while se trouve à la fin

de la boucle d’exécution des instructions. En C, il existe une autre structure de répétition

conditionnelle où ce test a lieu au début de la boucle, l’instruction while . Le programme de

la page précédente a été modifié pour utiliser un while au lieu d’un do... while .

Page 59: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

53

main() { int n;

printf("Donnez un nombre entier : "); scanf("%d", &n); /* ou bien n = 1 par exemple */ printf("voici son carré : %d\n", n*n);

while (n != 0) {

printf("Donnez un nombre entier : "); scanf("%d", &n); printf("voici son carré : %d\n", n*n);

}

printf("Fin du programme\n "); }

Comme le test a lieu en premier, il faut commencer par saisir une valeur de n avant de rentrer

dans le while (sinon, n n’est pas initialisé) ou bien initialiser n avec une valeur différente de

0 pour forcer le programme à rentrer dans le while.

Conclusion : les variables intervenant dans la condition doivent être initialisées avant le

while , ce qui n’est pas forcement nécessaire pour un do... while (cela peut être fait

dans le premier tour de la boucle). D’une façon générale, l’instruction se présente de la

manière suivante :

while (condition) instruction

Attention, il n’y a plus de ;.

D’une manière générale, on démontre que tout programme peut s’écrire avec une structure de

choix et une structure de répétition conditionnelle. Les instructions while et do ...

while sont donc redondantes, mais commodes à utiliser.

3.15 Structure de répétition inconditionnelle : l’instruction for...

Dans une structure de répétition inconditionnelle, la répétition est effectuée un nombre fixe de

fois, ce nombre dépendant d’un compteur. Pour imposer un nombre de tours fixe dans un

while , il faut définir un compteur de boucle et tester une condition sur sa valeur pour sortir

du while . Comme par exemple dans le programme suivant :

Page 60: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

54

main() { int i; i = 0; while ( i < 5 ) {

printf("Voici un nombre entier : %d\n", i); printf("Voici son carré : %d\n", i*i); i = i + 1;

} printf("Fin du programme\n ");

}

Le programme va tourner 5 fois dans la boucle pour i = 0, 1, 2, 3 et 4. Pour cela, trois

éléments doivent être définis :

1) la valeur de départ (i = 0),

2) la valeur d’arrivée (i=5),

3) et l’incrément (ici, 1).

Il est possible de paramétrer le nombre de tours dans la boucle en écrivant :

main() { int i, stop;

printf("Donnez le nombre de tours : "); scanf("%d", &stop);

i = 0; while (i < stop) {

printf("Voici un nombre entier : %d\n", i); printf("Voici son carré : %d\n", i*i); i = i + 1;

} printf("Fin du programme\n ");

}

Le nombre de tours dans la boucle n’est plus connu à l’écriture du programme, mais

seulement au moment de son exécution.

L’instruction for permet de simplifier l’écriture de la boucle inconditionnelle avec compteur.

Page 61: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

55

main() { int i; for(i = 0; i < 5; i = i + 1) {

printf("Voici un nombre entier : %d\n", i); printf("Voici son carré : %d\n", i*i);

} printf("Fin du programme\n ");

}

Il est à noter que l’instruction d’incrémentation i = i + 1; s’écrit aussi i++; . D’une

façon générale, l’instruction se présente de la manière suivante :

for (avant; condition; fin_de_tour) instruction

où :

• avant est une instruction simple qui sera exécutée avant le premier tour de boucle. C’est

généralement l’initialisation du compteur.

• condition est la condition de poursuite de la boucle examinée avant chaque tour de

boucle (y compris le premier).

• fin_de_tour est une instruction simple qui sera exécutée à la fin de chaque tour de

boucle. C’est généralement l’incrémentation du compteur.

• instruction est une instruction au sens large. Il peut s’agir d’un autre for (les for

imbriqués sont tout à fait utilisables).

Cette instruction est strictement équivalente à :

avant; while (condition) {

instruction fin_de_tour;

}

Il est possible d’utiliser un for pour faire autre chose qu’une simple boucle avec compteur.

Ceci est fortement déconseillé pour préserver la lisibilité et donc faciliter la maintenance du

programme. Il est aussi fortement conseillé de ne pas modifier la valeur du compteur de

boucle dans la boucle sous peine d’obtenir un comportement hasardeux durant l’exécution.

L’instruction break (vu avec le switch ) est utilisable dans une boucle. Elle permet de

Page 62: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

56

quitter la boucle en cours d’exécution. Vous n’avez le droit de l’utiliser que si vous pouvez

démontrer que le programme n’est pas faisable autrement (c’est-à-dire jamais).

Exercice 3.15 : modifiez le programme de la page 54 (boucle paramétrée) pour utiliser une

boucle for à la place du while . Insérez l’instruction i = i - 1; à la fin de la boucle.

Que va-t-il se passer ?

Exercice 3.16 : écrivez un programme qui affiche un nombre n d’entiers consécutifs à partir

d’une valeur p, n et p étant lues en données.

3.16 Algorithmes élémentaires

Vous disposez pour écrire les algorithmes suivants :

• de variables de type entier , caractère et réel ,

• des fonctions LireEntier() , LireCar() , LireRéel() ,

• de la fonction affiche " texte à afficher " variable ,

• du test conditionnel si condition vraie , sinon ,

• des deux boucles tant que condition vraie et faire tant que

condition vraie .

Programme 1 (comptage) : écrivez un programme qui lit une ligne de texte, c’est-à-dire une

suite de caractères terminée par un retour chariot ↵ (fin de ligne \n) et qui affiche :

1) le nombre de caractères contenu dans la ligne,

2) le nombre de e contenu dans la ligne,

3) le pourcentage de e par rapport au nombre total de caractères.

Programme 2 (comptage) : écrivez un programme qui lit 10 notes entières et qui affiche le

pourcentage de notes supérieures à 10.

Programme 3 (accumulation) : écrivez un programme qui lit 10 notes entières et qui

affiche leur somme.

Programme 4 (accumulation) : écrivez un programme qui affiche la somme des N premiers

nombres entiers positifs, N étant fourni par l’utilisateur.

Page 63: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

57

Programme 5 (accumulation): écrivez un programme qui lit des notes entières jusqu’à ce que

l’utilisateur tape 0 pour signaler qu’il n’a plus de valeurs à fournir et qui affiche alors leur

moyenne.

Programme 6 (accumulation) : écrivez un programme qui lit des notes entières positives et

négatives jusqu’à ce que l’utilisateur tape 0 pour signaler qu’il n’a plus de valeurs à fournir

puis qui affiche la moyenne des notes positives et la moyenne des notes négatives.

Programme 7 (recherche de maximum) : écrivez un programme qui lit des notes entières

positives et négatives jusqu’à ce que l’utilisateur tape 0 pour signaler qu’il n’a plus de valeurs

à fournir puis qui affiche la plus grande note positive et la plus petite note négative.

Programme 8 (imbrication de répétitions) : écrivez un programme qui calcule les moyennes

de 5 élèves. Pour chaque élève, le programme lit des notes entières jusqu’à ce que l’utilisateur

tape -1 pour signaler qu’il n’a plus de valeurs à fournir, puis affiche la moyenne de ces notes.

On identifiera les élèves avec un numéro : élève n°1, élève n°2, ...

Programme 9 (imbrication de répétitions) : écrivez un programme qui affiche les tables de

multiplication de 1 à 9. Chaque table se présentera comme suit :

TABLE DES 4

4 x 1 = 4

4 x 2 = 8

...

4 x 10 = 40

Programme 10 (imbrication de répétitions) : écrivez un programme qui affiche une diagonale

d’astérisques dont le nombre est fourni par l’utilisateur.

Nombre d’astérisques : 5

*

*

*

*

*

Page 64: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

58

Programme 11 (itération, la suite de Fibonacci) : un couple de lapins donne naissance à un

autre couple de lapin tous les mois. Le couple nouveau-né devient fertile au bout d’un mois et,

pour simplifier, on suppose que les lapins ne meurent jamais et qu’il y a assez de nourriture

pour tous. Sur une île déserte, il y a au départ un seul couple de lapin. Voici l’évolution du

nombre de lapins au cours des 4 premiers mois :

N évolution du nombre de couples FN

0 C1 1

1 C1→C2m 2

2 C1→C3m C2 3

3 C1→C4m C2→C21m C3 5

4 C1→C5m C2→C22m C3→C31m C4 C21 8

Un couple majeur CXX met au monde un couple mineur CXXm tous les mois. Le couple

mineur devient majeur au bout d’un mois puis commence à se reproduire au bout d’un mois.

On remarquera que le nombre de couples durant le mois N (FN) est égal au nombre de couples

du mois N-1 (FN-1) plus le nombre de couples au mois N-2 (FN-2).

FN = FN-1 + FN-2

Ecrivez le ou les programmes permettant de répondre aux questions suivantes :

1) Combien y aura-t-il de lapins sur l’île au bout de N mois ?

2) Combien faudra-t-il de mois pour atteindre une population de L lapins ? Par exemple, L =

100000.

Page 65: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

59

4. Les fonctions

4.1 Introduction

Un algorithme n’est facilement compréhensible que s’il tient sur une page. Au-delà, il faut le

diviser en plusieurs sous algorithmes plus petits. D’autre part, il arrive souvent dans un gros

programme que l’on effectue plusieurs fois un traitement identique et il serait dommage

d’avoir à écrire à chaque fois les instructions correspondant au traitement. On utilise alors ce

que l’on nomme un sous-programme en langage de programmation. Il s’agit d’un ensemble

d’instructions auquel on attribue un nom. Le sous-programme peut ensuite être utilisé en tout

point du programme en l’appelant par son nom. Il est possible de paramétrer un sous-

programme en lui passant des variables en entrée comme en sortie.

En programmation, on fait généralement la distinction entre procédure et fonction selon le

nombre de variables de sortie qu’il en possible de récupérer. En langage C, il n’existe que la

fonction (par analogie avec la fonction mathématique) qui permet de passer un ou plusieurs

paramètres (ou aucun) en entrée comme en sortie. La fonction permet d’enfermer certains

traitements dans une boite noire. Il n’y a plus alors à se soucier de la manière dont le

traitement s’effectue dans la fonction mais seulement de la manière dont on utilise cette

fonction (et notamment des paramètres d’entrée-sortie). Le langage C fait un usage intensif

des fonctions. En fait, à cause de son histoire (l’écriture du système UNIX), le langage C fait

une distinction très nette entre :

1) le langage. C’est ce qui est purement algorithmique (déclarations, instructions, ...) avec des

mots réservés que vous ne devez en aucun cas utiliser comme nom de variable ou de fonction

(les mots clés).

auto double int struct

break else long switch

case enum register typedef

char extern return union

const float short unsigned

continue for signed void

default goto sizeof volatile

do if static while

Page 66: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

60

2) la bibliothèque. Tout ce qui est interaction avec le système d’exploitation (entrées sorties,

gestion de la mémoire, ...) est réalisé par appel de fonctions se trouvant dans une bibliothèque

dite bibliothèque standard (ou librairie standard). Par exemple, les fonctions printf ,

scanf , getchar , putchar font partie de la bibliothèque standard.

En résumé, on crée une fonction dans les deux cas suivants :

1. Quand il y a une tache répétitive dans le programme,

2. pour clarifier le code source. On écrit souvent en C des fonctions assez courtes (pas trop

quand même, quelques lignes au moins) même si elles ne sont utilisées qu’une fois avec

pour but uniquement d’améliorer la lisibilité du programme.

4.2 Premier exemple

Le langage C ne dispose pas de l’opérateur mathématique de puissance comme par exemple

xy. Nous allons définir une fonction puissance(m,n) qui calcule mn (cette fonction ne

marchera qu’avec des entiers ce qui la rend peu pratique, mais il existe dans la bibliothèque

standard une fonction pow(x,y) qui calcule xy quels que soient x et y).

1. /* prototype de la fonction */ 2. int puissance(int m, int n); 3. void main() 4. { 5. int x = 2, n = 5, res;

6. /* appel n°1 de la fonction */ 7. res = puissance(2, 5); 8. printf("%d\n", res);

9. /* appel n°2 de la fonction */ 10. res = x * puissance(x, 5.2) / n; 11. printf("%d\n", res);

12. /* appel n°3 de la fonction */ 13. printf("%d\n", puissance(5, 2) / n); 14. } 15. /* définition de la fonction */ 16. int puissance(int x, int y) 17. { 18. int i, puis;

19. puis = 1;

Page 67: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

61

20. for (i = 0; i < y; i++) 21. puis = puis * x;

22. return puis; 23. }

Il est nécessaire de distinguer :

1. L’appel de la fonction (ligne 7 ou ligne 10 ou ligne 13). Chaque appel à la fonction

puissance passe deux arguments (un nombre directement ou le contenu d’une variable

ou le résultat d’une expression).

res = puissance(2, n);

res = puissance(2*x, n+1);

Cette fonction retourne un entier (le résultat d’un calcul par exemple, ou un code d’erreur)

qui peut être utilisé dans une expression.

res = x * puissance(x, 5.2) / n;

2. La définition de la fonction (lignes 16 à 23). La structure de la fonction est similaire à

celle du programme principal main (c’est normal, c’est aussi une fonction). Elle est

constituée d’une entête (ligne 16) puis du corps de la fonction entre {} qui contient les

instructions à exécuter. L’entête est de la forme :

int puissance( int x, int y)

type durésultat

nom de lafonction

type et nom du1er paramètre

type et nom du2ème paramètre

Les paramètres (appelés paramètres formels ou muets) de la fonction sont des variables

dont le nom et le type sont définis dans l’entête de la fonction. Ils servent à récupérer les

Page 68: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

62

arguments (ou paramètres effectifs) passés à la fonction. La valeur calculée par

puissance est retournée à main par l’instruction à la ligne 22 :

return puis;

Les fonctions ne retournent pas obligatoirement une valeur. Les variables définies dans une

fonction (y compris les paramètres) n’existent que dans cette fonction et ne peuvent ni être

vues ni donc utilisées à l’extérieur de la fonction. Ce sont des variables locales qui sont

créées lorsque l’on rentre dans la fonction et détruites lorsque l’on en sort. On dit que la

portée des variables locales est limitée à la fonction où elles sont définies. Cette remarque

est valable aussi pour la fonction main . Les variables créées dans la fonction main sont

locales à la fonction main et ne peuvent être vues dans la fonction puissance .

3. Le prototype de la fonction (ligne 2). Cette déclaration est normalement obligatoire en C

ANSI. Le prototype permet au compilateur de faire une vérification des types lors de

l’appel de la fonction. Il s’agit d’une sécurité afin de vérifier que vous utilisez

correctement la fonction.

int puissance(int m, int n);

Seuls les types des paramètres sont nécessaires et pas les noms. La ligne suivante est

équivalente à la précédente.

int puissance(int, int);

Il est toutefois préférable d’utiliser un nom de variable judicieusement choisi afin

d’améliorer la lisibilité (et la documentation) du programme. Il n’est pas nécessaire que les

noms de variable utilisés dans le prototype soient les mêmes que dans la définition de la

fonction.

En l’absence du prototype (qui n’est hélas pas obligatoire pour la compilation, même en

ANSI C), le compilateur considère en général que la fonction retourne un entier mais il ne

peut pas contrôler le type des paramètres ni leur nombre. Voyons deux exemples :

Page 69: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

63

• Avec le prototype, le compilateur émettra un avertissement (warning) indiquant une

possible perte de précision si vous appelez la fonction avec des flottants (il y a alors un

cast implicite).

res = puissance(2.5, 5.5);

• Avec le prototype, le compilateur émettra un message d’erreur si vous oubliez un

paramètre.

res = puissance(2);

Sans prototype, le programme est compilé sans erreur (Visual C++ par exemple).

Remarques :

1. Si la fonction puissance est définie avant la fonction main dans le même fichier, le

prototype n’est plus obligatoire. Le compilateur utilisera directement l’entête de la fonction

pour contrôler les paramètres.

2. En général, le programme principal et les fonctions se trouvent dans des fichiers différents

et sont compilés séparément. Les prototypes des fonctions se trouvent eux aussi dans un

fichier séparé appelé fichier d’entête (header) ayant une extension en « .h ». Par exemple,

les fonctions de la bibliothèque standard concernant les entrées sorties standards ont leurs

prototypes définis dans le fichier stdio.h . Pour inclure ces définitions dans votre

programme, il suffit d’ajouter au début du fichier :

#include <stdio.h> main() ...

Page 70: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

64

Exercice 4.1 : quels sont les résultats fournis par ce programme ?

int arrondi(float nombre); void main() { float nb_flt_1 = 1.3, nb_flt_2 = 2.8 ; int nb_arr; nb_arr = arrondi(nb_flt_1); printf("%d\n", nb_arr); nb_arr = arrondi(nb_flt_2); printf("%d\n", nb_arr); printf("%d\n", arrondi(nb_flt_1 + nb_flt_2)); printf("%d\n", arrondi(nb_flt_1) + arrondi(nb_flt_ 2)); } int arrondi(float nb_flt) { float tmp; int nb_arr; tmp = nb_flt + 0.5; nb_arr = (int)tmp; return nb_arr; }

Exercice 4.2 : écrivez la définition d’une fonction qui calcule la valeur de l’expression

y=a.x2+b.x+c. a, b, c et x seront les paramètres flottants de la fonction qui retournera la valeur

flottante de y. Donnez son prototype et écrivez un exemple d’appel à cette fonction.

4.3 Fonction sans résultat ou sans paramètres

Une fonction peut ne pas retourner de résultat. Par exemple :

void meteo(int nb_fois) { int i; for (i = 0; i < nb_fois; i++) printf("il fait beau\n"); }

Page 71: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

65

Le résultat retourné est de type void qui veut dire vide en français, ce qui signifie qu’il n’y a

pas de valeur retournée. L’instruction return n’existe pas dans la définition de la fonction.

Il n’est plus possible d’utiliser l’appel dans une expression comme :

y = meteo(5);

mais il faut l’appeler avec une instruction comme :

meteo(5);

Exercice 4.3 : écrivez la définition d’une fonction triangle affichant un triangle

d’astérisques avec le nombre de lignes nl comme paramètre :

* ** *** ****

Puis écrivez le programme principal utilisant triangle de façon à afficher :

* * ** * ** *** * ** *** ****

Une fonction peut aussi ne posséder aucun paramètre. Sa déclaration est alors :

int toto(void) {

... }

L’appel d’une telle fonction doit comporter des parenthèses vides comme dans :

n = toto();

Exercice 4.4 : écrivez la définition d’une fonction bonjour affichant le message bonjour

quand on l’appelle. Ecrivez le prototype et l’appel correspondant.

Page 72: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

66

4.4 L’instruction return

L’instruction return peut mentionner une variable ou bien une expression. Les exemples

suivants sont valables :

return x;

return x*y;

return(x*y);

return 0;

Si le type retourné est différent du type du résultat, alors le compilateur effectuera un cast

implicite comme dans :

int toto(...) { float x;

... return x;

}

Le rôle de l’instruction return est double. Il indique la valeur qui sera fourni en résultat et

il met fin à l’exécution des instructions de la fonction. L’instruction return ne se trouve

pas obligatoirement à la fin de la fonction. Il est tout à fait possible d’utiliser plusieurs

return dans une même fonction :

int max(int a, int b) {

if (a > b) return a;

else return b ; }

Si a est supérieur à b, alors la fonction max retourne a et le programme sort de la fonction.

Sinon, la fonction retourne b et le programme sort de max.

Lorsqu’une fonction ne retourne aucun résultat, soit on ne met pas de return dans la

fonction, soit on met un return sans le faire suivre d’une expression.

return;

Page 73: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

67

A l’appel de la fonction, il n’est pas obligatoire d’utiliser le résultat retourné. Si vous ne

souhaitez pas l’utiliser, il suffit d’écrire :

printf("il fait beau\n");

au lieu de (printf retourne le nombre de caractères affichés, ou une valeur négative en cas

d’erreur d’affichage) :

status = printf("il fait beau\n");

4.5 Variables globales et locales

Il est possible de définir en langage C des variables qui, contrairement aux variables locales,

sont accessibles à toutes les fonctions du programme : les variables globales. Il suffit pour

cela de les déclarer en dehors de toute fonction comme dans l’exemple suivant :

int nb_fois; /* variable globale */ main() { void meteo(void); /* prototype local */

nb_fois = 2; meteo();

nb_fois = 3; meteo();

}

void meteo(void) { int i; /* variable locale */ for (i = 0; i < nb_fois; i++) printf("il fait beau\n"); }

La variable nb_fois est globale et est accessible à la fois par main et par meteo . Le

programme principal affecte à nb_fois des valeurs qui sont utilisées dans la fonction

meteo . En revanche, la variable i dans meteo reste locale et n’est pas accessible depuis

main .

Page 74: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

68

De la même manière, le prototype, suivant l’endroit où il est déclaré, peut être local ou bien

global. Dans l’exemple précédent, le prototype n’est connu que dans la fonction main . Pour

qu’il soit vu par toutes les fonctions du programme, il faut déclarer le prototype en dehors de

toute fonction comme dans l’exemple suivant. Cette méthode est nécessaire quand on veut,

par exemple, appeler une fonction dans une autre fonction.

int nb_fois; /* variable globale */ void meteo(void); /* prototype global */ main() {

nb_fois = 2; meteo();

nb_fois = 3; meteo();

}

Dans l’exemple précédent, rien n’empêche meteo de modifier la valeur de la variable

globale nb_fois . On appelle effet de bord (side effect = effet secondaire) la modification

indirecte de la valeur d’une variable globale dans une fonction. Rien dans l’appel ou la

définition de la fonction ne vous permet de savoir qu’elle modifie une variable globale

puisque cette variable n’est pas passée comme paramètre. En n’utilisant que des variables

locales, vous pouvez savoir quelles variables sont modifiées par une fonction en regardant sa

définition ou son prototype. De plus, la fonction devient totalement indépendante car elle ne

dépend pas d’une variable globale déclarée ailleurs dans le programme. La fonction peut donc

être réutilisée dans n’importe quel programme. Ce sont les raisons pour lesquelles vous ne

devez pas utiliser de variables globales dans vos programmes sauf si vous pouvez

démontrer qu’elles sont indispensables.

D’une manière générale, on appelle « effet de bord » de l’évaluation d’une expression tout

changement apporté à la valeur d’une variable. Une affectation a évidemment un effet de bord

sur la variable à gauche du signe = comme dans :

y = 3*x;

Mais un appel de fonction peut aussi en avoir si on utilise des variables globales. Les

opérations d’incrémentation ou de décrémentation ont également un effet de bord :

y = 3*x++;

Page 75: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

69

Après cette instruction, y et x ont changé de valeur. Le comportement de l’instruction

suivante est particulièrement imprévisible :

y = b*x + 3*x++;

L’ordre d’évaluation des deux termes est non précisé et x peut être incrémenté avant ou après

sa multiplication par b. Il ne faut jamais utiliser un opérateur d’incrémentation (ou de

décrémentation) sur une variable apparaissant plus d’une fois dans une expression. Il y a un

autre exemple classique d’effet de bord causé par la confusion traditionnelle entre affectation

= et test conditionnel ==. La condition est toujours fausse et y passe à 0 par effet de bord :

if (y = 0) au lieu de if (y == 0)

Lorsqu’une variable locale est créée, son emplacement en mémoire est réservé à chaque fois

que l’on rentre dans la fonction où elle est déclarée. Si vous l’initialisez au moment de sa

déclaration, elle prendra sa valeur d’initialisation à chaque appel de la fonction.

Lorsqu’une variable globale est créée, son emplacement en mémoire est réservé à la

compilation du programme. Si vous l’initialisez au moment de sa déclaration, elle prendra sa

valeur d’initialisation une seule fois au début de l’exécution du programme. Une variable

globale non initialisée est toujours mise à zéro par le compilateur.

Une variable globale peut être cachée dans une fonction par une variable locale de même

nom. C’est alors la variable locale qui est utilisée, la variable globale n’étant plus accessible

dans cette fonction.

int n, p; /* variables globales */ main() {

int n; /* cette variable locale cache la variable globale n tandis que p est toujours accessible */

} void fct(float x, float p) {

/* ici n est une variable globale alors que p est une variable locale */

}

Page 76: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

70

4.6 Variable statique

La variable locale perdant sa valeur à chaque fois que l’on sort de la fonction (puisqu’elle est

détruite), il parait obligatoire d’utiliser une variable globale si l’on veut garder la trace d’un

état interne de cette fonction. L’utilisation de variables globales étant fortement déconseillée,

il existe la possibilité en langage C de créer des variables locales statiques, c’est-à-dire des

variables locales qui sont créées lors de la première entrée dans la fonction, mais qui ne sont

détruites que lorsque l’on sort du programme. Mais elles sont tout de même locales et ne sont

accessibles qu’à l’intérieur de la fonction où elles sont déclarées. Prenons l’exemple d’une

fonction qui compte le nombre de fois où elle est appelée.

void meteo(int nb_fois); main() { int nb_fois;

nb_fois = 1; meteo(nb_fois); nb_fois = 2; meteo(nb_fois); nb_fois = 3; meteo(nb_fois);

} void meteo(int nb_fois) { static int compteur = 0;

int i; for (i = 0; i < nb_fois; i++) printf("il fait beau\n"); compteur++ ; printf("appel n°%d\n", compteur) ; }

Le résultat est alors :

il fait beau appel n°1 il fait beau il fait beau appel n°2 il fait beau il fait beau il fait beau appel n°3

Page 77: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

71

Exercice 4.5 : écrivez la définition d’une fonction trois_etats qui affiche :

1. « c’est la première fois » lors du premier appel,

2. « c’est la deuxième fois » lors du deuxième appel,

3. « régime de croisière » lors des appels suivants.

4.7 La récursivité

On dit qu’une fonction est récursive quand elle s’appelle elle-même. Grâce au principe des

variables locales dans les fonctions (qui sont créées à l’appel de la fonction et détruites à sa

sortie), toutes les fonctions en C peuvent être récursives. Cette propriété est fondamentale

d’un point de vue algorithmique même si son utilisation réelle est en général assez rare. Elle

permet toutefois de simplifier certains algorithmes. Prenons l’exemple d'une fonction qui

affiche un nombre décimal positif à l’aide de la fonction putchar .

void affich_dec(int n); main() { affich_dec(479); } void affich_dec(int n) { static int compteur = 0; compteur++ ; printf("appel numero %d\n", compteur); if ((n / 10) != 0) affich_dec (n / 10); putchar(n % 10 + '0');

/* code ASCII de 0 + un chiffre entier = code ASCII du chiffre */

}

Le résultat affiché est le suivant :

appel numéro 1 appel numéro 2 appel numéro 3 479

Lors du premier appel affich_dec(479) , cette fonction appelle affich_dec(47) qui

appelle affich_dec(4) . Comme 4 est inférieur à 10, la fonction affiche 4 puis rend la

Page 78: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

72

main au niveau supérieur qui affiche 47%10=7 puis rend la main au premier niveau qui

affiche 479%10=9 et termine l’exécution. A chaque appel de fonction, un nouveau jeu de

variables locales (différentes de celles du niveau précédent) est créé. Seule la variable

compteur déclarée en statique en identique pour tous les niveaux d’appels.

Exercice 4.6 : écrivez la définition d’une fonction factor qui calcule la factorielle d’un

nombre n ! = 1.2.3.4 ... (n-1).n. Expliquez clairement le fonctionnement de la fonction avec

l’appel factor(5) .

4.8 Passage des paramètres par valeur.

Jusqu’à présent, nous avons considéré que les paramètres de la fonction étaient des entrées et

le résultat la seule sortie. C’est rarement le cas dans un programme réel. Voyons l’exemple

suivant :

void echange(int a, int b); main() { int n = 1, p = 2 ;

printf("avant appel : n = %d, p = %d\n", n, p); echange(n, p); printf("après appel : n = %d, p = %d\n", n, p);

} void echange(int a, int b) { int tmp;

printf("début échange : n = %d, p = %d\n", a, b); tmp = b; b = a; a = tmp;

printf("fin échange : n = %d, p = %d\n", a, b); }

Le résultat est le suivant :

avant appel : n = 1, p = 2 début échange : n = 1, p = 2 fin échange : n = 2, p = 1 après appel : n = 1, p = 2

Page 79: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

73

L’échange des valeurs des deux variables n et p a bien eu lieu dans la fonction echange ,

mais une fois revenu dans le programme principal, il ne reste plus trace de cette opération.

En effet, lors de l’appel de la fonction, il y a eu transmission de la valeur des expressions n et

p. Ces valeurs ont été recopiées localement dans les variables a et b de la fonction echange .

C’est sur ces copies de n et p que l’échange des valeurs a eu lieu, de sorte que les variables n

et p dans la fonction main n’ont pas été modifiées.

Les paramètres d’une fonction sont toujours transmis par valeur en C.

Evidemment, ce mode de travail pose de gros problèmes puisque :

1. la fonction est le seul sous programme existant en C,

2. elle ne peut retourner qu’un seul résultat.

Dans le cas général, une fonction doit pouvoir fournir plusieurs résultats. Pour cela, il n’y a

que deux solutions :

1. utiliser des variables globales (ce qui est fortement déconseillé sauf cas exceptionnel).

2. transmettre en paramètres la valeur de l’adresse d’une variable. La fonction travaillera sur

la copie de cette adresse, mais aura de ce fait accès à la case mémoire contenant la valeur

de la variable qu’elle pourra donc modifier. C’est ce qui est fait dans la fonction scanf :

scanf("%d", &a);

Cette méthode va nous amener à parler des pointeurs.

En C, on passe généralement les adresses des variables comme paramètre afin de pouvoir les

modifier (les paramètres sont donc des entrées-sorties). Le résultat de la fonction sert

généralement à vérifier son fonctionnement. En général, une valeur 0 indique que tout s’est

bien passé et une valeur non nulle indique une erreur.

Page 80: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

74

4.9 Les pointeurs

Il est possible, en langage C, de définir une variable destinée à contenir une adresse. On

appelle cette variable un pointeur. Il existe plusieurs type de pointeur suivant que l’adresse

pointe sur une variable entière, flottante, caractère, ... Pour un système d’exploitation 32 bits,

un pointeur contient toujours une adresse codée sur 32 bits, quel que soit le type de la variable

pointée. Voici un exemple de déclaration d’un pointeur sur un entier :

int *ptri;

ptri est une variable qui peut contenir une adresse codée sur 32 bits. Cette variable est pour

l’instant non initialisée et sa valeur est inconnue. Nous pouvons affecter une valeur à ce

pointeur en utilisant l’opérateur &. Par exemple en déclarant une variable entière initialisée ou

non :

int n = 20;

puis en exécutant :

ptri = &n;

Cette dernière instruction copie l’adresse de n dans ptri . On dit que ptri pointe sur n.

Représentons l’évolution des variables avant et après l’exécution de l’instruction :

avant après

variable contenu variable contenu

n 20 n 20

ptri ? ptri addresse_n

Lorsqu’il n’est pas utilisé dans une déclaration, mais dans une instruction, l’opérateur * joue

un rôle symétrique de l’opérateur &. En effet, quand il précède un pointeur, il désigne

l’information pointée. Supposons que l’on a déclaré :

int p;

Page 81: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

75

On peut transférer la valeur pointée par ptri dans p en exécutant :

p = *ptri;

ce qui revient à copier 20 dans p. On a aussi le droit d’écrire :

printf("%d", *ptri);

ou bien

*ptri = 50;

Récapitulons :

• &variable : c’est l’adresse de la variable.

• xxxx *ptr (ou xxxx est le type d’une variable comme par exemple int , char ,

float, void ) : c’est la déclaration d’un pointeur.

• *ptr : c’est la valeur pointée par le pointeur ptr . On l’utilise exactement comme une

variable de même type que celui défini lors de la déclaration du pointeur.

Récapitulons l’évolution des variables sur un exemple simple : int *ptri; int n = 20; int p;

variable contenu

n 20

p ?

ptri ?

Page 82: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

76

ptri = &n;

variable contenu

n 20

p ?

ptri addr_n

p = *ptri;

variable contenu

n 20

p 20

ptri addr_n

*ptri = 50;

variable contenu

n 50

p 20

ptri addr_n

Page 83: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

77

ptri = &p;

variable contenu

n 50

p 20

ptri addr_p

Exercice 4.7 : A l’aide d’un schéma, expliquez l’évolution des différentes variables au cours

de l’exécution du programme.

int *ptri1, *ptri2; int n = 1, p = 2, q = 3; ptri1 = &n; ptri2 = &p; *ptri1 = *ptri2 + 3; ptri1 = ptri2; *ptri1 = *ptri2 + 5;

Remarques importantes :

• Il n’est normalement pas permis d’affecter la valeur d’un pointeur d’un certain type à un

pointeur d’un type différent comme par exemple dans le programme suivant :

main() { int *ptri; char *ptrc; ptrc = ptri; ptri = ptrc; }

Copier un pointeur de type entier dans un pointeur de type caractère ou bien l’inverse

provoque un avertissement au moment de la compilation ce qui n’empêche d’ailleurs pas la

copie au moment de l’exécution du programme. Il est préférable toutefois de faire un

cast comme pour des variables normales.

Page 84: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

78

main() { int *ptri; char *ptrc; ptrc = (char *)ptri; ptri = (int *)ptrc; }

De la même manière, la copie de l’adresse d’une variable d’un type dans un pointeur d’un

autre type est normalement interdite comme dans le programme :

main() { int n; char *ptrc; ptrc = &n; }

Encore une fois, seul un avertissement est généré durant la compilation ce qui n’empêche

toujours pas la copie au moment de l’exécution du programme. Un cast est toutefois

souhaitable.

main() { int n; char *ptrc; ptrc = (char *)&n; }

• La priorité de l’opérateur unaire * est plus élevée que celle des opérateurs arithmétiques de

sorte que, par exemple, l’expression :

2 * *ptri est interprétée comme :

2 * (*ptri) c’est-à-dire comme le produit par 2 de la valeur pointée par ptri . Les espaces n’ont

aucune importance et les expressions suivantes sont strictement équivalentes :

Page 85: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

79

2 * *ptri

2**ptri

2 **ptri

2** ptri

Généralement, on n’utilise pas les parenthèses pour clarifier l’expression mais plutôt les

espaces. L’expression finalement retenue est la plus simple à comprendre et la plus lisible.

y = 2 * *ptri;

• Lors de la déclaration de plusieurs pointeurs, il ne faut pas écrire :

float * ptrf1, ptrf2;

En effet, on a déclaré ici un pointeur sur flottant ptrf1 et une variable flottante ptrf2 .

L’ * ne s’applique qu’à la première variable rencontrée. Si l’on souhaite que ptrf2 soit

aussi un pointeur, il faut écrire :

float * ptrf1, * ptrf2;

En supprimant les espaces, on supprime aussi les ambiguïtés. Il est clair alors que l’* ne

s’applique qu’à la variable suivante.

float *ptrf1, *ptrf2;

• Il existe un type de pointeur générique, c’est à dire capable de pointer vers n’importe quel

type d’objet. C’est le type void .

void *ptr;

Nous aurons l’occasion de le revoir dans le cours sur les variables dynamiques.

• La déclaration d’un pointeur ne réserve pas d’emplacement pour une information

pointée.

Page 86: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

80

La déclaration d’une variable pointeur ptri réserve 32 bits en mémoire pour stocker une

adresse. Cette adresse n’est pas initialisée au moment de la déclaration et le pointeur ne

pointe donc sur rien.

int *ptri;

Si, à la suite de cette déclaration, on essaye d’exécuter

*ptri = 100;

on force le programme à placer la valeur 100 à une adresse non définie, ce qui bien

entendu est une grave erreur (on parle d’erreur de débordement). Prenons un exemple

simple de programme et voyons ce qu’il se passe avec Visual C++ sous NT et avec GCC

sous Linux.

main() { int *ptri; *ptri = 100; printf("%d\n", *ptri); }

o Sous NT, le compilateur ne détecte aucune erreur. A l’exécution dans le debugger, le

programme produit l’erreur suivante et s’arrête :

A l’éxécution directement sous Windows NT (en dehors de Visual C++), le programme

produit l’erreur suivante et s’arrête :

Page 87: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

81

Dans les deux cas, le programme signale que vous essayez d’écrire dans un

emplacement mémoire auquel vous ne devriez pas avoir accès. Signalons que cet

emplacement mémoire peut contenir une autre variable ou même une instruction du

programme lui-même. Bien sur, il est aussi possible que cet emplacement soit libre et

qu’il ne contienne rien du tout. L’écriture dans une zone mémoire non réservée est la

principale cause d’erreur et de plantage aléatoires dans les programmes.

o Sous Linux, le programme fonctionne normalement, c’est à dire qu’il affiche 100. Il n’y

a aucun message d’erreur ni de warning à la compilation. En fait, la case mémoire

utilisée étant libre, le système d’exploitation autorise l’exécution du programme. C’est

là un comportement tout à fait désastreux car cela renforce le caractère aléatoire de

l’erreur selon les portions du programme qui seront exécutées. Si l’écriture de la donnée

écrase une instruction du programme, le programme s’arrête et génère normalement un

fichier core . Ce fichier contient l’image du programme au moment du plantage et il est

possible, si le programme a été compilé avec les options de debug, de restituer sous

debugger l’état exact du programme au moment du crash, à savoir la ligne où le

programme s’est arrêté ainsi que le contenu des variables (le tout bien entendu en

langage C). Sans la compilation en mode debug, la même chose est obtenue mais en

langage assembleur ce qui bien entendu ne facilite pas la découverte du problème.

Il existe une variante de cette erreur, c’est la lecture d’une zone mémoire non allouée.

Prenons l’exemple suivant :

main() { int *ptri, i; i = *ptri; printf("%d\n", i); }

Page 88: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

82

Ce programme copie dans la variable i la valeur pointée par ptri qui n’a pas été

initialisé. Bien sur, ptri contient forcément une adresse même si nous ne la connaissons

pas. Ce cas est moins grave puisque le programme n’écrase aucune donnée. On va

simplement obtenir dans i une valeur indéterminée qui sera ensuite utilisée dans le

programme. Il n’y a ni message d’erreur, ni avertissement à la compilation sous NT ou

sous Linux. A l’exécution, le programme affiche n’importe quoi sous les deux systèmes

d’exploitation. La lecture dans une zone mémoire non réservée est aussi une cause

d’erreur et de plantage aléatoires dans les programmes.

Exercice 4.8 : écrivez le code C correspondant aux actions suivantes.

1. Déclarer un entier i et un pointeur ptri vers un entier.

2. Initialiser l’entier à la valeur 10 et faire pointer ptri vers i.

3. imprimer la valeur de i.

4. Modifier la valeur de i à 5 en utilisant le pointeur.

5. imprimer la valeur de i en utilisant le pointeur.

Exercice 4.9 : expliquez ligne par ligne l’action du programme suivant.

#include <stdio.h> main() { int i, j, *ptri; *ptri = 1; ptri=&i; *ptri=19; i=19; ptri=&j; *ptri=45; scanf("%d", &i); scanf("%d", ptri); printf("%d\n", i); printf("%d\n", *ptri); }

Page 89: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

83

4.10 Passage de pointeurs comme paramètres d’une fonction

Nous avons vu précédemment qu’il n’était pas possible en C de modifier la valeur des

arguments d’une fonction. Prenons un exemple.

void inverse(float y); main() { float x =3.14; printf("adresse de x = %x\n", &x); printf("avant appel, x = %f\n", x); inverse(x); printf("après appel, x = %f\n", x); } void inverse(float y) { printf("adresse de y = %x\n", &y); printf("début fonction, y = %f\n", y); y = 1/y; printf("fin fonction, y = %f\n", y); }

Le résultat du programme est :

adresse de x = 12ff7c

avant appel, x = 3.140000

adresse de y = 12ff78

début fonction, y = 3.140000

fin fonction, y = 0.318471

après appel, x = 3.140000

La fonction inverse travaille sur une copie des arguments et les modifications qu’elle peut

y apporter restent locales et ne « remontent » pas dans la fonction main . A l’aide des

pointeurs, nous pouvons résoudre ce problème. Modifions l’exemple précédent.

void inverse(float *ptrx ); main() {

Page 90: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

84

float x =3.14; printf("adresse de x = %x\n", &x); printf("avant appel, x = %f\n", x); inverse( &x); printf("après appel, x = %f\n", x); } void inverse(float *ptrx ) { printf("adresse dans ptrx = %x\n", ptrx); printf("début fonction, *ptrx = %f\n", *ptrx ); *ptrx = 1/( *ptrx ); printf("fin fonction, *ptrx = %f\n", *ptrx ); }

Le résultat du programme est :

adresse de x = 12ff7c

avant appel, x = 3.140000

adresse dans ptrx = 12ff7c

début fonction, *ptrx = 3.140000

fin fonction, *ptrx = 0.318471

après appel, x = 0.318471

L’argument passé à la fonction est maintenant l’adresse de la variable x. Il y a toujours

transmission par valeur et la fonction va travailler avec une copie de l’adresse de x. La

fonction ne peut donc pas modifier l’adresse de x, mais par contre, elle peut maintenant

modifier la valeur pointée par l’adresse de x. Il suffit pour cela de déclarer un pointeur comme

paramètre et de changer la valeur pointée.

Exercice 4.10 : reprenez le programme utilisant la fonction echange du §4.8 (p72) et

modifiez-la à l’aide des pointeurs pour qu’elle réalise effectivement l’échange des deux

valeurs contenues dans les variables a et b.

Page 91: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

85

5. Les tableaux

5.1 Tableaux à une dimension

Les variables que nous avons utilisées jusqu’à présent ne pouvaient contenir qu’une seule

valeur à un moment donné. On les appelle des variables scalaires. Il existe en programmation

de nombreuses structures de données plus ou moins élaborées pouvant contenir plusieurs

valeurs. La plus simple et la plus répandue d’entre elles est le tableau. L’élément

mathématique équivalent au tableau en programmation est la matrice. Comme elle, un tableau

peut avoir un nombre quelconque de dimensions. Nous allons tout d’abord voir l’équivalent

du vecteur mathématique, le tableau à une dimension. Un exemple de déclaration est le

suivant :

float tab[5];

Comme pour une variable :

• un tableau a un nom (ici, c’est tab ).

• il contient des valeurs d’un certain type (ici, des flottants).

• De plus, il contient un certain nombre de valeurs spécifié entre [] (ici, 5 valeurs).

Lorsqu’il rencontre cette déclaration, le compilateur doit réserver 5 emplacements contigus de

4 octets chacun dans la mémoire du programme. Le tableau ainsi créé peut être vu sous la

forme suivante :

tab[0]

tab[1]

tab[2]

tab[3]

tab[4]

ou bien

tab[0] tab[1] tab[2] tab[3] tab[4]

Page 92: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

86

Attention, en langage C, le premier élément (la première case) d’un tableau est l’élément

0 et non l’élément 1.

Il est possible d’initialiser le tableau au moment de la déclaration de la manière suivante :

int x[5] = {1, 2, 3, 4, 5};

char car[3] = {’a’, ’b’, ’c’};

ce qui donne :

1 2 3 4 5

x[0] x[1] x[2] x[3] x[4]

a b c

car[0] car[1] car[2]

Il est aussi possible de ne mentionner que les premières valeurs :

int x[5] = {1, 2, 3};

ce qui donne :

1 2 3

x[0] x[1] x[2] x[3] x[4]

ou bien d’omettre la taille du tableau :

int x[] = {1, 2};

x prend alors automatiquement une taille égale au nombre de valeurs contenues entre les

accolades.

1 2

x[0] x[1]

Page 93: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

87

On peut repérer chaque élément d’un tableau par le nom de ce tableau suivi, entre [] d’une

valeur entière que l’on nomme un indice. Ainsi, tab[0] désigne le premier élément du

tableau tab et tab[i] désigne le ième élément du tableau tab , i devant obligatoirement

être une variable entière comprise entre 0 et 4. D’une manière générale, en langage C, un

indice peut être n’importe quelle expression arithmétique entière. Ainsi, les notations

suivantes sont légales (i et j sont des entiers) :

tab[i+j] , tab[2*j + i] , tab[2+3]

Une fois déclaré, on peut manipuler les éléments de ce tableau. Un élément d’un tableau

s’emploie exactement comme une variable de même type. Comme tous les tableaux que nous

allons manipuler maintenant ne contiendront que des éléments scalaires, un élément d’un

tableau pourra :

1. faire l’objet d’une affectation. Exemples :

int x[4]; x[0] = 1; x[1] = 2; x[2] = 3; x[3] = 4;

1 2 3 4

x[0] x[1] x[2] x[3]

ou bien

char voy[6]; voy[0] = ’a’; voy[1] = ’e’; voy[2] = ’i’; voy[3] = ’o’; voy[4] = ’u’; voy[5] = ’y’;

a e i o u y

voy[0] voy[1] voy[2] voy[3] voy[4] voy[5]

Page 94: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

88

Si l’on souhaite placer la même valeur dans tous les éléments, on utilise une boucle avec

compteur :

int x[4], i; for (i = 0; i < 4; i++) x[i] = 0;

Il est à noter qu’il est impossible de copier un tableau dans un autre tableau de la manière

suivante :

int x[4], y[4]; x = y;

il faut obligatoirement copier élément par élément.

int x[4], y[4]; for (i = 0; i < 4; i++) x[i] = y[i];

2. figurer dans une expression arithmétique. Comme pour toute variable, les notations

suivantes sont possibles :

y=tab[2]+1 , tab[0]=2*x+3 , tab[i]= 3*tab[j+1]+2

3. être utilisé dans une instruction de lecture (comme un scanf) ou dans une instruction

d’écriture (comme un printf). Exemples :

int x[4]; scanf("%d%d", &x[0], &x[1]);

&x[i] étant l’adresse du ième élément du tableau x. Bien entendu, il est possible

d’écrire :

for (i = 0; i < 4; i++) {

printf("donnez la valeur numéro %d : ", i);

scanf("%d", &x[i]);

}

Page 95: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

89

Comme nous l’avons déjà vu, il est impossible de manipuler globalement un tableau au

moment de l’affectation. Cela s’applique aussi pendant la lecture :

scanf("%d", &x);

Cette instruction ne signifie pas que le programme va lire sur l’entrée standard tous les

éléments du tableau x. Cependant, le compilateur laisse passer cette instruction. C’est

parce que, en C, &x est l’adresse de la première case de x, c’est à dire que &x est

équivalent à &x[0] . Nous verrons ultérieurement que x utilisé seul est un pointeur qui

pointe sur l’adresse de la première case du tableau x .

&x ≡ &x[0] ≡ x

Les trois instructions suivantes sont donc strictement équivalentes :

scanf("%d", &x);

scanf("%d", &x[0]);

scanf("%d", x);

L’écriture sur la sortie standard ne pose pas de problèmes particuliers comme le montre le

petit programme suivant :

main() { int tab[6], i;

tab[0] = 0; for (i = 1; i < 5; i++)

tab[i] = 1; tab[5] = 2; for (i = 0; i < 6; i++)

printf("%d\n", tab[i]); }

Exercice 5.1 : écrivez un programme qui lit 5 valeurs avant d’en afficher les carrés. Le

dialogue avec l’utilisateur se présentera comme dans l’exemple suivant :

donnez 5 nombres entiers :

1 3 5 7 9

Page 96: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

90

NOMBRE CARRE

1 1

3 9

5 25

7 49

9 81

5.2 Remarques importantes sur les tableaux

Il est important de noter que :

• La taille d’un tableau en mémoire dépend à la fois de son type et de sa dimension. En

langage C, cette taille doit être connue au moment de la compilation. Le programme

suivant ne devrait donc pas compiler (hélas si avec Dev-C++ et CodeBlocks à cause de

GCC qui est une vraie passoire) :

main() { int dim;

scanf("%d", &dim);

int tab[dim]; /* interdit */ ... }

En effet, il est impossible de disposer de tableaux dont le nombre d’éléments serait

déterminé pendant l’exécution du programme et bien entendu que ce nombre varie durant

son exécution. Les tableaux en C sont dit statiques. Il existe une technique appelée

« gestion dynamique de la mémoire » qui permet de résoudre ce problème. Nous la verrons

ultérieurement.

Il est toutefois possible (et même souhaitable) d’utiliser une constante pour définir le

nombre d’éléments dans le tableau afin de pouvoir modifier facilement sa taille.

#define DIM 100 main() { float tab[DIM], tab1[DIM+1], tab2[2*DIM]; int i;

Page 97: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

91

for (i = 0; i < DIM; i++) tab[i] = ...

for (i = 0; i < DIM+1; i++)

tab1[i] = ... for (i = 0; i < 2*DIM; i++)

tab2[i] = ... }

Le préprocesseur va remplacer, avant la compilation, la macro DIM par 100 (comme la

commande de remplacement d’un traitement de texte). Le programme sera ensuite compilé

normalement.Par convention, les macros sont écrits en majuscules afin de les différencier

facilement des variables qui elles ne sont jamais entièrement écrites en majuscules.

• Il est interdit de lire ou d’écrire dans un élément du tableau qui se trouve en dehors du

tableau, c’est à dire que l’indice utilisé soit négatif ou supérieur à la valeur maximale

possible (on appelle cela un « débordement d’indice » ou une « erreur de

débordement »). Exemple :

int x[10];

x[15] = 0;

Il faut savoir que le compilateur fait correspondre à la notation x[15] un calcul de

l’adresse de l’élément 15, c’est à dire que x[15] est strictement équivalent à *(x +

15) . La déclaration de x a réservé 40 octets pour stocker les éléments du tableau. x[15] se

trouve 60 octets au-dessus de la première case de x, c’est à dire au-delà de la zone

réservée. Ecrire :

x[15] = 0;

revient à essayer d’écrire dans une zone mémoire non réservée à l’aide d’un pointeur

(problème déjà vu au §4.9). Vous risquez donc d’écraser une variable existante. En cas de

lecture :

y = x[15];

La valeur contenue par y est indéterminée.

Page 98: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

92

Exercice 5.2 : donnez les résultats du programme suivant.

#define DIM 6 main() {

int tab[DIM], i;

tab[0] = 1; for (i = 1; i < DIM; i++) tab[i] = tab[i-1] + 2;

for (i = 0; i < DIM; i++)

printf("%d\n", tab[i]); }

Exercice5.3 : donnez les résultats du programme suivant.

#define DIM 8 main() {

int suite[DIM], i;

suite[0] = 1; suite[1] = 1; for (i = 2; i < DIM; i++) suite[i] = suite[i-1] + suite[i-2];

for (i = 0; i < DIM; i++)

printf("%d\n", suite[i]); }

Exercice5.4 : écrivez la portion de programme calculant la somme de tous les éléments d’un

tableau.

Exercice 5.5 : écrivez la portion de programme déterminant le plus grand élément d’un

tableau.

Exercice 5.6 : écrivez la portion de programme déterminant le plus grand élément d’un

tableau ainsi que sa position.

Page 99: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

93

Exercice5.7 : soit le programme incomplet suivant.

#define NEL 20 main() {

int x[NEL], i, j, temp;

printf("donnez 20 valeurs entières \n"); for (i = 0; i < NEL; i++) scanf("%d", &x[i]); ... printf("voici vos valeurs triées par ordre croissan t\n"); for (i = 0; i < NEL; i++)

printf("%d", x[i]); }

Complétez-le afin de trier le tableau x par ordre croissant.

Exercice 5.8 : même chose mais en ordre décroissant.

5.3 Les tableaux à deux dimensions

Nous avons jusqu’à maintenant utilisé des tableaux à une dimension (une ligne ou bien une

colonne) similaires aux vecteurs en mathématiques. Dans la vie courante, le mot tableau est

plutôt utilisé pour un ensemble de valeurs rangées en lignes et en colonnes (comme dans un

tableur par exemple). Un exemple de déclaration d’un tableau à deux dimensions est le

suivant :

float tab2[4][2];

Le compilateur va réserver 8 (2 fois 4) emplacements mémoire de 4 octets soit 32 octets au

total. Par convention, nous considérerons que le premier indice entre crochets correspond aux

lignes et que le second indice entre crochets correspond aux colonnes, ce qui donne la

représentation suivante :

Page 100: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

94

colonne 0 colonne 1

ligne 0 tab2[0][0] tab2[0][1]

ligne 1 tab2[1][0] tab2[1][1]

ligne 2 tab2[2][0] tab2[2][1]

ligne 3 tab2[3][0] tab2[3][1]

Evidemment, il est tout à fait possible de voir ce tableau sous la forme colonnes x lignes :

colonne 0 colonne 1 colonne 2 colonne 3

ligne 0 tab2[0][0] tab2[1][0] tab2[2][0] tab2[3][0]

ligne 1 tab2[0][1] tab2[1][1] tab2[2][1] tab2[3][1]

En général, on utilise la convention lignes x colonnes. Il est possible d’initialiser le tableau au

moment de sa déclaration :

float tab2[4][2] = {{0, 1},

{2, 3},

{4, 5},

{6, 7}};

ce qui donne la représentation suivante :

colonne 0 colonne 1

ligne 0 0 1

ligne 1 2 3

ligne 2 4 5

ligne 3 6 7

Cette notation peut être remplacée par la suivante (qui est cependant moins claire) :

Page 101: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

95

float tab2[4][2]={{0, 1},{2, 3},{4, 5},{6, 7}};

Mais aussi par :

float tab2[4][2] = {0, 1, 2, 3, 4, 5, 6, 7};

Le compilateur remplit alors le tableau ligne par ligne. Cette notation est encore moins claire

que la précédente. La première déclaration est nettement préférable.

Il est impossible d’omettre les dimensions du tableau au moment de la déclaration. La

déclaration suivante est illégale :

float tab2[][] = {{0, 1},{2, 3},{4, 5},{6, 7}};

Par contre, l’initialisation incomplète est toujours possible :

float tab2[4][2]={{0},{2, 3},{4},{6, 7}};

ce qui donne la représentation suivante :

colonne 0 colonne 1

ligne 0 0

ligne 1 2 3

ligne 2 4

ligne 3 6 7

L’affectation d’une valeur à un élément d’un tableau à deux dimensions se fait soit élément

par élément :

tab2[0][0] = 0;

tab2[0][1] = 1;

tab2[1][0] = 2;

Page 102: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

96

tab2[1][0] = 3;

...

Soit à l’aide de deux boucles for imbriquées :

float tab2[5][3] ; int i, j; for (i = 0; i < 5; i++) for (j = 0; j < 3; j++) tab2[i][j] = 2*i + j;

Exercice 5.9 : représentez le tableau précédent avec ses valeurs.

La lecture sur l’entrée standard de tous les éléments d’un tableau s’effectue de la manière

suivante :

int tab2[2][5], i, j; for (i = 0; i < 2; i++) for (j = 0; j < 5; j++) scanf("%d", &tab2[i][j]);

Exercice 5.10 : les données suivantes sont fournies au programme précédent. Représentez le

tableau avec ses valeurs.

10 20 30 40 50 60 70 80 90 100

Exercice 5.11 : même question en permutant les deux boucles for .

Exercice 5.12 : écrivez la portion de programme calculant la somme de tous les éléments d’un

tableau à deux dimensions.

Exercice 5.13 : écrivez la portion de programme déterminant le plus grand élément d’un

tableau à deux dimensions ainsi que sa position.

Page 103: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

97

Les tableaux à deux dimensions servent souvent à représenter des images quoique l’utilisation

des pointeurs soit plus commode pour cela. Il est possible d’utiliser des tableaux ayant plus de

deux dimensions. Pour ce faire, il suffit de généraliser ce que nous avons vu pour les tableaux

à deux dimensions. Ainsi, les instructions suivantes sont légales :

float tab3[5][3][4];

tab3[0][1][2] = 1.5;

L’utilisation de tableaux à plus de deux dimensions est assez rare.

5.4 Passage d’un tableau comme paramètre d’une fonction

En utilisant les pointeurs, le passage d’un tableau comme paramètre d’une fonction est assez

facile à résoudre. Le programme suivant vous montre un exemple :

void raz(int tab[10]); main() { int tableau[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, i; printf("tableau avant raz :"); for (i = 0; i < 10; i++) printf(" %d", tableau[i]); printf("\n"); raz(tableau); printf("tableau après raz :"); for (i = 0; i < 10; i++) printf(" %d", tableau[i]); printf("\n"); } void raz(int tab[10]) { int i; for (i = 0; i < 10; i++) tab[i] = 0; }

Le résultat est le suivant :

tableau avant raz : 0 1 2 3 4 5 6 7 8 9

tableau après raz : 0 0 0 0 0 0 0 0 0 0

Page 104: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

98

Nous avons déjà vu que le nom du tableau est identique à son adresse, c’est-à-dire à l’adresse

de sa première valeur. L’appel à raz(tableau); provoque la transmission à la fonction

raz de l’adresse du tableau. Dans la fonction raz , à chaque appel, cette adresse est recopiée

dans la variable tab . tab pointe donc maintenant sur tableau et tab[i] est la même

valeur que tableau[i] . Le passage du paramètre s’effectue donc toujours par valeur.

La déclaration dans l’entête de la fonction n’a pas pour rôle de réserver de l’espace mémoire

puisque tab pointe sur tableau qui a déjà son emplacement réservé dans le programme

principal. La dimension du tableau est donc inutile et la déclaration suivante est possible :

void raz(int tab[]) { int i; for (i = 0; i < 10; i++) tab[i] = 0; }

On peut aussi utiliser un simple pointeur (void raz(int *toto) ) pour récupérer

l’adresse du tableau. La fonction raz doit connaître la taille du tableau pour pouvoir modifier

ses valeurs. Il est possible de passer cette taille en paramètre.

void raz(int tab[], int taille); main() { int t1[10], t2[5]; raz(t1, 10); raz(t2, 5);

...

} void raz(int tab[], int taille) { int i; for (i = 0; i < taille; i++) tab[i] = 0; }

Page 105: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

99

Rien n’empêche d’appeler :

raz(t1, 20);

Il se produit alors un débordement d’indice avec écrasement des 40 octets se trouvant au-

dessus de t1 dans la mémoire (sans doute t2).

Il est possible de ne traiter qu’une partie d’un tableau en utilisant :

raz(t1, 5); /* les 5 premiers éléments */ raz(&t1[4], 3); /* les éléments 5, 6 et 7 */

Exercice 5.14 : écrivez une fonction retournant la valeur maximale d’un tableau à une

dimension. Ecrivez un petit programme principal appelant cette fonction.

Exercice 5.15 : même question pour une fonction fournissant la valeur maximale et la

position.

Dans le cas d’un tableau à deux (ou plus) dimensions, le mécanisme reste le même.

void raz(int tab2[2][3]); main() { int tab2x3[2][3]; raz(tab2x3);

... } void raz(int tab2[2][3]) { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) tab2[i][j] = 0; }

Page 106: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

100

Par contre, il faut obligatoirement spécifier les dimensions de tab2. Le programme suivant ne

se compile pas.

void raz(int tab2[][]); ♦ERREUR main() { int tab2x3[2][3]; raz(tab2x3);

... } void raz(int tab2[][]) ♦ERREUR { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) tab2[i][j] = 0; }

Le compilateur doit savoir, au moment de la déclaration, l’organisation du tableau (nombre de

lignes et de colonnes) pour pouvoir les ranger en mémoire dans l’ordre ligne x colonne.

5.5 Relations entre tableaux et pointeurs

L’opérateur + permet de réaliser la somme de deux valeurs arithmétiques, mais il permet aussi

de réaliser la somme d’un pointeur et d’un entier. Il n’y a rien là d’anormal puisqu’un

pointeur n’est, en général, qu’un entier sur 32 bits. Quand on additionne un entier à un

pointeur de type T, on lui ajoute la valeur de l’entier que multiplie le nombre d’octets

nécessaire pour coder une variable de type T (char = 1, short = 2, int = 4, float = 4,

double = 8). Prenons un exemple :

int *t, *u; int tab[5]; t = &tab[0]; u = t + 2; La représentation en mémoire est la suivante :

Page 107: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

101

&tab[0] &tab[1] &tab[2] &tab[3]

un entier = 4 octetst u

adresses croissantes

u est égal à t plus 2*4 octets, le type entier étant codé avec 4 octets. Comme t pointe sur le

premier élément de tab , alors u pointe sur le troisième élément de tab. On voit que u = t

+ 2; aurait pu être écrit u = &tab[2]; .

Il en va de même pour l’opérateur soustraction. Si t pointe sur l’élément i d’un tableau, alors

t-j pointe sur l’élément i-j de ce tableau.

Exercice 5.16 : représentez la mémoire correspondant au programme suivant. Sur quoi

pointent p, q, r , et s .

#define N 10 main() { int *p, *q, *r, *s; int tab[N]; p = &tab[0]; q = p + (N-1); r = &tab[N-1]; s = r - (N-1); }

On peut appliquer les opérateurs d’incrémentation ++ et de décrémentation -- à des

pointeurs. On peut les utiliser, par exemple, pour parcourir les différents éléments d’un

tableau.

Page 108: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

102

Exercice 5.17 : quel résultat fournit le programme suivant.

#define N 10 main() { int *p; int tab[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, -1}; for (p = &tab[0]; *p != -1; p++) printf("%d ", *p); }

Il existe des relations très étroites en langage C entre les tableaux et les pointeurs. En fait, tout

identificateur de type « tableau de X » est converti automatiquement en une valeur constante

dont :

1. le type est « pointeur vers X »

2. la valeur est l’adresse du premier élément du tableau.

Quand on déclare int tab[10] , le compilateur comprend que tab est un tableau de 10

entiers et réserve 40 octets en mémoire. Lorsque l’on utilise l’identificateur tab dans le

programme, il est converti en type int * , de valeur d’adresse &tab[0] . Cette conversion

automatique de l’identificateur du tableau empêche de désigner un tableau comme un tout.

C’est pour cette raison que l’on ne peut copier tous les éléments d’un tableau dans un autre

tableau comme dans le programme suivant :

int tab1[10]; int tab2[10]; tab2 = tab1; ♦ERREUR

On essaye de copier &tab1[0] dans &tab2[0] , ce qui est impossible car tab2 est une

constante puisque c’est l’adresse du début de la zone de mémoire réservée pour le tableau. Il

faut copier les valeurs élément par élément avec une boucle.

Pour les mêmes raisons, il est impossible d’affecter une autre adresse à l’identificateur d’un

tableau.

int *t; int tab[10]; tab = t; ♦ERREUR

Page 109: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

103

Par contre, il est tout à fait possible de définir un pointeur qui pointe aussi sur la première

valeur du tableau :

int *t; int tab[10]; t = tab;

En effet, cela revient à écrire t = &tab[0] , ce qui est tout à fait légal.

En langage C, l’opérateur d’indexation est défini de telle manière que, après déclarations :

int i; int tab[N];

tab[i] est rigoureusement équivalent à *(tab + i) .

Vérifions cela. Nous avons vu que tab a pour valeur l’adresse du premier élément du

tableau. D’autre part, d’après ce que nous avons vu sur l’addition entre un pointeur et un

entier, nous savons que tab + i est l’adresse de l’élément de rang i du tableau. En

appliquant l’opérateur d’indirection * , nous voyons que *(tab + i) est bien identique à

tab[i] . Cela a trois conséquences plus ou moins importantes :

1. L’opérateur d’indexation noté [] est inutile et n’a été offert au programmeur que pour la

lisibilité des programmes et pour ne pas changer les habitudes des programmeurs.

2. Puisque l’opérateur d’indexation s’applique à des valeurs de type pointeurs, on va pouvoir

l’appliquer à n’importe quelle valeur de type pointeur et pas seulement aux constantes

repérant les tableaux. Après les déclarations :

int *t; int tab[10]; on peut écrire :

t = &tab[4];

et utiliser l’opérateur d’indexation sur t , t[0] étant tab[4] , t[1] étant tab[5] , etc.

t peut donc être utilisé comme un sous tableau de tab .

Page 110: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

104

3. L’opérateur d’indexation est commutatif ! En effet, tab[i] étant équivalent à *(tab +

i) , et l’addition étant commutative, tab[i] est équivalent à *(i + tab) et donc à

i[tab] . L’élément de rang i d’un tableau tab peut donc être indifféremment appelé

tab[i] ou i[tab] . Il est évident que pour des raisons de lisibilité, il vaut mieux utiliser

la première notation.

5.6 Allocation dynamique de la mémoire

Jusqu’à présent, nous n’avons jamais directement réservé de la mémoire puis affecté l’adresse

de début de zone à un pointeur. Nous sommes toujours passés par l’intermédiaire d’un tableau

en deux étapes :

1. déclaration d’un tableau et donc réservation de la mémoire,

2. copie dans le pointeur de l’adresse du ième élément du tableau.

Cette méthode peut avoir son utilité, mais elle a aussi des inconvénients :

1. Le tableau est créé une fois pour toutes lors de la compilation. Il continue d’occuper de la

mémoire inutilement même si le programme n’en a plus besoin.

2. Le tableau est statique. Sa taille est déterminée à la compilation et n’est donc pas

paramétrable en fonction du déroulement du programme. De plus, elle ne peut pas être

modifiée.

En C, deux fonctions permettent de réserver de la mémoire et retournent l’adresse de début de

zone : malloc et calloc . La fonction free permet de libérer la mémoire allouée. La

fonction malloc admet un paramètre qui est le nombre d’octets à réserver et retourne un

pointeur de type void . Voici un exemple d’utilisation :

#include <stdlib.h> main() { int *ptr, taille, i; scanf("%d", &taille); ptr = (int *) malloc(taille*4);

Page 111: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

105

for (i = 0; i < taille; i++) scanf("%d", &ptr[i]); for (i = 0; i < taille; i++) printf("%d ", ptr[i] ); free(ptr); }

Remarques :

• Bien que malloc retourne un pointeur de type void , il est préférable de faire un cast

pour l’affecter à ptr .

• La fonction sizeof permet de connaitre la taille d’un type de variable. Par exemple,

sizeof(double) retourne 8. Il aurait donc été possible d’écrire :

ptr = (int *) malloc(taille* sizeof(int));

L’appel de la fonction malloc réserve 4*taille octets pour stocker des valeurs dans la

mémoire de l’odinateur, l’adresse du début de la zone de stockage étant copié dans ptr .

La variable taille étant saisie par l’utilisateur avec un scanf, le tableau pointé par ptr

est devenu dynamique.

• Vous noterez l’utilisation de ptr[i] dans le programme. On aurait pu écrire *(ptr+i)

à la place.

• free(ptr) libère l’espace mémoire réservé pointé par ptr . En cas de besoin, on peut

maintenant allouer à nouveau la mémoire (avec un malloc ) pour créer un nouveau

tableau dont on copiera l’adresse dans ptr .

La fonction calloc admet deux paramètres : le nombre d’éléments désirés et la taille en octets

d’un élément. Elle retourne un pointeur de type void . Dans le programme précédent, vous

auriez pu utiliser :

ptr = (int *) calloc(taille, sizeof(int));

Page 112: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

106

à la place du malloc pour réserver l’emplacement mémoire. Contrairement à malloc ,

calloc initialise à 0 les octets alloués ce qui prend beaucoup plus de temps.

En langage C, l’allocation dynamique de la mémoire est utilisée systématiquement dès que la

taille d’un tableau devient élevée ou bien dès que la taille du tableau est inconnue à la

compilation. C’est le cas par exemple dans un programme qui lit une image dans un fichier

puis qui l’affiche à l’écran.

La fuite mémoire (memory leak ou memory leakage) est une erreur classique de

programmation dans un programme qui utilise l’allocation dynamique de la mémoire. La

mémoire qui est allouée n’est pas libérée complètement au fur et à mesure du fonctionnement

du programme, ce qui conduit à une consommation de mémoire de plus en plus importante,

voir à des erreurs de débordement. Il existe des outils logiciels permettant de détecter les

fuites mémoires car ce type d’erreur est difficile à détecter lorsque la complexité du

programme augmente.

Page 113: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

107

6. Les chaînes de caractères

6.1 Déclaration

Nous avons déjà utilisé des chaînes de caractères constantes avec les instructions printf et

scanf . Une chaîne de caractères (« string » en anglais) est une suite de caractères entourés

de double quotes ", par exemple : "ceci est une chaîne" . Des séquences

d’échappement sont possibles afin de représenter des caractères spéciaux tels que :

séquence d’échappement signification

\n saut à la ligne

\t tabulation

\0 caractère nul

\" "

\\ \

On peut les désigner par la notation \xnb où nb est le code en hexadécimal du caractère. Par

exemple, \n est équivalent à \x0A . On peut aussi utiliser le code octal directement avec \nb

où nb est le code en octal du caractère. Par exemple, \0 est le caractère nul. Toutes ces

séquences d’échappement peuvent être utilisées dans les chaînes de caractères comme dans :

"ligne 1\nligne 2\nligne 3"

Le caractère \ suivi d’un retour à la ligne est ignoré. Cela permet d’écrire une longue chaîne

sur plusieurs lignes dans vos programmes en C comme dans l’exemple :

"longue ligne 1\

longue ligne 2\

longue ligne 3"

Si deux chaînes sont adjacentes dans le source, le compilateur les fusionne. On appelle cela

une concaténation :

printf("cou" "cou"); ≡ printf("coucou");

Page 114: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

108

La variable de type chaîne de caractères n’existe pas en langage C. Elle est remplacée par le

tableau de caractères, la chaîne étant toujours terminée par le caractère nul (\0). Ce caractère

est en général inséré automatiquement par le compilateur. Voici un exemple de chaîne et sa

représentation en mémoire :

"coucou" c

0x63

o

0x6F

u

0x75

c

0x63

o

0x6F

u

0x75

\0

0x00

Les chaînes suivantes sont possibles :

" " ^

0x20

\0

0x00

"" \0

0x00

"\"" "

0x22

\0

0x00

"\0" \0

0x00

\0

0x00

"c: \\ tmp" c

0x63

:

0x3A

\

0x5C

t

0x74

m

0x6D

p

0x70

\0

0x00

On peut évidemment initialiser un tableau de caractères de manière traditionnelle avec une

liste de constantes :

char toto[4] = {'c','o','u','\0'};

mais c’est assez lourd à l’usage. Heureusement, le C propose une facilité en utilisant

directement une chaîne littérale :

char toto[4] = "cou";

Page 115: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

109

Le compilateur ajoutant automatiquement le caractère nul en fin de chaîne, il faut que le

tableau ait au moins un élément de plus que le nombre de caractères de la chaîne littérale.

Mais rien n’empêche que la taille déclarée pour le tableau soit supérieure à la taille de la

chaîne (c’est le caractère nul qui signale la fin de la chaîne au programme).

char toto[100] = "coucou";

Seuls les 7 premiers caractères de la variable toto sont utilisés. Il est également possible de

ne pas spécifier la taille du tableau. Dans ce cas, le compilateur va automatiquement réserver

la bonne taille pour le tableau, y compris le caractère nul.

char toto[] = "coucou";

Vous pouvez aussi utiliser un pointeur :

char *toto = "coucou";

Il est très important de bien comprendre la différence entre tableau et pointeur. Prenons

l’exemple suivant :

char toto[100]; toto = "coucou"; � illégal

A l’exécution du programme, toto est converti en un pointeur sur char constant qui

contient l’adresse du premier élément du tableau. L’instruction toto = "coucou";

essaye d’affecter à toto l’adresse du début de la chaîne, ce qui est impossible (puisqu’il est

constant). Par contre, l’exemple suivant est tout à fait légal :

char *toto; toto = "coucou";

car le pointeur sur char toto reçoit l’adresse du début de la chaîne "coucou" que le

compilateur a placé quelque part en mémoire lors de sa création. C’est exactement le même

résultat que lorsque l’on déclare : char *toto = "coucou";

c

toto

o u c o u \0

Page 116: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

110

Il n’est pas possible de manipuler des chaînes de caractères sans passer par des fonctions

spécifiques de la librairie standard. En effet, des instructions telles que :

char *s1 = "coucou"; char *s2 = "c’est moi"; char *s3; s3 = s1 + s2; s1 = s2;

se contentent d’additionner les pointeurs s1 et s2 et de mettre le résultat dans s3 (ce qui ne sert

à rien et en aucun cas à concaténer les deux chaînes) puis de faire pointer s1 sur le premier

caractère de la chaîne "c’est moi" (ce qui entraîne la perte définitive de la chaîne

"coucou" et ne copie pas la première chaîne dans la deuxième). Toutes ces opérations

portent sur les pointeurs de début de chaînes mais en aucun cas sur les chaînes elles-mêmes.

De la même manière, le programme :

char *s1 = "coucou"; char *s2 = "c’est moi"; if(s1 == s2) printf("les deux chaînes sont identiques");

ne teste pas que les chaînes sont identiques, mais vérifie l’égalité entre les pointeurs. La

condition sera donc toujours fausse.

6.2 Lire ou écrire des chaînes

Les prototypes des fonctions permettant de lire ou bien d’écrire des chaînes se trouvent dans

le fichier d’entête stdio.h . Si vous souhaitez les utiliser, la ligne suivante doit être inclue

au début de votre fichier source :

#include <stdio.h>

Les fonctions printf et scanf opèrent à partir des entrées-sorties standard stdin (le

clavier par défaut) et stdout (l’écran par défaut). Le programme suivant :

Page 117: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

111

#include <stdio.h> main() { char s1[10]; scanf("%s", s1); }

va lire une suite de caractères au clavier pour les ranger dans le tableau s1 en commençant à

partir de s1[0] et en ajoutant automatiquement un caractère nul en fin de chaîne. Le code de

format %s fonctionne comme les codes de formats numériques (et non comme le code de

format %c), c’est-à-dire qu’il commence par sauter les délimiteurs éventuels (espace ou fin de

ligne) et qu’il s’interrompt lorsqu’il rencontre un de ces délimiteurs. scanf ne peut donc

pas lire de chaîne commençant par un espace ou contenant un espace.

Il est tout à fait possible que scanf provoque un débordement de tableau. Il suffit pour cela

de taper au clavier plus de 9 caractères. Les caractères excédentaires seront placés après la fin

du tableau et risque donc d’écraser d’autres données. Il faut donc réserver généreusement

l’emplacement nécessaire au stockage de la chaîne (char s1[255]; ).

Notez bien que scanf ne marche qu’avec un tableau (ou un pointeur initialisé par malloc )

et pas avec un pointeur non-initialisé (il faut que l’espace mémoire soit réservé pour accueillir

la chaîne).

#include <stdio.h> main() { char *s1; scanf("%s", s1); � débordement }

ATTENTION : la chaîne littérale est constante et ne peut pas être modifiée :

#include <stdio.h> main() { char *s1, *s2 = "toto", s3[128] = "titi"; s1 = "tata";

Page 118: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

112

s1[0] = '1'; � chaine constante s2[0] = '1'; � chaine constante s3[0] = '1'; ♥ chaine modifiable }

Pour être sûr que le buffer du clavier est vide de tout caractère avant de faire un scanf , la

fonction fflush peut être utilisée :

#include <stdio.h> main() { char s1[100], s2[100]; scanf("%s", s1); fflush(stdin); /* vide le buffer clavier */ scanf("%s", s2); }

Avec le programme précédent, si vous tapez :

un deux ↵

trois ↵

Vous obtiendrez un dans s1 et trois dans s2. Sans fflush , vous auriez obtenu un dans s1

et deux dans s2 (sans attente sur le deuxième scanf ).

La fonction printf accepte elle aussi le code de format %s et l’utilise comme les autres

codes de format.

#include <stdio.h> main() { char s1[100]; scanf("%s", s1); printf( "s1 = %s" , s1); }

Le résultat obtenu est :

essai ↵

s1 = essai

Page 119: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

113

Exercice 6.1 : quels résultats fournira le programme suivant ?

#include <stdio.h> main() { char *s1; s1 = "bonjour"; printf("%s\n", s1); s1 = "maitre !"; printf("%s\n", s1); }

Exercice 6.2 : expliquez le fonctionnement du programme suivant. Quels résultats fournira-t-

il ?

#include <stdio.h> #include <stdlib.h> #define NB_CHAR 100 main() { char *s1; s1 = malloc(NB_CHAR * sizeof(char)); scanf("%s", s1); printf("%s\n", s1); }

Exercice 6.3 : expliquez le fonctionnement du programme suivant. Quels résultats fournira-t-

il ?

#include <stdio.h> main() { char *s1 = "bonjour"; int i; for (i = 0; i < 3; i++) putchar(s1[i]); printf("\n"); i = 0; while(s1[i] != '\0') putchar(s1[i++]);

Page 120: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

114

printf("\n"); i = 0; while(s1[i]) putchar(s1[i++]); }

Exercice 6.4 : modifiez le programme de l’exercice 6.3 en n’utilisant que les pointeurs (sans

utiliser le formalisme « tableaux »). Deux versions sont possibles.

Il est possible d’utiliser une variable chaîne à la place de la chaîne littérale du

printf comme dans cet exemple :

#include <stdio.h> main() { char s1[100] = "essai"; char s2[100] = "s1 = %s"; printf(s2, s1); }

Le résultat obtenu est :

s1 = essai

Attention, il faut noter que printf ne peut pas connaître la longueur de la chaîne. Il ne connaît

que le pointeur de début de chaîne s1 (qui est égal à &s1[0]) et affiche tous les caractères dans

la chaîne jusqu’à ce qu’il rencontre un caractère nul (\0). Si vous supprimez le caractère nul à

la fin de s1, printf affichera tout ce qu’il trouve en mémoire jusqu’à ce qu’il tombe sur un \0.

Exercice 6.5 : faites un tableau représentant les 8 premiers emplacements en mémoire de s1 et

montrer son évolution lors de l’exécution du programme suivant.

#include <stdio.h> main() { char s1[100] = "coucou"; s1[6] = 1; printf("s1 = %s", s1); }

Page 121: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

115

Exercice 6.6 : modifiez le programme suivant pour qu’il n’imprime que les 4 derniers

caractères de s1.

#include <stdio.h> main() { char *s1 = "bonjour"; printf("%s\n", s1); }

Les fonctions sprintf et sscanf opèrent à partir d’une chaîne de caractères. La fonction

sscanf réalise la même chose que scanf mais à partir d’une chaîne et pas à partir du

clavier (entrée standard = stdin ). Exemple :

#include <stdio.h> main() { char s1[100] = "1 2"; int x, y; sscanf( s1 , "%d%d", &x, &y); printf("x = %d, y = %d", x, y); }

Le résultat obtenu est :

x = 1, y = 2

La fonction sprintf se comporte de la même manière que printf sauf qu’au lieu d’écrire

sur l’écran (sortie standard = stdout ), elle écrit dans une chaîne de caractères. Exemple :

#include <stdio.h> main() { char s1[100] = "1 2"; int x = 1, y = 2; sprintf( s1 , "x = %d, y = %d\n", x, y); printf(s1); }

Page 122: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

116

Le résultat obtenu est :

x = 1, y = 2

Exercice 6.7 : écrivez un programme qui lit deux nombres entiers fournis obligatoirement sur

une même ligne. Le programme ne devra pas « planter » en cas de réponse incorrecte (avec

des caractères invalides par exemple) comme le ferait scanf("%d%d", …); mais

simplement afficher un message et demander une autre saisie. Le comportement du

programme devra être le même si le nombre de valeurs correctement saisies est insuffisant.

Par contre, il devra ignorer les valeurs excédentaires. On utilisera gets et sscanf .

Exercice 6.8 : complétez les ???? dans le programme suivant afin d’obtenir le

comportement suivant :

1) l’utilisateur tape un nom de fichier sans extension (nom_de_base),

2) le programme crée 10 fichiers appelés nom_de_base.1, nom_de_base.2, ...,

nom_de_base.10.

#include <stdio.h> main() { char nom_de_base[100]; char nom_de_fichier[100]; FILE *fp; /* pointeur sur fichier */ int i; scanf( ???? ); for (i = 0; i < 10; i++) { sprintf( ???? ); /* création du fichier */ fp = fopen(nom_de_fichier, "w"); fclose(fp); /* fermeture du fichier */ } }

Les fonctions gets et puts opèrent à partir des entrées-sorties standards stdin (le clavier

par défaut) et stdout (l’écran par défaut). Elles manipulent exclusivement des chaînes de

caractères. Dans le programme suivant :

Page 123: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

117

#include <stdio.h> main() { char s1[100]; gets(s1); puts(s1); }

Le résultat obtenu est :

^^un^^deux^^trois ↵

^^un^^deux^^trois

L’instruction gets(s1) va lire une suite de caractères et la ranger dans s1 , terminée par un

caractère nul. Il faut noter qu’à la différence de scanf :

• aucun délimiteur n’est sauté avant la lecture,

• les espaces sont lus comme les autres caractères,

• la lecture ne s’arrête qu’à la rencontre d’un caractère fin de ligne qui n’est pas copié

dans la chaine. Aucun contrôle du nombre de caractères lus n’étant réalisé, le

débordement de s1 est tout à fait possible si la chaine saisie est trop longue. C’est une

faille de sécurité importante du langage C.

La fonction puts(s1) affiche les caractères trouvés à partir de &s1[0] jusqu’à atteindre le

caractère nul, puis réalise un changement de ligne (c’est là la seule différence notable avec

printf ).

Exercice 6.9 : écrivez un programme ayant le comportement suivant en utilisant les fonctions

gets, puts, scanf et printf.

Où habitez-vous ? paris ↵ Donnez votre nom et prénom : dupont jean ↵ Bonjour M. dupont jean qui habitez paris

6.3 Connaître la longueur d’une chaîne

La fonction strlen (STRing LENtgh) permet de connaître la longueur d’une chaîne de

caractères (entre l’adresse de début de chaîne et le caractère nul). Voici son prototype :

Page 124: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

118

int strlen(const char *chaine);

qui se trouve dans le fichier string.h . Elle accepte en entrée une chaîne de caractères

qu’elle ne peut pas modifier (c’est le rôle du qualificatif de type const ) et retourne la

longueur de la chaîne, c’est-à-dire le nombre de caractères contenus excepté le caractère nul.

#include <stdio.h> #include <string.h> main() { char s1[100] = "essai"; int l; l = strlen(s1); printf("s1 est de longueur %d\n", l); }

Le résultat obtenu est :

s1 est de longueur 5

6.4 Copier une chaîne dans une autre chaîne

La fonction strcpy (STRing CoPY) permet de copier une chaîne dans une autre chaîne.

Voici son prototype :

char * strcpy(char *destination, const char *source );

qui se trouve dans le fichier string.h . Elle copie la chaîne source (qu’elle ne peut pas

modifier) dans la chaine destination, y compris le caractère nul. Elle retourne l’adresse de la

chaîne de destination.

#include <stdio.h> #include <string.h> main() { char s1[100], s2[100], *s3; s3 = strcpy(s1, "bonjour"); /* s3 → s1 */ s3 = strcpy(s2, "bonjour"); /* s3 → s2 */ printf("s1 = %s\n", s1); printf("s2 = %s\n", s2); }

Page 125: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

119

Le résultat obtenu est :

s1 = bonjour

s2 = bonjour

Il existe une fonction strncpy analogue à strcpy qui permet de ne copier qu’un nombre

fini de caractères. Par exemple,

strncpy(s1, s2, 10);

ne copiera que les 10 premiers caractères de s2 dans s1.

6.5 Comparer deux chaînes

La fonction strcmp (STRing CoMPare) permet de comparer deux chaînes caractère par

caractère. Voici son prototype :

int strcmp(const char *chaine1, const char *chaine2 );

qui se trouve dans le fichier d’entête string.h . Elle compare les deux chaînes (qu’elle ne

peut pas modifier) depuis le premier caractère et jusqu’à ce que les caractères soient différents

ou qu’une chaîne se termine. Elle retourne un entier :

• < 0 si chaine1 est inférieure à chaine2 (au sens des codes ASCII),

• = 0 si les deux chaînes sont identiques,

• > 0 si chaine1 est supérieure à chaine2.

#include <stdio.h> #include <string.h> main() { char s1[] = "abcdef", s2[] = "abcdEF"; int cmp; cmp = strcmp(s1, s2); if (cmp == 0) printf("s1 est égale à s2\n"); else if (cmp < 0) printf("s1 est inférieure à s2\n"); else printf("s1 est supérieure à s2\n"); }

Si le nombre de caractères copiés est inférieur à la longueur

de S2, le caractère \0 n’est pas inséré à la fin de s1.

Page 126: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

120

Le résultat obtenu est :

s1 est supérieure à s2

car les minuscules ont un code ASCII plus grand que les majuscules. Il existe une fonction

strncmp analogue à strcmp qui permet de ne comparer qu’un nombre fini de caractères.

Par exemple :

strncmp (s1, s2, 10);

ne comparera s1 et s2 que pour les 10 premiers caractères.

6.6 Concaténer deux chaînes

La fonction strcat (STRing conCATenation) permet de concaténer deux chaînes, c’est à

dire de fabriquer une chaîne en mettant bout à bout deux autres chaînes. Voici son prototype :

char * strcat(char *destination, const char *source );

qui se trouve dans le fichier string.h . Elle ajoute la chaîne source (qu’elle ne peut pas

modifier) à la fin de la chaîne destination. Elle retourne l’adresse de la chaîne de destination.

#include <stdio.h> #include <string.h> main() { char s1[ 100 ] = "bonjour"; char s2[] = " maitre !", *s3; s3 = strcat(s1, s2); printf("s3 = %s\n", s3); }

Le résultat obtenu est :

s3 = bonjour maitre !

Vous noterez que s1 n’est pas déclarée de la manière suivante : char s1[] =

"bonjour"; car la concaténation provoquerait alors un débordement par manque de place.

Il existe une fonction strncat analogue à strcat qui permet de ne concaténer que les n

premiers caractères de la chaîne source. Par exemple :

Page 127: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

121

strncat(s1, s2, 10);

n’ajoutera à la fin de s1 que les 10 premiers caractères de s2. Contrairement à strncpy , le

caractère \0 est copié quelque soit le nombre de caractères concaténés.

6.7 Rechercher un caractère dans une chaîne

La fonction strchr (STRing CHaRacter) permet de chercher un caractère dans une chaîne.

Voici son prototype :

char * strchr(const char *chaine, int c);

qui se trouve dans le fichier string.h . Elle cherche dans la chaîne chaine (qu’elle ne

peut pas modifier) la première occurrence du caractère c et retourne un pointeur sur ce

caractère ou bien un pointeur NULL si le caractère n’existe pas.

#include <stdio.h> #include <string.h> main() { char s1[] = "bonjour"; char *p, c='j'; p = strchr(s1, c); if (p != NULL)

printf("le caractère %c est en position %d\n", c, p - s1);

else printf("le caractère %c ne se trouve pas dans la chaine \"%s\"\n", c, s1);

}

Le résultat obtenu est :

le caractère j est en position 3

6.8 Rechercher une chaîne dans une autre chaîne

La fonction strstr (STRing STRing) permet de chercher une chaîne dans une autre chaîne.

Voici son prototype :

char * strstr (const char *s1, const char *s2);

Page 128: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

122

qui se trouve dans le fichier string.h . Elle cherche dans la chaîne s1 (qu’elle ne peut pas

modifier) la première occurrence de la chaîne s2 (qu’elle ne peut pas modifier) et retourne un

pointeur sur le caractère de s1 où commence s2 ou bien un pointeur NULL si s2 n’existe

pas dans s1 .

#include <stdio.h> #include <string.h> main() { char s1[] = "bonjour maitre !"; char *p, s2[] = "jour"; p = strstr(s1, s2); if (p != NULL) { printf("la chaine \"%s\" existe dans la chaine \"%s\"", s2, s1); printf(" en position %d.\n", p - s1); printf("le reste de la chaine est \"%s\"\n", p) ; } }

Le résultat obtenu est :

la chaine "jour" existe dans la chaine "bonjour mai tre !" en

position 3.

le reste de la chaine est "jour maitre !"

Exercice 6.10 : écrivez un programme qui lit deux mots et qui les affiche par ordre

alphabétique.

Exercice 6.11 : écrivez un programme qui lit un mot et qui vérifie qu’il se termine par « er ».

Exercice 6.12 : écrivez un programme qui lit une ligne de texte puis qui supprime dans cette

ligne toutes les lettres e. Le programme modifiera le texte directement dans l’emplacement

mémoire de la ligne saisie. On pourra utiliser la fonction strchr .

Page 129: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

123

Exercice 6.13 : dans le projet mastermind, la proposition du joueur était stockée dans un entier

(voir programme suivant). Proposez une solution avec une chaîne de caractères permettant les

contrôles suivants :

1) vérification du nombre de chiffres de la proposition,

2) vérification que chaque caractère est bien un chiffre.

int entree(int essai[], int dim) { unsigned int x , limite; int i; printf("\nEntrez votre combinaison de %d chiffres : ", dim); scanf("%d", &x); limite = (int)pow(10, dim) - 1; if (x > limite) { printf("\nnombre de chiffres trop grand, recomm encez\n"); return (-1); } for (i = 0; i < dim; i++) { essai[i] = x %10; x = x/10; } return(0); }

6.9 Fonctions diverses

Voici quatres fonctions permettant de tester des caractères :

fonction description

isalpha teste si le caractère est une lettre

isdigit teste si le caractère est un chiffre

islower teste si le caractère est une lettre minuscule

isupper teste si le caractère est une lettre majuscule

Ces fonctions acceptent un caractère en entrée (vous pouvez utiliser un char ou un int ),

retournent 0 si le test est faux ou ≠ 0 si le test est vrai. Leur prototype se trouve dans

l’entête <ctype.h >. Il est le suivant :

int isXXXXX( int carac);

Page 130: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

124

Voici deux fonctions permettant de changer la casse d’un caractère :

fonction description

tolower convertit une lettre en minuscule

toupper convertit une lettre en majuscule

Ces fonctions acceptent un caractère en entrée, et retournent le caractère converti si c’est

possible. Leur prototype se trouve dans l’entête <ctype.h >. Il est le suivant :

int toXXXXX( int carac);

Voici deux fonctions permettant de convertir une chaine en un nombre :

fonction description

atof conversion d’une chaîne en nombre flottant

atoi conversion d’une chaîne en nombre entier

Elles acceptent l’adresse d’une chaîne en entrée et retournent la valeur entière ou flottante si

c’est possible ou 0 sinon. Leur prototype se trouve dans l’entête <stdlib.h >. Il est le

suivant :

int atoi(const char *str);

double atof(const char *str);

Il existe bien d’autres fonctions de manipulation de chaînes de caractères dans la librairie

standard (voir §12.4). Elles sont toutefois d’un usage moins courant.

6.10 Le passage d’une chaîne comme paramètre d’une fonction.

Une chaîne se traite exactement comme un tableau. Il faut passer en paramètre l’adresse du

début du tableau.

#include <stdio.h> void imprime(char *s1); /* prototype */ main() {

Page 131: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

125

char ligne[] = "coucou"; imprime(ligne); /* appel de la fonction */ } void imprime(char *s1) /* entête de la fonction */ { int i; for (i = 0; i < 10; i++) printf("%s\n", s1); }

L’appel de la fonction peut être indifféremment :

imprime(ligne);

imprime(&ligne);

imprime(&ligne[0]);

On peut aussi passer directement une chaîne littérale (c’est ce qui est fait dans printf) :

imprime("essai");

Le compilateur réserve l’espace mémoire nécessaire au stockage de la chaîne "essai" et

passe à la fonction imprime l’adresse de début de chaîne.

Dans l’entête de la fonction, il est inutile de spécifier la taille de la chaîne car seule l’adresse

de début de tableau ou de chaîne va être copiée dans s1 . s1 pointe alors soit sur le début d’un

tableau, soit sur le début d’une chaîne littérale. On peut donc utiliser soit :

void imprime(char *s1)

soit :

void imprime(char s1[])

Il est possible d’interdire à la fonction de modifier la chaîne en déclarant le pointeur constant.

#include <stdio.h> void imprime(const char *s1); /* prototype */ main() { char ligne[] = "coucou";

Page 132: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

126

imprime(ligne); /* appel de la fonction */ } void imprime(const char *s1) /* entête de la foncti on */ { int i; for (i = 0; i < 10; i++) printf("%s\n", s1); }

Si dans la fonction imprime vous essayez de modifier s1 :

s1[0] = 'a';

alors le compilateur refuse de compiler votre programme avec un message d’erreur. Il s’agit

d’une sécurité pour le développeur.

6.11 Les tableaux de chaînes de caractères.

Les tableaux de chaînes de caractères sont des tableaux de tableaux de caractères (des

tableaux à deux dimensions). L’exemple suivant montre la déclaration et l’initialisation d’un

tel tableau :

char tab[5][10] = {"zéro", "un", "deux", "trois", " quatre"};

Le stockage en mémoire peut être représenté de la manière suivante :

z é r o \0

u n \0

d e u x \0

t r o i s \0

q u a t r e \0

Pour entrer une chaîne dans ce tableau à la quatrième ligne, il faut utiliser l’instruction :

scanf("%s", &tab[3][0]);

Page 133: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

127

scanf va lire les caractères sur stdin et les ranger dans les emplacements consécutifs à

partir de l’élément tab[3][0] . Si le nombre de caractères est plus grand que 10, il va

passer à la ligne suivante. De la même manière,

strcpy(&tab[1][0], "ceci est un essai");

va se traduire en mémoire par :

z é r o \0

c e c i ^ e s t ^ u

n ^ e s s a i \0

t r o i s \0

q u a t r e \0

Tout ceci n’est guère pratique. De plus, une grande quantité de mémoire peut être perdue si

les chaînes sont de longueur différente. Il y a une autre manière bien plus efficace pour

résoudre ce problème, c’est d’utiliser un tableau de pointeur sur char :

char *tab[5] = {"zéro", "un", "deux", "trois", "qua tre"};

Dans le tableau tab , on trouve maintenant 5 pointeurs 32 bits initialisés chacun avec

l’adresse de début de chaque chaîne.

tab[0] z é r o \0

tab[1] u n \0

tab[2] d e u x \0

tab[3] t r o i s \0

tab[4] q u a t r e \0

Page 134: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

128

La réservation mémoire est maintenant minimale. Chaque chaîne peut être traitée

indépendamment avec des instructions comme :

scanf("%s", tab[3]);

gets(tab[0]);

printf("%s", tab[1]);

Bien entendu, les problèmes de débordement demeurent comme pour les opérateurs sur des

chaînes ordinaires. Attention, si vous déclarez seulement char *tab[5]; , aucune

réservation mémoire n’est effectuée pour stocker des chaînes. Vous n’avez créé qu’un tableau

de 5 pointeurs 32 bits qui ne pointent sur rien.

Exercice 6.14 : écrivez un programme qui demande à l’utilisateur de lui fournir un nombre

entier compris entre 1 et 7 et qui affiche le nom du jour de la semaine correspondant (le lundi

vaut 1).

Exercice 6.15 : écrivez un programme qui lit un verbe du premier groupe et qui en affiche la

conjugaison au présent sous la forme :

Entrez un verbe du premier groupe : chanter ↵

je chante

tu chantes

il chante

nous chantons

vous chantez

ils chantent

Le programme vérifiera que le verbe se termine bien par er et on supposera qu’il s’agit d’un

verbe régulier.

Page 135: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

129

7. Les paramètres de la fonction main.

Jusqu’ici, nous avons toujours écrit des programmes utilisant les fonctions printf , scanf ,

gets , puts , … pour dialoguer avec l’utilisateur. Nous avons utilisé un mode de dialogue

interactif, non-graphique. Ce n’est pas le seul mode possible. En C, il existe trois manières

classiques (l’utilisation de la base de registre « à la Microsoft » n’est pas un exemple de

portabilité ni de transparence) de passer des arguments à un programme au début ou en cours

d’exécution :

1) L’interface graphique (mode interactif, graphique),

2) Le fichier de configuration (mode non-interactif, non-graphique),

3) Le passage direct de paramètres à la fonction main depuis la ligne de commande (mode

non-interactif, non-graphique).

Il faut avoir conscience du fait que la majorité des programmes s’exécutent sans dialogue

interactif avec l’utilisateur. On les configure au moment du lancement, puis on récupère les

résultats par exemple dans un fichier. Le mode interactif graphique (comme avec Windows)

est l’exception en informatique industrielle plutôt que la règle. Nous aurons l’occasion de

revenir sur le mode 2 (fichier de configuration) à la fin du §9. Voyons maintenant le mode 3.

La fonction main peut recevoir des paramètres de son appelant. Ce sera le plus souvent

l’utilisateur qui les fournira au lancement du programme sur la ligne de commande, mais cela

peut aussi être une autre application qui lancera le programme. Voici un exemple de

déclaration :

int main(int argc, char *argv[], char *env[])

Les trois paramètres que peut recevoir la fonction main sont :

• un entier indiquant le nombre de paramètres reçus. Par convention, on l’appelle argc

pour « argument count ».

• un tableau de chaînes de caractères qui contiennent les paramètres. On le nomme argv

pour « argument vector ». Le dernier élément de ce tableau sera toujours le pointeur

NULL.

Page 136: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

130

• un tableau de chaînes de caractères qui contiennent les variables d’environnement du

système d’exploitation. On le nomme env pour « environnement ». Le dernier élément de

ce tableau sera toujours le pointeur NULL.

La fonction main peut aussi retourner un paramètre, généralement un entier qui contient un

code de retour. Si le programme s’appelle par exemple essai et que l’on tape :

essai toto 1 "un 2" ↵

On obtiendra argc = 4 car le nom du programme est compris dans les arguments, et le

tableau argv sera le suivant :

argv[0] e s s a i \0

argv[1] t o t o \0

argv[2] 1 \0

argv[3] u n ^ 2 \0

argv[4] NULL

De la même manière, env contiendra les variables d’environnement. Voici un exemple de

programme affichant les arguments de la fonction main :

#include <stdio.h> int main(int argc, char *argv[], char *env[]) { int i; printf("la fonction main a reçu %d arguments\n", argc); for (i = 0; i < argc; i++) printf("argument[%d] : %s \n", i, argv[i]);

printf("\nLes variables d'environnement sont : \n "); for (i = 0; env[i]!= NULL; i++) printf("variable d'environnement[%d] : %s \n", i, env[i]); }

Page 137: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

131

Après avoir tapé la commande :

essai un deux "3 3 3" 4 ↵

Le résultat suivant a été obtenu sur un compte dut1g1 sous linux :

la fonction main a reçu 5 arguments argument[0] : essai argument[1] : un argument[2] : deux argument[3] : 3 3 3 argument[4] : 4 Les variables d'environnement sont : variable d'environnement[0] : PWD=/home/dut1g1 variable d'environnement[1] : XAUTHORITY=/home/dut1 g1/.Xauthority variable d'environnement[2] : LC_MESSAGES=fr_FR variable d'environnement[3] : HOSTNAME=ray12.cnam.f r variable d'environnement[4] : LESSKEY=/etc/.less variable d'environnement[5] : LESSOPEN=|/usr/bin/le sspipe.sh %s variable d'environnement[6] : LANGUAGE=fr_FR:fr variable d'environnement[7] : HISTIGNORE=[ ]*:&:b g:fg variable d'environnement[8] : PS1=[\u@\h \W]\$ variable d'environnement[9] : KDEDIR=/usr variable d'environnement[10] : LESS=-MM variable d'environnement[11] : BROWSER=/usr/bin/net scape variable d'environnement[12] : USER=dut1g1 variable d'environnement[13] : LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so =01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;3 2:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.tar= 01;31:*.tgz=01;31:*.tbz2=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:* .lzh=01;31:*.lha=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*. bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.jpg=01;35:*.jpeg=01;35 :*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=0 1;35:*.tiff=01;35: variable d'environnement[14] : LC_TIME=fr_FR variable d'environnement[15] : MACHTYPE=i586-mandra ke-linux-gnu variable d'environnement[16] : MAIL=/var/spool/mail /dut1g1 variable d'environnement[17] : INPUTRC=/etc/inputrc variable d'environnement[18] : BASH_ENV=/home/dut1g 1/.bashrc variable d'environnement[19] : LANG=fr variable d'environnement[20] : LC_NUMERIC=fr_FR variable d'environnement[21] : COLORTERM= variable d'environnement[22] : DISPLAY=:0 variable d'environnement[23] : LOGNAME=dut1g1 variable d'environnement[24] : SHLVL=1 variable d'environnement[25] : LC_CTYPE=fr_FR variable d'environnement[26] : SHELL=/bin/bash variable d'environnement[27] : USERNAME= variable d'environnement[28] : HOSTTYPE=i586 variable d'environnement[29] : OSTYPE=linux-gnu variable d'environnement[30] : HISTSIZE=1000 variable d'environnement[31] : HOME=/home/dut1g1

Page 138: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

132

variable d'environnement[32] : TERM=xterm variable d'environnement[33] : PATH=/usr/local/bin:/bin:/usr/bin::/usr/X11R6/bin:/ usr/games:/home/dut1g1/bin:/usr/X11R6/bin:/usr/games:/usr/X11R6/bin: /usr/games variable d'environnement[34] : SECURE_LEVEL=3 variable d'environnement[35] : RPM_INSTALL_LANG=fr_ FR:fr variable d'environnement[36] : LC_MONETARY=fr_FR variable d'environnement[37] : LC_COLLATE=fr_FR variable d'environnement[38] : _=./essai variable d'environnement[39] : OLDPWD=/home/dut1g1/ hanoi

Exercice 7.1 : écrivez un programme qui lit sur la ligne de commande deux noms de fichier et

qui affiche un court message explicatif si le nombre d’arguments est incorrect.

essai nom_de_fichier1 nom_de_fichier2

Exercice 7.2 : écrivez un programme qui réalise les opérations suivantes.

• lecture d’un nom de fichier sur la ligne de commande,

• vérification de la présence ou non de l’option -x,

• récupération du nombre se trouvant après l’option -o,

• affichage d’un court message explicatif en cas d’erreur de saisie ou si l’option -h a été

détectée.

Le programme doit s’utiliser de la manière suivante :

essai [-x] [-h] [-o n] nom_de_fichier Les paramètres entre crochets sont optionnels mais ils doivent être saisis dans l’ordre. Vous

disposerez pour cela des fonctions :

• void exit(int status). Cette fonction force la sortie du programme et renvoie la

valeur status au programme appelant.

• int atoi(const char *s). Cette fonction convertit s en un entier. Elle retourne

l’entier correspondant ou bien 0 si la conversion est impossible. Il existe aussi une

fonction double atof(const char *s) que nous n’utiliserons pas dans ce

programme.

Page 139: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

133

8. Les structures

8.1 Définition

Les types de variables simples que nous avons vus jusqu’ici ne permettent pas de représenter

les données complexes que l’on est souvent amené à traiter en informatique. Il faudrait

pouvoir regrouper plusieurs types simples en agrégats afin de définir un type de données plus

compliqué. Imaginons par exemple que nous souhaitions créer un type de données comportant

le nom et le prénom d’une personne, son adresse, son âge, son numéro de sécurité sociale, ...

En langage C, il existe la structure qui permet de désigner sous un seul nom un ensemble de

valeurs de types différents. Il y a plusieurs méthodes pour déclarer une structure. La méthode

préférée consiste en une déclaration d’un modèle de structure comme par exemple :

struct personne { char nom[100]; char prenom[100]; char adresse[100]; int age; int noss; } ;

On appelle personne l’étiquette de la structure. On doit ensuite utiliser cette étiquette pour

déclarer des variables comme :

struct personne p1, p2;

p1 et p2 sont des variables de type personne destinées à contenir chacune 3 chaînes de

caractères et deux entiers. Pour abréger, on dira de la variable p1 (ou p2) dont le type est un

modèle de structure qu’elle est une structure de type personne . Les autres méthodes telles

que la déclaration de variables de type structure sans utiliser d’étiquette :

struct { char nom[100]; char prenom[100]; char adresse[100]; int age; int noss; } p1, p2 ;

Page 140: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

134

Ou bien encore la déclaration d’étiquette et des variables en même temps :

struct personne { char nom[100]; char prenom[100]; char adresse[100]; int age; int noss; } p1, p2 ;

sont fortement déconseillées. En effet, il est préférable de séparer la définition du modèle de

structure de son utilisation.

L’accès à chaque élément de la structure (nommé « champ » ou « membre ») se fait par son

nom au sein de cette structure. Chaque champ se manipule comme n’importe quelle variable

du type correspondant. La désignation d’un champ se note en faisant suivre le nom de la

variable structure d’un point (.), puis du nom du champ défini dans le modèle. Par exemple :

p1.age = 20;

affecte la valeur 20 au champ age de la structure p1 . Le type de p1.age est le type du

champ age , c’est-à-dire un entier. D’une manière générale, le type de

nom_structure.champ est le type du champ. Par exemple :

scanf("%s", p2.nom);

lit les caractères au clavier et les range à l’adresse contenue dans le champ nom de p2 , qui est

l’adresse de la première case du tableau de char nom[100] . Il est possible d’initialiser les

champs d’une structure au moment de sa déclaration comme avec un tableau :

struct personne p2 = {"dupond", "jean", "5 rue de l a paix 75008 PARIS", 55, 111111};

A la différence des tableaux, il est possible d’affecter à une structure le contenu d’une autre

structure de manière globale à condition qu’elles soient toutes deux définies à partir du même

modèle. Par exemple, on peut écrire :

p1 = p2;

Page 141: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

135

Par contre, aucune opération globale de comparaison n’est permise. L’opération d’affectation

suivante est impossible, il faut remplir chaque champ séparément.

p2 = {"dupond", "jean", "5 rue de la paix 75008 PAR IS", 55};

Nous avons déjà vu que les déclarations de variables ou de fonctions peuvent être globales

(accessibles à toutes les fonctions) ou locales (accessible à une seule fonction) suivant

qu’elles sont faites dans une fonction ou bien en dehors de toute fonction. Cette règle

s’applique aussi bien aux structures qu’aux modèles de structure. Voyons un exemple :

#include <stdio.h> struct modele_global { int numero; int quantite; float prix; }; struct modele_global var_global; int main() { struct modele_local { int numero; int quantite; float prix; }; struct modele_global var_local1; struct modele_local var_local2; /* ... */ } int fct() { struct modele_global var_local3; /* ... */ }

Dans cet exemple, on voit qu’une structure peut être locale ou globale et que son modèle peut

être aussi local ou global. Le modèle global est indispensable pour que l’on puisse passer une

structure comme paramètre d’une fonction. Nous utiliserons des structures locales (comme

pour les variables) avec un modèle global.

Page 142: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

136

En langage C, il est possible de réaliser des tableaux de structures, c’est-à-dire des tableaux

dont les éléments sont d’un type structure, comme dans l’exemple suivant :

struct point { char nom[100]; int x; int y; } ;

struct point courbe[100];

le tableau courbe est rempli d’éléments de type point . On accède aux différents champs

d’un élément avec :

courbe[5].x

courbe[10].nom[2]

Par ailleurs,

courbe[3]

représente la structure de type point correspondant au quatrième élément du tableau

courbe . Enfin, courbe est un identificateur de tableau qui équivaut à un pointeur

contenant l’adresse de son premier élément. Il est possible d’initialiser (partiellement) le

tableau lors de sa déclaration :

struct point courbe[100]= {{"A", 0, 0}, {"B", 1}, { "E", 4,

4}};

Exercice 8.1 : écrivez un programme qui :

1) lit au clavier des informations et les range dans un tableau de structures du type point

défini comme suit :

struct point { int num ; float x; float y; } ;

Le nombre d’éléments du tableau sera fixé par une instruction #define .

2) affiche à l’écran l’ensemble des informations précédentes.

Page 143: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

137

Une structure peut contenir d’autres structures. Dans l’exemple qui suit, une structure date

est utilisée dans une autre structure qui stocke des renseignements concernant un salarié.

#include <stdio.h> struct date { int jour; int mois; int annee; }; struct salarie { char nom[100]; char prenom[100]; int age; struct date date_embauche; struct date date_poste; }; int main() { struct salarie truc, machin; /* ... */ }

On accèdera à un champ de la manière suivante :

truc.date_embauche.annee = 1990;

Comme les structures date_embauche et date_poste ont le même modèle, l’instruction

suivante est possible :

truc.date_embauche = machin.date_poste;

8.2 Transmission d’une structure en paramètre d’une fonction

La transmission du paramètre se fait toujours par valeur. Les valeurs de la structure sont

recopiées localement lors de l’appel et les modifications effectuées dans la fonction n’ont

aucune incidence sur la structure de la fonction appelante.

Page 144: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

138

#include <stdio.h> struct str { int a; int b; }; void echange(struct str z); main() { struct str x; x.a = 1; x.b = 2;

printf("avant appel : a = %d, b = %d\n", x.a, x.b); echange(x);

printf("après appel : a = %d, b = %d\n", x.a, x.b); } void echange(struct str z) { int tmp;

printf("début échange : a = %d, b = %d\n", z.a, z.b ); tmp = z.b; z.b = z.a; z.a = tmp;

printf("fin échange : a = %d, b = %d\n", z.a, z.b); }

Le résultat est le suivant :

avant appel : a = 1, b = 2 début échange : a = 1, b = 2 fin échange : a = 2, b = 1 après appel : a = 1, b = 2

Comme prévu, l’échange n’a pas eu lieu. Vous noterez que le modèle de structure str doit

être global pour pouvoir être utilisé dans echange .

Comme nous l’avons déjà vu pour les variables simples ou les tableaux, c’est l’adresse de la

structure qu’il faut passer à la fonction.

#include <stdio.h> struct str { int a; int b; };

Page 145: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

139

void echange(struct str *z); main() { struct str x; x.a = 1; x.b = 2;

printf("avant appel : a = %d, b = %d\n", x.a, x.b); echange(&x);

printf("après appel : a = %d, b = %d\n", x.a, x.b); } void echange(struct str *z) { int tmp;

printf("début échange : a = %d, b = %d\n", (*z).a, (*z).b);

tmp = (*z).b; (*z).b = (*z).a; (*z).a = tmp;

printf("fin échange : a = %d, b = %d\n", (*z).a, (* z).b); }

Le résultat est alors :

avant appel : a = 1, b = 2 début échange : a = 1, b = 2 fin échange : a = 2, b = 1 après appel : a = 2, b = 1

L’échange a bien eu lieu. Dans la fonction echange , c’est maintenant un pointeur sur la

structure qui est utilisé pour recueillir l’adresse du paramètre. L’accès aux champs de la

structure se fait à l’aide de (*z).a et (*z).b . Les parenthèses sont importantes car

l’opérateur * est moins prioritaire que l’opérateur (. ). *z est la structure pointée par z . Pour

éviter cette notation un peu lourde, le langage C propose une facilité que vous devez utiliser,

l’opérateur -> . L’accès au champ se fait alors avec : z->a et z->b . La fonction echange

peut être réécrite de la manière suivante :

void echange(struct str *z) { int tmp;

printf("début échange : a = %d, b = %d\n", z->a, z- >b); tmp = z->b; z->b = z->a;

Page 146: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

140

z->a = tmp; printf("fin échange : a = %d, b = %d\n", z->a, z->b );

}

Exercice 8.2 : reprenez le programme de l’exercice 8.1 mais en utilisant une fonction pour la

lecture de toutes les informations contenues dans le tableau et une fonction pour l’affichage.

8.3 La définition de types nouveaux

Dans la norme ANSI C, il est possible de définir des types qui n’existent pas à partir de types

simples. Il suffit pour cela de faire suivre le mot clé typedef d’une construction ayant

exactement la même syntaxe qu’une déclaration de variable, puis de définir le nom du

nouveau type. Par exemple :

typedef unsigned long int * PULONG

mot clé déclarationnom du

nouveau type

Le nouveau type peut ensuite être utilisé dans une déclaration de variable :

PULONG x;

x est de type PULONG, c’est-à-dire un pointeur sur entier long non signé. En réalité,

l’opérateur typedef ne créé pas un nouveau type de donnée. Il ne fait qu’attribuer un

nouveau nom à un type existant. Cette méthode de déclaration permet de simplifier le code et

de résoudre les problèmes de portabilité. Pour cela, il suffit de changer les typedef pour les

types qui dépendent d’un système d’exploitation donné. La déclaration du typedef peut être

locale ou globale. Voici un autre exemple :

typedef int tab[10];

tab y;

tab est maintenant le type tableau de 10 entiers. La déclaration typedef peut aussi être

utilisée pour simplifier l’utilisation des structures comme par exemple dans :

Page 147: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

141

typedef struct {

int x;

int y;

} point;

point *z ;

point est maintenant le type structure à deux champs x et y. z pointe sur une structure de ce

type. On peut aussi utiliser cette méthode tout en spécifiant le nom de la structure :

typedef struct nom_de_la_structure { int a; int b; } NOUVEAU_TYPE, *PTR_SUR_NOUVEAU_TYPE;

Ces nouveaux types peuvent maintenant être utilisés :

NOUVEAU_TYPE x;

PTR_SUR_NOUVEAU_TYPE ptrx;

Le programme suivant résume toutes ces possibilités :

#include <stdio.h> #include <stdlib.h> typedef unsigned long int * PULONG; typedef int tab[10]; typedef struct nom_de_la_structure { int a; int b; } NOUVEAU_TYPE, *PTR_SUR_NOUVEAU_TYPE; typedef struct point { int x; int y; } POINT, *PPOINT; main() { PULONG x; tab y; NOUVEAU_TYPE w; PPOINT z;

Page 148: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

142

x = malloc(sizeof(unsigned long int)); *x = 10; z = malloc(sizeof(POINT)); z->x = 5; z->y = -5; /* .. */ }

ATTENTION : après exécution de la ligne « PPOINT z; » , le compilateur n’a réservé que

32 bits pour stocker l’adresse de la structure dans z. Cette adresse est non initialisée, comme

après toute déclaration de pointeurs. Il faut ensuite obligatoirement exécuter un malloc pour

réserver la mémoire nécessaire aux différents éléments de la structure puis attribuer l’adresse

de début de zone au pointeur. Si vous ne le faites pas, il y aura une erreur de débordement

quand vous essayerez d’écrire dans un des champs de la structure.

Exercice 8.3 : soit le modèle de structure suivant.

typedef struct _POINT {

char c;

int x, y;

} POINT, *PPOINT ;

Ecrire une fonction qui reçoit en argument l’adresse d’une structure de type _POINT et qui

retourne une structure de même type correspondant à un point de même nom et de

coordonnées opposées. La fonction mettra à 0 le point reçu. Ecrire un petit programme d’essai

de cette fonction.

Page 149: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

143

9. Les fichiers

9.1 Introduction

Un fichier est un objet physique qui sert à créer des entrées-sorties entre les programmes qui

s’exécutent et le monde extérieur. Dans le monde Unix (et donc dans Linux), toutes les

entrées-sorties sont vues comme des fichiers (écran, clavier, terminaux, disques, ...). On peut

accéder à ces fichiers :

• soit grâce à des fonctions de bas niveaux (les appels systèmes) qui gèrent des descripteurs

de fichiers. Les descripteurs de fichiers sont des valeurs de type int que le système

d’exploitation associe à un fichier à la demande d’un programme.

• soit grâce à des fonctions de plus haut niveau (dont le degré d’abstraction est plus élevé

car elles masquent les appels systèmes) qui manipulent des flux. C’est la méthode que

nous allons voir maintenant.

Les flux (stream ou flot de données) sont des abstractions ajoutant automatiquement aux

descripteurs de fichiers des mémoires tampons d’entrée-sortie, des verrous ainsi que d’autres

informations de contrôle. Les flux sont de type « opaque » FILE, c’est à dire que le modèle de

structure de type FILE est défini dans <stdio.h> , mais que l’on ne s’intéresse pas au

contenu de la structure. Il ne faut pas chercher à accéder aux champs internes de la structure

FILE ni à utiliser des objets de type FILE, mais uniquement à utiliser des pointeurs sur ces

objets. Les fonctions de haut niveau de la bibliothèque standard utilisent le pointeur sur le flux

et gèrent automatiquement les allocations et les libérations mémoires nécessaires.

Lorsque l’on désire accéder à un fichier par l’intermédiaire d’un flux, on invoque la fonction

fopen() . Cette fonction prend en argument le nom du fichier désiré ainsi qu’une chaîne de

caractères indiquant le mode d’accès au fichier. Elle renvoie un pointeur sur un flux de type

FILE. La fonction fclose() met fin à l’association entre le fichier et le flux et donc ferme

le fichier. On utilise ces fonctions ainsi :

Page 150: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

144

1) #include <stdio.h> 2) main() 3) { 4) FILE *fp; 5) fp = fopen("essai.txt", "w"); 6) fclose(fp); 7) }

Ligne 4 : création d’un pointeur sur un flux de type FILE. Le pointeur est bien sur non

initialisé.

Ligne 5 : fopen() ouvre le fichier essai.txt (dans le répertoire courant) en mode

écriture et lui associe un flux dont l’adresse est copiée dans le pointeur fp .

Ligne 6 : le flux dont l’adresse est dans fp est détruit et le fichier correspondant est

fermé.

Tout programme s’exécutant sous Linux (et sous Windows) dispose de trois flux ouverts

automatiquement lors de son démarrage :

• stdin : flux d’entrée standard. Ce flux est ouvert en lecture seule. Il s’agit par défaut du

clavier. Le programme peut y prendre ses données.

• stdout : flux de sortie standard. Le programme affiche ses résultats dans ce flux qui est

ouvert en écriture seule. Par défaut, il s’agit de l’écran.

• stderr : flux d’erreur standard. Ce flux, ouvert en écriture seule, sert à afficher des

informations concernant le fonctionnement du programme ou ses éventuels problèmes.

Par défaut, ces informations sont aussi affichées sur l’écran.

Au niveau de l’interpréteur de commandes (le shell), il est possible de rediriger les flux

d’entrée et de sortie d’un programme grâce aux opérateurs >, < et | . On peut par exemple :

• rediriger la sortie standard d’un programme vers un fichier grâce à l’opérateur > ;

mon_programme > sortie.txt

• rediriger l’entrée standard d’un programme depuis un fichier grâce à l’opérateur < ;

mon_programme < entree.txt

• rediriger la sortie standard d’un programme vers l’entrée standard d’un autre programme

en utilisant l’opérateur | (le pipe) ; programme1 | programme2

Page 151: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

145

9.2 Ouverture et fermeture d’un flux

Comme nous l’avons déjà vu, la fonction fopen crée un lien entre un flux et un fichier. Son

prototype est le suivant :

FILE * fopen(char *nom_de_fichier, char *mode) ;

Le nom du fichier est une chaîne de caractères qui peut utiliser un nom relatif

(../dir/toto.txt ) ou bien un nom absolu (/users/dut/titi.txt ). Si aucun

chemin n’est spécifié (essai.txt ) alors le fichier se trouve dans le même répertoire que le

programme exécutable.

Le mode indiqué en deuxième argument permet de préciser le type d’accès au fichier. Six

modes sont possibles :

mode type d’accès

r lecture seule

w écriture seule

a écriture seule en fin de fichier

r+ lecture et écriture

w+ lecture et écriture

a+ écriture en fin de fichier et lecture

Sur certains systèmes non standard comme MS-DOS (et tous les Windows), on peut

rencontrer des lettres supplémentaires comme « b » pour indiquer que le flux ne contient que

des données binaires ou « t » pour indiquer qu’il ne contient que du texte. On rencontre par

exemple des modes comme wb : écriture seule en binaire ou bien rt : lecture seule en mode

texte. On doit distinguer le mode texte du mode binaire pour la raison suivante. La fin de la

ligne dans un fichier texte sous Windows est constituée d’un code 0x0D (retour chariot) et

d’un code 0x0A (ligne suivante) alors qu’en langage C, la fin de ligne est seulement

constituée d’un code 0x0A.

Page 152: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

146

Il faut donc obligatoirement que la fin de ligne « langage C » soit convertie en fin de ligne

Windows à l’écriture dans le fichier (transformation du code 0x0A en deux codes 0x0D,

0x0A) et que la fin de ligne Windows soit convertie en fin de ligne « langage C » à la lecture

du fichier (transformation des deux codes 0x0D, 0x0A en un seul code 0x0A). Cette

transformation est effectuée automatiquement si le fichier a été ouvert en mode texte. Si le

fichier est ouvert en mode binaire, aucune transformation n’est effectuée. En effet, si vous

souhaitez lire une image à partir d’un fichier, il vaut mieux que les octets valant 0x0A dans ce

fichier ne soient pas transformés en 2 octets 0x0D, 0x0A (ce qui changerait la taille de

l’image).

Cette distinction binaire/texte n’a aucun sens sous Unix et est ignorée car la fin de ligne dans

un fichier texte est constituée d’un code 0x0A comme en langage C. Il n’y a donc aucune

transformation à effectuer. Voici deux exemples écrivant la chaine "coucou" dans deux

fichiers ouverts respectivement en mode texte et en mode binaire sous Windows :

En mode texte :

#include <stdio.h> main() {

char str[] = "coucou"; FILE *fp; fp = fopen("essai.txt", " wt "); fputs(str, fp); fputs("\n", fp); fputs(str, fp); fputs("\n", fp); fclose(fp); }

Le contenu du fichier vu avec un éditeur hexadécimal est le suivant :

Page 153: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

147

En mode binaire :

#include <stdio.h> main() {

char str[] = "coucou"; FILE *fp; fp = fopen("essai.txt", "wb"); fputs(str, fp); fputs("\n", fp); fputs(str, fp); fputs("\n", fp); fclose(fp); }

Le contenu du fichier vu avec un éditeur hexadécimal est le suivant. C’est ce que l’on verrait

sous Unix.

Voyons maintenant plus en détails l’influence des modes sur la position de la première lecture

ou écriture dans le fichier, ainsi que ce qui se passe si le fichier existe déjà ou si le fichier

n’existe pas.

type d’accès position à l’ouverture fichier existant fichier non existant

r lecture seule début de fichier ok erreur

w écriture seule début de fichier initialisation � création

a écriture seule fin de fichier ok création

r+ lecture et écriture début de fichier ok erreur

w+ lecture et écriture début de fichier initialisation � création

a+ lecture et écriture écriture à la fin lecture au début

ok création

Page 154: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

148

Une erreur classique du débutant consiste à ouvrir un fichier existant en mode « w »

ou « w+ ». La fonction fopen() écrase alors l’ancien fichier avec un nouveau

fichier vide de même nom. Les données contenues dans l’ancien fichier sont perdues.

La fonction fopen() retourne l’adresse du flux ouvert. Cette adresse doit être copiée dans

un pointeur de type FILE qui sera passé en paramètre à toutes les fonctions permettant

d’accéder au fichier ouvert. Si l’ouverture est impossible, fopen() renvoie le pointeur

NULL. Il est fortement conseillé de tester la valeur retournée par fopen() et de traiter

dans le programme le cas de la mauvaise ouverture du fichier, sinon il se produira une erreur

de débordement dans la suite du programme lors de l’exploitation du flux.

#include <stdio.h> #include <stdlib.h> main() { FILE *fp; char *FileName = "essai.txt"; if ( ( fp = fopen(FileName, "r") ) == NULL) {

printf("ouverture du fichier %s impossible\n", FileName);

exit(-1); /* arrêt du programme */ } /* lectures/ecritures sur le fichier */ fclose(fp); }

Quand on a terminé les opérations d’entrées-sorties sur le fichier, on ferme le flux ainsi que le

fichier à l’aide de la fonction fclose dont voici le prototype.

int fclose(FILE *stream);

Cette fonction accepte en entrée un pointeur de type FILE qui pointe sur un flux (stream) et

retourne la valeur 0 si le fichier a été fermé ou bien EOF en cas d’erreur de fermeture. La

sortie du programme force la fermeture de tous les fichiers ouverts. Il est toutefois préférable

de sortir proprement du programme en fermant explicitement tous les flux ouverts.

Page 155: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

149

9.3 Buffers associés aux flux

Un buffer (ou mémoire tampon ou mémoire cache) est une petite quantité de mémoire vive de

l’ordinateur qui est réservée par le système d’exploitation pour stocker temporairement des

données. Il existe, lors de l’écriture dans un flux, trois niveaux de buffers susceptibles de

différer l’écriture. Tout d’abord, le flux lui-même est l’association d’un buffer et d’un

descripteur de fichier. Des fonctions existent dans la librairie standard (setvbuf , setbuf )

qui permettent de paramétrer le comportement de ces buffers. La fonction fflush permet de

forcer le vidage du buffer :

int fflush(FILE *flux);

Elle provoque :

1) Le vidage du buffer dans le fichier pour un flux en sortie,

2) La mise à 0 du buffer pour un flux en entrée. On n’utilise généralement que

fflush(stdin) pour vider le buffer clavier. La mise à 0 d’un buffer en entrée n’a

généralement pas de sens pour un fichier.

fflush retourne 0 si l’opération s’est bien déroulée ou EOF dans le cas contraire. L’appel

de fflush(NULL); vide les buffers de tous les flux en attente d’écriture. La fin de

programme vide automatiquement tous les buffers associés aux flux ouverts. La fonction

fclose(FILE *flux) provoque automatiquement le vidage du buffer associé au flux.

Toutefois, le vidage du buffer ne provoque pas obligatoirement l’écriture des données dans le

fichier physique. En effet, le système d’exploitation gère son propre niveau de mémoire cache

pour limiter les accès aux disques. En général, tous les mécanismes de mémoire tampon sont

destinés à différer les écritures sur périphérique lent afin qu’elles aient lieu à un moment où

l’ordinateur est inoccupé, ceci afin d’améliorer les performances globales du système. L’appel

système :

int sync(void);

force l’écriture sur le contrôleur du disque. Mais même avec cette fonction, l’écriture n’est

pas certaine car il existe des mémoires tampons sur le contrôleur matériel du disque dur ainsi

que sur le disque dur lui-même.

Page 156: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

150

fluxbuffer

fichiermémoire cache

programme

contrôleurmémoire cache

mémoirecacheintégré

utilisateur

librairiestandard C

systèmed’exploitation

périphérique

supportphysique

(disque dur)

fflush

sync

C’est la raison pour laquelle il ne faut pas éteindre son ordinateur brutalement en appuyant sur

le bouton marche/arrêt car la coupure de l’alimentation vous ferait perdre le contenu des

divers buffers et mémoires tampons qui ne sont pas encore écrits sur le disque. En suivant la

procédure d’extinction du système d’exploitation, vous avez la garantie que toutes les

écritures sont effectivement réalisées et que les caches sont vidés.

9.4 Lecture et écriture dans un flux

La majorité des fonctions permettant de lire ou d’écrire dans un fichier nous est déjà connue.

On retrouve trois méthodes d’entrées-sorties connues.

9.4.1 Les lectures et écritures par caractère

La fonction :

int fgetc(FILE *stream);

lit un caractère dans un flux et retourne son code ASCII dans un entier. La fonction :

int fputc(int c, FILE *stream);

Page 157: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

151

écrit le caractère c (de type entier) dans un flux et retourne la valeur du caractère écrit si

l’écriture s’est bien passée ou EOF en cas d’erreur. Le programme suivant copie le fichier

entree.txt dans le fichier sortie.txt caractère par caractère.

#include <stdio.h> #include <stdlib.h> main() { FILE *fi, *fo; int c; fi = fopen("entree.txt", "r"); fo = fopen("sortie.txt", "w"); while((c = fgetc(fi)) != EOF) fputc(c, fo); fclose(fi); fclose(fo); }

Exercice 9.1 : écrivez un programme qui calcule la taille d’un fichier en le lisant caractère par

caractère.

9.4.2 Les lectures et écritures par ligne

La fonction :

char * fgets(char *chaine, int n, FILE *stream);

accepte en paramètres une chaîne de caractères, un entier qui contient le nombre de caractères

à lire et un pointeur sur FILE . Elle retourne le pointeur sur le début de chaîne en cas de

lecture sans erreur ou bien le pointeur NULL en cas de fin de fichier ou d’erreur. Le

fonctionnement de fgets est un peu plus compliqué que celui de gets . gets lit sur

stdin tous les caractères disponibles jusqu’au caractère fin de ligne ↵. Si le nombre de

caractères lus est supérieur à la taille du tableau où ils doivent être stockés, il y a

débordement. Avec fgets , on peut spécifier le nombre de caractères à lire, ce qui empêche

le débordement du tableau. Son comportement est le suivant. fgets lit les caractères du

fichier et les range dans le tableau pointé par chaine jusqu’à ce qu’une des trois conditions

suivantes soit vraie :

Page 158: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

152

1) rencontre d’un caractère fin de ligne ↵ (qui est mis dans le tableau).

2) rencontre de la fin du fichier.

3) il ne reste plus qu’un seul caractère libre dans le tableau.

fgets complète alors le tableau avec un caractère null (\0 ). Par exemple, la lecture de la

première ligne du fichier essai.txt suivant :

Avec le programme :

#include <stdio.h> main() { char s1[100]; FILE *fp; fp=fopen("essai.txt", "r"); fgets(s1, 99, fp); fclose(fp); }

va donner dans s1 :

c

0x63

o

0x6F

u

0x75

c

0x63

o

0x6F

u

0x75

\n

0x0A

\0

0x00

La fonction fputs :

int fputs(const char * chaine, FILE *stream);

accepte en paramètres un pointeur vers une chaîne se terminant par un caractère null et un

pointeur vers FILE . Elle retourne une valeur positive ou nulle si l’écriture se déroule

normalement et EOF en cas d’erreur. fputs écrit dans le fichier le contenu d’un tableau

contenant une chaîne dont la fin est indiquée par un caractère null. Ce tableau peut contenir un

caractère fin de ligne. fputs peut donc servir à écrire indifféremment une ligne de texte ou

Page 159: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

153

une chaîne quelconque. Le programme suivant copie le fichier entree.txt dans le fichier

sortie.txt ligne par ligne si la taille de la chaîne est supérieure à la taille de la ligne.

Sinon, la copie se fait portion de ligne par portion de ligne.

#include <stdio.h> main() { FILE *fi, *fo; char ligne[20]; fi = fopen("entree.txt", "r"); fo = fopen("sortie.txt", "w"); while(fgets(ligne, 10, fi) != NULL) fputs(ligne, fo); fclose(fi); fclose(fo); }

Exercice 9.2 : écrivez un programme qui lit une phrase au clavier, qui l’écrit dans un fichier,

puis qui relit le fichier et affiche le contenu à l’écran.

9.4.3 Les lectures et écritures formatées

Les fonctions :

int fscanf( FILE *stream, const char *format [, arg ument ]... );

int fprintf( FILE *stream, const char *format [, ar gument ]...);

s’utilisent de la même manière que les fonctions printf et scanf sauf qu’elles lisent ou

écrivent sur un flux au lieu de le faire sur les entrées-sorties standards. Par exemple :

fprintf(fp, "bonjour maitre\n");

écrit la chaîne de caractères "bonjour maitre" dans le fichier associé au flux fp et

provoque un changement de ligne. Le programme suivant lit dans le fichier entree.txt

deux valeurs flottantes et les écrit dans les variables x et y.

#include <stdio.h> main() { FILE *fp; float x, y;

Page 160: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

154

fp = fopen("entree.txt", "r"); fscanf(fp, "%f%f", &x, &y); fclose(fp); }

La gestion des espaces avec la fonction fscanf est aussi pénible qu’avec la fonction

scanf , notamment en ce qui concerne les chaînes de caractères. Sauf cas particulier, on

utilisera plutôt la fonction fgets (associée éventuellement avec sscanf ).

Exercice 9.3 : écrivez un programme qui lit des double au clavier et qui les écrit dans un

fichier au fur et à mesure de la saisie, puis qui relit le fichier et affiche les nombres à l’écran.

On interrompra la saisie en saisissant un 0. On testera l’ouverture du fichier.

Il faut noter qu’il est possible de remplacer, pour chacune de ces trois méthodes, le flux

associé à un fichier par un des trois flux ouverts automatiquement au démarrage. Cela veut

dire par exemple que les deux lignes suivantes sont équivalentes :

printf("bonjour maitre\n");

fprintf(stdout, "bonjour maitre\n");

Le traitement d’une mauvaise ouverture d’un fichier peut donc se faire comme suit :

#include <stdio.h> #include <stdlib.h> main() { FILE *fp; char *FileName = "essai.txt"; if ( ( fp = fopen(FileName, "r") ) == NULL) {

fprintf( stderr , "ouverture du fichier %s impossible\n", FileName);

exit(-1); } /* ...*/ fclose(fp); }

Le message d’erreur est maintenant envoyé vers stderr au lieu de stdout comme avec

printf .

Page 161: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

155

9.4.4 Les lectures et écritures binaires

En langage C, il existe deux manières de lire et d’écrire des valeurs dans des fichiers :

• le mode texte avec les fonctions que nous venons de voir, fgetc , fputc , fgets ,

fputs , fscanf , fprintf .

• le mode binaire avec les fonctions fread et fwrite .

Dans le mode texte, on écrit (ou on lit) dans le fichier les codes ASCII des chiffres ou des

lettres qui composent la valeur. Dans le mode binaire, on écrit (ou on lit) dans le fichier les

octets qui codent la valeur. Prenons par exemple un entier. En mode texte, on écrira la valeur

de l’entier avec un fprintf :

#include <stdio.h> main() { FILE *fp; int x = 123456; fp = fopen("entier_texte.txt", "wt");

fprintf(fp, "%d", x); fclose(fp); } Avec un éditeur hexadécimal, on trouvera dans la colonne du milieu la valeur des octets dans

le fichier (ce sont bien des codes ASCII) et dans la colonne de droite les caractères

correspondants :

En mode binaire, on écrira la valeur de l’entier avec un fwrite :

#include <stdio.h> main() { FILE *fp; int x = 123456; fp = fopen("entier_binaire.txt", "wb"); fwrite(&x, sizeof(int), 1, fp); fclose(fp); }

Page 162: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

156

Avec le même éditeur hexadécimal, on trouvera dans le fichier les 4 octets (poid faible en

premier) qui codent le nombre entier (123456 est bien égal à 0001E240 en hexadécimal).

Comme ce ne sont plus des codes ASCII, il y a n’importe quoi dans la colonne de droite :

On obtiendra le même genre de contenu avec un nombre flottant : des codes ASCII en mode

texte et des octets (4 pour un float ou 8 pour un double) en mode binaire. En général, le fichier

binaire est plus petit que le fichier texte. Donc, si vous souhaitez sauvegarder une grande

quantité de résultats numériques tout en conservant un fichier de taille raisonnable, il vaut

mieux utiliser le mode binaire. En contrepartie, le fichier ne sera plus lisible avec un éditeur

de texte.

Remarque : en ce qui concerne les variables de type char, la distinction entre mode texte ou

mode binaire n’a guère de sens. Dans les deux cas, on va écrire dans le fichier la valeur de

l’octet. Si vous écrivez dans un fichier des octets qui correspondent à des codes ASCII, alors

ce sera du texte que vous pourrez éditer. Si vous écrivez dans un fichier des octets qui

correspondent par exemple aux points (pixels) d’une image alors un éditeur de texte ne pourra

pas le lire correctement. Faites juste attention au "wb" et "rb" sous Windows.

Voyons maintenant plus en détail les fonctions dîtes d’entrées-sorties binaires, fread et

fwrite . Elles permettent de lire ou d’écrire le contenu d’un bloc mémoire sans se soucier de

son interprétation.

int fwrite(const void * ptr, int taille_elem, int n b_elem, FILE *flux);

La fonction fwrite accepte comme paramètres :

1) un pointeur sur un tableau de n’importe quel type,

2) la taille du type de donnée à écrire,

3) le nombre d’éléments à écrire,

4) un pointeur sur FILE.

Page 163: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

157

Elle retourne le nombre d’éléments effectivement écrits. Dans l’exemple suivant, le

programme écrit un tableau de 256 entiers 32 bits dans le fichier sortie.bin .

#include <stdio.h> #include <stdlib.h> main() { FILE *fp; int *ptr, i; ptr = malloc(256*sizeof(int)); for (i = 0; i < 256; i++) ptr[i] = i;

fp = fopen("sortie.bin", "wb"); i = fwrite(ptr, sizeof(int), 256, fp); fclose(fp); }

Attention, sous Windows, il faudrait utiliser le mode « wb » car les fichiers sont en mode

texte par défaut. Voyons maintenant la fonction de lecture binaire :

int fread(void * ptr, int taille_elem, int nb_elem, FILE *flux);

Comme pour fwrite , la fonction fread accepte comme paramètres :

1) un pointeur sur un tableau de n’importe quel type,

2) la taille du type de donnée à lire,

3) le nombre d’éléments à lire,

4) un pointeur sur FILE.

Elle retourne le nombre d’éléments effectivement lus. Dans l’exemple suivant, le programme

lit 256 entiers 32 bits dans le fichier sortie.bin et les range dans un tableau.

#include <stdio.h> #include <stdlib.h> main() { FILE *fp; int *ptr, i; ptr = malloc(256 * sizeof(int)); fp = fopen("sortie.bin", "rb"); i = fread(ptr, sizeof(int), 256, fp); fclose(fp); }

Page 164: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

158

9.5 Positionnement dans un flux

Il est rare que dans un programme un peu complexe, on ait uniquement besoin de lire les

données d’un fichier séquentiellement, c’est à dire les unes à la suite des autres, du début à la

fin, sans jamais revenir en arrière ou sans sauter des portions de fichier. Il est donc naturel que

la librairie standard mette à notre disposition des fonctions permettant de se déplacer

librement dans un flux (fichier) avant de lire son contenu ou d’y écrire des données.

Le système d’exploitation gère un compteur de position sur chaque flux qui indique la

différence en octets entre le début du flux et la position courante. Ce compteur est mis à jour

après chaque opération de lecture ou d’écriture. La fonction :

long ftell(FILE *flux);

retourne la valeur de la position courante dans le flux, c’est à dire la valeur du compteur de

position. Ce compteur vaut 0 au début du flux. Si la position est fausse, ftell retourne -1.

La fonction :

int fseek(FILE *flux, long position, int depart);

permet de se déplacer dans le flux. La position (deuxième argument, positif ou négatif) est

indiquée en octets depuis le point de départ (troisième argument). Celui-ci peut prendre les

valeurs suivantes :

• SEEK_SET : le point de départ est le début du fichier (0).

• SEEK_CUR : le point de départ est la position courante (1).

• SEEK_END : le point de départ est la fin du fichier (2).

Ces trois constantes symboliques (macros) sont définies dans <stdio.h> . La fonction

fseek retourne 0 si elle réussit et -1 en cas d’échec. Par exemple, à l’appel suivant, le

compteur d’octets se place à la fin du fichier (sur l’octet qui suit le dernier octet du fichier de

façon à être prêt pour une écriture) :

fseek(fp, 0, SEEK_END);

Page 165: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

159

Par exemple, à l’appel suivant, le compteur recule de trois octets par rapport à la position

courante :

fseek(fp, -3, SEEK_CUR);

La fonction rewind :

void rewind(FILE *flux);

permet de ramener le compteur de position au début du flux. Elle est équivalente à

fseek(fp, 0, SEEK_SET);

Il n’y a aucun rapport entre le pointeur de flux fp et le compteur de position dans le

fichier. Lire une valeur dans le fichier ne modifie en rien l’adresse contenue dans fp.

L’adresse de cette structure est déterminée lors de l’ouverture du fichier et ne doit

jamais être modifiée. Ecrire fp++; n’a jamais fait avancer d’un octet dans le fichier.

Voici par exemple un programme qui indique diverses positions sur un fichier (qui contient

18 octets quelconques) et qui détermine la taille de ce fichier.

#include <stdio.h> main() { FILE *fp; int position; fp = fopen("entree.txt", "r"); position = ftell(fp); printf("Au début, position = %d\n", position); fseek(fp, 0, SEEK_END); position = ftell(fp); printf("A la fin, position = %d\n", position); printf("taille du fichier = %d\n", position); rewind(fp); position = ftell(fp); printf("Après rewind, position = %d", position); fclose(fp); }

Page 166: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

160

Le résultat est le suivant :

Au début, position = 0

A la fin, position = 18

taille du fichier = 18

Après rewind, position = 0

entree.txt

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

début

fin

Lorsque fseek ou rewind sont invoquées, le contenu éventuel du buffer de sortie associé

au flux est écrit dans le fichier avant le déplacement. Il existe des systèmes d’exploitation

ayant une restriction à l’utilisation d’un flux en lecture et en écriture. Sur ces systèmes, une

lecture ne peut suivre une opération d’écriture que si on a appelé fflush , fseek ou

rewind entre les deux opérations. De même, avant une écriture qui suit une lecture, il faut

obligatoirement appeler fseek ou rewind . Ces limitations n’existent ni sous Linux, ni sous

Windows.

Exercice 9.4 : reprenez le programme de l’exercice 9.3 mais affichez les nombres stockés

dans le fichier en partant de la fin.

9.6 Utilisation d’un fichier de configuration

Le passage direct de paramètres à la fonction main depuis la ligne de commande devient peu

pratique lorsque le nombre de paramètres est trop important. On utilise alors un fichier de

configuration dont on passe le nom en ligne de commande. L’utilisation du programme

devient par exemple :

essai [-h] nom_du_fichier_de_configuration

Page 167: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

161

Par convention, on appelle le fichier de configuration essai.cfg . Il s’agit d’un fichier texte

que l’on écrit avec un éditeur ASCII standard (et surtout pas un traitement de texte). Il

contient généralement :

• Une première ligne de commentaires que l’on utilise uniquement pour identifier le

programme associé.

• Plusieurs lignes contenant les paramètres à récupérer.

Voici un exemple de fichier essai.cfg :

fichier de configuration du programme essai toto.bin /* nom du fichier à traiter */ 50 /* paramètre 1 */ 100 /* paramètre 2 */ 0 /* paramètre 3 */

Exercice 9.5 :

1. On pourrait bien sur utiliser la fonction fscanf pour essayer de récupérer les valeurs

directement. Pourquoi ne le fait-on pas ?

2. Quels sont les avantages de l’utilisation d’un fgets suivi d’un sscanf ?

3. Ecrivez le programme permettant de récupérer les paramètres contenus dans le fichier de

configuration essai.cfg .

4. Cette manière de procéder a-t-elle des limitations ?

Il est à noter que l’on peut associer l’utilisation d’un fichier de configuration avec celle d’une

interface graphique par exemple pour sauver une configuration. Il suffit de créer un fichier de

configuration par défaut lors de la première utilisation du programme dans le répertoire racine

de l’utilisateur, puis de sauver les changements de configuration du programme dans ce

fichier avant d’en sortir. Traditionnellement, sous Unix, le nom des fichiers de configuration

commence par un point, car ce type de fichier n’est pas listé par défaut avec la commande ls

(on appelle cela un fichier invisible car il n’est listé que par la commande ls -la ). Par

exemple, l’éditeur Linux nedit crée un fichier de configuration appelé .neditdb que vous

pouvez éditer.

L’autre solution (propriétaire) consiste à utiliser la base de registre de Windows pour sauver

la configuration propre à chaque utilisateur. Il n’y a guère d’autre choix car Windows (avant

Page 168: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

162

2000) ne crée pas de compte utilisateur par défaut. Si vous utilisez un fichier de configuration,

il s’appliquera automatiquement à tous les utilisateurs (ce qui n’est pas forcément gênant).

Page 169: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

163

10. Divers

10.1 Exécution de commandes

La fonction system permet d’exécuter une commande à partir d’un programme. Voici son

prototype :

int system(const char *str);

Cette fonction exécute la commande contenue dans la chaîne str , puis reprend l’exécution

du programme en cours. Par exemple :

system("date");

Cette instruction exécute le programme date qui affiche la date et l’heure sur la sortie

standard (l’écran par défaut). Elle retourne la valeur retournée par le programme exécuté. En

général, une valeur 0 indique que tout s’est bien passé. Il est important de noter le phénomène

suivant : si vous lancez l’exécution d’un programme qui fonctionne en mode interactif comme

un éditeur ou bien un programme qui dure très longtemps, le programme en cours (celui qui a

lancé la fonction system ) est arrêté tant que le programme lancé n’a pas terminé. Par

exemple, sous Linux, si vous exécutez

system("nedit toto.txt");

dans votre programme, celui-ci sera suspendu tant que l’éditeur nedit n’aura pas été fermé.

Evidemment, si vous lancez la commande en tache de fond :

system("nedit toto.txt &");

le programme appelant reprend la main immédiatement.

Exercice 10.1 : écrivez un programme qui fasse la copie dans le répertoire courant d’un

fichier dont le nom est contenu dans le tableau nom1 dans un fichier dont le nom est contenu

dans le tableau nom2.

Page 170: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

164

#include <stdlib.h> void main() { char nom1[] = "toto.txt"; char nom2[] = "titi.txt";

... system( ??? ) ;

}

10.2 Les opérateurs binaires

Le C fournit six opérateurs qui réalisent des manipulations au niveau des bits. On ne peut les

appliquer qu’aux variables entières, c’est-à-dire de type char , short , long et int , signés

ou non.

opérateur opération effectuée

& ET bit à bit

| OU bit à bit

^ OU exclusif bit à bit

~ inversion bit à bit

<< décalage à gauche

>> décalage à droite

Ne confondez pas les opérateurs bit à bit & et | avec les opérateurs logiques && et || dont le

résultat est booléen.

Exercice 10.2 : Soit la portion de programme suivante. Donnez les valeurs successives de y.

Dans quel cas utilise-t-on chacun de ces 4 opérateurs ?

unsigned int x = 255, y; y = x & 0xff; y = y & 0x33; y = x | 0xcc; y = (y ^ y) | 1; y = y ^ 0x81; y = ~y; y = ~0;

Page 171: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

165

Les opérateurs << et >> décalent leur opérande de gauche du nombre de bits indiqué par leur

opérande de droite, qui doit être un entier positif. Ainsi, x << 2 décale la valeur de x de 2

bits vers la gauche, en remplissant les 2 bits de droite par des 0, ce qui revient à une

multiplication par 4. Ceci est vrai que x soit signé ou non.

Si l’on décale une quantité non signée vers la droite (x >> 2 ), les bits de gauche sont

toujours mis à 0. Si cette quantité est signée, alors les bits de gauche peuvent soit se remplir

avec les bits de signe (décalage arithmétique avec extension de signe) soit se remplir avec des

0 (décalage logique). Cela dépend de la machine. Sur PC (sous Windows comme sous Linux),

il y a extension de signe. Sauf exception, il vaut mieux éviter d’utiliser les opérateurs

binaires sur des entiers signés.

Exercice 10.3 : écrivez une fonction qui change la position des octets dans un entier 32 bits

(4321 → 1234) : unsigned change_indian(unsigned x)

10.3 Les énumérations

Il est toujours possible de définir des constantes avec la directive #define comme dans :

#define LUNDI 0 #define MARDI 1 #define MERCREDI 2 #define JEUDI 3 #define VENDREDI 4 #define SAMEDI 5 #define DIMANCHE 6

mais l’affectation d’une valeur à chaque constante devient pénible quand le nombre de

constantes est grand. Le langage C offre une autre méthode pour créer des constantes

multiples, l’énumération :

enum {LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAME DI,

DIMANCHE};

L’avantage de cette méthode est que, par défaut, les identificateurs LUNDI, MARDI, ...

sont des constantes de type int dont les valeurs sont 0, 1, 2, 3, 4, 5, 6. La génération des

valeurs se fait de manière automatique à partir de 0. Si on désire donner des valeurs

particulières aux constantes, c’est possible en écrivant :

Page 172: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

166

enum {FRANCE = 10, ESPAGNE = 20, ITALIE = 30};

Il n’est d’ailleurs pas nécessaire de donner une valeur à chaque constante :

enum {FRANCE = 10, ESPAGNE, SUISSE, ITALIE = 30};

donnera la valeur 11 à ESPAGNE et 12 à SUISSE. Les caractères en C étant des entiers, il est

aussi possible d’écrire :

enum {OUI = ‘O’, NON = ‘N’};

N’oubliez pas qu’il est d’usage de mettre en majuscule les noms des constantes dans un

programme. Cela améliore grandement sa lisibilité. Les énumérations sont identiques aux

structures d’un point de vue syntaxique. Après le mot clé enum, il peut y avoir une étiquette

qui permettra plus loin dans le programme de déclarer des variables de type énumération

comme par exemple :

enum reponse {OUI = ‘O’, NON = ‘N’}; enum reponse rep; rep = OUI;

Il est aussi possible de créer un nouveau type en utilisant l’instruction typedef :

typedef enum {OUI = ‘O’, NON = ‘N’} REPONSE; REPONSE rep; rep = OUI;

10.4 Les opérateurs d’incrémentation et de décrémentation

Nous avons déjà vu les opérateurs d’incrémentation et de décrémentation du langage C dont

les équivalences sont les suivantes :

i = i + 1; i++; ++i;

i = i - 1; i--; --i;

Page 173: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

167

Nous sommes en présence de deux expressions qui non seulement possèdent une valeur, mais

qui réalisent aussi une action. Utilisées seules, les expressions i++; et ++i; sont

équivalentes. Utilisées dans une autre expression, il faut savoir que dans i++; on évalue

d’abord et on incrémente après alors que dans ++i; on incrémente d’abord et on évalue

après.

i--; évaluation puis décrémentation

--i; décrémentation puis évaluation

Ainsi, si i vaut 5, l’expression :

n = i++ - 5;

affectera à i la valeur 6 et à n la valeur 0, alors que dans les mêmes conditions initiales,

l’expression :

n = ++i - 5;

affectera à i la valeur 6 et à n la valeur 1. Cela signifie aussi que dans l’expression :

tab[i++] = getchar();

on met le caractère lu au clavier dans tab[i] , puis on incrémente i alors que dans

tab[++i] = getchar();

on incrémente d’abord i avant de mettre le caractère lu au clavier dans tab[i] .

Exercice 10.4 : remplissez le tableau suivant avec les valeurs de x, y et z.

x y z

x = y = 1;

z = x++ - 1;

z += - x++ + ++y;

z = x / ++x;

Page 174: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

168

10.5 L’opérateur virgule

Deux expressions séparées par une virgule :

expr1 , expr2

s’évaluent de gauche à droite (expr1 puis expr2 ), et le résultat de l’expression prend le

type et la valeur de l’opérande de droite. On peut écrire par exemple :

i = (j = 2, 1);

ce qui est une manière particulièrement affreuse d’écrire :

i = 1;

j = 2;

Par contre, une utilisation courante de l’opérateur virgule se trouve dans les expressions d’une

boucle for . Si on désire utiliser deux indices dans une seule boucle, il est agréable et lisible

d’écrire :

for (i = 0, j = 1; i < LIMITE; i++, j = j + 3) {

...

}

10.6 L’opérateur conditionnel ?

Les instructions :

if(a > b) z = a; else z = b;

affectent à z le maximum de a et de b. L’expression conditionnelle qui utilise

l’opérateur ? : est une autre manière, plus compacte, d’arriver au même résultat :

z = (a > b) ? a : b;

Page 175: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

169

Dans l’expression conditionnelle :

expr1 ? expr2 : expr3

on commence par évaluer l’expression expr1 . Si elle n’est pas nulle (c’est-à-dire si elle est

vraie) alors la valeur de l’expression conditionnelle est la valeur de expr2 . Sinon la valeur

de l’expression conditionnelle est la valeur de expr3 .

Exercice 10.5 : écrivez l’expression conditionnelle calculant la valeur absolue d’un nombre.

Exercice 10.6 : écrivez l’expression conditionnelle qui détermine si un nombre est pair ou

non.

10.7 Les macros avec paramètres

Nous avons déjà vu l’utilisation de constantes avec la directive #define comme dans :

#define NB_COLONNES 100

En réalité, pour le préprocesseur, il s’agit de macros sans paramètres au même titre que

#define forever for( ; ; )

qui réalise une boucle infinie. Il est possible de créer des macros (en majuscules par

convention) avec paramètres de la manière suivante :

#define NOM_MACRO(liste_de_paramètres_formels) défi nition_macro

La liste de paramètres formels est une liste d’identificateurs séparés par des virgules. Par

exemple, la macro MAX détermine le maximum de a et de b :

#define MAX(a,b) (((a) > (b)) ? (a) : (b))

Tout appel de cette macro dans le programme devra se faire sous la forme :

z = MAX(x,y);

Page 176: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

170

Avant la compilation du programme, le préprocesseur va remplacer l’ensemble formé du nom

de la macro suivi de la liste de paramètres entre parenthèses (ici MAX(x,y) ) par

définition_macro dans laquelle chaque paramètre formel est remplacé par le paramètre

effectif correspondant. Cette opération de remplacement s’appelle l’expansion de la macro.

Dans notre exemple, on trouvera dans le code source après expansion (attention au ;) :

z = (((x) > (y)) ? (x) : (y));

ce qui correspond bien à la recherche de maximum vu précédemment. L’utilité principale des

macros avec paramètres est de bénéficier de la clarté d’expression de la fonction sans souffrir

de sa lenteur : le code est inséré directement dans le source, il n’y a donc aucune perte de

temps dû à l’appel et au retour de la fonction. On n’utilise des macros avec paramètres que

pour réaliser des traitements courts (quelques lignes au maximum). Attention, la distinction

entre macro avec et sans paramètre se fait sur le caractère qui suit le nom de la macro. Si c’est

un espace, il n’y a pas de paramètre, si c’est une parenthèse, il y a des paramètres. L’écriture

suivante est une erreur classique :

#define MAX (a,b) (((a) > (b)) ? (a) : (b))

L’espace après MAX indique qu’il s’agit d’une macro sans paramètre. L’expansion de

z = MAX(x,y);

risque de vous réserver quelques surprises. L’écriture de macros recèle de nombreux pièges,

certains évitables et d’autres non. Supposons que l’on écrive :

#define CARRE(a) a * a

ce qui semple parfaitement correct. L’expansion de cette macro va donner :

z = CARRE(x); z = x * x;

ce qui parait toujours correct. Si on teste l’expansion avec une expression au lieu d’une

variable :

Page 177: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

171

z = CARRE(x + y); z = x + y * x + y;

On voit que la macro ne donne pas le bon résultat qui devrait être (x + y) * (x + y) .

Pour éviter ce genre de problème, il faut respecter les deux règles suivantes dans la définition

d’une macro :

• mettre entre parenthèses les occurrences des paramètres formels,

• mettre entre parenthèses le corps de la macro.

La définition correcte de CARRE est donc :

#define CARRE(a) ((a) * (a))

et l’expansion avec une expression est maintenant correcte.

z = CARRE(x + y); z = ((x + y) * (x + y));

Par contre, le problème suivant ne peut être résolu :

z = CARRE(x++); z = x++ * x++;

L’opérateur d’incrémentation est appliqué deux fois. Il vaut mieux éviter d’utiliser des effets

de bord sur les paramètres effectifs d’une macro, car les conséquences sont généralement

imprévisibles. N’oubliez pas que l’option -E du compilateur (GCC ou Visual C++) vous

permet d’obtenir le source après preprocessing sur la sortie standard et donc de tester

l’expansion des macros. C’est la méthode normale de mise au point.

Exercice 10.7 : écrivez la définition de la macro ABS(x) calculant la valeur absolue d’un

nombre.

Exercice 10.8 : écrivez la définition de la macro IsEvenNumber(x) qui détermine si un

nombre est pair ou non.

Page 178: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

172

Exercice 10.9 : écrivez la définition de la macro IsOddNumber(x) qui détermine si un

nombre est impair ou non.

Exercice 10.10 : écrivez la définition de la macro SIGN(x) qui détermine si le signe d’un

nombre est positif ou non.

Exercice 10.11 : écrivez la définition de la macro RANGE(val,min_val,max_val) qui

fonctionne de la manière suivante (opérateur de saturation) :

• si val est inférieure à min_val, val prend la valeur de min_val.

• si val est supérieure à max_val, val prend la valeur de max_val.

• si val est comprise entre min_val et max_val, val est inchangée.

Exercice 10.12 : écrivez la définition de la macro MIN(x,y) qui détermine le minimum de

deux nombres.

Exercice 10.13 : écrivez la définition de la macro ROUND(x) qui arrondit un nombre flottant

à l’entier le plus proche.

Exercice 10.14 : écrivez la définition de la macro TEST_BIT_5(x) qui teste la présence du

6ème bit dans un entier.

10.8 Ce que vous ne verrez pas

Les unions.

Les champs de bits.

Les fonctions avec un nombre variable de paramètres.

10.9 Définition de macro à l’invocation du compilateur

La plupart des compilateurs permettent de définir des macros sans paramètres. Il est alors

possible d’écrire un programme utilisant une macro qui n’est définie nulle part dans le source.

La définition de cette macro se fera à l’invocation du compilateur de la manière suivante :

cc -c -DNB_LIGNES=24 essai.c -o essai

Page 179: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

173

La macro NB_LIGNES prend la valeur 24 pendant la compilation de essai.c. Cette méthode

peut être très commode, notamment si elle est associée à la compilation conditionnelle.

10.10 Compilation conditionnelle

Les mécanismes de compilation conditionnelle ont pour but de compiler ou d’ignorer

certaines parties du programme, en fonction d’un test effectué à la compilation. La commande

du préprocesseur permettant de réaliser la compilation conditionnelle est la commande #if

qui peut prendre plusieurs formes :

• Le #if simple. Quand le préprocesseur rencontre :

#if condition

ensemble_lignes_de_code

#endif

il évalue la condition. Si la condition est vraie, les lignes de code sont compilées sinon,

elles sont ignorées.

• Le #if avec #else . Le else permet de compiler deux blocs de lignes différents suivant

l’état de la condition :

#if condition

ensemble_lignes_de_code_1

#else

ensemble_lignes_de_code_2

#endif

• Le #if avec #elif . Pour réaliser plusieurs branches, on utilise le #elif qui est

équivalent à un else if .

#if condition1 ensemble_lignes_de_code_1 #elif condition2 ensemble_lignes_de_code_2 #elif condition3 ensemble_lignes_de_code_3 #else ensemble_lignes_de_code_4 #endif

Page 180: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

174

Dans les 3 cas précédents, il est possible de remplacer les #if par des #ifdef ou

#ifndef :

#ifdef nom_de_macro

ou bien

#ifndef nom_de_macro

Dans ce cas, le test porte sur la définition ou non d’une macro (et non sur sa valeur). #ifdef

nom_de_macro est équivalent à « si la macro est définie » et #ifndef nom_de_macro est

équivalent à « si la macro n’est pas définie ». Une autre possibilité existe, l’utilisation de

l’opérateur defined associé à un #if . Elle est similaire au #ifdef , mais elle permet en

plus de tester plusieurs macros en même temps.

#if defined( macro1) || defined( macro2)

Un cas classique d’utilisation de la compilation conditionnelle est d’éviter plusieurs

inclusions d’un même fichier d’entête .h. Cela arrive notamment dans le cas d’un projet

complexe comprenant plusieurs fichiers sources. Pour être certain d’inclure un fichier une

seule fois, il suffit d’utiliser le mécanisme suivant :

/* fichier exemple.h */ #ifndef EXEMPLE_H #define EXEMPLE_H /* corps du fichier exemple.h */ ... #endif

Dans cet exemple, le fichier exemple.h ne peut être inclus qu’une fois dans un source. En

effet, la macro EXEMPLE_H n’est définie que dans ce fichier. Lors du premier passage du

préprocesseur, la variable étant indéfinie, celui-ci analyse l’intégralité du fichier et définit la

macro. Si ce fichier est à nouveau inclus dans le source par erreur, le préprocesseur ne le lira

pas car la macro EXEMPLE_H existe déjà. Il faut noter que l’on aurait aussi pu utiliser un

defined :

Page 181: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

175

/* fichier exemple.h */ #if !defined(EXAMPLE_H)

#define EXEMPLE_H /* corps du fichier exemple.h */ ... #endif

La commande #error permet d’afficher un message d’erreur lors de la compilation. Cette

commande a pour intérêt de vérifier des conditions permettant au programme de s’exécuter

sur ce système d’exploitation. Voici un exemple où on vérifie que la taille des entiers est

suffisante :

#include <limits.h> #if INT_MAX < 1000000 #error " entiers trop petits sur cette machine" #endif

Une des applications traditionnelle de la compilation conditionnelle est l’adaptation d’un

programme à un environnement comme WINDOWS ou LINUX. Par exemple, dans le projet

image , la commande système permettant la copie d’un fichier était différente entre les deux

systèmes d’exploitation. La macro WIN32 étant systématiquement définie sous Visual C++, il

suffit donc d’écrire les lignes suivantes pour régler définitivement le problème :

#ifdef WIN32 system("copy toto tutu"); #else system("cp toto tutu"); #endif

De la même manière, il est parfois commode d’avoir une version d’un programme en mode

Debug avec beaucoup de printf pour surveiller l’évolution des variables et une version

normale sans ces printf . Voici par exemple, le projet mastermind :

main() { int ref[NBCHIFFRES], essai[NBCHIFFRES]; int NbPos, NbChif, NbCoup, i, status;

Page 182: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

176

NbCoup = 0; tirage(ref, NBCHIFFRES);

#ifdef DEBUG for (i = 0 ; i < NBCHIFFRES ; i++)

printf("ref[%d]=%d", i, ref[i]); #endif

do { do { status = entree(essai, NBCHIFFRES); } ...

Sous Linux, si vous compilez le programme avec :

cc -DDEBUG master.c -o master

vous allez visualiser le tirage alors que si vous compilez normalement :

cc master.c -o master

le tirage n’apparaît plus. Il est à noter qu’avec Visual C++, une variable _DEBUG est définie

automatiquement par le compilateur quand vous êtes en mode Debug mais qu’elle ne l’est

plus quand vous êtes en mode Release (version finale).

Exercice 10.15 : le fichier suivant est un exemple réel de fichier d’entête, limits.h . Il indique

à Visual C++ les limitations numériques des variables qui dépendent du système d’exploitation.

Expliquez son fonctionnement. Faites attention à bien repérer les paires #if ... #endif.

/*** *limits.h - implementation dependent values * * Copyright(c)1985-1997, Microsoft Corporation. All rights reserved. * * Purpose: * Contains defines for a number of implementa tion dependent values * which are commonly used in C programs. [ANS I] ****/

#if _MSC_VER > 1000 #pragma once #endif

Page 183: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

177

#ifndef _INC_LIMITS #define _INC_LIMITS #if !defined(_WIN32) && !defined(_MAC) #error ERROR: Only Mac or Win32 targets supported! #endif #define CHAR_BIT 8 /* number of bits i n a char */ #define SCHAR_MIN (-128) /* minimum signed c har value */ #define SCHAR_MAX 127 /* maximum signed c har value */ #define UCHAR_MAX 0xff /* maximum unsigned char value */ #ifndef _CHAR_UNSIGNED #define CHAR_MIN SCHAR_MIN /* mimimum char val ue */ #define CHAR_MAX SCHAR_MAX /* maximum char val ue */ #else #define CHAR_MIN 0 #define CHAR_MAX UCHAR_MAX #endif /* _CHAR_UNSIGNED */ #define MB_LEN_MAX 2 /* max. # bytes in multibyte char */ #define SHRT_MIN (-32768) /* minimum (sig ned) short value */ #define SHRT_MAX 32767 /* maximum (sig ned) short value */ #define USHRT_MAX 0xffff /* maximum unsi gned short value */ #define INT_MIN (-2147483647 - 1) /* minimum (s igned) int value */ #define INT_MAX 2147483647 /* maximum (sig ned) int value */ #define UINT_MAX 0xffffffff /* maximum unsi gned int value */ #define LONG_MIN (-2147483647L - 1) /* minimum ( signed) long value */ #define LONG_MAX 2147483647L /* maximum (sig ned) long value */ #define ULONG_MAX 0xffffffffUL /* maximum unsi gned long value */ #if _INTEGRAL_MAX_BITS >= 8 #define _I8_MIN (-127i8 - 1) /* minimum sign ed 8 bit value */ #define _I8_MAX 127i8 /* maximum sign ed 8 bit value */ #define _UI8_MAX 0xffui8 /* maximum unsi gned 8 bit value */ #endif #if _INTEGRAL_MAX_BITS >= 16 #define _I16_MIN (-32767i16 - 1) /* minimum sign ed 16 bit value */ #define _I16_MAX 32767i16 /* maximum sign ed 16 bit value */ #define _UI16_MAX 0xffffui16 /* maximum unsi gned 16 bit value */ #endif #if _INTEGRAL_MAX_BITS >= 32 #define _I32_MIN (-2147483647i32-1) /* minimum s igned 32 bit value */ #define _I32_MAX 2147483647i32 /* maximum sign ed 32 bit value */ #define _UI32_MAX 0xffffffffui32 /* maximum uns igned 32 bit value */ #endif #if _INTEGRAL_MAX_BITS >= 64 /* minimum signed 64 bit value */ #define _I64_MIN (-9223372036854775807i64 - 1) /* maximum signed 64 bit value */ #define _I64_MAX 9223372036854775807i64 /* maximum unsigned 64 bit value */ #define _UI64_MAX 0xffffffffffffffffui64 #endif

Page 184: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

178

#if _INTEGRAL_MAX_BITS >= 128 /* minimum signed 128 bit value */ #define _I128_MIN (-17014118346046923173168730371 5884105727i128 - 1) /* maximum signed 128 bit value */ #define _I128_MAX 17014118346046923173168730371 5884105727i128 /* maximum unsigned 128 bit value */ #define _UI128_MAX 0xfffffffffffffffffffffffffff fffffui128 #endif #ifdef _POSIX_ #define _POSIX_ARG_MAX 4096 #define _POSIX_CHILD_MAX 6 #define _POSIX_LINK_MAX 8 #define _POSIX_MAX_CANON 255 #define _POSIX_MAX_INPUT 255 #define _POSIX_NAME_MAX 14 #define _POSIX_NGROUPS_MAX 0 #define _POSIX_OPEN_MAX 16 #define _POSIX_PATH_MAX 255 #define _POSIX_PIPE_BUF 512 #define _POSIX_SSIZE_MAX 32767 #define _POSIX_STREAM_MAX 8 #define _POSIX_TZNAME_MAX 3 #define ARG_MAX 14500 /* 16k heap , minus overhead */ #define LINK_MAX 1024 #define MAX_CANON _POSIX_MAX_CANON #define MAX_INPUT _POSIX_MAX_INPUT #define NAME_MAX 255 #define NGROUPS_MAX 16 #define OPEN_MAX 32 #define PATH_MAX 512 #define PIPE_BUF _POSIX_PIPE_BUF #define SSIZE_MAX _POSIX_SSIZE_MAX #define STREAM_MAX 20 #define TZNAME_MAX 10 #endif /* POSIX */ #endif /* _INC_LIMITS */

Page 185: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

179

11. Edition de lien

11.1 Les pointeurs de fonction

Peu courante dans les langages de programmation, la possibilité de définir des variables

pointant sur des fonctions en très riche en potentialités. Cela permet de considérer une

fonction comme une variable qui pourra être affectée ou transmise comme paramètre à une

autre fonction. Ceci est possible car, en langage C, le nom d’une fonction est équivalent à

l’adresse du début de son programme en langage machine dans la mémoire (de la même

manière que le nom d’un tableau est équivalent à l’adresse de sa première case). Bien

entendu, la valeur de cette adresse ne peut pas être modifiée. Il est toutefois possible de

définir une variable de type pointeur de fonction et d’affecter l’adresse d’une fonction à cette

variable. Prenons un exemple, l’affectation de l’adresse de la fonction putchar . On rappelle

le prototype de cette fonction qui affiche un caractère à l’écran :

int putchar(int c)

La méthode la plus simple pour créer un pointeur sur la fonction putchar est :

#include <stdio.h> void main() { int status ;

char c = 'A'; int (* toto)(int c); toto = putchar; status = putchar(c) ; status = toto(c); }

toto est un pointeur de fonction recevant un entier en entrée et retournant un entier.

int (* toto)(int c);

Le nombre et le type des variables reçues et retournées doivent être les mêmes que pour la

fonction sur laquelle toto doit pointer. La ligne :

toto = putchar;

Page 186: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

180

affecte à toto l’adresse de la fonction putchar . Les deux lignes suivantes sont alors

strictement équivalentes et le programme affiche deux A consécutifs.

status = putchar(c); status = toto(c);

La méthode préférée pour créer un pointeur de fonction (et notamment utilisée pour l’appel

des librairies dynamiques sous Windows) utilise un typedef :

#include <stdio.h> typedef int (* PTC_TYPE)(int c); void main() { int status ; char c = 'A'; PTC_TYPE putchar_ptr; putchar_ptr = putchar; status = putchar(c) ; status = putchar_ptr(c); }

On voit là que la directive typedef est bien plus efficace qu’un simple #define

puisqu’elle permet des substitutions de texte plus puissantes que celles du préprocesseur. En

effet,

typedef int (* PTC_TYPE)(int c);

crée le type équivalent à « pointeur de fonction ayant un paramètre entier et retournant un

entier ». On peut ensuite s’en servir pour déclarer un pointeur de fonction :

PTC_TYPE putchar_ptr;

qui, après affectation de l’adresse de la fonction putchar :

putchar_ptr = putchar;

peut être utilisé à sa place.

Page 187: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

181

status = putchar(c) ; status = putchar_ptr(c);

Il est à noter que la directive typedef ne définit pas un nouveau type, mais qu’elle crée un

nouveau nom désignant un type existant.

Une des applications possible des pointeurs de fonction est le passage d’une fonction comme

paramètres d’une autre fonction. Exemple :

#include <math.h> #include <stdio.h> typedef double (* MAFCT)(double); void affiche(double x, MAFCT trigo); void main(void) { double pi = 3.1415926535; double x; x = pi / 2; affiche(x, sin); affiche(x, sinh); affiche(x, cos); affiche(x, cosh); } void affiche(double x, MAFCT trigo) { double y; y = trigo(x); printf("result(%f) = %f\n", x, y); }

On passe à la fonction affiche les fonctions sin , sinh , cos , cosh qui sont des

fonctions de la librairie standard qui acceptent un double en paramètre et retournent un double

et qui calculent respectivement (en radian) le sinus, le sinus hyperbolique, le cosinus et le

cosinus hyperbolique. Les résultats suivants sont obtenus :

result(1.570796) = 1.000000

result(1.570796) = 2.301299

result(1.570796) = 0.000000

result(1.570796) = 2.509178

Page 188: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

182

Exercice 11.1 : on souhaite concevoir une fonction qui accepte en entrée une fonction

trigonométrique sous forme de chaîne de caractères (suite à une saisie au clavier par exemple)

et qui retourne un pointeur sur la fonction de la bibliothèque mathématique correspondante ou

bien NULL si elle n’existe pas. Vous utiliserez pour cela une structure qui contiendra une

chaîne de caractères pour stocker le nom de la fonction et un pointeur sur cette fonction.

Exercice 11.2 : expliquez à quoi correspondent les déclarations suivantes.

void fonction1(void);

int fonction2(double);

int *fonction3(char *);

int (*PtrFct1)(char);

int (*PtrFct2)(char *);

int *(*PtrFct3)(char *);

int *tab[10];

int (*tab)[10];

int (* TabFct[10])(char);

11.2 Notion de processus

Avec un système d’exploitation moderne, toute tâche en cours d’exécution est représentée par

un processus. On peut imaginer un processus comme un programme en cours d’exécution,

mais cette représentation est très imparfaite car un programme peut lancer plusieurs

processus. Un programme exécutable n'est qu'une suite d'octets totalement inerte stockée sur

disque. Il ne deviendra processus que lorsqu'il sera chargé en mémoire et pris en charge par le

système d'exploitation. Le scheduler, mécanisme de régulation des tâches du noyau du

système d’exploitation (en général multi-tâches) lui attribuera un état (en attente d’exécution,

en cours d’exécution, inactif, ...) selon l’activité générale de l’ordinateur. En effet, seul un

processus peut s’exécuter à la fois sur un ordinateur mono processeur. Le processus héritera

des droits de l’utilisateur qui l’aura lancé. Il héritera par exemple de droits :

• sur certains fichiers,

• sur une certaine quantité de mémoire physique de l’ordinateur pour pouvoir s’exécuter,

• de priorité vis à vis des autres processus actifs du système.

Page 189: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

183

Il faut noter qu’en cas de lancement en tâche de fond (commande at sous Unix ou sous

Windows XP ; cette commande n’existe pas sous Windows 98), le processus est rattaché

directement au noyau et pas à la fenêtre de commande de l’utilisateur qui a servie à le lancer.

Cela permet à un utilisateur de lancer un programme puis de se déconnecter de son compte

afin de laisser l’ordinateur disponible pour d’autres utilisateurs. Le programme continue alors

à s’exécuter, ce qui est très pratique pour des traitements qui durent longtemps (plusieurs

jours, voir plusieurs semaines).

11.3 Les zones mémoires d’un processus

Grâce au mécanisme de la mémoire virtuelle, un processus dispose d’un espace linéaire

d’adressage de 4 Go sur un système d’exploitation 32 bits. Sous Linux, cet espace est séparée

en deux zones, le segment noyau (1 Go) et le segment utilisateur (3 Go).

environnement

variables locales (pile)

variables allouéesdynamiquement (tas)

variables globales non initialisées

variables statiquesvariables globales initialisées

code exécutable

noyau du système d’exploitation

text

data

bss

heap

stack

env

kernel

0

3 Go

4 Gosegmentnoyau

segmentutilisateur

bibliothèques partagées

Le segment utilisateur est lui-même divisé en plusieurs sous-ensembles :

• Le segment text contient le code exécutable du processus.

• Le segment data contient les variables statiques ainsi que les variables globales

initialisées au chargement du processus.

Page 190: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

184

• Le segment bss (block started by symbol) contient les variables globales non-initialisées.

• Le segment heap contient les variables allouées dynamiquement (via malloc ). On

appelle cette zone le tas.

• Le segment stack contient les variables locales. On appelle cette zone la pile.

• Le segment env contient des données concernant l’environnement.

Il existe des relations étroites entre la durée de vie d’une variable, sa portée (variable locale ou

globale) et les différentes classes (segments) de mémoire. Du point de vue de la durée de vie,

il existe trois types de variables :

• Les variables statiques qui sont allouées au début de l’exécution du programme et ne sont

libérées qu’à la fin de son exécution. Une variable globale (c’est-à-dire déclarée en dehors

de toute fonction) est toujours statique (par définition puisqu’elle est créée au début de

l’exécution et libérée à la fin). Une variable locale peut être statique à condition de faire

précéder sa déclaration du mot clé static .

• les variables dynamiques qui sont allouées et libérées explicitement par le programmeur

grâce à des fonctions telles que malloc et free . Elles peuvent être locales ou bien

globales, cela dépend de l’endroit où est déclaré le pointeur.

• Les variables automatiques qui sont allouées à l’entrée de la fonction où elles ont été

déclarées et libérées lors de la sortie de cette fonction. Par définition, il s’agit de variables

locales (y compris les arguments de la fonction). Pour créer une variable automatique, il

suffit de faire précéder sa déclaration du mot clé auto . En pratique, personne ne le fait

puisque c’est le mode par défaut quand vous déclarez une variable locale.

Selon sa durée de vie, la variable se retrouvera :

1. dans la zone contenant les variables statiques (et globales) ; data et bss .

2. dans la zone contenant les variables dynamiques ; le tas .

3. dans la zone contenant les variables automatiques (locales) ; la pile .

Le programme Linux suivant illustre l’utilisation des différents emplacements de la mémoire.

Vous noterez que la limite basse de la pile diminue à mesure que l’on crée des variables.

Page 191: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

185

#include <stdio.h> int glob_init1[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int glob_init2[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int glob_non_init1[1024]; int glob_non_init2[1024]; main() { int auto_var1[1024]; int auto_var2[1024]; int *ptr1, *ptr2; static int stat_var1[1024]; static int stat_var2[1024]; ptr1 = (int *)malloc(1024); ptr2 = (int *)malloc(1024); printf("auto_var1 = %8x, %10u\n", auto_var1 , auto_var1); printf("auto_var2 = %8x, %10u\n", auto_var2 , auto_var2); printf("ptr2 = %8x, %10u\n", ptr2, ptr 2); printf("ptr1 = %8x, %10u\n", ptr1, ptr 1); printf("glob_non_init2 = %8x, %10u\n", glob_non_ init2, glob_non_init2); printf("glob_non_init1 = %8x, %10u\n", glob_non_ init1, glob_non_init1); printf("stat_var2 = %8x, %10u\n", stat_var2 , stat_var2); printf("stat_var1 = %8x, %10u\n", stat_var1 , stat_var1); printf("glob_init2 = %8x, %10u\n", glob_init 2, glob_init2); printf("glob_init1 = %8x, %10u\n", glob_init 1, glob_init1); printf("adresse main = %8x, %10u\n", main, mai n); printf("adresse printf = %8x, %10u\n", printf, p rintf); } Les adresses suivantes sont obtenues pour les différentes fonctions et variables :

auto_var1 = bfffe9ec , 3221219820auto_var2 = bfffd9ec , 3221215724ptr2 = 804dcd0, 134536400ptr1 = 804d8c8, 134535368glob_non_init2 = 804b8c0, 134527168glob_non_init1 = 804c8c0, 134531264stat_var2 = 804a8c0, 134523072stat_var1 = 80498c0, 134518976glob_init2 = 80497a0, 134518688glob_init1 = 8049780, 134518656adresse main = 8048424, 134513700adresse printf = 8048344, 134513476

text

data

bss

heap

stack

Un problème rencontré fréquemment en programmation est la saturation de la pile (stack

overflow). Elle se produit quand vous essayez de créer un tableau local de grande taille sans

utiliser l’allocation dynamique de la mémoire. En effet, la valeur par défaut de la taille de la

Page 192: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

186

pile est assez faible : 1 Mo avec Visual C++ et 8 Mo avec gcc. Cela veut dire par exemple que

le programme suivant plante avec Visual C++ sous Windows.

#include <stdio.h> void main() { int tab[500000]; /* demande 2 Mo */ printf("coucou\n"); }

Pour corriger ce problème, il faut modifier la taille de la pile avec par exemple sous Linux, la

commande « ulimit -s 32768 » pour passer la taille de la pile à 32 Mo (sous Visual

C++, c’est une option du projet).

11.4 Projets multi-fichiers : édition de liens

Dès que l’on développe un programme d’une taille un peu importante (ce qui est

généralement le cas dans un projet réel), il devient nécessaire d’utiliser plusieurs fichiers

source. Il y a au moins deux raisons pour cela :

1. Il est plus facile de chercher des informations et de modifier des fonctions dans plusieurs

fichiers source de taille raisonnable (quelques centaines de lignes au maximum) que dans

un seul gros fichier.

2. En cas de modification, il est plus rapide de ne compiler qu’un seul petit fichier (celui où a

eu lieu la modification) qu’un gros fichier qui contient tout le programme.

Chaque fichier est compilé séparément, puis les fichiers objets obtenus sont réunis pour

former un exécutable à l’aide de l’éditeur de liens (linker).

DUTmain.c

DUTfct1.c

DUTfct2.c

compilateur

DUTmain.o

DUTfct1.o

DUTfct2.o

linker dut.exe

Pour réaliser ce genre de projet, il faut pouvoir dire au compilateur que certaines variables ou

fonctions sont définies dans un autre fichier source. Pour cela, deux méthodes existent :

Page 193: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

187

l’utilisation du mot clé extern ou bien l’utilisation d’un fichier d’entête (.h) général qui

contiendra toutes les définitions globales. C’est la deuxième méthode que nous utiliserons.

Notre projet multi-fichiers sera donc constitué :

• d’un fichier source qui ne contiendra que la fonction main .

• d’un fichier source par famille de fonctions.

• d’un fichier d’entête (ou plusieurs si nécessaire) qui contiendra toutes les déclarations

globales (structures, prototypes de fonction, macros, ...). Il sera inclus au début de tous les

fichiers sources.

DUTmain.c

DUTfct1.c

DUTfct2.c

compilateur

DUTmain.o

DUTfct1.o

DUTfct2.o

linker dut.exe

DUTstd.h

Les variables globales seront déclarées dans un des fichiers source et déclarées comme

extern dans le fichier d’entête.

Penchons-nous maintenant un peu sur le contenu des fichiers objets et exécutables ainsi que

sur le travail effectué par l’éditeur de lien. Il existe deux grands formats de fichiers objets (et

exécutables) en informatique : le format COFF « Common Object File Format » et le format

ELF « Executable and Linking Format ». Le format COFF est le format historique d’Unix

(avec le format a.out ). Il a été repris par Microsoft et est toujours utilisé sous Windows

sous une forme évoluée (format PE « Portable Executable »). Le format ELF a remplacé le

format COFF sous Unix et est devenu le format standard sous Linux.

Un fichier objet (extension .o sous Linux, .obj sous Windows) contient les instructions en

langage machine correspondant au source et des données destinées à l’éditeur de lien afin que

celui-ci puisse créer un exécutable. Chaque fonction ou chaque variable définie dans un

fichier objet est référencée par un nom de symbole. Ces symboles peuvent avoir une

définition locale (utilisation à l’intérieur du fichier seulement) ou globale (utilisation à

Page 194: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

188

l’extérieur du fichier). Toute référence à un symbole qui n’est pas défini dans le fichier objet

(un appel à une fonction définie dans un autre fichier par exemple) est appelée une référence

externe. Pour garder la trace des différents symboles internes ou externes, un fichier objet

contient une table des symboles. L’éditeur de lien utilise cette table pour relier les références

externes avec les définitions globales des symboles. Voici de manière très schématique la

structure d’un fichier objet ou exécutable :

entête fichier

section text

table dessymboles

section datasection bss

On retrouve la section text qui contient le code exécutable de la fonction, la section data

qui contient les variables statiques (et les variables globales initialisées) et enfin la section

bss qui contient les variables globales non-initialisées. Le but de l’édition de lien va être de

fusionner les sections de chaque fichier objet afin de créer un fichier exécutable.

essai

essai2.o

essai1.o

entête fichier

section text

table dessymboles

section datasection bss

entête fichier

section text

table dessymboles

section datasection bss

entête fichier

section text

table dessymboles

section datasection bss

Page 195: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

189

Pour cela, il va falloir relier les références externes avec les définitions globales contenues

dans d’autres fichiers objets (c’est la résolution de symbole) et changer les adresses du code

et des données (c’est le relogement du code ou code relocation en anglais). Prenons un

exemple simple. Le programme essai comprend un fichier source ESSmain.c :

#include <stdio.h> extern void affiche(char *); main() { static char str[] = "coucou\n"; affiche(str); }

qui appelle une fonction affiche contenue dans un autre fichier source ESSfct.c :

void affiche(char *s) { printf(s); }

Sous Linux, la compilation via la commande cc -c *.c donne deux objets ESSmain.o et

ESSfct.o. Grâce au programme objdump , nous pouvons visualiser le contenu de certaines

parties d’un objet, à savoir les entêtes de section (option h), la table des symboles (option t) et

le code machine désassemblé (option d). La commande « objdump -dth ESSmain.o »

donne le résultat suivant :

ESSmain.o: file format elf32-i386 Sections: Idx Name Size VMA LMA Fil e off Algn 0 .text 0000001a 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, REA DONLY, CODE 1 .data 00000008 00000000 00000000 00000050 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 000 00058 2**2 ALLOC 3 .note 00000014 00000000 00000000 000 00058 2**0 CONTENTS, READONLY 4 .comment 00000029 00000000 00000000 000 0006c 2**0 CONTENTS, READONLY

Page 196: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

190

SYMBOL TABLE: 00000000 l df *ABS* 00000000 ESSmain.c 00000000 l d .text 00000000 00000000 l d .data 00000000 00000000 l d .bss 00000000 00000000 l .text 00000000 gcc2_compiled. 00000000 l O .data 00000008 str.3 00000000 l d .note 00000000 00000000 l d .comment 00000000 00000000 g F .text 0000001a main 00000000 *UND* 00000000 affiche Disassembly of section .text: 00000000 < main >: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 c4 f4 add $0xfffffff4,%esp 9: 68 00 00 00 00 push $0x0 e: e8 fc ff ff ff call f <main+0xf> 13: 83 c4 10 add $0x10,%esp 16: 89 ec mov %ebp,%esp 18: 5d pop %ebp 19: c3 ret

Le fichier objet est au format ELF 32 bits Intel 386, sa section .text comporte 26 octets

(taille du code machine) et sa section .data en compte 8 (taille de la chaîne str avec le \0).

On voit dans la table des symboles que str est définie (dans .data ), que main est définie

(dans .text ) mais qu'affiche ne l’est pas. Par ailleurs, les adresses 32 bits dans les

entêtes de section et dans la table des symboles sont toutes à 0. Dans le code assembleur de la

fonction main , on note que l’appel à la fonction affiche (c’est le call) est non résolu

(l’adresse est fausse) et que le push qui précède ce call est à 0. Ce push correspond à la mise

sur la pile du paramètre passé à la fonction, c’est-à-dire str dans notre exemple. En général,

les paramètres d’une fonction sont toujours passés par la pile. Dans notre exemple, str ne se

trouve bien entendu pas à l’adresse 0, mais on ne connaît pas encore son adresse. La

commande « objdump -dth ESSfct.o » donne le résultat suivant :

ESSfct.o: file format elf32-i386 Sections: Idx Name Size VMA LMA Fil e off Algn 0 .text 00000019 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, REA DONLY, CODE

Page 197: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

191

1 .data 00000000 00000000 00000000 00000050 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 000 00050 2**2 ALLOC 3 .note 00000014 00000000 00000000 000 00050 2**0 CONTENTS, READONLY 4 .comment 00000029 00000000 00000000 000 00064 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 ESSfct.c 00000000 l d .text 00000000 00000000 l d .data 00000000 00000000 l d .bss 00000000 00000000 l .text 00000000 gcc2_compiled. 00000000 l d .note 00000000 00000000 l d .comment 00000000 00000000 g F .text 00000019 affiche 00000000 *UND* 00000000 printf Disassembly of section .text: 00000000 < affiche >: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 c4 f4 add $0xfffffff4,%esp 9: 8b 45 08 mov 0x8(%ebp),%eax c: 50 push %eax d: e8 fc ff ff ff call e <affiche+0xe> 12: 83 c4 10 add $0x10,%esp 15: 89 ec mov %ebp,%esp 17: 5d pop %ebp 18: c3 ret

On retrouve le même format d’objet, la taille du code est de 25 octets mais la section .data

est vide. La fonction affiche est définie dans le fichier objet, mais pas la fonction

printf . Dans le code assembleur de la fonction, on note l’appel à printf ainsi que le passage

de paramètre qui le précède.

L’éditeur de lien doit combiner les deux objets avec une fonction d’initialisation (_start ) et

les routines de la librairie standard du C pour produire l’exécutable essai (cc *.c -o

essai ). La commande « objdump -dth essai» donne le résultat suivant :

Page 198: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

192

essai: file format elf32-i386 Sections: Idx Name Size VMA LMA Fil e off Algn 0 .interp 00000013 080480f4 080480f4 0000 00f4 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA ... 11 .text 00000134 08048320 08048320 0000 0320 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE ... 14 .data 00000014 08049478 08049478 0000 0478 2**2 CONTENTS, ALLOC, LOAD, DATA ... 20 .bss 00000018 08049560 08049560 0000 0560 2**2 ALLOC ... SYMBOL TABLE: 080480f4 l d .interp 00000000 ... 08048400 l .text 00000000 gcc2_c ompiled. 08049484 l O .data 00000008 str.3 080483e4 g F .text 00000019 affich e 080494c0 g O .dynamic 00000000 _DY NAMIC 08048454 g O *ABS* 00000000 _etext 08048298 g F .init 00000000 _init 08048320 g .text 00000000 _start 08049560 g O *ABS* 00000000 __bss_ start 08048400 g F .text 0000001a main 08049478 w .data 00000000 data_s tart ... Disassembly of section .text: 08048320 <_ start >: 8048320: 31 ed xor %ebp,%ebp 8048322: 5e pop %esi 8048323: 89 e1 mov %esp,%ecx 8048325: 83 e4 f8 and $0xfffffff8, %esp 8048328: 50 push %eax 8048329: 54 push %esp 804832a: 52 push %edx 804832b: 68 54 84 04 08 push $0x8048454 8048330: 68 98 82 04 08 push $0x8048298 8048335: 51 push %ecx 8048336: 56 push %esi 8048337: 68 00 84 04 08 push $0x8048400 804833c: e8 bb ff ff ff call 80482fc <_in it+0x64> 8048341: f4 hlt ...

Page 199: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

193

080483e4 < affiche >: 80483e4: 55 push %ebp 80483e5: 89 e5 mov %esp,%ebp 80483e7: 83 ec 08 sub $0x8,%esp 80483ea: 83 c4 f4 add $0xfffffff4, %esp 80483ed: 8b 45 08 mov 0x8(%ebp),%e ax 80483f0: 50 push %eax 80483f1: e8 16 ff ff ff call 804830c <_in it+0x74> 80483f6: 83 c4 10 add $0x10,%esp 80483f9: 89 ec mov %ebp,%esp 80483fb: 5d pop %ebp 80483fc: c3 ret 08048400 < main >: 8048400: 55 push %ebp 8048401: 89 e5 mov %esp,%ebp 8048403: 83 ec 08 sub $0x8,%esp 8048406: 83 c4 f4 add $0xfffffff4, %esp 8048409: 68 84 94 04 08 push $0x8049484 804840e: e8 d1 ff ff ff call 80483e4 <aff iche> 8048413: 83 c4 10 add $0x10,%esp 8048416: 89 ec mov %ebp,%esp 8048418: 5d pop %ebp 8048419: c3 ret ...

L’éditeur de liens a donc fusionné les sections .text , .data et .bss des différents objets.

Par exemple, dans la section .text , les codes machines des différentes fonctions ont été

mises les uns à la suite des autres, ce qui implique que toutes les adresses ont été modifiées :

le code a été relogé. L’adresse de la fonction affiche est maintenant connue et l’appel dans

la fonction main se fait à cette adresse. D’autre part, l’adresse de str est correctement

passée en paramètre. Un lien a donc été créé entre les références externes et les définitions

globales des symboles.

Pour exécuter ce programme, le système d’exploitation doit maintenant charger chacune des

trois sections du fichier exécutable dans la mémoire virtuelle du processus à l’adresse fixe

contenue dans le fichier puis lancer le programme au début de la fonction _start . Cette

opération appelée « program loading » est réalisée par le loader (sous Linux, il s’agit de la

fonction système exec() ). La fonction d’initialisation _start sert, entre autres, à

récupérer les paramètres se trouvant sur la ligne de commande et à les passer à la fonction

main .

Page 200: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

194

Il est à noter que le fichier exécutable contient un certain nombre d’informations qui ne sont

pas nécessaires à son bon fonctionnement (comme la table des symboles par exemple) mais

qui servent seulement pour la mise au point. Ces informations peuvent être supprimées grâce

au programme strip (strip -s essai ) afin de réduire la taille du fichier.

Page 201: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

195

12. Les librairies

12.1 Les bibliothèques statiques (archive)

Une évolution naturelle de la programmation multi-fichiers a été de regrouper toutes les

fonctions d’un programme (sauf la fonction main ) dans une archive que l’on a appelé une

bibliothèque (library en anglais). Il s’agit un fait d’une autre forme de fichier objet (un fichier

qui contient plusieurs fichiers objets) dont le format est connu de l’éditeur de lien mais qui ne

contient aucune information supplémentaire. Sous Linux, l’extension d’une bibliothèque de

type archive est .a pour archive (extension .lib sous Windows). La création de la

bibliothèque se déroule de la manière suivante :

1. création des fichiers objets à l’aide de la commande « cc -c *.c » .

2. création de l’archive à l’aide du programme ar, « ar -rv libDUT.a DUTfct1.o

DUTfct2.o » .

DUTfct1.c

DUTfct2.c

compilateur

DUTfct1.o

DUTfct2.o

ar libDUT.a

DUTstd.h

Pour utiliser la bibliothèque ainsi créée, il suffit de dire à l’éditeur de lien de l’utiliser.

cc DUTmain.o libDUT.a -o dut.exe

Vous noterez que la commande cc inclut la phase d’édition de lien (en réalité, le linker

s’appelle ld ). En ne désignant au compilateur que des objets, il procède automatiquement à

l’édition de lien.

Page 202: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

196

libDUT.a

DUTmain.o

compilateur

(linker) dut.exe

Du point de vue de l’édition de lien, c’est-à-dire de la résolution des symboles et du

relogement du code, l’utilisation d’une bibliothèque archive est strictement équivalente à la

solution sans bibliothèque. Il n’y aura pas une ligne de code générée en plus. Il est important

de noter que le code machine des fonction utilisées sera incorporé dans le fichier exécutable

mais pas le code machine des fonctions que l’on n’utilise pas. On dit que cette bibliothèque

est statique.

L’utilisation de la bibliothèque standard du C est automatique lorsque vous compilez un

programme. C’est la libc qui contient le code des fonctions printf , malloc , fopen , ...

Par contre, sous Linux, l’utilisation de la bibliothèque mathématique libm doit être spécifiée.

Il existe pour cela une convention. Comme tous les noms de bibliothèque commencent par

lib , il suffit de passer l’option -lfin pour utiliser la bibliothèque libfin . Par exemple, la

commande suivante indique l’utilisation de la libm .

cc essai.c -o essai -lm

La commande ar -t libDUT.a permet de connaître les noms des fichiers objets inclus

dans l’archive mais pas de connaître le nom des fonctions qui se trouvent dans les objets. Pour

cela, il faut utiliser objdump associé au programme grep qui recherche une chaîne de

caractères dans un fichier texte.

objdump -t /usr/lib/libc.a|grep fgets

12.2 Les bibliothèques dynamiques (partagées)

Un inconvénient important des bibliothèques statiques est que le code machine des fonctions

est incorporé dans chaque fichier exécutable qui les utilise. Par exemple, cela veut dire que le

code de la fonction printf de la libc va être copié autant de fois qu’il y aura de

programmes qui l’utilise dans chaque espace d’adressage virtuel. Cela conduit à un gaspillage

important de ressources mémoire.

Page 203: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

197

pile

tas

bss

data

text prog2

bibliothèques partagées

printf

pile

tas

bss

data

text prog3

bibliothèques partagées

printf

pile

tas

bss

data

text prog1

bibliothèques partagées

printf

Le partage du code permet à plusieurs processus de partager des portions de code machine

communes. Le code commun est géré directement par le système d’exploitation et se trouve

en dehors de l’espace d’adressage virtuel des processus. Par exemple, dans le cas de la libc ,

chaque appel à la fonction printf dans prog1, prog2 et prog3 va pointer sur une adresse

dans la zone des bibliothèques partagées, adresse qui pointe en réalité sur la bibliothèque

partagée.

pile

tas

bss

data

text prog1

bibliothèquespartagées

bibliothèquepartagée libc.so

printf

pile

tas

bss

data

text prog2

bibliothèquespartagées

pile

tas

bss

data

text prog3

bibliothèquespartagées

Page 204: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

198

L’utilisation de bibliothèques partagées a des conséquences importantes :

1. Toutes les bibliothèques partagées ne peuvent être chargées en mémoire en permanence.

Le chargement se fait de manière dynamique lors du premier appel à la bibliothèque (d’où

son nom).

2. La résolution des symboles et donc le calcul des adresses ne peuvent plus être effectués au

moment de l’édition de lien puisque l’on ne connaît pas l’adresse exacte de la fonction. Il

faut donc modifier le source du programme (par rapport à l’utilisation traditionnelle d’une

librairie statique) et utiliser une procédure spéciale pour récupérer l’adresse de la fonction

et la copier dans un pointeur de fonction. Il existe cependant un mode simplifié appelé

édition de lien implicite qui évite cette modification du code source.

3. Selon les bibliothèques déjà chargées en mémoire, l’adresse de chargement d’une

bibliothèque varie avec le temps et n’est jamais la même. Cela veut dire que le code

machine des fonctions doit obligatoirement être indépendant de la position en mémoire.

On appelle ce type de code du PIC (Position Independent Code).

Reprenons maintenant l’exemple du programme essai. Il était composé du fichier ESSfct.c qui

contenait la fonction affiche() et du fichier ESSmain.c qui contenait la fonction main()

qui appelait affiche() . Nous allons créer une bibliothèque dynamique libESS.so qui

contiendra affiche() puis nous l’appellerons depuis main() . Le contenu de ESSfct.c n’a

pas changé :

void affiche(char *s) { printf(s); }

Il faut compiler ce fichier de façon à obtenir un objet relogeable :

cc -c ESSfct.c -fPIC

Le contenu de cet objet ESSfct.o est identique à celui du §11.4. Nous pouvons maintenant

créer la bibliothèque partagée libESS.so (.so pour shared object) :

cc -shared -o libESS.so ESSfct.o

Page 205: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

199

Seule l’option -shared change par rapport à la création d’un exécutable « normal » (et

l’absence de la fonction main() bien sur). Il existe deux méthodes permettant d’utiliser une

librairie dynamique :

• L’édition de lien implicite (ou Shared library sous Unix). C’est la méthode la plus simple.

La librairie est chargée automatiquement au lancement de l’exécutable. C’est le mode par

défaut sous Linux quand vous développez un programme. Il n’y a rien de spécial à écrire

dans le programme qui utilise la librairie. Sous Windows, il est nécessaire d’utiliser une

libraire d’importation (voir projet).

• L’édition de lien explicite (ou Dynamically Loaded Library sous Unix). C’est une

méthode plus compliquée. La librairie est chargée dynamiquement pendant l’exécution du

programme, à la demande du programmeur. Il faut modifier ESSmain.c de la façon

suivante :

#include <stdio.h> #include <dlfcn.h> main() { static char str[] = "coucou\n"; void *handle; void (*affiche_dyn)(char *); handle = dlopen("/root/format/libESS.so", RTLD_LAZ Y); affiche_dyn = dlsym(handle, "affiche"); affiche_dyn(str); dlclose(handle); } La fonction dlopen() permet de charger la bibliothèque en mémoire si nécessaire et

retourne son adresse dans le pointeur handle (en anglais, un handle est une variable

qui identifie un objet. Ici, il s’agit seulement du nom du pointeur). La fonction dlsym()

retourne l’adresse de la fonction affiche() de la bibliothèque dans le pointeur de

fonction affiche_dyn . Celui-ci peut alors être utilisé à la place de la fonction

affiche() puisqu’il pointe dessus. dlclose() décharge la bibliothèque de la

mémoire s’il n’y a pas d’autre programme qui l’utilise. En fait, il y a un compteur qui

s’incrémente à chaque fois qu’un programme charge la librairie avec dlopen() et qui se

Page 206: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

200

décrémente quand un programme décharge la libraire avec dlclose() . La compilation

du source (libdl contient les fonctions dlopen , dlsym et dlclose ) se fait avec :

cc -rdynamic ESSmain.c -o essai -ldl

Avec le programme objdump , on peut voir que le code de la fonction affiche() ne se

trouve plus dans l’exécutable essai. Il n’y a pas de symbole non résolu pendant l’édition de

liens puisqu’il n’y a pas d’appel direct à affiche() . Il n’y a plus qu’un pointeur de

fonction affiche_dyn qui récupère l’adresse de affiche() en cours d’exécution du

programme.

12.3 Avantages et inconvénients des bibliothèques dynamiques

Les bibliothèques dynamiques ont la réputation d’être plus « modernes » que les

bibliothèques statiques. Sous Windows notamment, l’utilisation des DLL (Dynamic Link

Library) est systématique et la plupart des développeurs ignore même qu’il existe une autre

méthode. Il est cependant important de bien comprendre les avantages et les inconvénients

des bibliothèques dynamiques sur les points suivants :

1. taille de l’exécutable.

• Avantage : La taille d’un exécutable utilisant des bibliothèques dynamiques est

beaucoup plus petite que celle d’un exécutable lié statiquement. Sous Linux, Vous

pouvez essayez de compiler un programme avec l’option -static pour forcer

l’utilisation des bibliothèques standards en statique (un programme avec un simple

printf suffit). Le résultat est assez spectaculaire (une dizaine de ko contre 1Mo

environ). Il y a donc un gain en espace de stockage.

• Inconvénient. Aucun.

2. temps de chargement de l’exécutable.

• Avantage / Inconvénient. L’exécutable statique étant plus gros, son chargement en

mémoire doit prendre plus de temps que le chargement d’un exécutable dynamique.

Mais dans ce dernier cas, il faut aussi compter le temps de chargement des bibliothèques

dynamiques si elles ne sont pas déjà chargées en mémoire. Il n’est pas évident de

déterminer laquelle des deux méthodes est la plus rapide.

Page 207: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

201

3. Partage du code.

• Avantage : plusieurs programmes peuvent utiliser la même bibliothèque chargée en

mémoire. Le code n’est pas dupliqué dans chaque segment text , ce qui réduit

l’utilisation de la mémoire de l’ordinateur.

• Inconvénient : toutes les fonctions de la bibliothèque sont chargées en mémoire alors

qu’en statique, seul le code des fonctions utilisées est copié dans l’exécutable. Si toutes

les fonctions d’une bibliothèque dynamique sont utilisées par plusieurs programmes, il

y a un gain. Si un seul programme n’utilise qu’une partie de la bibliothèque dynamique,

il peut y avoir perte.

4. Evolution du programme sans recompilation.

• Avantage : quand les fonctions de la bibliothèque dynamique évoluent, les programmes

qui les utilisent évoluent aussi sans qu’il soit nécessaire de refaire une édition de lien.

Avec une bibliothèque statique, l’édition de lien est obligatoire pour copier le nouveau

code machine dans l’exécutable. Pour faire évoluer une application chez un client, un

fournisseur de programme a juste à livrer de nouvelles versions de ses bibliothèques

dynamiques. Le client les installe et son programme obtient automatiquement de

nouvelles fonctionnalités ou bien des corrections de défauts.

• Inconvénient : le programme n’est plus autonome, il a besoin des bibliothèques

dynamiques pour fonctionner. S’il en manque une, le système d’exploitation génère une

erreur. Si plusieurs programmes livrés par des fournisseurs différents utilisent la même

bibliothèque dynamique, toute évolution de cette bibliothèque aura des conséquences

sur ces programmes. Il y a un gros problème (notamment sous Windows) de version de

DLL et d’incompatibilité entre les versions de programmes et les versions de DLL.

Chaque nouveau programme installe sa version de DLL qui provoque des

incompatibilités avec des programmes déjà installés sur l’ordinateur. En statique, les

exécutables sont autonomes et ne dépendent d’aucune bibliothèque externe.

5. Accès depuis d’autres langages.

• Avantage / Inconvénient. Une bibliothèque statique ou dynamique développée en

langage C peut être utilisée avec des programmes écrits dans d’autres langages. Il faut

juste respecter la convention d’appel (telle que C, Pascal ou bien standard) qui définit la

Page 208: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

202

méthode de passage de paramètres. Un programme en C peut aussi utiliser une

bibliothèque développée dans un autre langage.

12.4 La bibliothèque standard du C

La bibliothèque standard contient toutes les fonctions normalisées en ANSI C. Nous allons

voir les plus importantes dans ce document. Commençons par les fonctions d’entrées-sorties.

12.4.1 Les entrées-sorties <stdio.h>

Opérations sur les fichiers :

fonction description

remove destruction de fichier

rename modification de nom de fichier

tmpfile création d’un fichier temporaire

tmpnam génération de nom pour un fichier temporaire

Accès aux fichiers :

fclose fermeture de fichier (flux)

fflush vidage des buffers dans un fichier

fopen ouverture d’un fichier

freopen ouverture d’un fichier

Entrées-sorties formatées :

printf écriture formatée sur la sortie standard

scanf lecture formatée sur l’entrée standard

fprintf écriture formatée dans un fichier

fscanf lecture formatée depuis un fichier

sprintf écriture formatée dans une chaîne de caractères

sscanf lecture formatée depuis une chaîne de caractères

Entrées-sorties caractères :

fgetc lecture d’un caractère dans un fichier

fputc écriture d’un caractère dans un fichier

getc lecture d’un caractère sur l’entrée standard (� ne pas utiliser, c’est une macro. Il y a des effets de bord )

getchar lecture d’un caractère sur l’entrée standard

putc écriture d’un caractère sur la sortie standard (� ne pas utiliser, c’est une macro. Il y a des effets de bord)

Page 209: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

203

putchar écriture d’un caractère sur la sortie standard

ungetc annule la lecture d’un caractère dans un flux.

fgets lecture d’une chaîne de caractères dans un fichier

fputs écriture d’une chaîne de caractères dans un fichier

gets lecture d’une chaîne de caractères sur l’entrée standard

puts écriture d’une chaîne de caractères sur la sortie standard

Entrées-sorties binaires :

fread lecture d’un groupe d’octets dans un fichier

fwrite écriture d’un groupe d’octets dans un fichier

Positionnement dans un fichier :

ftell indique la position courante dans un fichier

fseek permet de se positionner dans un fichier

rewind permet de se positionner au début d’un fichier

fgetpos indique la position courante dans un fichier

fsetpos permet de se positionner dans un fichier

12.4.2 Les fonctions mathématiques <math.h>

Fonctions trigonométriques et hyperboliques :

fonction description

acos arc cosinus

asin arc sinus

atan arc tangente

atan2 arc tangente

cos cosinus

cosh cosinus hyperbolique

sin sinus

sinh sinus hyperbolique

tan tangente

tanh tangente hyperbolique

Fonctions exponentielles et logarithmiques :

exp exponentielle

frexp étant donné x, trouve n et p tels que x = n * 2p

ldexp multiplie un nombre par une puissance entière de 2

log logarithme népérien

Page 210: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

204

log10 logarithme en base 10

modf calcule la partie entière et la partie décimale d’un flottant

Fonctions diverses :

ceil arrondi à l’entier le plus proche par valeur supérieure

floor arrondi à l’entier le plus proche par valeur inférieure

fabs valeur absolue

fmod reste de la division

pow puissance xy

sqrt racine carrée

12.4.3 Les manipulations de caractères <ctype.h>

fonction description

isalnum teste si le caractère est une lettre ou un chiffre

isalpha teste si le caractère est une lettre

isctnrl teste si le caractère est caractère de contrôle

isdigit teste si le caractère est un chiffre

isgraph teste si le caractère est imprimable

islower teste si le caractère est une lettre minuscule

isprint teste si le caractère est imprimable

ispunct teste si le caractère est une ponctuation

isspace teste si le caractère est un espace

isupper teste si le caractère est une lettre majuscule

isxdigit teste si le caractère est un chiffre hexadécimal

tolower convertit une lettre en minuscule

toupper convertit une lettre en majuscule

12.4.4 Les manipulations de chaînes <string.h>

fonction description

memcpy copie de n caractères d’une chaîne vers une autre

memmove déplacement de n caractères d’une chaîne vers une autre

strcpy copie d’une chaîne dans une autre

strncpy copie de n caractères d’une chaîne dans une autre

strcat concaténation de deux chaînes

strncat concaténation de n caractères d’une chaîne avec une autre

memcmp comparaison de n caractères entre deux chaînes

Page 211: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

205

strcmp comparaison de deux chaînes

strncmp comparaison de n caractères entre deux chaînes

strcoll comparaison de deux chaînes

strxfrm transformation de chaîne

memchr recherche d’un caractère dans une chaîne

strchr recherche d’un caractère dans une chaîne

strcspn recherche d’une chaîne dans une autre chaîne

strpbrk recherche d’une liste de caractères dans une chaîne

strrchr recherche d’un caractère dans une chaîne (par la fin)

strspn recherche d’une chaîne dans une autre chaîne

strstr recherche d’une chaîne dans une autre chaîne

strtok décomposition d’une chaîne en sous chaîne

memset remplissage d’une chaîne avec un caractère

strlen calcul de la longueur d’une chaîne

12.4.5 Manipulations de l’heure <time.h>

fonction description

clock calcul du temps d’exécution du programme

difftime calcul de la différence entre deux heures

mktime conversion d’heure

time retourne l’heure courante

asctime conversion d’heure

ctime conversion d’heure

gmtime conversion d’heure

localtime conversion d’heure

strftime formatage de l’heure

12.4.6 Diverses fonctions utilitaires <stdlib.h>

Conversions de nombres :

fonction description

atof conversion d’une chaîne en nombre flottant

atoi conversion d’une chaîne en nombre entier

atol conversion d’une chaîne en nombre entier long

strtod conversion d’une chaîne en nombre flottant

strtol conversion d’une chaîne en nombre entier long

strtoul conversion d’une chaîne en nombre entier long non signé

Page 212: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

206

Génération de nombres pseudo-aléatoires :

rand génération d’un nombre pseudo aléatoire

srand initialisation du générateur pseudo aléatoire

Gestion de la mémoire :

malloc allocation d’une zone mémoire

calloc allocation d’une zone mémoire mise à 0

free libération d’une zone mémoire allouée

realloc changement de taille d’une zone mémoire allouée

Communication avec l’environnement :

abort arrêt anormal d’un programme

system exécution d’une commande système

getenv obtention d’une variable d’environnement

atexit définition d’une fonction exécutée en cas de sortie normale du programme

Recherche et tri :

bsearch recherche d’un élément dans un tableau

qsort tri d’un tableau par ordre croissant

Arithmétique sur entier :

div calcul du quotient et du reste d’une division entière

abs calcul de la valeur absolue d’un entier

labs calcul de la valeur absolue d’un entier long

Page 213: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

207

13. Introduction à la programmation en C++

13.1 Généralités

Le langage C++ est une évolution du langage C et apporte en particulier la notion de

Programmation Orienté Objet (POO). Le premier langage orienté POO a été conçu en 1967

par une équipe de chercheurs norvégiens. Il s’agissait de Simula. Ce langage avait été conçu

en vue de simulation de systèmes physiques et en particulier pour la recherche nucléaire. Tout

comme le C, le C++ a été conçu au Bell Labs. Bjarne Stroustrup, son concepteur, souhaitait

ajouter au C les notions de classes introduites par Simula. La première version stable voit le

jour en 1983, avant de trouver plus récemment sa forme définitive au travers d’une norme.

13.2 Intérêt de la conception objet

L’intérêt d’une conception orientée objet est mis en évidence par l’examen des différentes

contraintes imposées à un logiciel tout au long du cycle de vie, qui va de la conception à la

maintenance avec pour objectif de produire un logiciel de qualité.

Les critères à prendre en compte pour apprécier la qualité d’un logiciel sont :

• la validité du logiciel : le logiciel effectue les tâches pour lesquelles il a été conçu.

• l’extensibilité : le logiciel peut intégrer de nouvelles fonctionnalités demandées par

l’utilisateur.

• la ré-utilisabilité : le logiciel doit pouvoir être réutilisable complètement ou en partie.

• la robustesse : C’est la capacité du logiciel à fonctionner dans des situations non prévues, à

détecter ces situations.

Il faut également noter que la modularité est un facteur essentiel permettant de parvenir à

produire un logiciel de qualité et à faciliter la réutilisation de tout ou partie du code

développé. Le développement d’un programme informatique peut se résumer par la relation

suivante :

Algorithmes + Structure de données = programme.

Le développeur peut opter pour une architecture de son application, soit basée sur les données

soit sur les traitements.

Page 214: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

208

Dans les méthodes de programmation organisées autour des traitements, l’ajout de nouvelles

fonctionnalités notablement différentes de ce qui était envisagé initialement pourra s’avérer

difficile. Par contre, les données sont beaucoup plus stables dans le temps et il peut apparaître

plus sain de baser sa conception autour de la représentation des données.

Les considérations liées à l’évolutivité, la ré-utilisabilité du logiciel, à sa modularité

conduisent vers des programmations orientées objet. B.Meyer [5] donne la définition suivante

de la conception orientée objet :

« La conception par objet est la méthode qui conduit à des architectures logicielles fondées

sur les objets que tout système ou tout sous-système manipule »

« Ne commencez pas par demander ce que fait le système, demandez A QUOI il le fait »

13.3 Un exemple de programmation classique

Nous allons illustrer l’intérêt de la programmation orientée objet en C++ par un exemple que

nous commencerons par traiter en C. Nous considérons dans cet exemple un rectangle. Pour

un néophyte en programmation, il apparaît assez évident de considérer que le rectangle est un

objet, possédant certaines caractéristiques (couleur, taille,…) et sur lequel on peut agir. On

peut par exemple le déplacer, changer sa couleur, le faire tourner. Si maintenant nous confions

cet objet à un programmeur qui ne pratique pas la programmation objet, nous allons obtenir

un ensemble de variables et de fonctions, qui vont rendre fastidieuses toutes les opérations

que le néophyte considère pourtant comme élémentaires.

Dans un premier temps, nous allons définir les caractéristiques de notre rectangle et voir ce

qu’un programmeur en C peut en faire. On suppose qu’un rectangle est représenté par un

certain nombre de pixels de couleur, chaque couleur étant codée en RGB, avec 1 octet pour

chacune des composantes RGB. Pour définir un rectangle, il faut donc définir :

• Sa taille : largeur, hauteur

• Sa position : par exemple, les coordonnées du point en bas à gauche

• Un tableau bidimensionnel contenant tous les pixels.

Page 215: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

209

Au cours de la « manipulation informatique » de ce rectangle, on peut être amené à faire un

certain nombre de modifications, plus ou moins compliquées. On se bornera ici à considérer 2

actions basiques :

• Changement de position du rectangle,

• Changement de couleur du rectangle.

En programmant de façon élémentaire, on aboutit à un programme organisé de la façon

suivante :

• Déclaration des variables définissant taille, position, couleur, pointeur sur un tableau bi-

dimensionnel,

• Initialisation de la taille, de la position, de la couleur,

• Allocation des pointeurs du tableau bidimensionnel,

• Initialisation des pixels avec la couleur définie,

• Changement de position du triangle,

• Changement de couleur des pixels du rectangle.

Le code obtenu peut par exemple être celui-ci :

#include <malloc.h> int main(int argc, char* argv[]) {

unsigned int XPosition , YPosition ; // Position du triangle unsigned int XTaille , YTaille ; // Taille du trian gle unsigned char RComp ; // Codage de la composante R de la couleur unsigned char GComp ; // Codage de la composante G de la couleur unsigned char BComp ; // Codage de la composante B de la couleur unsigned char ** PixelR ; // Tableau bidimensionnel pour les pixels R unsigned char ** PixelG ; // Tableau bidimensionnel pour les pixels G unsigned char ** PixelB ; // Tableau bidimensionnel pour les pixels B unsigned int CbLigne ; // Compteur sur les lignes d u rectangle unsigned int CbColonne ; // Compteur sur les colonn es du rectangle // Initialisation de la taille du rectangle XTaille= 200 ; YTaille= 50; //Initialisation de la position du rectangle XPosition=0; YPosition=150; // Allocation des pointeurs pour PixelR PixelR = (unsigned char **)malloc(YTaille*sizeof(ch ar *)); for(CbLigne=0 ; CbLigne<YTaille ; CbLigne ++) Pixel R[CbLigne]=(unsigned char *)malloc(XTaille*sizeof(unsigned char *));

Page 216: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

210

// Allocation des pointeurs pour PixelG PixelG = (unsigned char **)malloc(YTaille*sizeof(ch ar *)); for(CbLigne=0 ; CbLigne<YTaille ; CbLigne ++) Pixel G[CbLigne]=(unsigned char *)malloc(XTaille*sizeof(unsigned char *)); // Allocation des pointeurs pour PixelB PixelB = (unsigned char **)malloc(YTaille*sizeof(ch ar *)); for(CbLigne=0 ; CbLigne<YTaille ; CbLigne ++) Pixel B[CbLigne]=(unsigned char *)malloc(XTaille*sizeof(unsigned char *)); // Définition de la couleur RComp=128 ; GComp=0; BComp=36; // On initialise les pixels du rectangle for (CbColonne=0 ; CbColonne < XTaille ; CbColonne+ +){ for(CbLigne=0 ; CbLigne< YTaille ; CbLigne++){ PixelR[CbLigne][CbColonne]=RComp; PixelG[CbLigne][CbColonne]=GComp; PixelB[CbLigne][CbColonne]=BComp; } // fin du for sur CbLigne }// fin du for sur CbColonne // Si on veut créer un deuxième rectangle ??? printf("Fin du programme\n"); return 0;

}

La façon dont est écrit ce programme n’est toutefois pas satisfaisante. En effet, si on veut

créer un deuxième rectangle, comment doit-on procéder ? Cela est toujours possible, mais en

créant à nouveau l’ensemble des variables définissant la taille, la position, la couleur, les

pointeurs…

Sans aborder les notions de programmation objet au sens stricte du langage C++, ce

programme aurait pu être écrit plus aisément en utilisant la notion de structure et le code

correspondant à des actions effectuées sur le rectangle aurait pu être placé dans des fonctions.

Nous définissons alors les deux structures suivantes :

typedef struct { unsigned char R ; unsigned char G ; unsigned char B ; } PIXEL ;

Page 217: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

211

typedef struct { PIXEL Color ; // définit la couleur du rectangle unsigned int XPosition ; unsigned int YPosition ; unsigned int XTaille ; unsigned int YTaille ; PIXEL ** Image ; } RECTANGLE ;

Par ailleurs on définit les fonctions qui prennent en charge l’ensemble des actions que nous

voulons effectuer sur le rectangle.

#include "stdafx.h" #include <malloc.h> #include "rectangle.h" // Cette fonction change la couleur de tous les pi xels du rectangle void ColorRectangle(RECTANGLE *PtRect, PIXEL NewCol or){ unsigned int CbLigne;// Compteur sur les lignes unsigned int CbColonne;// Compteur sur les colonne s // On change tous les pixels de couleur for (CbColonne=0 ; CbColonne < PtRect->XTaille ; C bColonne++){ for(CbLigne=0 ; CbLigne< PtRect->YTaille ; CbLi gne++){ PtRect->Image[CbLigne][CbColonne].R=NewColor.R; PtRect->Image[CbLigne][CbColonne].G=NewColor.G; PtRect->Image[CbLigne][CbColonne].B=NewColor.B; } // fin du for sur CbLigne }// fin du for sur CbColonne // On met à jour la couleur PtRect->Color.R=NewColor.R; PtRect->Color.G=NewColor.G; PtRect->Color.B=NewColor.B; }// fin de la fonction ColorRectangle // Cette fonction initialise les paramètres du rect angle *PtRect void InitRectangle(RECTANGLE * PtRect, unsigned int XTaille, // Taille en X unsigned int YTaille, // Taille en Y unsigned int XPosition,// Position en X unsigned int YPosition,// Position en Y PIXEL Couleur)// Couleur du rectangle { unsigned int CbLigne; PtRect->XTaille=XTaille; PtRect->YTaille=YTaille; PtRect->XPosition=XPosition; PtRect->YPosition=YPosition; // Initialisation des pointeurs PtRect->Image=(PIXEL **)malloc(YTaille*sizeof(PIX EL *));

Page 218: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

212

for(CbLigne=0 ; CbLigne<YTaille ; CbLigne ++) PtRect->Image[CbLigne]=(PIXEL *)malloc(XTaille* sizeof(PIXEL *)); // Initialisation de la couleur ColorRectangle( PtRect,Couleur); } // fin de la fonction InitRectangle // Cette fonction modifie la position du rectangle void ChangePosition( RECTANGLE * PtRect,unsigned in t X, unsigned int Y){ PtRect->XPosition=X; PtRect->YPosition=Y; } // fin de la fonction ChangePosition

Le programme principal devient alors beaucoup plus compact et lisible.

int main(int argc, char* argv[]) {

RECTANGLE Rect1; RECTANGLE Rect2; PIXEL Couleur ; PIXEL NewCouleur ; // Définition de la couleur Couleur.R=128 ; Couleur.G=0; Couleur.B=36; // Définition de NewCouleur NewCouleur.R=36 ; NewCouleur.G=100; NewCouleur.B=36; // Initialisation du rectangle // XTaille=200 /YTaille=50 /XPosition=0 /YPosition= 150 InitRectangle(&Rect1,200,50,0,150,Couleur); // Changement de position du rectangle //Ancien code //XPosition=XPosition+20; //YPosition =YPosition+10; ChangePosition(&Rect1,Rect1.XPosition+20,Rect1.YPos ition+10); //Changement de couleur des pixels ColorRectangle(&Rect1,NewCouleur); // initialisation du deuxième rectangle InitRectangle(&Rect2,200,50,0,150,NewCouleur); printf("Fin du programme\n"); return 0;

}

Page 219: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

213

Bien qu’ayant conservé une écriture classique en C, nous avons commencé à concevoir notre

programme selon une méthode orientée objet, en considérant d’une part les données

regroupées dans la structure RECTANGLE et d’autre part les fonctions (appelées méthodes

en POO) s’appliquant aux éléments contenus dans la structure RECTANGLE. Le langage

C++ va nous permettre de regrouper les définitions des données et des fonctions au sein d’une

classe. Avant de poursuivre l’amélioration de notre programme, nous allons définir ce qu’est

une classe et les différents éléments qui la constituent.

13.4 Les classes

13.4.1 Définition

On appelle classe la structure d’un objet, c’est à dire la déclaration de l’ensemble des entités

qui composeront un objet. Un objet est donc « issu » d’une classe, c’est le produit qui sort

d’un moule. En réalité, on dit qu’un objet est une instanciation d’une classe, c’est la raison

pour laquelle on pourra parler indifféremment d’objet ou d’instance (éventuellement

d’occurrence). Une classe est composée de deux parties :

• Les attributs (parfois appelés données membres) : il s’agit des données représentant l’état

de l’objet

• Les méthodes (parfois appelées fonctions membres) : il s’agit des opérations applicables

aux objets.

Si on définit une classe « Peugeot 207 », alors on pourra instancier (créer) à partir de cette

classe plusieurs objets :

• Voiture 1 => peugeot 207 bleue immatriculée 007QG75.

• Voiture 2 => peugeot 207 orange immatriculée 747CPP25.

13.4.2 Syntaxe

D’un point de vue syntaxe, on définit une classe de la façon suivante :

class MaClasse { public: MaClasse(); // Constructeur ~MaClasse();// Destructeur void Fonction1() ;// Fonction typeA var3 ; // variable membre protected : void Fonction2(type param1,type param2) ; // Fonct ion void Fonction3() ;// Fonction typeB var1 ;// variable membre

Page 220: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

214

private: typeC var2 ;// variable membre unsigned int var4 ; //variable membre };

La déclaration d’une classe commence toujours avec le mot-clé class suivi du nom de la

classe (ici MaClasse ) et se termine par un point-virgule. Les variables var1 , var2 , var3

sont des variables membres. On trouve un certain nombre de fonctions dans cette classe :

Fonction1(), Fonction2(type param1,type param2) , Fonction3() . Ce

sont des fonctions membres de la classe. Il y a aussi 2 fonctions particulières, MaClasse()

et ~MaClasse() , qui sont respectivement le constructeur et le destructeur attachés à cette

classe.

13.4.3 Constructeur

Un constructeur est une fonction membre qui est appelée automatiquement à la déclaration de

l’objet. Une fonction constructeur doit avoir le même nom que la classe et ne pas avoir de

type de retour. Nous verrons plus loin qu’il est possible de définir plusieurs constructeurs

pour une même classe. C’est typiquement dans cette fonction constructeur que l’on pourra

faire l’initialisation des différentes variables membres.

Rappelons qu’il faut bien faire la distinction entre la notion de classe et d’objet. La classe

n’est que la définition d’un type d’objet mais n’est pas un objet en elle-même. Il faut ensuite

créer des objets à partir de cette classe.

13.4.4 Destructeur

Il existe une fonction dont le nom est toujours le nom de la classe précédé de ~. Dans

l’exemple ci-dessus, il s’agit de ~MaClasse() . Cette fonction est le destructeur et de façon

similaire au constructeur, cette fonction est appelée lors de la disparition de l’objet. Le

destructeur doit toujours être unique.

13.4.5 Restriction d’accès

La définition de la classe comporte 3 mots clé (public : , protected : et

private : ) qui divisent en trois catégories les fonctions et variables membres. Ces 3

catégories définissent les restrictions d’accès aux fonctions et variables.

Page 221: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

215

Il est à noter que si une catégorie ne comporte ni variable, ni fonction, il n’est pas

indispensable de faire figurer le mot-clé correspondant. Ainsi dans l’exemple précédent, si

var2 n’existait pas, nous aurions pu supprimer le mot-clé private: .

Private :

On ne peut accéder aux données et fonctions private que de l’intérieur de l’objet. Cela

implique qu’une fonction private ne peut être appelée qu’à partir des fonctions

membres de la classe et qu’une donnée membre private ne peut être lue ou modifiée

que par des fonctions membres de la classe.

Notamment pour ce qui est des variables, on dispose ici d’un mécanisme permettant de

sécuriser la modification en imposant l’utilisation d’une fonction membre (qui elle n’est

forcément pas private ). Cette fonction membre pourra par exemple effectuer une

détection d’erreur par rapport à la modification demandée sur la variable et le cas échéant,

ne pas modifier la variable.

Public :

Pour les données et variables membres public, il n’y a pas de restriction et on peut y

accéder à l’extérieur comme à l’intérieur de l’objet.

Protected :

Il s’agit d’un cas intermédiaire entre private et public . Les fonctions et variables

membres de type protected sont accessibles par toute fonction membre de l’objet ou

d’une des classes dérivées de l’objet. La notion de classe dérivée n’ayant pas encore été

introduite, on se reportera au chapitre traitant des notions d’héritage dans lequel on revient

sur ces notions.

13.4.6 Les fonctions (méthodes) de la classe

Nous avons défini dans le paragraphe précédent une classe avec ses données membres et ses

fonctions membres, mais nous n’avons pas écrit le code de la fonction. Nous allons définir

dans ce paragraphe les quelques règles qui définissent l’écriture du code des fonctions.

Pour la fonction nommée « Fonction1 », nous écrirons :

Page 222: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

216

MaClasse ::Fonction1(){ // Code source de la fonction } ;

:: est l’opérateur binaire de résolution de portée de classe.

Les données membres de la classe, quelle que soit leur catégorie ( private , protected

ou public ) sont directement accessibles dans toutes les fonctions membres de la classe.

Ainsi pour modifier var2 , qui est une donnée membre private , il suffit, par exemple,

d’écrire dans la fonction :

var2=3 ;

En fait, chaque fonction membre dispose implicitement du pointeur sur l’objet auquel on

applique la fonction. C’est le pointeur implicite sur l’objet dont le nom est « this ». Ainsi

lorsque l’on écrit :

var2=3 ;

Cela est équivalent à écrire :

this->var2=3 ;

13.4.7 Organisation en fichiers source et header

Classiquement, lorsque l’on définit une classe, on répartit le code de la façon suivante :

• Dans le fichier entête MaClasse.h , on place la définition de la classe, comme celle

présentée au paragraphe 13.4.2.

• Dans un fichier source MaClasse.cpp , on écrit le code des différentes fonctions

membres de la classe.

On aura alors le fichier MaClasse.cpp qui sera de la forme :

#include « MaClasse.h » MaClasse ::MaClasse(){ // Code source du constructeur } ; MaClasse : :~MaClasse(){ // Code source du destructeur } ;

Page 223: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

217

// Code des différentes fonctions MaClasse : :Fonction1(){ // Code source de la fonction } ; MaClasse : :Fonction2(type param1,type param2){ // Code source de la fonction } ; MaClasse : :Fonction3(){ // Code source de la fonction } ;

Une classe étant définie, on peut à partir de celle-ci créer et manipuler des objets.

13.5 Création d’un objet

Il existe essentiellement 2 méthodes pour créer un objet, soit au moyen d’une déclaration, soit

en déclarant un pointeur et en allouant la mémoire avec l’opérateur new.

13.5.1 Au moyen d’une déclaration

MaClasse Objet;

ou bien

MaClasse Objet();

Ces deux formes sont équivalentes. Nous sommes dans le cas d’un constructeur auquel il

n’est pas besoin de passer des paramètres.

13.5.2 Avec l’opérateur new

En définissant un pointeur sur un objet de type MaClasse et en allouant ce pointeur au moyen

de l’opérateur new.

MaClasse * PtObjet;

PtObjet = new MaClasse;

ou bien sous une forme équivalente.

MaClasse * PtObjet;

PtObjet = new MaClasse();

Page 224: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

218

L’opérateur new qui est introduit ici permet de faire des allocations de mémoire. Dans

l’exemple ci-dessus, nous avons alloué la mémoire pour un seul objet de type MaClasse . On

aurait pu allouer plusieurs objets en utilisant la syntaxe suivante :

PtObjet = new MaClasse[10] ();

Dans ce cas, on alloue un pointeur sur 10 objets de classe MaClasse.

Remarque

Il est à noter que nous sommes dans le cas particulier où le constructeur ne prend aucun

paramètre d’entrée. Si nous avions un constructeur qui prend des paramètres en entrée, il

faudrait alors noter que les syntaxes suivantes sont fausses :

MaClasse Objet; car équivalente à MaClasse Objet();

PtObjet=new Ma Classe; car équivalente à PtObjet=new MaClasse();

Si on prend l’exemple d’un constructeur ainsi défini :

MaClasse ::MaClasse(unsigned int a)

alors les syntaxes correctes pour créer un objet sont :

int b=5 ;

MaClasse Objet(b);

Ou bien :

int b=5 ;

MaClasse *PtObjet ;

PtObjet= new MaClasse(b) ;

Il est également à noter que l’allocation de mémoire pour plusieurs objets n’est possible que si

la classe dispose d’un constructeur sans argument d’entrées. L’écriture :

PtObjet = new MaClasse [10] (b);

n’est pas admise pour le cas d’objets multiples.

Page 225: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

219

Opérateur delete

Lorsqu’un objet a été alloué avec l’opérateur new, comme par exemple avec la déclaration

suivante :

MaClasse *PtObjet ;

PtObjet= new MaClasse(b) ;

alors on détruit l’objet et on libère la mémoire avec l’opérateur delete :

delete PtObjet ;

L’opérateur delete ne doit être utilisé que dans le cas où l’objet a été créé avec

l’opérateur new.

13.6 Manipulation des objets

La notion de classe étant une extension de la notion de structure, on retrouve des syntaxes

similaires.

13.6.1 Accès à une variable

Tout comme pour une structure nous aurons la syntaxe suivante :

NomObjet.NomVariable

Ou

PointeurSurObjet->NomVariable

13.6.2 Accès à une fonction

Le principe est strictement identique à celui utilisé pour une variable :

NomObjet.NomFonction(arguments)

Ou

PointeurSurObjet->NomFonction(arguments)

On reprend ici la définition de la classe faite au paragraphe 13.4.2. Nous aurons les lignes

suivantes dans notre programme :

Page 226: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

220

typeA alpha ; // Déclaration d’une variable de type typeA MaClasse Objet() ; // Déclaration d’un premier obje t MaClasse * PtObjet() ;// Déclaration d’un pointeur sur un deuxième objet PtObjet = new MaClasse() ; // Allocation du pointeu r du deuxième objet Objet.var3=alpha ;// On affecte alpha à var3 de Ob jet Objet.Fonction1() ; // Appel de Fonction1 sur le pr emier objet. PtObjet->var3=2*alpha ; // On affecte 2.alpha à var 1 du deuxième objet PtObjet->Fonction1() ; // Appel de Fonction1 sur le deuxième objet

Exercice 13.1 : Dans les éléments de code source suivants, trouvez les erreurs, expliquez ce

qui est faux et proposez une solution pour corriger chacune des erreurs.

La définition de la classe est la suivante dans le fichier MaClasseExo.cpp :

class MaClasseExo { public: MaClasseExo(); // Fonction toujours présente, c’es t le Constructeur ~MaClasseExo();//Fonction toujours présente, c’est le Destructeur void Fonction1() ;// Fonction unsigned int var3 ; // variable membre protected : void Fonction2(int ParamInt,char ParamChar) ; // F onction void Fonction3() ;// Fonction unsigned char var1 ;// variable membre private: unsigned int var2 ;// variable membre unsigned char var4 ; //variable membre };

Le fichier main.cpp contient ceci :

int main() { MaClasseExo Objet ; // Déclaration d’un objet MaClasseExo * PtObjet ; // Déclaration d’un point eur sur un objet // Initialisation des variables Objet.var1=0 ; Objet.var2=0 ; Objet.var3=0 ; Objet.var4=0 ; PtObjet.var1=0 ; PtObjet.var2=0 ; PtObjet.var3=0 ; PtObjet.var4=0 ; Objet.var1=(unsigned char)Objet.var3 ; PtObjet.var2=Objet.var2 ; // On applique la Fonction1 et la Fonction2 sur l es 2 objets PtObjet.Fonction1() ; PtObjet.Fonction3() ; Objet.Fonction1() ;

Page 227: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

221

Objet.Fonction3 ; }

Dans le fichier MaClasseExo.cpp, pour la Fonction1, on trouve ceci :

void IncrementeVar1_2 () { var1++ ; var2++ ; } ;// Fin de la fonction IncrementeVar1_2 ; void MaClasseExo::Fonction1(){ IncrementeVar1_2() ; } ; // fin de la fonction Fonction1

Exercice 13.2 : reprendre ce qui a été fait pour la création d’un rectangle à l’aide de structure

et donnez la structure de la classe et des fonctions. Ré-écrire le code source du programme

principal.

13.7 Surcharge des fonctions et des opérateurs

Contrairement au C où pour une fonction Fonction1() , il y a un seul et unique prototype

et une seule et unique définition de la fonction, le C++ autorise pour une même fonction

plusieurs déterminations. Nous pourrons par exemple modifier la déclaration de la classe

MaClasse en ajoutant un deuxième prototype de la fonction Fonction1() :

class MaClasse { public: MaClasse(); // Constructeur ~MaClasse();// Destructeur void Fonction1() ;// Fonction void Fonction1 ( unsigned int a) ; typeA var3 ; // variable membre protected : void Fonction2(type param1,type param2) ; // Fonct ion void Fonction3() ;// Fonction typeB var1 ;// variable membre private: typeC var2 ;// variable membre unsigned int var4 ; //variable membre };

Il nous faudra alors définir la deuxième définition de la fonction Fonction1(unsigned int alpha) : MaClasse ::Fonction1(unsigned int a ) { // code de la fonction } ;

Page 228: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

222

Nous pourrons alors utiliser l’une et/ou l’autre de ces fonctions :

CMaClasse MonObjet() ; unsigned int alpha ; MonObjet.Fonction1() ; MonObjet.Fonction1(alpha) ;

Les deux écritures sont valides et correspondent à deux fonctions différentes, même si leur

nom est identique. La distinction sera faite par le compilateur en examinant les arguments qui

sont passés à la fonction. Si aucun paramètre n’est passé à la fonction, il s’agit de la première

fonction Fonction1() . Si on passe un unsigned int , alors il s’agit forcément de la

deuxième définition de la fonction que nous venons d’introduire dans ce paragraphe.

De la même façon qu’il est possible de redéfinir une fonction, on peut faire de même avec les

opérateurs. Par exemple, les opérateurs « + » ou « = » qui s’appliquent d’ordinaire à des

nombres entiers ou flottants peuvent s’appliquer à des nombres complexes.

Considérons tout d’abord une classe constituée par des nombres complexes dont les parties

réelle et imaginaire sont du type float . La définition de la classe sera la suivante :

class CComplex { public: CComplex(); CComplex(float x, float y); float I; float R; virtual ~CComplex(); };

On notera ici la présence de deux constructeurs. On veut ajouter à cette classe, une fonction

membre, « Egal », telle que la valeur des arguments réels et imaginaires de l’objet deviennent

égaux à ceux de l’objet complexe que l’on passe comme argument à cette fonction.

Nous redéfinirons alors la classe en ajoutant la fonction Egal(CComplex u) . La classe

complexe devient alors :

Page 229: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

223

class CComplex { public: CComplex(); CComplex(float x, float y);

Egal(CComplex u) ; float I; float R; virtual ~CComplex(); };

Et la définition de la fonction :

CComplex : :Egal(CComplex u){ R=u.R ; I=u.I ; return *this ; } ;

Nous pourrons écrire :

CComplex d(3,4) ; CComplex a,b,c; c.Egal(d) ; // c=d ; a.Egal(b.Egal(c)) ; // a=b=c ;

Si au lieu de manipuler des nombres complexes qui sont des instances de la classe

CComplex , nous traitions des nombres de type float par exemple, alors on aurait pu écrire

directement :

c=d;

a=b=c;

Si l’on souhaite pouvoir disposer de cette forme d’écriture en utilisant l’opérateur « = »

comme dans le cas de n’importe quel nombre alors, il faut ajouter une fonction membre à la

classe CComplex. Cette fonction membre sera définie par :

CComplex operator=(CComplex u);

Et son implémentation sera la suivante :

CComplex CComplex : :operator=(CComplex u) { R=u.R ; I=u.I ; return *this };

Page 230: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

224

Exercice 13.3 : Ajouter deux fonctions membres à la classe CComplex qui permettent

d’effectuer le produit. La première de ces fonctions sera une fonction classique nommée

Plus . La deuxième de ces fonctions sera une surcharge de l’opérateur +.

13.8 Passage par référence

Le C++ offre par rapport au C une souplesse d’écriture pour le passage par pointeur.

Reprenons tout d’abord un exemple en C. Supposons que dans le programme principal, on ait

une variable de type unsigned int a que l’on souhaite incrémenter dans une fonction

que l’on appellera Incremente . Nous aurons alors :

void Incremente (int *);

unsigned int a;

Incremente (&a);

La seule solution pour que la fonction Incremente puisse modifier la variable a du

programme principal est de passer le pointeur sur a à Incremente . L’implémentation de la

fonction sera alors la suivante :

void incremente ( int * PtAlpha) {

*PtAlpha ++ ;

}

Le C++ permet l’écriture suivante selon le principe du passage par référence :

void Incremente (int &);

unsigned int a;

incremente (a);

L’implémentation de la fonction sera alors la suivante :

void incremente ( int & Alpha) {

Alpha ++ ;

}

Bien que l’appel à la fonction fasse apparaître a, ce n’est en fait pas a qui est passé à la

fonction mais le pointeur sur a et la variable Alpha de la fonction Incremente n’est pas

une copie locale de la valeur de a, mais correspond à la même variable que a.

Page 231: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

225

Si dans le programme principal, nous examinons l’adresse de a, (&a) et que nous faisons la

même opération dans la fonction Incrémente avec Alpha (&Alpha ), nous constaterons

que les deux adresses sont strictement identiques, et que l’on manipule en réalité la même

variable sous deux noms différents. Alpha est un alias de a.

Exercice 13.4 : soit le code suivant écrit en C++. Représentez graphiquement le signal obtenu

dans le tableau Signal[] . Trouvez l’erreur dans ce programme et proposez au moins 2

méthodes pour la corriger.

#include <math.h> void ampli(double gain,double Sample); void ecrete(double limite,double &Tension); int main(int argc, char* argv[]) { double Signal[200]; double Sample; double pi=3.14159265359; unsigned int CbSample; for (CbSample=0; CbSample<200; CbSample++){ Sample=sin(2*pi*CbSample/200); ampli(2, Sample); ecrete(1,Sample); Signal[CbSample]=Sample; }// fin du for printf("fin du programme sample\n"); return 0; } void ampli( double gain, double Sample){ Sample=Sample*gain; }// fin de la fonction ampli void ecrete(double limite, double &Tension){ if(Tension>limite) Tension=limite; }// fin de la fonction ecrete

13.9 Héritage et composition

13.9.1 Introduction

Les intérêts de la programmation objet du code en C++ sont notamment la modularité et la

possibilité de ré-utiliser du code déjà développé. Si l’on fait une analogie avec la construction

d’un module électronique, le concepteur n’a pas à redéfinir l’ensemble des fonctions à partir

du niveau le plus bas (le transistor par exemple) mais utilise des composants existants. Les

possibilités de composition et d’héritage de classe facilitent en C++ la ré-utilisation des

Page 232: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

226

classes déjà existantes pour en construire de nouvelles adaptées au besoin de l’application à

développer.

13.9.2 La composition

La composition (appelée aussi agrégation) de classes permet l’utilisation de la définition

d’une ou de plusieurs classes dans la définition d’une nouvelle classe. Lorsqu’une donnée

membre d’une classe est un objet d’une autre classe, on dit que la nouvelle classe est une

classe composée des autres objets. C’est cette possibilité que nous avons utilisé dans la

correction de l’exercice 13.2, en incluant un objet de classe CPixel dans la définition de la

classe CRectangle .

13.9.3 L’héritage

L’héritage est quelquefois appelé « Spécialisation » ou « Dérivation ». Elle nous conduit à

une organisation hiérarchique en classe et en sous-classe. Un camion et une voiture sont tout

deux des véhicules. On peut donc concevoir que la classe des véhicules est composée de deux

sous-classes, les camions et les voitures. On crée alors une classe de base véhicule qui

contient les caractéristiques et les opérations communes à tous les véhicules et on introduit

ces caractéristiques dans les classes camion et voiture par héritage de la classe véhicule. Cela

évite de réécrire les caractéristiques et opérations communes à tous les véhicules pour chaque

nouveau type de véhicules.

13.9.3.1 Principe de l’héritage

Si nous reprenons l’exemple du rectangle, et que de façon plus générale nous voulions traiter

un ensemble de formes différentes dont le rectangle, nous pouvons établir la hiérarchie

suivante de classe :

Page 233: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

227

Ceci nous permet par exemple de développer une classe Forme , puis ensuite une classe

Deux_Dimensions et Trois_Dimensions qui ont les mêmes données et fonctions

membres que la classe Forme et pour chacune de ces deux classes d’ajouter des données et

des fonctions membres spécifiques.

Par exemple, pour toute forme, on peut définir comme données membres la position et la

couleur. Pour un objet à deux dimensions, on peut définir sa surface, et pour un objet à trois

dimensions son volume. Si l’on construit la classe Deux_Dimensions à partir de la classe

Forme par héritage, alors tout objet instancié à partir de la classe Deux_Dimensions aura

pour données membres :

• la position (par héritage de Forme )

• la couleur (par héritage de Forme )

• la surface.

Pour la classe Trois_Dimensions qui sera construite à partir de la classe Forme, nous aurons,

de façon similaire comme données membres :

• la position (par héritage de Forme )

• la couleur (par héritage de Forme )

• le volume.

Supposons que l’on veuille créer une nouvelle classe Y à partir d’une classe X existante. La

définition de la classe sera alors la suivante :

class Y : public X{

// Définition des membres de la classe Y

} ;

On appelle X la classe de base (ou encore superclasse) et Y la classe dérivée (ou sous-classe).

Ici nous avons choisi de faire un héritage de type public, ce qui signifie que toutes les

membres (données ou fonctions) de la classe de base qui sont publiques seront également

publiques pour les objets instanciés à partir de la classe dérivée Y. Ceci appliqué à l’exemple

précédent nous donne :

Page 234: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

228

Classe Forme {

// Définition des membres de la classe

public :

double XPosition ; // coordonnées de la position d u centre de gravité de l’objet

double YPosition ;

double Zposition ;

unsigned char CouleurR ; // composantes RGB de la couleur de l’objet

unsigned char CouleurG ;

unsigned char CouleurB ;

}

Remarque : Pour simplifier la présentation de l’exemple, on a fait apparaître séparément les

trois composantes RGB pour la couleur. L’utilisation d’une structure ou d’une classe couleur

est en fait préférable. Il en est de même pour la définition des coordonnées du centre de

gravité de l’objet, où il serait préférable d’utiliser une classe définissant des points.

Class Deux_Dimensions : public Forme{

// Définition des membres de la classe

public :

double Surface ; // surface de la forme à 2 dimen sions

}

Si on déclare alors un objet Objet2D, on accédera aux données membres de la façon suivante :

Deux_Dimensions Objet2D(); // Création de l’objet

// Initialisation des coordonnées du centre de grav ité

Objet2D.Xposition=2 ;

Objet2D.Yposition=4;

Objet2D.Zposition=11;

Objet2D.Surface=54.3;

13.9.3.2 Restriction d’accès

Les restrictions d’accès pour les membres locaux de la sous-classe sont directement

déterminées par le choix public :, protected : ou private . Pour les membres

de la classe de base, nous aurons le tableau suivant :

Page 235: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

229

Type d’héritage Niveau d’accès classe de base Niveau d’accès équivalent

classe dérivée

public public public

protected protected

private inaccessible

protected public protected

protected protected

private inaccessible

private public private

protected private

private inaccessible

13.9.3.3 Substitution des membres hérités

La démarche de construction de classes dérivées à partir de classes de base permet de

reprendre des éléments déjà développés pour les étendre, les adapter à son besoin. Il est

toutefois possible que certaines données ou fonctions membres hérités de la classe de base ne

conviennent pas. Il est alors possible de les modifier dans la sous-classe et elles se substituent

alors à la définition initiale de la classe de base. Exemple :

class X{

public :

void f() { a=-100 ;}

int a ;

} ;

classY :public X{

public :

void f() {a=500 ;}

unsigned int a ;

} ;

Si l’on crée un objet instance de la classe dérivée Y :

Y ObjetY ();

Alors ObjetY.a accède à unsigned int a , et ObjetY.f() exécute la fonction

définie dans la classe dérivée. On aura ObjetY.a qui vaudra 500.

Page 236: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

230

Si l’on veut toutefois accéder à la fonction f() de la classe de base, alors on utilisera la

syntaxe suivante :

ObjetY.X::f() ;

C’est la fonction définie dans la classe de base qui s’exécutera et qui modifiera int a. On

accède à la variable membre définie dans la classe X par la syntaxe :

ObjetY.X::a;

ObjetY.X::a vaudra alors –100 ;

13.9.3.4 Gestion des constructeurs

Lorsque l’on fabrique une classe par dérivation d’une classe de base, cela pose le problème

des constructeurs. En effet, parmi les membres de la nouvelle classe obtenue, nous aurons :

• les membres hérités de la classe de base et s’il existe, un constructeur membre de la classe

de base qui s’applique en particulier à l’initialisation des données membres,

• les membres de la sous-classe ( membres locaux) et éventuellement un constructeur.

Le mécanisme mis en place pour l’héritage permet de conserver l’action du constructeur de la

classe de base et de compléter cette action en écrivant un constructeur spécifique pour la

classe dérivée. Considérons l’exemple suivant. Soit la classe ClasseBase , ainsi définie :

class ClasseBase{ ClasseBase(int a) ; ~ClasseBase() ; int X; … } ; ClasseBase::ClasseBase(int a){ x=a ; }

Soit la sous-classe SousClasse

class SousClasse :ClasseBase{ SousClasse(int a, float b); ~SousClasse() ; float Y; … };

Page 237: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

231

Pour que le constructeur de la classe de base s’exécute correctement, il faut lui passer un

argument de type int. Pour cela, on écrira le nouveau constructeur de la façon suivante :

int a=2;

float b=3.5;

SousClasse ::SousClasse(int a, float b) :ClasseBase (a) {

// Code propre au constructeur de SousClasse

Y=b;

} ;

L’appel au constructeur de la classe SousClasse se fera de la façon suivante :

SousClasse Objet( a, b)

et le constructeur de la classe de base sera appelé en lui passant l’argument int a avant que le

constructeur de la sous-classe ne s’exécute. Détaillons les différentes étapes de la construction

de l’objet dans ce cas. Que se passe t-il à la construction de l’objet ?

1. Programme principal :

int a =2 ;

float b =3.5

SousClasse Objet (a, b) ;

2. Entraîne l’appel du constructeur de la classe dérivée

SousClasse ::SousClasse(int a, float b) : ClasseBase(a) {

// Code propre au constructeur de SousClasse

Y=b;

} ;

3. Entraîne l’appel au constructeur de la classe de base

ClasseBase:: ClasseBase(a){

X=a;

}

( X membre de la classe de base est alors initialisé)

Page 238: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

232

4. On retourne au constructeur de la classe dérivée

SousClasse ::SousClasse(int a, float b) :ClasseBase (a) {

// Code propre au constructeur de SousClasse

Y=b;

} ;

5. On exécute le code de ce constructeur et on initialise la variable membre Y de la

classe dérivée.

6. On retourne au programme principal. L’objet est créé et toutes les variables

membres sont initialisées.

13.9.3.5 Fonctions virtuelles

Dans l’exemple précédent, nous avons défini 2 fois la fonction f() , à la fois dans la classe de

base et dans la classe dérivée. Déclarons 2 pointeurs sur des objets de type X et de type Y :

Y ObjetY ;

X * PtX ;

Y * PtY ;

PtX = & ObjetY ;

PtY = & ObjetY ;

PtX->f() appelle la fonction f() définie dans la classe de base X, et PtY->f() appelle la

fonction f() définie dans la classe dérivée. C’est le type du pointeur qui détermine la

fonction qui va être appelée et non pas la classe de l’objet sur lequel porte le pointeur.

Si l’on modifie la déclaration de la classe de base, en ajoutant le mot clé virtual dans la

déclaration de f() , nous aurons ceci :

class X{

public :

virtual void f() { a=-100 ;}

int a ;

} ;

Page 239: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

233

Dans ce cas, nous modifions le comportement des appels de fonction. Les lignes de code

PtX->f(); et PtY->f(); appellent toutes les deux la fonction f() de la classe dérivée.

Pour déterminer la fonction qui doit être appelée, c’est la classe de l’objet sur lequel on pointe

qui est pris en compte et non plus le type du pointeur.

On a maintenant un lien dynamique :

• dans le paragraphe précédent, comme c’était la nature du pointeur qui déterminait le choix

de l’une ou l’autre des fonctions, le choix pouvait être établi a priori.

• dans ce cas, il doit être fait au moment de l’exécution, dynamiquement.

Quelques Règles pour un bon héritage

La syntaxe :

X classe de base

Y classe dérivée

class Y : public X{

// Définition des membres de la classe Y

} ;

La substitution :

La fonction ou variable définie dans la classe dérivée qui a le même nom que celle de la

classe de base l’emporte. Si on veut avoir accès à la fonction f() de la classe de base plutôt

qu’à celle de la classe dérivée, la syntaxe est alors : ObjetY.X::f();

Restriction d’accès : héritage public

Toutes les variables membres de la classe de base sont accessibles comme si elles avaient été

définies dans la classe dérivée avec les mêmes règles. Si héritage non public, voir 13.9.2.2 sur

cours

Page 240: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

234

Constructeur :

Si le constructeur de la classe de base ne nécessite pas de paramètre, on écrit un constructeur

pour la classe dérivée sans ajout particulier. Si le constructeur de la classe de base nécessite

des paramètres, on ajoute ces paramètres au constructeur de la classe dérivée puis on génère

un appel au constructeur de la classe de base avec la syntaxe indiquée ci-dessous.

Paramètre propre au constructeur SousClasse

Appel du constructeur de la classe de base en passant a en argument

Paramètre rajouté car nécessaire pour le constructeur de la classe de base

SousClasse ::SousClasse(int a, float b) :ClasseBase(a) {

….} ;

ClasseBase::ClasseBase(int a) ;

Paramètre propre au constructeur SousClasse

Appel du constructeur de la classe de base en passant a en argument

Paramètre rajouté car nécessaire pour le constructeur de la classe de base

SousClasse ::SousClasse(int a, float b) :ClasseBase(a) {

….} ;

ClasseBase::ClasseBase(int a) ;

Exercice 13.5 : on reprend la hiérarchie des classes définie au paragraphe 13.9.2.1. On

suppose que :

• pour tous les objets on a une fonction calculant l’aire de l’objet

• que pour les objets en deux dimensions, on calcule le périmètre

• que pour les objets en trois dimensions on calcule le volume.

On implémentera en particulier les classes correspondant au cercle et au cône.

Page 241: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

235

13.10 Les flux d’entrée sortie

13.10.1 Généralités

Le langage C++ définit des classes permettant de gérer les flux d’entrée et de sortie des

données. Un flux d’entrée est un objet de classe istream et un flux de sortie un objet de

classe ostream . Pour gérer les entrées et sorties standard, il existe deux objets qui sont des

instances des classes istream et ostream . Ces deux objets sont cin ( flux entrant) et

cout (flux sortant). Pour manipuler les flux d’entrées sorties, deux opérateurs ont été

surchargées : << et >>. Pour un flux de sortie, on utilisera << et pour un flux d'entrée >>.

13.10.2 Opérateur d'insertion et d’extraction de flux

13.10.2.1 Extraction de flux >>

La classe istream définit l’opérateur >> d’insertion de flux qui s’applique donc à l’objet

cin . Cet opérateur permet l'entrée formatée car il reconnaît le type des objets et effectue le

formatage en conséquence. On peut ainsi écrire directement :

int n,p ;

cin >> n ;

Si on saisit alors 16, on obtiendra n = 16. Comme l’opérateur >> renvoie une référence à

l’objet, on peut enchaîner de la façon suivante :

cin >>n >>p ;

Si on saisit, 16 puis 76, on obtient alors n=16 , p=76 .

13.10.2.2 Insertion de flux >>

De façon parfaitement similaire, il existe un opérateur d'insertion de flux, qui s'applique au

flux de données sortant c'est à dire à cout . Nous pouvons donc écrire :

int n=16;

int p=45;

cout << n <<endl;

qui produira :

16

Page 242: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

236

endl rajoute un saut de ligne et vide le tampon. En réalité cette écriture permet l'appel à la

fonction endl() définie dans la classe ostream .

De la même façon, comme le formatage se fait en fonction du type des objets transmis, et que

l'on peut chaîner les opérateurs <<, nous pourrons écrire :

cout << "La valeur de n est égale à " << n << "et c elle de p à " << p << endl;

ce qui nous donnera :

La valeur de n est égale à 16 et celle de p à 45

13.10.3 Modification des formatages

13.10.3.1 Liste des drapeaux

Il y a possibilité de modifier le formatage en sortie des données en jouant sur un certain

nombre de drapeaux :

Drapeau Effets

ios::skipws saute les blancs de tête dans une entrée formatée. (par défaut)

ios::left sortie justifiée à gauche, bourrage à droite pour obtenir la bonne

largeur

ios::right sortie justifiée à droite, bourrage à gauche. (par défaut)

ios::internal sortie numérique justifiée à droite, signe ou base justifiée à gauche et

bourrage au milieu

ios::dec entrée et sortie d'entiers en base 10. ( défaut pour la sortie)

ios::oct entrée et sortie d'entiers en base 8

ios:hex entrée et sortie d'entiers en base 16

ios::showbase sortie d'entiers avec préfixe de base ; ex 027 (oct), 0x2c1(hex).

(défaut)

ios::showpoint sortie de nombres réels avec point décimal et zéros de traîne

ios::uppercase utilisation des majuscules pour sorties nombres hexadécimaux et réels

en scientifique

Page 243: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

237

ios::showpos entiers positifs précédés par '+'

ios::scientific sortie des nombres réels en notation scientifique ; ex 1.23456e-09

ios::fixed sortie des nombres réels avec n chiffre à droite du point décimal où n

est la précision (_prec)

ios::unitbuf vidage du flux de sortie après chaque insertion

ios::stdio vidage de stdio et stderr après chaque insertion

13.10.3.2 Fonctions permettant de modifier les drapeaux

Pour appliquer ces drapeaux sur le flux considéré, il faut faire appel à la fonction flags().

Nous aurons donc :

cout.flags( ios::uppercase | ios::showpos|ios::fixe d);

si nous voulons appliquer les trois drapeaux uppercase, showpos et fixed . La

fonction flags() remet à zéro tous les autres drapeaux. Si nous voulons uniquement

positionner certains drapeaux sans modifier ceux qui sont déjà positionnés, alors nous

utiliserons la fonction setf() :

cout.setf( ios::uppercase | ios::showpos|ios::fixed );

Pour annuler des drapeaux, nous avons la fonction unsetf() . On pourra par exemple

écrire :

cout.unsetf(ios::uppercase | ios::fixed );

13.10.3.3 Formatage de la sortie

Dans la classe de base ios sont définies trois grandeurs qui sont :

• la précision : le nombre de chiffres affichés pour les double et les float,

• le caractère de remplissage : c'est le caractère utilisé pour combler les vides du champ de

sortie,

• la taille du champ de sortie : le nombre de caractères du champ de sortie.

L'interrogation de ces valeurs se fait respectivement au travers des fonctions :

cout.precision();

cout.fill() ;

cout.width();

Page 244: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

238

et la modification :

cout.precision(int);

cout.fill(char) ;

cout.width(int);

A noter que le paramètre de remplissage et le paramètre définissant la taille du champ de

sortie reviennent à leur valeur par défaut, après que l'on ait effectué une sortie sur le flux. Les

valeurs par défaut sont pour la précision 6, pour la largeur de champ 0, et pour le caractère de

remplissage, l'espace.

13.10.4 Manipulateurs non paramétriques

Ces manipulateurs s'utilisent avec les opérateurs << et >> selon qu'on agisse sur un flux de

sortie ou d'entrée. Ils permettent une simplification de l'écriture et sont équivalents à l'appel

d'une fonction membre ou au positionnement d'un drapeau. Nous avons en particulier les

manipulateurs suivants :

Manipulateur Application Action Equivalent à

cin >>dec ou

cout << dec

Entrée / Sortie Active le bit de

conversion décimale

cin.setf(ios::dec)

ou

cout.setf(ios::dec)

cin >>hex ou

cout <<hex

Entrée / Sortie Active le bit de

conversion

hexadécimale

cin.setf(ios::dec)

ou

cout.setf(ios::dec)

cin >>oct ou

cout <<oct

Entrée / Sortie Active le bit de

conversion octale

cin.setf(ios::oct)

ou

cout.setf(ios::oct)

cout<<endl Sortie insère un saut de

ligne et vide le

tampon

cout.endl()

cout<<ends Sortie insère un caractère

de fin de chaîne

cout.ends()

Page 245: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

239

Exercice 13.6 : on considère le code suivant :

int n ,p; cin >> n >> p; // Interrogation des paramètres de configuration de cout cout << "precision :" << cout.precision()<<endl; cout << "width " << cout.width()<<endl; cout << "bourrage :" << cout.fill()<<endl; // première sortie cout << 2200. <<"\n"<<54<<endl; // modification des drapeaux cout.flags(ios::scientific|ios::showpos|ios::hex|io s::showbase) ; // deuxième sortie cout << 2200. <<"\n"<<54<<endl; // Modification des paramètres de sortie cout.fill('#'); cout.width(20); cout.precision(3); // Troisième sortie cout << 2200. <<"\n"<<54<<endl; // Quatrième sortie cout << 2200. <<"\n"<<54<<endl;

Qu'obtient on ?

13.10.5 Entrée et sortie non formatées

Il est également possible de lire et d'écrire sur les flux cin et cout au moyen de fonctions

qui ne prennent pas en charge le formatage des données.

13.10.5.1 La fonction get()

Pour le flux cin , on peut utiliser la fonction get dont le prototype est le suivant : int

get();. Cette fonction retourne le caractère suivant dans le flux d'entrée. Une utilisation

typique de cette fonction est présentée dans le code ci dessous :

main {

char c;

while ((c=cin.get())!=EOF)

cout << c ;

cout <<endl;

}

Page 246: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

240

Tant que la fin de ligne n'est pas détectée, la boucle continue de récupérer un par un tous les

caractères. Une fois que le caractère de fin de ligne est détecté, alors on génère sur le flux

sortant une fin de ligne et on vide le buffer avec le manipulateur endl .

13.10.5.2 La fonction getline()

Le prototype de la fonction getline est le suivant :

istream& get ( char* buffer, int n, char delim ='\n ');

Cette fonction place dans buffer les caractères reçus sur le flux entrant cin et s'arrête

lorsqu'on a lu n-1 caractères (le dernier caractère que l'on placera dans le buffer sera le

caractère nul '\0' ) ou lorsqu'on a reçu le caractère de délimitation. Dans l'exemple, c'est le

retour à la ligne.

Exercice 13.7 : écrire un programme basé sur getline qui récupère au plus 19 caractères, et

qui termine la saisie lorsqu'on détecte un point.

13.10.5.3 La fonction read()

Les différents prototypes de la fonction read sont :

istream& read (char* buffer, int n);

istream& read(unsigned char*buffer, int n);

Cette fonction transfère dans buffer les n premiers caractères du flux d'entrée. On n'ajoute pas

de caractère nul '\0' .

Exercice 13.8 : Soit le code suivant.

char buffer[]="??????????"

cin.read(buffer,4);

cout << buffer <<endl;

cin.read(buffer,2);

cout << buffer <<endl;

Si l'on tape ABCDEFG, qu'obtient-on ?

Page 247: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

241

13.10.6 Fonctions de sortie non formatées

On retrouve les fonctions symétriques à celles que l'on a pour l'entrée.

13.10.6.1 La fonction put

Son prototype est le suivant : ostream & put(char c);

cout.put('a');

place le caractère 'a' sur le flux de sortie cout .

Exercice 13.9 : soit la ligne de code suivante.

cout.put('s').put('r').put('e').put('v').put('n').p ut('e').put('\n');

Qu'obtient on ?

13.10.6.2 La fonction write

La fonction est symétrique de la fonction read . Son prototype est :

ostream& write(const char* buffer, int n); ou

ostream& write(const unsigned char* buffer, int n);

Cette fonction écrit sur le flux de sortie les n premiers octets de buffer .

13.10.7 Les fonctions de manipulations évoluées

Voici quelques fonctions complémentaires dont le fonctionnement n'est pas décrit en détail.

On se reportera à l'aide en ligne :

• ignore(); Cette fonction permet d'ignorer un certain nombre de caractères dans le flux

entrant.

• peek(); lit le caractère du flux d'entrée sans le retirer du flux d'entrée. Ainsi, plusieurs

appels consécutifs à peek() retourneront le même caractère, contrairement à la fonction

get() qui à chaque fois extrait et retourne le caractère.

• putback(); elle place un caractère dans le flux d'entrée.

Page 248: PROGRAMMATION EN C/C++ - easytp.cnam.freasytp.cnam.fr/.../index_fichiers/support/ele002_programmation_c.pdf · 2.2 Méthodologie pour l’écriture d’un programme ... INTRODUCTION

242

13.11 Bibliographie

[1] Cours de l’Ecole Polytechnique Fédérale de Lausanne

http://ltiwww.epfl.ch/Cxx/

[2] Programmation en C++ de John Hubbard MacGraw-Hill 1997

[3] Introduction à la programmation objet

http://www.commentcamarche.net/poo/poointro.php3

[4] Introduction au langage C++

http://www.commentcamarche.net/cpp/cppintro.php3

[5] Introduction à la conception objet et à C++ de Philippe Dosch - Université Nancy2

Institut Universitaire de Technologie

http://www.developpez.com/c/cours dans la rubrique "langage C++"