0% ont trouvé ce document utile (0 vote)
24 vues106 pages

Cours C

Le document présente un cours sur le langage de programmation C, abordant son historique, ses caractéristiques, et la structure d'un programme. Il détaille les étapes de création d'un programme, y compris l'édition, la compilation et l'édition de liens, ainsi que les types de données et les déclarations de variables. Enfin, il traite des entrées/sorties, en se concentrant sur l'affichage des données à l'écran à l'aide de la fonction printf.

Transféré par

Razak Tarpaga
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
24 vues106 pages

Cours C

Le document présente un cours sur le langage de programmation C, abordant son historique, ses caractéristiques, et la structure d'un programme. Il détaille les étapes de création d'un programme, y compris l'édition, la compilation et l'édition de liens, ainsi que les types de données et les déclarations de variables. Enfin, il traite des entrées/sorties, en se concentrant sur l'affichage des données à l'écran à l'aide de la fonction printf.

Transféré par

Razak Tarpaga
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Cours de C L1ELN-L1GE-L1EEA

Chapitre I : Généralités
I. Introduction
1. Historique
• Le langage C est né en 1972 dans les laboratoires de la Bell Telephone (AT&T) des travaux de Brian
Kernighan et Dennis Ritchie.
• Il a été conçu à l'origine pour l'écriture du système d'exploitation UNIX (90-95% du noyau est écrit en
C) et s'est vite imposé comme le langage de programmation sous UNIX.
• Très inspiré des langages BCPL (Martin Richard) et B (Ken Thompson), il se présente comme un
"super-assembleur" ou "assembleur portable". En fait c'est un compromis entre un langage de haut
niveau (Pascal, Ada ...) et un langage de bas niveau (assembleur).
Il a été normalisé en 1989 par le comité X3J11 de l'American National Standards Institute (ANSI).
2. Caractéristiques
• Langage procédural, cela signifie que les instructions sont exécutées linéairement.
• Langage structuré, conçu pour traiter les tâches d'un programme en les mettant dans des blocs.
• Langage compilé, par opposition aux langages interprétés.
• Il produit des programmes efficaces : il possède les mêmes possibilités de contrôle de la machine
que l'assembleur et il génère un code compact et rapide.
• Déclaratif : normalement, tout objet C doit être déclaré avant d'être utilisé. S'il ne l'est pas, il est
considéré comme étant du type entier.
• Format libre : la mise en page des divers composants d'un programme est totalement libre.
Cette possibilité doit être exploitée pour rendre les programmes lisibles.
• Modulaire : une application pourra être découpée en modules qui pourront être compilés
séparément. Un ensemble de programmes déjà opérationnels pourra être réuni dans une librairie.
Cette aptitude permet au langage C de se développer de lui même.
• Souple et permissivité : peu de vérifications et d'interdits, hormis la syntaxe. Il est important de
remarquer que la tentation est grande d'utiliser cette caractéristique pour écrire le plus souvent des
atrocités.
• Transportable : les entrées/sorties sont réunies dans une librairie externe au langage.
3. Structure d’un programme en C
Un programme C est composé de :
• Directives du préprocesseur : elles permettent d'effectuer des manipulations sur le texte du
programme source avant la compilation :
o inclusion de fichiers,
o substitutions,
o macros,
o compilation conditionnelle.
Une directive du préprocesseur est une ligne de programme source commençant par le caractère
dièse (#).
Le préprocesseur est appelé automatiquement, en tout premier, lors de la compilation.
• Déclarations/définitions :
o Déclaration : la déclaration d'un objet C donne simplement ses caractéristiques au
compilateur et ne génère aucun code.
o Définition : la définition d'un objet C déclare cet objet et crée effectivement cet objet.
• Fonctions : Ce sont des sous-programmes dont les instructions vont définir un traitement sur
des variables. Tout programme doit comporter la fonction principale main qui constitue le point
d’entrée du programme lors de l’exécution.
• Des commentaires : éliminés par le préprocesseur, ce sont des textes compris entre /* et */.
On ne doit pas les imbriquer et ils peuvent apparaître en tout point d'un programme (sauf dans
une constante de type chaîne de caractères ou caractère).
Pour ignorer une partie de programme il est préférable d'utiliser une directive du préprocesseur
(#if 0 ... #endif)

1
Cours de C L1ELN-L1GE-L1EEA

4. Ecriture de programme en C
Il y a trois grandes étapes dans la création d’un programme :
a) L’édition du programme
L’édition ou saisie du programme consiste à créer, à partir d’un clavier, tout ou partie d’un programme
qu’on nomme programme ou code source. En général, ce texte sera conservé dans un fichier appelé
fichier source et en général a l’extension c.
Afin d'écrire des programmes C lisibles, il est important de respecter un certain nombre de règles de
présentation
• ne jamais placer plusieurs instructions sur une même ligne
• utiliser des identificateurs significatifs
• grâce à l'indentation des lignes, on fera ressortir la structure syntaxique du programme.
Les valeurs de décalage les plus utilisées sont de 2, 4 ou 8 espaces.
• on laissera une ligne blanche entre la dernière ligne des déclarations et la première ligne des
instructions.
• une accolade fermante est seule sur une ligne (à l'exception de l'accolade fermante du bloc de la
structure do ... while) et fait référence, par sa position horizontale, au début du bloc qu'elle
ferme.
• aérer les lignes de programme en entourant par exemple les opérateurs avec des espaces.
• il est nécessaire de commenter les listings. Eviter les commentaires triviaux.
b) La compilation
Elle consiste à traduire le code source (c’est à dire le contenu du fichier source) en langage machine, en
faisant appel un programme appelé compilateur. En langage C, compte tenu de l’existence du
préprocesseur, cette comporte deux étapes :
Traitement du préprocesseur : ce dernier exécute simplement les directives du préprocesseur c’est à
dire il inclut dans le fichier source les éléments référencés par ces directives.
Compilation proprement dite, c’est à dire la traduction en langage machine du texte en langage C fourni
par le préprocesseur. Le résultat est le code objet, sauvegardé un fichier objet..
c) L’édition de liens
Le code objet crée par le compilateur n’est pas directement exécutable. Le compilateur fait donc appel à
un éditeur de liens (en anglais linker ou binder) qui permet d'intégrer dans le fichier final tous les
éléments annexes (fonctions ou librairies) auquel le programme fait référence mais qui ne sont pas
stockés dans le fichier source.
Puis il crée un fichier exécutable qui contient tout ce dont il a besoin pour fonctionner de façon
autonome, le fichier ainsi créé possède l'extension .exe.

2
Cours de C L1ELN-L1GE-L1EEA

Exemple :
#include <stdio.h> /*demande au préprocesseur d’inclure le fichier stdio.h */
#define DEBUT -10 /*demande au préprocesseur de remplacer par la suite DEBUT par -10*/
#define FIN 10
#define MSG "Programme de démonstration\n"

int carre(int x); /* déclaration des fonctions carre et cube*/


int cube(int x);

int main(void) /* programme principal */


{ /* début du bloc de la fonction main */
int i; /* définition des variables locales */

printf(MSG);
for ( i = DEBUT; i <= FIN ; i++ )
{
printf("%d carré: %d cube: %d\n", i , carre(i) , cube(i) );

} /* fin du bloc for */


return 0;
} /* fin du bloc de la fonction main */

int cube(int x) { /* définition de la fonction cube */


return x * carre(x);
}

int carre(int x) { /* définition de la fonction carre */


return x * x;
}

3
Cours de C L1ELN-L1GE-L1EEA

II. Déclarations
1. Types de base
Catégorie Type Signification Taille (en octets) Etendue
vide void Absence de type
booléen bool booléen 1 false, true
char Caractère 1 -128 à 127
Caractères
unsigned char Caractère non signé 1 0 à 255
short int ou short Entier court 2 -32768 à 32767
unsigned short
Entier court non signé 2 0 à 65535
int
2 (processeur 16 bits) -32768 à 32767
int Entier
Entiers 4 (processeur 32 bits) -2147483648 à 2147483647
2 (processeur 16 bits) 0 à 65535
unsigned int Entier non signé
4 (processeur 32 bits) 0 à 4294967295
long int ou long Entier long 4 -2147483648 à 2147483 47
unsigned long int Entier long non signé 2 0 à 4 294 967 295 Précision
float flottant (réel) 4 3.4*10-38 à 3.4*1038 6chiffres (10-6)
Réels double flottant double 8 1.7*10-308 à 1.7*10308 15chiffres (10-15)
long double flottant double long 10 3.4*10-4932 à 3.4*104932 17chiffres (10-17)

Les constantes caractères se notent entre quotes (′).


On peut noter un caractère non accessible au clavier en donnant son code en octal, précédé du caractère
'\' ou son code en hexadécimal, précédé de '\x'.
Exemple :
Le caractère 'A' peut aussi être noté '\101'(code en octal) ou '\x41' (code en
hexadécimal).
Enfin, il existe des séquences d'échappement particulières qui permettent de noter certains caractères
spéciaux dits caractères de contrôle. Les principales séquences d'échappement sont les suivantes :
'\a'(Bip sonore); '\b'(Backspace); '\f'(Début de page suivante) ;
'\r'(Retour à la ligne (sans saut de ligne));'\n'(Passage à la ligne);
'\t'(Tabulation) ; '\v'(Tabulation verticale) ; '\\'(Le caractère \) ;
'\"'(Le caractère ") ; '\''(Le caractère ') ; '\0' (Le caractère nul)…

Un caractère peut être considéré comme un entier, la valeur du code qui le représente.
Exemple : 'A' → 65.

Les entiers sont signés par défaut, cela signifie qu'ils comportent un signe.
Les constantes entières se notent :
En base décimale (10) avec les chiffres 0 à 9 et les signes + (facultatif) et -.
Exemple : 1251, -960123, +784 ;
En base hexadécimale (16) avec les0 à 9 et les lettres A à F ou a à f (A=10, B=11, …,
E=14,F=15). Les entiers notés en hexadécimal doivent toujours être précédents de 0x. On
ne peut pas utiliser le signe – en notation hexadécimale. Exemple : 0x1AE→430.
En base octale (8) avec les chiffres 0 à 7. Les entiers en notation octale doivent être
précédés d’un 0 ; le signe – ne peut pas être utilisé. Exemple : 01, 0154.
Les constantes entier long se notent en faisant suivre la valeur de la lettre L.
Exemple : 100L.
Les constantes réelles se notent :
[signe] chiffres [.[chiffres]][e|E [signe] exposant][f|L]
En notation décimale, le point décimal est obligatoire.

4
Cours de C L1ELN-L1GE-L1EEA

f permet de préciser si le nombre est de type float et L permet de préciser si le nombre est de type long
double. En cas d’absence de f et L, le nombre est de type double.

Exemple :
-125.56f (constante float), 12e-12 (constante double), 2 (entier décimal), 2. (réel double),
2.3E5L (constante long double).
2. Déclaration de variables
<type> <var1>[=<val1>],<var2>[=<val2>],…,<varn>[=valn>];
L’identificateur d’une variable doit suivre quelques règles de base :
• C'est une suite de lettres, de chiffres ou du caractère _ (souligné).
• Le premier caractère est obligatoirement une lettre (ou _ mais il vaut mieux le réserver au
compilateur).
• Le C distingue les minuscules des majuscules
• Le blanc est interdit dans un identificateur (utiliser _). Les lettres accentuées sont également
interdites.
• La longueur de l'identificateur dépend de l'implémentation. La norme ANSI prévoit qu'au moins
les 31 premiers caractères soient significatifs pour le compilateur.
• Un identificateur ne peut pas être un mot réservé du langage :
3. Déclaration de constantes
#define <symbole> <valeur>
const <type> <nom_const> = <valeur> ;
4. Alias de type
typedef <alias> <type> ;

5
Cours de C L1ELN-L1GE-L1EEA

Chapitre II: Entrées/sorties conversationnelles


Tout programme a pour but d'effectuer des opérations sur des données et de délivrer éventuellement des
résultats. La structure fondamentale d’un programme est donc la suivante :
ENTRÉE DES DONNÉES
(clavier, souris, fichier, autres périphériques)

TRAITEMENT DES DONNÉES

SORTIE DES DONNÉES


(écran, imprimante, fichier, autres périphériques)

Nous nous intéressons ici aux échanges de données conversationnels entre l’utilisateur et le programme
c’est à dire à :
- La lecture à partir du clavier ;
- L’écriture (affichage) sur l’écran.

I. L’affichage
L’affichage des données à l’écran est réalisé principalement par la fonction printf (print formatted
en anglais) définie dans le fichier d’en-tête stdio.h. La fonction printf est une fonction
d'impression formatée, ce qui signifie que les données sont converties selon le format particulier choisi.

1. Affichage d’un texte

printf(″ <texte à afficher>″) ;


La suite de caractères (chaîne de caractères) à afficher est délimitée par des doubles quotes (″). Les
caractères de contrôle (\n, \t,…) peuvent figurer dans un texte à afficher.
Exemple :
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{ printf(″Afficher tel quel. ″);
printf(″Retour a la ligne.\nLigne suivante ″);/* l’affichage du caractère \n va provoquer un saut
de ligne*/
return 0 ;
}
A l’exécution, on aura sur l’écran :
Afficher tel quel. Retour a la ligne.
Ligne suivante

Remarque :
- Pour afficher un texte de plusieurs lignes, il faut terminer chaque ligne par des doubles quotes et
commencer chaque nouvelle ligne par des doubles quotes. Par exemple,
printf("Pour ecrire une phrase, il faut terminer chaque ligne"
" par des doubles quotes, et commencer chaque nouvelle ligne par "
"des doubles quotes ");
- Lorsque le texte à afficher contienne un double quote, il doit être représenté par \".
- Sur certains compilateurs, l'appel à la librairie stdio.h par la directive au préprocesseur

6
Cours de C L1ELN-L1GE-L1EEA

#include <stdio.h> n'est pas nécessaire pour utiliser printf.

2. Affichage des entiers


a) Affichage d’un entier
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{int n = 10 ;
printf(″%d″,n);/* %d indique que la valeur à afficher est un entier*/
return 0 ;
}
A l’exécution, on aura à l’écran :
10
Le premier argument est une chaîne de caractères contenant le caractère %. Ce caractère indique que le
caractère suivant n’est pas un texte à afficher tel quel, mais un code de format qui précise comment
afficher la valeur de l’argument n. Ici le caractère qui suit % est d; il précise qu’il faut considérer la
valeur de l’argument n comme un entier et l’afficher en décimal (pour l’afficher en octal ou en
hexadécimal on remplacera d par o ou x respectivement). La suite %d est appelé formateur.

b) Affichage de plusieurs entiers


#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{int n = 10, p = 25;
printf(″%d %d ″,n, p);
return 0 ;
}
A l’exécution, on aura à l’écran :
10 25
Le premier argument est une chaîne de caractères contenant deux formateurs %d. Le premier formateur
%d est destiné au deuxième argument n et le deuxième formateur %d au troisième argument p.

c) Affichage de la valeur d’une expression


#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{ int n = 10, p = 25;
printf(″ %d″, n+p);
printf(″ %d %d %d ″, n, p, n+p);
}
A l’exécution, on aura à l’écran :
35 10 25 35

c) Affichage avec libellé


#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{int n = 10, p = 25;
printf(″La somme de %d et de %d est %d.″, n, p, n+p);
return 0 ;
}
A l’exécution, on aura à l’écran : La somme de 10 et 25 est 35.
Le premier argument est une chaîne de caractères contenant du texte à afficher tel quel et autant de
formateurs que de variables ou expressions à afficher. Les formateurs sont placés dans le texte là où les
valeurs des variables doivent être affichées.
Remarque :

7
Cours de C L1ELN-L1GE-L1EEA

Pour afficher le caractère % tel quel dans un texte, il faut le représenter par %%

d) Affichage sur un gabarit donné


Par défaut, les entiers sont affichés avec le nombre de caractères nécessaires (sans espace avant ou
après).
Il est possible de préciser pour un entier un gabarit d’affichage, c’est à dire un nombre minimal de
caractères à utiliser pour son affichage. Si l’entier peut s’écrire avec moins de caractères, printf le
fera précéder d’un nombre suffisant d’espace ; par contre, si l’entier ne peut s’afficher correctement avec
le gabarit donné, printf utilisera le nombre de caractères nécessaires. On précise le gabarit en plaçant
un nombre entre le symbole % et la lettre d.
Exemple :
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{int n;
.......
printf(″%3d″,n);/* affiche la valeur de n sur 3 caractères au minimum*/
return 0 ;
}
A l’exécution, on aura à l’écran,
si n = 20 : 20
si n = 3 : 3
si n = 2358 : 2358
si n = -52 : -52
avec la convention que représente l’espace.

3. Affichage des flottants


Un réel flottant peut être affiché soit sous sa notation décimale, soit sa notation exponentielle.

a) Affichage des réels sous forme décimale


Les réels s’affichent, sous leur notation décimale, avec six chiffres après le point décimal.
Exemple :
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{
float x = 12.21, y, z;
printf(″%f″,x); /* %f indique que la valeur à afficher est un réel qui sera
affiché sous forme décimale*/
y=6;
z = x/y ;
printf(″ x = %f″,x);
printf(″ y = %f″,y);
printf(″ x + y = %f″,x+y);
printf(″ x/y = %f″,z);
return 0 ;
}
A l’exécution, on aura à l’écran :
12.210000 x = 12.210000 x + y = 12.270000 x/y = 2.035000

b) Affichage des réels sous forme exponentielle


Par les réels s’affichent, sous leur notation exponentielle, avec :
- une mantisse entre 1 et 10 en valeur absolue;
- six chiffres après le point décimal ;
- un exposant sur trois chiffres.
Exemple :

8
Cours de C L1ELN-L1GE-L1EEA

#include <stdio.h> /* pour pouvoir utiliser printf */


int main(void)
{ float x = 12.21, y, z;

printf(″%e″,x); /* %e indique que la valeur à afficher est un réel qui sera


affiché sous forme exponentielle*/
return 0 ;
}
A l’exécution, on aura à l’écran :
1.221000e+001

c) Affichage des réels avec gabarit et précision


La précision permet de spécifier le nombre de chiffres après le point décimal.
Exemple :
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{float x;
.......
printf(″%10f″,x);/* affiche la valeur de x sur 10 caractères au minimum
avec six chiffres après le point*/
printf(″ %10.3f″,x);/* affiche la valeur de x sur 10 caractères au minimum
3 chiffres après le point */
printf(″ %12.4e″,x);/* affiche la valeur de x sur 12 caractères au minimum
avec 4 chiffres après le point*/
return 0 ;
}
A l’exécution, on aura à l’écran,
si x = 1.2345 : 1.234500 1.235 1.2345e+000
si x = 1.2345E3 : 1234.500000 1234.500 1.2345e+003
si x = -123.456789e8 : -12345678900.000000 –12345678900.000 –1.2346e+010
avec la convention que représente l’espace.

Remarque :
Le caractère * figurant à la place d’un gabarit ou d’une précision indique que la valeur effective est
fournie dans la liste des arguments de printf. Par exemple,
printf(″ %10.*f″,n, x);
la valeur n sera utilisé comme précision de x.

3. Affichage des caractères


#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{char car = ‘l’;
printf(″%c″,car);/* %c indique que la valeur à afficher est un caractère */
return 0 ;
}
A l’exécution, on aura à l’écran,
L

4. Affichage avec changement de ligne


La fonction printf affiche et attend sur la même ligne. Pour forcer le passage à la ligne suivante, on
utilise le caractère de contrôle saut de ligne ‘\n’ dans la chaîne à afficher.

9
Cours de C L1ELN-L1GE-L1EEA

Exemple :
#define pi 3.14
#include <stdio.h> /* pour pouvoir utiliser printf */
int main(void)
{float diam =16.01,ray, haut = 25.5,vol;
ray = diam/2;
vol = pi*ray*ray*haut;
printf(″Rayon de la base du cylindre :%.3f\n″,ray);
printf(″Hauteur du cylindre :%.3f\n″,haut);
printf(″Volume du cylindre :%.3f\n″,vol);
printf(″\n″) ;
printf(″Fin de l’exemple.″) ;
return 0 ;
}
A l’exécution, on aura à l’écran :
Rayon de la base du cylindre :8.005
Hauteur du cylindre :25.500
Volume du cylindre :5130.888

Fin de l’exemple.

5. Syntaxe générale de printf


printf(<chaîne de format>,<p1>, <p2>, ..., <pn>);
<p1>, <p2>, ..., <pn>: sont des paramètres (valeurs) à afficher ;
<chaîne de format> : chaîne de spécification contenant des caractères à imprimer au fur et à mesure,
des caractères de contrôle (\n, ..) et des formateurs. La chaîne <chaîne de format> doit contenir
autant de formateurs que de paramètres <pi>.
Les formateurs sont de la forme :%-[Link]α
avec :
- (facultatif) : précise un cadrage à gauche (par défaut le cadrage est à droite) c’est à dire les
éventuels espaces supplémentaires sont placés à droite de l’information affichée.
m (facultatif) :définit le gabarit c’est à dire le nombre total minimum de colonnes réservées à
l’affichage de l’information.
p( facultatif) : nombre de chiffres après le point décimal.
α: précise le type de l’expression à afficher. α peut être :
d nombre entier signé en décimal (int)
u ou i nombre entier non signé en décimal(unsigned int)
o nombre entier non signé en octal (int )
x nombre entier non signé en hexadécimal(int)
c caractère (char)
e nombre flottant en notation exponentielle (double. Les float sont convertis en double)
f nombre flottant en notation décimale (double. Les float sont convertis en double)
s chaîne de caractères (char*)
p pointeur
t (facultatif) : précise la taille du type. On peut avoir :
h devant d ou x ou o ou u pour les entiers courts ( short int ou unsigned short int)
l devant d ou x ou o ou u pour les entiers longs ( long int ou unsigned long)
l devant e ou f pour les nombres flottants longs (long double)
Remarque :
La fonction printf fournit une valeur de retour qui est le nombre de caractères qu’elle a
effectivement affichés (-1 en cas d’erreur).

10
Cours de C L1ELN-L1GE-L1EEA

6. Autres fonctions de sortie


a) La macro putchar
On peut utiliser la macro putchar définie dans le fichier en-tête stdio.h pour afficher un caractère.
#include <stdio.h> /* pour pouvoir utiliser putchar */
int main(void)
{
char car = ‘l’;
putchar(car);
}
A l’exécution, on aura à l’écran,
L

b) La fonction puts
La fonction puts permet d'afficher un texte, avec passage à la ligne suivante.
#include <stdio.h> /* pour pouvoir utiliser puts*/
int main(void)
{
puts("bonjour");/* équivaut à printf("bonjour\n");*/
return 0 ;
}

Remarque :
Il vaut mieux utiliser puts et putchar si cela est possible; ces fonctions, non formatées, sont
d'exécution plus rapide, et nécessitent moins de place en mémoire lors de leur chargement.

II. La lecture de données


La fonction scanf de la librairie stdio.h permet de saisir des données au clavier et de les stocker
aux adresses spécifiées par les arguments de la fonction.
scanf(<chaîne de format>,argument_1,...,argument)
argument_1,...,argument_n doivent être des adresses d’emplacements mémoire.
La chaîne de format indique le format dans lequel les données lues sont converties (représentées) c’est à
dire précise leurs représentations en interne. Elle ne contient que des formateurs. Comme pour printf,
les formateurs sont spécifiés par un caractère précédé du signe %. Les formats valides pour la fonction
scanf diffèrent légèrement de ceux de la fonction printf :
Format Type d'objet pointé Représentation de la donnée saisie
%d Int décimale signée
%hd short int décimale signée
%ld long int décimale signée
%u unsigned int décimale non signée
%hu unsigned short int décimale non signée
%lu unsigned long int décimale non signée
%o Int octale
%ho short int octale
%lo long int octale

11
Cours de C L1ELN-L1GE-L1EEA

%x Int hexadécimale
%hx short int hexadécimale
%lx long int hexadécimale
%f Float flottante notation décimale
%lf double flottante notation décimale
%Lf long double flottante notation décimale
%e Float flottante notation exponentielle
%le double flottante notation exponentielle
%Le long double flottante notation exponentielle
%c Char caractère
%s char* chaîne de caractères
Les données à entrer au clavier doivent être séparées par des blancs ou des <RETURN> sauf s'il s'agit de
caractères.
Remarque :
La fonction scanf fournit une valeur de retour qui est le nombre de valeurs convenablement lues.

1. Lecture de données de type numérique


Exemple
#include <stdio.h>
int main(void)
{ int i;
float x;
unsigned int nb;
long l; double d;

printf(" entrer un entier entre -32768 et 32767 \n");


scanf("%d", &i); /*&i est l’adresse de la variable i*/
printf(" Saisir un reel x , \n");
scanf("%f", &x);
printf("i = %d \n x = %f \n",i, x);
puts("nombre entre 0 et 65535 ? ");
scanf("%u", &nb);
printf("moitie de %u : %u \n", nb, nb/2);
puts("nombre entre -2 147 483 648 et +2 147 483 647 ?");
scanf("%ld", &l);
printf("moitie de %ld : %ld \n", l, l/2);
puts("nombre avec decimales ? ");
scanf("%lf", &d);
printf("double de %20.14f : %20.14f\n", d, d*2);
}
Les formateurs correspondant à un nombre amènent scanf à se positionner sur le premier caractère
différent d’un séparateur (espaces ou return), à prendre en compte tous caractères suivants jusqu’à la

12
Cours de C L1ELN-L1GE-L1EEA

rencontre d’un séparateur, à convertir cette suite de caractères en un nombre, enfin à convertir ce
nombre en une valeur binaire qui est mise dans la variable correspondante.

2. Lecture des caractères


Exemple
#include <stdio.h>
int main(void)
{ char car;
int n ;
scanf("%d%c", &n,&car);
return 0 ;
}
A l’exécution, si l’on saisit 12 et a séparés un espace, on a : n = 12 mais car = ′ ′. En effet le formateur
correspondant aux caractères entraîne la prise en compte le premier caractère suivant même si c’est un
séparateur. Pour ignorer les séparateurs, il faut placer de l’espace entre deux formateurs ; par exemple
scanf("%d %c", &n,&car);

3. Lecture avec gabarit


On peut fixer le nombre maximal de caractères de la donnée à lire.
Exemple :
scanf(″%3d%10d″,&n,&c) ;

4. Problème de synchronisation
Considérons l’exemple suivant :
Exemple :
#include <stdio.h>
int main(void)
{int n, p ;
printf("Entrer une valeur pour n : ") ;
scanf("%d", &n);
printf("Vous avez saisi %d\ n ", n) ;
printf("Entrer une valeur pour p: ") ;
scanf("%d", &p);
printf("Vous avez saisi%d ", p) ;
return 0 ;
}
Considérons ce cas d’exécution du programme où l’utilisateur a tapé, à l’invite du programme, deux
valeurs séparées par un espace puis la touche « return » symbolisé ci-dessous par ↵ :
Entrer une valeur pour n : 12 25 ↵
Vous avez saisi 12
Entrer une valeur pour p : Vous avez saisi 25
On voit qu’à la seconde invite du programme pour la saisie de la valeur de p, le programme n’a pas
attendu que l’utilisateur tape une valeur ; il a placé dans p la seconde valeur entrée lors de la première
invite. Cela s’explique par le fait que les informations tapées au clavier ne sont pas traitées
instantanément par scanf mais mémorisées dans un tampon. Cette mémorisation se poursuivra jusqu’à
ce que l’utilisateur ait tapé la touche de validation « return » ; cela déclenche alors le traitement des
informations mémorisées. Le tampon n’est pas vidé à chaque nouvel appel de scanf, autrement
lorsque le traitement est terminé, s’il existe une partie du tampon non encore utilisée, celle-ci est
conservée pour une prochaine lecture. C’est ce qui s’est passé dans notre exemple.
Pour synchroniser la lecture, il faut vider le tampon par l’instruction fflush(stdin) ;

13
Cours de C L1ELN-L1GE-L1EEA

printf("Entrer une valeur pour p: ") ;


fflush(stdin) ;
scanf("%d", &p);
printf("Vous avez saisi%d ", p) ;

Remarque :
La frappe de la touche de validation provoque aussi la mémorisation dans le tampon du caractère fin de
ligne (\n) qui sera ignoré dans le cas de lecture de valeurs numériques mais lu dans le cas de lecture de
caractères.

5. Autres fonctions d’entrée


a) La macro getchar
La macro getchar de la bibliothèque stdio.h permet aussi la saisie d'un caractère.
Exemple :
#include <stdio.h> /* pour pouvoir utiliser getchar */
int main(void)
{char car;
car = getchar();/* équivaut à scanf(″%c″,&car);*/
putchar(car) ;
return 0 ;
}
A l’exécution, on aura à l’écran, si l’on saisit l
l

b) La fonction getch
La fonction getch de la bibliothèque conio.h permet la saisie clavier d’un caractère
alphanumérique, sans écho écran (c’est à dire le caractère tapé ne s’affiche pas à l’écran). La saisie
s'arrête dès que le caractère a été frappé. L’appel de getch suspend l’exécution jusqu’à ce qu’un
caractère soit tapé.
#include <stdio.h> /* pour pouvoir utiliser printf */
#include <conio.h> /* pour pouvoir utiliser getch */
int main(void)
{ char car;
printf("Pour continuer, frapper une touche ");
getch();
printf("Entrer un caractere (Attention pas de return) ");
car = getch();
printf("\nVoici ce caractere: %c",car);
return 0 ;
}

14
Cours de C L1ELN-L1GE-L1EEA

Chapitre III : Opérateurs du langage C


I. Principaux opérateurs
C offre un jeu très étendu d'opérateurs, ce qui permet l'écriture d'une grande variété d'expressions.

1.1. Opérateurs arithmétiques


Ce sont des opérateurs qui s’appliquent aux types numériques.
Opérateur Signification
+ Addition
- Soustraction
* Multiplication
/ Divisions entière et complète
% Reste de la division entière
- Changement de signe

Lorsque les deux opérandes sont de types différents, le compilateur prévoit une conversion implicite
suivant l'ordre : {char et short -> int ->long -> float -> double-> long
double} et {signed -> unsigned}.

1.2. Opérateurs relationnels


Les opérateurs relationnels s’appliquent sur deux expressions comparables.

Opérateur Signification
== teste si égalité
!= teste si différent
< teste si inférieur
<= teste si inférieur ou égal
> teste si supérieur
>= teste si supérieur ou égal

Le résultat de la comparaison entre deux expressions vaut :


• 0 si le résultat de la comparaison est faux
• 1 si le résultat de la comparaison est vrai.

1.3. Opérateurs booléens


Les opérateurs booléens agissent sur des booléens et retournent des booléens.

Opérateur Signification
&& ET booléen
|| OU booléen
! NON booléen

Il n'existe pas en C de type booléen. La convention suivante est utilisée :


• Une expression est vraie si elle est non nulle
• Une expression est fausse si elle est égale à zéro.

1.4. Opérateur d’affectation.


En C, l'affectation (=) est un opérateur binaire qui évalue l’opérande de droite, place sa valeur dans
l’opérande de gauche et retourne la valeur affectée. L’opérande de gauche doit être une lvalue c'est-à-

15
Cours de C L1ELN-L1GE-L1EEA

dire faire référence à un emplacement mémoire.


Lorsque les opérandes numériques d’une affectation ne sont pas de même type, le compilateur
effectuera une conversion implicite: la valeur de l'expression de droite est convertie dans le type du
terme de gauche.

1.5. Les opérateurs d'affectation composée


Un opérateur d’affectation composée permet de réaliser en une seule étape une opération arithmétique
et l’affectation du résultat de cette opération dans son premier opérande.
Opérateur Signification
+= Addition suivie d’affectation
-= Soustraction suivie d’affectation
*= Multiplication suivie d’affectation
/= Division suivie d’affectation
%= Modulo suivi d’affectation
~= NON bit à bit suivi d’affection
&= ET bit à bit suivi d’affection bi
|= OU bit à bit suivi d’affection
^= OU exclusif suivi d’affection
<<= Décalage à gauche suivi d’affection
>>= Décalage à droite suivi d’affection

Pour tout opérateur op, l'expression <lvalue> op= <expression>


est équivalente à <lvalue> =<lvalue> op <expression>
Exemple : a+=5 est équivalent à a = a+5

1.6. Opérateurs d'incrémentation et de décrémentation


Les opérateurs d'incrémentation ++ et de décrémentation - - sont des opérateurs unaires qui incrémentent
et décrémentent respectivement de l’unité la valeur de son seul opérande qui doit être une lvalue. Les
opérateurs d'incrémentation et de décrémentation s'utilisent aussi bien en suffixe (i++ ou i--) qu'en
préfixe (++i ou --i).
Exemple :
int a = 3, b, c;
b = ++a; /* a et b valent 4. b prend la nouvelle valeur de a */
c = b++; /* c vaut 4 et b vaut 5. c prend l’ancienne valeur de b */
c-- ; /* la valeur de cette expression est 4,ancienne valeur de c*/
printf("\n %d \n",c); /* imprime pour c la valeur 3 */

1.7. Opérateur séquentiel


L’opérateur séquentiel (ou virgule) permet d’évaluer de gauche à droite une série d’expressions séparées
par des virgules et de renvoyer la valeur de la dernière expression évaluée. Sa syntaxe est :
<expression_1>, <expression_2>, ... , <expression_n>
Exemple :
int a, b;
b = ((a = 3), (a + 2));
printf("\n b = %d \n",b);/* imprime b = 5.*/

1.8. Opérateur conditionnel


L’opérateur conditionnel est un opérateur ternaire (trois opérandes) qui retourne la valeur de son
deuxième opérande si la valeur de son premier opérande est non nul (cela correspond à vrai) et la valeur
de son troisième opérande si la valeur de son premier opérande est nul (cela correspond à faux). Sa
syntaxe est :<expression1> ? <expression2 >: <expression3>

16
Cours de C L1ELN-L1GE-L1EEA

Exemple :
float a = 11.62, b = -27,min, max, maxabs;
min = (a<b)?a :b ;/* minimum de a et b, ici –27*/
max = (a>b)?a:b;/* maximum de a et b. ici 11.62*/
maxabs=((a >=0?a:-a)>(b >=0 b:-b))?(a > 0 ? a : -a):(b >= 0 ? b : -b);
printf("\n %f \n",maxabs); /*imprime 27, le maximum des valeurs de a et b*/

1.9. Opérateur de transtypage


Il est possible à l’utilisateur de modifier explicitement le type d'une expression grâce l'opérateur de
transtypage. Sa syntaxe est :(<type>) <expression>
Exemple:
int i = 3, j = 2, x;
x = 8.324;/*conversion implicite de 8.324 en int avant affectation à x; x contiendra 8 après
affectation*/
x = (int)8.324; /* conversion explicite de 8.324 en int avant affectation à x; x contiendra 8
après affectation*/
printf("%f \n",(float)i/j); /* conversion explicite de i en float puis conversion implicite de j en
float puis division du réel i par le réel j enfin affichage du résultat (1.5)*/

1.10. Opérateur taille


L'opérateur sizeof est utilisé pour déterminer la taille (en octets) d'une expression ou d'un type. Ses
syntaxes possibles sont :
sizeof <var> fournit la taille de la variable <var>
sizeof <const> fournit la taille de la constante <const>
sizeof (<expr>)fournit la taille de l’expression <expr>
sizeof (<type>)fournit la taille d’une expression de type <type>
Exemple :
int i ;
printf("Taille d'une variable entière %d\n", sizeof i);
printf("Taille d'un type entier %d", sizeof(int));

1.11. Opérateur d’adresse


L’opérateur d’adresse & appliqué à une variable permet d’obtenir l’adresse de celle-ci. Sa syntaxe est :
&<nom_variable>
Exemple:
int i ;
printf(“i est réservé à l’adresse:%d”, &i ) ;

1.12. Opérateurs de manipulation de bits


Les opérateurs de manipulation de bits opèrent bit à bit et s'appliquent à des opérandes de type entier (ou
caractère) et de préférence unsigned (sinon ils risquent de modifier le bit de signe).
Ils procurent des possibilités de manipulation de bas-niveau de valeurs, traditionnellement réservées à la
programmation en langage assembleur.
Ces opérateurs traitent leurs opérandes sous leurs représentations binaires mais retournent des valeurs
numériques standard dans leur format d’origine (int, unsigned int, long,..).
• ~ : négation bit à bit ou complément à 1
Cet opérateur unaire inverse un à un tous les bits du motif binaire de son unique opérande.
Exemple : complément à 1 de 0x5F (95) de type int (16bits)
0x5F 000000000 1 0 1 1 1 1 1
~0x5F 111111111 0 1 0 0 0 0 0 -> 160 ->0xA0
d’où ~0x5F = 0xA0.
• & : ET bit à bit entre les valeurs de 2 expressions.
Chaque bit du motif binaire du résultat de l’expression a & b vaut 1 si les bits de même rang des
motifs binaires de a et b valent 1, 0 sinon.

17
Cours de C L1ELN-L1GE-L1EEA

• | : OU (inclusif) bit à bit entre les valeurs de 2 expressions.


Chaque bit du motif binaire du résultat de l’expression a | b vaut 1 si les bits de même rang des
motifs binaires de a ou b valent 1, 0 sinon.
• ^ : OU exclusif bit à bit entre les valeurs de 2 expressions.
Chaque bit du motif binaire du résultat de l’expression a ^ b vaut 1 si les bits de même rang de a
et b sont différents, 0 sinon.
• << : décalage à gauche.
L’opérateur << est un opérateur binaire qui permet de réaliser un décalage à gauche, sur le motif
binaire de son premier opérande, d’un nombre de bits égal à la valeur son second opérande. Par
exemple, a << 3 décale de 3 rangs à gauche la valeur contenue dans a. Les bits sortants à
gauche sont perdus et des 0 sont introduits à droite.
Cet opérateur permet en fait de multiplier le premier opérande par 2 à la puissance le second
opérande.
Si la variable est signée, le bit de signe est conservé.
• >> : décalage à droite.
L’opérateur >> est un opérateur binaire qui permet de réaliser un décalage à droite, sur le motif
binaire de son premier opérande, d’un nombre de bits égal à la valeur son second opérande. Par
exemple, a >> 3 décale de 3 rangs à droite la valeur contenue dans a. Les bits sortants à droite
sont perdus et des 0 sont introduits à gauche.
Cet opérateur permet en fait de diviser le premier opérande par 2 à la puissance le second
opérande.
Si la variable est signée, le bit de signe est conservé et propagé.
Exemple :
Considérons par exemple les entiers a=77 et b=23 de type unsigned char (i.e. 8 bits). En base 2 il
s'écrivent respectivement 01001101 et 00010111.

valeur
Expression binaire décimale
A 01001101 77
B 00010111 23
a&b 00000101 5
a|b 01011111 95
a^b 01011010 90
~a 10110010 178
b << 2 01011100 92 multiplication par 4
b << 5 11100000 112 ce qui dépasse disparaît
b >> 1 00001011 11 division entière par 2

18
Cours de C L1ELN-L1GE-L1EEA

1.13. Synthèse

Catégorie Opérateur Signification


Catégorie Opérateur Signification
= Affectation simple
+ Addition
+= Addition suivie d’affectation
- Soustraction
Opérateurs -= Soustraction suivie d’affectation
* Multiplication
arithmétiqu *= Multiplication suivie d’affectation
es / Divisions entière et complète
/= Division suivie d’affectation
% Reste de la division entière
%= Modulo suivi d’affectation
- Changement de signe Opérateurs
d’affectation ~= NON bit à bit suivi d’affection
== Test d’égalité
&= ET bit à bit suivi d’affection bi
Opérateurs != Test de différence
|= OU bit à bit suivi d’affection
de < Test d’infériorité
^= OU exclusif suivi d’affection
comparaison <= Test d’infériorité ou d’égalité
Décalage à gauche suivi
> Test de supériorité <<=
d’affection
>= Test de supériorité ou d’égalité >>= Décalage à droite suivi d’affection
Opérateurs && ET booléen Opérateurs ++ Incrémentation
booléens || OU booléen d’incrémentation
et de -- décrémentation
! NON booléen
décrémentation
~ négation bit à bit
Opérateur
& ET bit à bit , virgule
Opérateurs séquentiel
de | OU (inclusif) bit à bit Opérateur
?: Opérateur conditionnel
manipulatio ^ OU exclusif bit à bit conditionnel
n de bit << décalage à gauche
>> décalage à droite

II. Ordre de priorité


Priorité des opérateurs
Priorité Catégorie Opérateurs Evaluation
15 Référence () [] ----
Unaire +
+ - -- ! ~ * & ----
+
14
(cast
sizeof
)
13 Arithmétique * / % ----
12 Arithmétique + - ----
11 Décalage << >> ----
Relationnel >
10 < <= > ----
=
9 Relationnel == != ----
8 Manip. de bits & ----
7 Manip. de bits ^ ----
6 Manip. de bits | ----
5 Logique && ----
4 Logique || ----
3 Conditionnel ?: ----
Affection - * / % << >> & ^ |
2 = += ----
= = = = = = = = =
1 Séquentiel , ----

19
Cours de C L1ELN-L1GE-L1EEA

Chapitre IV: Instructions du langage C


Le langage C dispose de trois types d’instructions :
- les instructions simples ;
- les instructions composées ou blocs d’instructions ;
- les instructions de contrôle ou instructions structurées.

I. Les instructions simples


Une instruction simple est terminée obligatoirement par un point virgule. Une instruction simple peut
être :
• L’instruction vide
Syntaxe :
;
L’instruction vide se compose uniquement d'un point virgule (;).Elle est utilisée là où une instruction
est nécessaire d'après la syntaxe. Par exemple, elle peut être utile quand on désire une étiquette à la fin
d’un bloc d’instructions ou pour mettre un corps nul à certaines instructions itératives.
• Une expression
Syntaxe :
<expression> ;
L’expression <expression> est évaluée, et sa valeur est ignorée. Les instructions expressions n’ont
d’utilité que lorsque les expressions réalisent des effets de bord. Deux cas particuliers intéressants
d’instructions expressions sont l’affectation et les appels de fonctions dans lesquels on ignore les
valeurs renvoyées.
Exemple :
#include <stdio.h>
int main(void)
{
int n ;
float x ;

123 ; /* n’a pas d’utilité pour le programme* /


printf("Entrer un entier et un reel\n");
scanf("%d%f",&n,&x);
n + 1 ; /* n’a pas d’utilité pour le programme* /
n++ ; /* expression avec effet de bord*/
x = 2*x + 3 ; /* expression avec effet de bord*/
printf("%d %.2f",n, x);
return 0;
}

II. Les instructions composées


Une instruction composée ou bloc d’instructions est une suite de déclarations / définitions et
d’instructions délimitée par des accolades {}. Du point de vue syntaxique, un bloc se comporte comme
une instruction unique et peut figurer en tout endroit où une instruction simple est permise. Les
instructions figurant dans un bloc peuvent être quelconques : instructions simples, blocs ou instructions
structurées.
Syntaxe :
{
[<liste de déclarations>]
<liste d’instructions>
}
Tout ce qui est entre crochets [ ] est facultatif.
Remarque :
Les variables déclarées dans un bloc ne sont accessibles qu’à l’intérieur de ce bloc et disparaissent à la
sortie du bloc.

20
Cours de C L1ELN-L1GE-L1EEA

III. Les structures de contrôle


Les structures de contrôle permettent de gérer l’exécution des autres instructions du programme ; elles
ont pour effet de modifier (on parle de rupture de séquence) le cours normal de l’exécution du
programme, à savoir l’exécution séquentielle des instructions. Le langage C dispose de toutes les
structures de contrôle classiques des langages de programmation.

1. Les structures de choix


a) La structure conditionnelle if
La structure conditionnelle if permet de réaliser un test et d'exécuter une instruction ou non selon le
résultat de ce test. Sa syntaxe est :
if ( <expression> )
<instruction1>
[else
<instruction2>]
La valeur de l'expression <expression> est évaluée et, si elle est non nulle, l’instruction <instruction1>
est exécutée sinon c'est l’instruction <instruction2>qui est exécutée (si elle existe).
Les parenthèses de <expression> sont obligatoires.
Rappelons que la valeur d’une expression logique vaut 1 s’elle est vraie et 0 sinon.
Les instructions <instruction1> et <instruction2>peuvent être des instructions simples, ou des blocs ou
d’autres structures de contrôle.
La clause else peut être omise.
Exemple :
if (a > b)
max = a;
else
max = b;
if (an % 4 == 0 && an % 100 != 0 || an % 400 == 0)
printf("annee bissextile\n");
Remarque:
if (N>0)
if (N>0)
if (A>B)
if (A>B)
MAX=A;
MAX=A;
else
else
MAX=B;
MAX=B;

Les deux instructions sont identiques.


Un else se rapporte toujours au dernier if rencontré auquel un else n’a pas encore été attribué.
Pour forcer la deuxième interprétation de l'expression ci-dessus, on peut écrire:
if (N>0)
{
if (A>B)
MAX=A;
}
else
MAX=B;
b) La structure de choix multiple switch
Il est possible de combiner des structures conditionnelles if pour obtenir une structure permettant de
faire un choix entre plusieurs alternatives :
if ( <expression1> )
<bloc1>
else if (<expression2>)
<bloc2>

21
Cours de C L1ELN-L1GE-L1EEA

else if (<expression3>)
<bloc3>
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..
else if (<expressionN>)
<blocN>
else <blocN+1>

Exemple:
#include <stdio.h>
int main(void)
{
int A,B;
printf("Entrez deux nombres entiers :");
scanf("%i %i", &A, &B);
if (A > B)
printf("%i est plus grand que %i\n", A, B);
else if (A < B)
printf("%i est plus petit que %i\n", A, B);
else
printf("%i est égal à %i\n", A, B);
return 0 ;
}
Mais cette imbrication peut devenir fastidieuse, surtout si chaque clause if compare la même
expression à des valeurs distinctes. Dans ce cas, il est préférable d’utiliser la structure switch dont la
syntaxe est :
switch ( <expression> )
{
case <e1> : [<instruction1> ]
[break;]
case <e2> : [<instruction2>]
[break;]
..................................
case <e3> : [<instruction3>]
[break;]
[default : <instruction_default>]
}
L'évaluation de <expression> doit donner pour résultat une valeur de type int.
<e1>, <e2>, <e3>... sont des expressions constantes qui doivent être un entier unique de type int ou
char.
La valeur de <expression> est recherchée successivement parmi les valeurs des différentes expressions
constantes <e1>, <e2>, <e3>....
En cas d'égalité les instructions (facultatives) correspondantes sont exécutées jusqu'à une instruction
break ou jusqu'à la fin du bloc du switch (et ceci indépendamment des autres conditions case).
S'il n'y a pas de valeur correspondante, on exécute les instructions du cas default (s'il existe).
Exemple:
#include <stdio.h>
int main(void)
{
int car;
car = getchar() ;
switch (car)
{

22
Cours de C L1ELN-L1GE-L1EEA

case 'a' :
case 'A':
case 'e' :
case 'E':
case ‘i’:
case ‘I’: printf(‘Voyelle\n’’);
break;
case ' ' :
case ‘\n’:
case ’\t’ : printf(‘’Espace\n’’);
break;
default : printf(‘‘Consonne\n’’);
}
return 0 ;
}

2. Les structures itératives


Les structures itératives ou boucles permettent de répéter plusieurs fois un bloc d'instructions.
a) La boucle while
Cette instruction permet de répéter une instruction (ou un bloc) tant qu'une condition est vraie. Sa
syntaxe est :
while ( <expression> )
<instruction>

Tant que <expression> est vérifiée (c’est à dire non nulle), <instruction> est exécutée. Si <expression>
est nulle au départ, <instruction> ne sera jamais exécutée. <instruction> peut être une instruction simple,
une instruction composée ou une autre instruction structurée.
Exemple : affichage des entiers de 0 à 9.
#include<stdio.h>
int main(void)
{
int i = 0 ;
/* affiche sur la même ligne les entiers de 0 à 9 */
while (i != 10)
{
printf("%d ", i);
i++; /* permet de passer à l’entier suivant */
}
return 0 ;
}
b) La boucle do ... while
Il peut arriver que l'on ne veuille effectuer le test de continuation qu'après avoir exécuté l'instruction.
Dans ce cas, on utilise la boucle do…while. Sa syntaxe est :
do
<instruction >
while ( <expression>) ;
L’instruction <instruction >sera exécutée tant que <expression>est non nulle. Cela signifie donc que
<instruction >est toujours exécutée au moins une fois. Noter le point virgule qui termine la syntaxe.
Exemple : somme des n premiers entiers; n inférieur ou égal à 10.
#include<stdio.h>
int main(void)
{

23
Cours de C L1ELN-L1GE-L1EEA

int n, i = 1 , somme = 0;
/* test de saisie */
do
{printf("\n Entrez un entier entre 1 et 10 ");
scanf("%d",&n);
}while ((a <= 0) || (a > 10));
/* somme des entiers de 1 a n */
do
{somme += i;
++i;
}
while (i <= n);
printf(‘’\nLa somme des entiers compris entre 1 et %d est:\nS = %d’’, n, somme) ;
return 0 ;
}
c) La boucle for
Cette boucle est surtout utilisée lorsque l'on connaît à l'avance le nombre d'itérations à effectuer. Sa
syntaxe est :
for ( [<expression1>] ; [<expression2>] ; [<expression3>] )
<instruction >
L'expression <expression1> est effectuée une fois, en premier. Puis on évalue l’expression
<expression2>.
On effectue alors l'instruction <instruction > puis l’expression <expression3> tant que l’expression
<expression2>est non nulle. L'instruction <instruction > et l’expression <expression3> peuvent ne
jamais être effectuées.
En général, l’expression <expression1> sert à initialiser les variables de la boucle, l’expression
<expression2> de test de continuation de la boucle et l’expression <expression3> à incrémenter les
compteurs de la boucle.
Exemple : factorielle d’un entier n.
#include<stdio.h>
int main(void)
{
int n, i;
long fact;
do
{printf(‘’entrer un entier naturel\n’’) ;
scanf(‘’%d’’,&n) ;
}
while(n<0) ;
/* calcul de la factorielle de n*/
fact = 1;
for (i = 1; i <= n; i++)
fact *= i;
printf("%d ! = %ld \n", n, fact);
return 0;
}
Remarques:
- La boucle for est équivalente à la structure suivante :
<expression1>;
while ( <expression2> )
{<instruction>
<expression3>;
}

24
Cours de C L1ELN-L1GE-L1EEA

- L'opérateur virgule, peut être utilisé dans <expression1> et <expression3>. Par exemple, pour
calculer la factorielle d'un entier, on peut écrire :
int n, i, fact;
for (i = 1, fact = 1; i <= n; i++)
fact *= i;
printf("%d ! = %d \n", n, fact);

3. Les instructions de branchement non conditionnel


Ces instructions permettent de poursuivre l'exécution du programme en un autre point de celui-ci.
Ces instructions sont à éviter si possible, car ils rendent le programme plus complexe à maintenir, le fait
d'être dans une ligne de programme ne suffit plus pour connaître immédiatement quelle instruction on a
fait auparavant, et donc ne permet plus d'assurer que ce qui est au dessus est correctement terminé. Il ne
faut les utiliser que dans certains cas simples.
a) L’instruction break
L’instruction break provoque la sortie immédiate de la boucle ou switch en cours. En cas de
boucles imbriquées, break fait sortir de la boucle la plus interne. Sa syntaxe:
break ;
Exemple :
#include<stdio.h>
int main(void)
{ int i;
for (i = 0; i < 5; i++)
{ printf("i = %d; ",i);
if (i = = 3)
break;
}
printf("\nvaleur de i a la sortie de la boucle = %d\n",i);
return 0 ;
}
Ce programme affichera:
i = 0 ; i = 1; i = 2; i = 3;
valeur de i a la sortie de la boucle = 3
b) L’instruction continue
L'instruction continue provoque l’abandon de l’itération courante d’une boucle et le passage à la
prochaine itération ; les instructions qui se trouvent entre l’instruction continue et la fin du corps de
la boucle sont ignorées pour l’itération courante. En cas de boucles imbriquées, continue permet
uniquement de continuer la boucle la plus interne.
Exemple : calcul de la somme des n premiers pairs
#include<stdio.h>
int main(void)
{int n, somme_pairs=0;
scanf(‘’%d’’, &n) ;
for (i=0; i<n; i=i+1)
{if (i % 2 == 1) continue;
somme_pairs=somme_pairs + i;
}
return 0;
}
c) L’instruction goto
L'instruction goto permet d'effectuer un saut jusqu'à l'instruction préfixée par l’étiquette
correspondante.
Sa syntaxe est la suivante : goto <étiquette>;

25
Cours de C L1ELN-L1GE-L1EEA

où <étiquette>est une étiquette marquant la ligne destination dans le programme. Les étiquettes sont
simplement déclarées avec la syntaxe suivante :
<étiquette>:
Les étiquettes peuvent avoir n'importe quel nom d'identificateur.
Il n'est pas possible d'effectuer des sauts en dehors d'une fonction.

26
Cours de C L1ELN-L1GE-L1EEA

Chapitre V: Tableaux et chaînes de caractères


Un tableau est un ensemble fini d’éléments de même type stockés en mémoire dans des adresses
contiguës.

A. Tableau à une dimension


Un tableau à une dimension ou vecteur est une suite séquentielle d'éléments d’un type commun autre
que le type tableau.

I. Généralités
1. Déclaration de tableau
<type_tableau> <nom_tableau> [<taille_tableau>];
où : <type_tableau> est le type des éléments du tableau
<nom_tableau> est le nom du tableau
<taille_tableau> est le nombre maximal des éléments du tableau appelé taille du tableau.
<taille_tableau> est une expression constante entière positive.
Exemple :
#define MAXEL 20
int tab[10] ;/* déclare tab comme un tableau de 10 entiers et alloue un espace mémoire de
10*4 octets */
char ligne[80] ; /* déclare ligne comme un tableau de 80 caractères et alloue un espace
mémoire de 80*1 octets */
double moyenne[MAXEL] ;
Remarque :
− Pour plus de clarté, il est recommandé de donner un nom à la constante <taille_tableau> à l’aide de
la directive #define du préprocesseur.
− Il est à noter qu'avec la nouvelle norme C99, il est possible de déclarer un tableau dont la taille est
donnée par une variable const; ce qui était impossible auparavant.
2. Accès aux éléments d’un tableau
On accède en lecture et en écriture aux éléments d’un tableau à l’aide de l’opérateur d’indexation [ ]
dont la syntaxe est :
< nom _tableau>[<indice>]
<indice> est l’indice à l’élément auquel on veut accéder. En C le premier élément d’un tableau est à
l’indice 0. <indice> peut être une expression dont la valeur est comprise entre 0 et la taille du
tableau moins un.
Exemple :
int x ;
tab[0]= 12 ;
scanf("%c", &ligne[2]);
printf("%c", ligne[2]);
x=tab[0]*3;
3. Initialisation de tableau
Les tableaux peuvent être initialisés tout comme les variables de types simples. La valeur servant à
l’initialisation est décrite en mettant les valeurs du tableau entre accolades et en les séparant par des
virgules :
<type_tableau> <nom_tableau> [<taille_tableau>] = {<val1>, ...,<valn>};

Exemple:
#include <stdio.h>

27
Cours de C L1ELN-L1GE-L1EEA

#define NB 7
char mot[10] = {′a′, ′b′, ′e′, ′f′} ;
int main(void)
{float note[NB] = { 10.5, 11, 7, 14, 0, 6, 9} ;
int i ;
/* Affichage des notes */
for (i = 0 ; i < NB ; i++)
printf(‘’note[%d] = %.1f\n’’, i, note[i]);
return 0 ;
}
Remarque :
− Si le nombre de valeurs dans la liste d’initialisation est inférieur à la taille du tableau, seuls les
premiers éléments seront initialisés ; les autres éléments seront mis à zéro si le tableau est une
variable globale (c’est à dire déclaré hors de toute fonction) ou une variable locale statique.
− Lors de l’initialisation d’un tableau, il est possible de ne pas spécifier le nombre d’éléments du
tableau ; le compilateur attribue alors à la taille du tableau le nombre effectif de valeurs initiales.
Par exemple la déclaration int test [] = {0, 1, 2, 3, 4, 5};
allouera 6 emplacements mémoire de taille sizeof(int)octets(2 ou 4)contenant respectivement 0,
1, 2, 3, 4, 5.
4. Remplissage et affichage de tableau
Le remplissage et l’affichage de tableau permettent d’accéder généralement à plusieurs éléments d’un
tableau
Exemple :
#include <stdio.h>
#define DIM 101
int main( void)
{
int i, j, T[DIM] ;
/* remplissage du tableau */
T[0] = 101 ;
for (i = 1 ; i<= DIM-1 ; i++)
T[i] = DIM-i ; /* 100, 99, 98, …, 1*/
/* affichage du tableau */
for (i = 1 ; i <= ((DIM-1)/2) ; i++)
{
for (j = 1 ; j<= ((DIM-1)/2; j++)
printf(“\nT[%d]= %6d\n”,( DIM-1)*10+j, T[( DIM-1)*10+j]);
printf(“\n”);
}
/* remplissage du tableau par des valeurs fournies par l’utilisateur*/
for (i = 1 ; i<= DIM-1 ; i++)
scanf(“%d”, &T[i] ) ;

return 0 ;
}
Remarque :
En C, il n’est pas possible faire l’affectation de tableaux.
5. Type tableau
En C, on définit un type tableau par :
typedef <type_tableau> <alias> [<taille_tableau>] ;
Exemple :
typedef double NOTE [12]; /* NOTE : ensemble des tableaux de 12 réels */
NOTE notes; /* notes : tableau de 12 réels */

28
Cours de C L1ELN-L1GE-L1EEA

II. Traitements simples sur les tableaux unidimensionnels


1. Insertion d’une valeur à une position donnée
Il s’agit d’insérer un élément à une position donnée dans un tableau.
Principe :
− Vérifier que le tableau n’est pas plein ;
− Obtenir la position d’insertion ;
− Décaler vers la droite tous les éléments du tableau, s’ils existent, situés à droite de la position
d’insertion (y compris l’élément qui s’y trouve) en commençant par l’élément le plus droite ;
− Insérer la valeur à la position dégagée ;
− Incrémenter éventuellement le nombre effectif d’éléments du tableau.
Exemple :
Traduire en C l’algorithme qui permet d’insérer dans un tableau un élément à une position donnée.
2. Recherche d’éléments
a) Recherche de toutes les occurrences d’une valeur
Principe :
− Parcourir tous les éléments du tableau en les comparant à la valeur recherchée.
Programme :
Traduire en C l’algorithme qui permet de déterminer le nombre d’apparitions d’une valeur dans un
tableau.
b) Recherche de la première occurrence d’un élément
Principe :
− Parcourir les éléments du tableau et s’arrêter dès qu’on a trouvé l’élément ou si on a terminé le
tableau.
Programme :
Traduire en C l’algorithme qui recherche la première occurrence d’une valeur dans un tableau et affiche
sa position.
c) Recherche linéaire du minimum, du maximum
Principe :
Pour rechercher le minimum d’un tableau :
− Supposer que le premier élément contient le minimum (le maximum) ;
− Parcourir les éléments du tableau et comparer chaque élément avec le minimum (le maximum)
supposé : si l’élément courant est inférieur au minimum (est supérieur au maximum) supposé, il
devient le minimum (le maximum).
Programme :
Traduire en C l’algorithme qui recherche le minimum et le maximum dans un tableau.
3. Suppression d’éléments
a) Suppression d’un élément connu par sa position
Principe :
− Décaler vers la gauche tous les éléments, s’ils existent, situés à droite de l’élément à supprimer ;
− Décrémenter éventuellement le nombre effectif d’éléments du tableau.
Programme :
Traduire en C l’algorithme qui permet de supprimer d’un tableau l’élément situé à une position donnée.
b) Suppression d’un élément connu par sa valeur
Principe :
Le principe consiste à se ramener au cas précédent :
− Rechercher la position de l’élément à supprimer à l’aide de sa valeur ;
− Si l’élément est retrouvé, décaler vers la gauche tous les éléments, s’ils existent, situés à droite de
l’élément à supprimer ;
− Décrémenter éventuellement le nombre effectif d’éléments du tableau.
Programme :
Traduire en C l’algorithme qui permet de supprimer d’un tableau, si elle s’y trouve, une occurrence
d’une valeur donnée.

29
Cours de C L1ELN-L1GE-L1EEA

III. Tris des tableaux


1. Tri par sélection
a) Tri par sélection du minimum
Principe :
Le principe du tri par sélection du minimum est d'aller chercher le plus petit élément du tableau pour le
mettre en premier, puis de repartir du second élément et d'aller chercher le plus petit élément du tableau
pour le mettre en second, etc...
Ainsi si T est un tableau de n éléments (indicés de 1 à n), le principe est le suivant:
− Placer dans l'élément d'indice 1 du tableau T la plus petite valeur présente dans le tableau :
pour cela, on recherche la plus petite valeur dans T et on la place dans T[1] ; la valeur qui se
trouvait auparavant dans T[1] est mise à sa place.
− Placer dans l'élément d'indice 2 de T la plus petite valeur présente dans la tranche T[2],…, T[n] du
tableau T; la valeur qui se trouvait auparavant dans T[2] est mise à sa place.
− Placer dans l'élément d'indice 3 de T la plus petite valeur présente dans la tranche T[3], ..., T[n] du
tableau T ; la valeur qui se trouvait auparavant dans T[3] est mise à sa place.
− et ainsi de suite jusqu'à l'étape n-1.
A chaque étape i (de 1 à n-1), on recherche l’élément qui doit se placer à la ième position
Programme :
Traduire en C l’algorithme de tri par sélection du minimum.
b) Tri par sélection du maximum
Principe :
Le principe du tri par sélection exposé ci-dessus consiste en une recherche successive des minima. Une
variante du tri par sélection consiste en une recherche successive des maxima :
− Placer dans l’élément d’indice n du tableau T la plus grande valeur présente dans le tableau pour
cela, on recherche la plus grande valeur dans T et on la place dans T[n] ; la valeur qui se trouvait
auparavant dans T[n] est mise à sa place.
− Placer dans l’élément d’indice n-1 de T la plus grande valeur présente dans la tranche T[1],…, T[n-1]
du tableau T ; la valeur qui se trouvait auparavant dans T[n-1] est mise à sa place.
− Placer dans l’élément d’indice n-2 de T la plus grande valeur présente dans la tranche T[1],…, T[n-2]
du tableau T ; la valeur qui se trouvait auparavant dans T[n-2] est mise à sa place.
− et ainsi de suite jusqu’à placer dans l’élément d’indice 2 la plus grande valeur présente dans la
tranche T[1], T[2].
Programme :
Traduire en C l’algorithme du tri par sélection du maximum.
2. Tri par insertion
Principe :
Si T est un tableau de n éléments (indicés de 1 à n), le principe est le suivant:
− Insérer à sa position l’élément d’indice 2 dans la tranche triée T[1] du tableau ;
− Insérer à sa position l’élément d’indice 3 dans la tranche triée T[1], T[2] ;
− Et ainsi jusqu’à l’étape n où il faut insérer à sa position l’élément T[n] dans la tranche triée
T[1], T[2], …, T[n-1] ;
A chaque étape i (de 2 à n), on recherche dans la tranche triée T[1], T[2], …, T[i-1] la position où va
être placé le ième élément. Pour cela il faut parcourir la tranche triée T[1], T[2], …,T[i-1] pour savoir à
quel endroit insérer T[i], puis décaler d'une case toutes les valeurs supérieures à T[i]. En pratique, la
tranche triée est parcourue de droite à gauche, c'est à dire dans l'ordre décroissant. Les éléments sont
donc décalés vers la droite tant que T[i] est plus petit qu'eux. Dès que T[i] est plus grand qu'un des
éléments de la partie triée il n'y a plus de décalage, et T[i] est inséré dans la place laissée vacante par les
éléments qui ont été décalés.
Programme :
Traduire en C l’algorithme du tri d’insertion.
3. Tri bulle
a) Principe
Le tri bulle consiste à parcourir le tableau et à comparer deux à deux des éléments consécutifs ; s’ils ne
sont pas ordonnés, ils sont permutés. On continue ainsi jusqu’à ce qu'il n'y ait plus de permutation.

30
Cours de C L1ELN-L1GE-L1EEA

Si T est un tableau de n éléments (indicés de 1 à n), le principe est le suivant:


− Remonter en première position le plus petit des n éléments T[1], …, T[n]. Pour cela, on
compare T[n] et T[n-1] ; si T[n] est inférieur à T[n-1], une permutation est effectuée.
Ensuite, sont comparés et éventuellement permutés T[n-1] et T[n-2], T[n-2]et T[n-3], …,
T[2] et T[1]. Une fois ce parcours achevé, il est certain que T[1] est le plus petit.
− Remonter en deuxième position le plus petit des n-1 éléments T[2], …, T[n]. Pour cela,
sont comparés et éventuellement permutés T[n-1] et T[n-2], T[n-2]et T[n-3], …, T[3] et
T[2].
− et ainsi jusqu’à remonter en avant dernière position T[n-1], T[n].
A chaque parcours i (de 1 à n-1), on remonte à la ième position le plus petit des T[i], …, T[n]. n-1
parcours suffisent.
b) Traduction en C
Traduire en C l’algorithme du tri par bulle.
c) Première variante
Principe :
Au lieu de remonter respectivement les minimums vers le début du tableau, on peut descendre
respectivement les maximums vers la fin du tableau.
Programme :
Traduire en C l’algorithme correspondant à cette variante du tri par bulle.
d) Deuxième variante
Principe :
Avec les deux algorithmes précédents, on est contraint de faire n-1 parcours même si, après un certain
nombre de parcours, tous les éléments sont ordonnés. Mais on peut parcourir le tableau et permuter les
éléments consécutifs qui ne sont pas dans l’ordre jusqu’à ce qu’il n’y a plus de permutation.
Programme :
Traduire en C l’algorithme correspondant à cette variante du tri par bulle.
IV. Traitements dans des tableaux triés
1. Recherche d’éléments dans un tableau trié
a) Recherche linéaire
Principe :
Lorsqu’on effectue la recherche linéaire dans un tableau quelconque, on s’arrête quand :
− soit on a trouvé l’élément recherché ;
− soit on a atteint la fin du tableau.
Dans un tableau trié, il existe une troisième condition d’arrêt : quand l’élément cherché devient inférieur
à l’élément courant du tableau.
Exemple :
Traduire en C l’algorithme qui recherche séquentiellement une valeur dans un tableau trié.
b) Recherche dichotomique
Principe :
On suppose le tableau trié par ordre croissant.
− Comparer l’élément recherché et l’élément médian du tableau :
• Si l’élément recherché est strictement inférieur à l’élément médian, il est obligatoirement, s’il
trouve dans le tableau, dans la partie gauche du tableau ; réappliquer alors le principe sur cette
partie ;
• Si l’élément recherché est strictement supérieur à l’élément médian, il est obligatoirement, s’il se
trouve dans le tableau, dans la partie droite du tableau ; réappliquer alors le principe sur cette
partie ;
• Si l’élément recherché est égal à l’élément médian alors fin de la recherche ;
− Arrêter la rechercher si l’élément est trouvé ou si la subdivision du tableau n’est plus possible.
Programme:
Traduire en C l’algorithme de recherche par dichotomie.
2. Insertion dans un tableau trié
Il s’agit d’insérer un élément dans un tableau sans perturber l’ordre.

31
Cours de C L1ELN-L1GE-L1EEA

Principe :
− Vérifier que le tableau n’est pas plein ;
− Rechercher dans le tableau la position d’insertion de l’élément à insérer en parcourant les éléments
du tableau et en les comparant avec l’élément à insérer jusqu’ à ce que l’élément devient inférieur à
l’élément courant ;
− Décaler vers la droite tous les éléments du tableau, s’ils existent, situés à droite de la position
d’insertion (y compris l’élément qui se trouve à la position d’insertion) en commençant par
l’élément le plus droite ;
− Insérer l’élément à la position dégagée ;
− Incrémenter éventuellement le nombre effectif d’éléments du tableau.
Programme:
Traduire en C l’algorithme d’insertion dans un tableau trié.
3. Fusion deux tableaux triés
Principe :
Il s’agit de remplir un tableau par les éléments de deux tableaux ordonnés de sorte qu’à la fin, le tableau
soit trié. Pour cela :
− Parcourir simultanément les tableaux triés : si l’élément courant du premier tableau est inférieur à
celui du deuxième tableau, il est placé dans le troisième tableau sinon c’est l’élément courant du
deuxième tableau qui est mis dans le tableau ;
− Continuer ainsi jusqu’à ce que l’un des deux tableaux soit fini ;
− Recopier dans le tableau le reste des éléments du tableau restant.
Programme :
Traduire en C l’algorithme de fusion de deux tableaux fusionnés.
V. Applications des tableaux à une dimension aux polynômes
n −1
Tout polynôme P de degré n défini par : P ( x ) = a n x + a n −1 x + .... + a 1 x + a 0
n

sera représenté par un tableau de n+1 nombres réels, tel que la valeur de l'élément d'indice k du tableau
soit le coefficient ak-1 du polynôme. Par exemple, le polynôme Q(x) = 5x4-4x3+3x2-2x+1 est représenté
par un tableau contenant 1, -2, 3, -4, 5.
1. Lecture et affichage de polynôme
Ecrire un programme en C qui lit et affiche un polynôme de degré n.
2. Calcul de la valeur d’un polynôme par la méthode de Horner
Principe :
n −1
Soit P ( x ) = a n x + a n −1 x + .... + a 1 x + a 0 un polynôme de degré n.
n

Normalement pour calculer la valeur de p en une valeur x 0 , il faut calculer successivement


x 0 , x 02 , x 30 , x 04 , ..., x 0n −1 , x 0n (soit n-1 multiplications), puis a n x 0n , a n -1 x 0n −1 , ... a 2 x 02 , a 1 x 0 (soit n
multiplications) enfin faire la somme de ces termes et a 0 (soit n additions). Ainsi on aboutit au résultat
au bout de 2n-1 multiplications et n additions.
Le mathématicien anglais Horner a mis au point une méthode efficace pour économiser des opérations.
On observe d’abord que :
P ( x ) = ((...(( a n x + a n − 1 ) x + a n − 2 ) x + ... + a 2 ) x + a 1 ) x + a 0 .
Avec cette expression, pour calculer P ( x 0 ) , on calcule successivement:
hn = an
h n −1 = h n x 0 + a n −1 (1 multiplica tion et 1 addition)
h n − 2 = h n −1 x 0 + a n − 2 (1 multiplica tion et 1 addition)
.......... .......... .......... .......... .......... .......... .......... .......... .......
h1 = h 2 x 0 + a 1 (1 multiplica tion et 1 addition)
h 0 = h1 x 0 + a 0 (1 multiplica tion et 1 addition)
h est la valeur de P ( x 0 ) , obtenue après n multiplications et n additions. On économise donc des
0

multiplications.

32
Cours de C L1ELN-L1GE-L1EEA

Programme :
Traduire en C l’algorithme de calcul de valeurs de polynôme par la méthode de Horner.
3. Somme de deux polynômes
Principe :
n m
Soit P ( x ) = ∑ a i x i et Q(x) = ∑ b i x i deux polynômes de degrés n et m. On veut calculer la somme
i=0 i=0
R de P et Q.
min ( n,m ) max ( n,m )
a si n = max(n, m)
R= ∑ (ai + bi )x i + ∑ mi x i où mi = i
i=0 i= min ( n,m )+1 b i si m = max(n, m)
Programme :
Traduire en C l’algorithme de calcul de la somme de deux polynômes.
4. Produit de deux polynômes
Principe :
n m
Si P ( x ) = ∑ a i x i et Q(x) = ∑ b i x i alors le produit de P et Q est défini par:
i=0 i=0

R( x) =
n+m

∑r x k

rk = ∑aibj
k i+j=k
k =0 0≤i≤n
0≤j≤m
Programme :
Traduire en C l’algorithme de calcul du produit de deux polynômes.

B. Tableaux à deux dimensions

I. Généralités
1. Définition
Un tableau à deux dimensions est un tableau dont les éléments sont des tableaux à une dimension.
Chaque élément d’un tableau à deux dimensions est repéré par deux indices.
En C, un tableau à deux dimensions est un tableau à une dimensionnelle dont les éléments sont des
tableaux à une dimension.
2. Déclaration
< type_elt> <nom_tableau> [<taille1>][<taille2>];
Cette syntaxe déclare comme <nom_tableau> un tableau de <taille1> tableaux de <taille2> éléments de
type <type_elt> chacun ; autrement dit <nom_tableau> est un tableau de <taille1>*<taille2> éléments de
type < type_elt>.

Exemple :
double noteEtudiant[40][12] ;
3. Accès aux éléments
L’accès à un élément d’un tableau à deux dimensions se fait selon la syntaxe suivante :
<nom_tableau> [<indice1>][<indice2>]
Cette syntaxe désigne l’élément placé à la ligne <indice1> et à la colonne <indice2>.
4. Initialisation de tableau
Les tableaux à deux dimensions peuvent s’initialisés comme les tableaux unidimensionnels ; les valeurs
d’initialisation étant des tableaux.

33
Cours de C L1ELN-L1GE-L1EEA

Exemple :
int Matrice[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}} ; ou
int Matrice[3][4]={1,2,3,4,5,6,7,8,9,10,11,12} ;
5. Lecture et affichage de tableau
La lecture, de même que l’affichage, d’un tableau à deux dimensions se fait par ligne et pour chaque
ligne par colonne.
Exemple:
#include<stdio.h>
#define NBMAX 50
int main(void)
{float M[NBMAX][NBMAX];
int m;/* nombre effectif de lignes*/
int n; /* nombr effectif de colonnes*/
int i,j;/* compteurs*/
/****************** Saisie du tableau **************/
do{printf("Entrer le nombre de lignes:");
scanf("%d",&m);
}while(m<=0||m>NBMAX);
do{printf("Entrer le nombre de colonnes:");
scanf("%d",&n);
}while(n<=||n>NBMAX);
for(i=0; i<m; i++)
for(j=0; j<n; j++)
{printf("Enter l'élément de la ligne %d et de la colonne %d:",i,j);
scanf("%d", &M[i][j]);
}
/****************** Affichage du tableau **************/
for(i=0; i<m; i++)
{
for(j=0; j<n; j++)
printf("Elément de la ligne %d et de la colonne %d: %d",i,j,M[i][j]);
printf("\n");
}
return 0;
}

II. Applications
1. Opérations sur les matrices
Les matrices sont représentées par les tableaux à deux dimensions.

a) Addition de matrices
Principe :
( )
Soient A = aij 1≤i ≤ m
1≤ j ≤ n
( )
et B = bij 1≤i ≤ m
1≤ j ≤ n
deux matrices de mêmes dimensions. La somme de A et B est

défini par : S = A + B = (s ) ij 1≤i ≤ m avec s ij = aij + bij


1≤ j≤ n

Programme :
Traduire en C l’algorithme de calcul de la somme de deux matrices.

34
Cours de C L1ELN-L1GE-L1EEA

b) Multiplication de matrices
Principe :
( )
Soient A = aij 1≤i ≤m et B = bij
1≤ j ≤ n
( ) 1≤i ≤ m deux
1≤ j ≤l
matrices. Le produit de A par B est une matrice mxl définie

par :
n

P = AxB = ( pij )1≤i ≤m avec p ij = ∑ a ik bkj


1≤ j ≤l k =1
Programme:
Traduire en C l’algorithme de calcul du produit de deux matrices.

Remarque :
La multiplication d’une matrice A par une matrice B est possible si le nombre de colonnes de A est égal
au nombre de lignes de B.

c) Permutation de lignes, de colonnes


Principe :
( )
Soit A = aij 1≤i ≤m
1≤ j ≤ n

La permutation de la iième ligne et de la k ième ligne de A consiste à parcourir les n colonnes et, pour
chaque colonne, à permuter les éléments de la iième ligne et de la k ième ligne.
Programme :
Traduire en C des algorithmes de permutations des lignes et des colonnes

d) Transposition de matrices
Principe :
Soit une matrice carrée d’ordre n A = (a ij )1 ≤ i , j ≤ n . La transposée de A est définie par : A = (t ij )
t
1≤ i , j ≤ n

avec t ij = a ji .
Programme :
Traduire en C, l’algorithme de transposition de matrices.
2. Triangle de Pascal
Principe :
On peut utiliser un tableau à deux dimensions pour représenter le triangle de Pascal :
1
11
121
1331
14641
.........
Le triangle contient les coefficients du binôme de Newton.
Chaque ligne i du triangle commence et se termine par 1.
L’élément d’une colonne j (1≤j≤i-1) de ligne i (2≤i≤n) est la somme des éléments des colonnes j-1 et j
de la ligne i-1.
Programme :
Traduire en C l’algorithme qui remplit et affiche le triangle de Pascal d’ordre n.

C. Chaînes de caractères
I. Généralités
1. Définition
Il n’existe pas en langage C de type chaîne de caractères. En C, une chaîne de caractères est représentée
par un tableau de caractères dont le dernier élément est le caractère nul '\0' de code 0. Ainsi une chaîne
composée de n caractères sera en fait un tableau de n+1 éléments de type char.
Une constante chaîne de caractères est une suite de symboles placés entre double quote ″ ″.

35
Cours de C L1ELN-L1GE-L1EEA

2. Déclaration de chaînes
Pour déclarer une chaîne de caractères, il suffit de déclarer un tableau de caractères.
Exemple :
char nom[26] ;
3. Initialisation de chaînes
Il y a deux façons d’initialiser à la déclaration une chaîne de caractères:
− en mettant entre accolades la suite des caractères de la chaîne servant de valeur initiale ; cette
suite devant se terminer par le caractère de fin de chaîne '\0'.
Exemple :char Chaine[21]={ 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };
− à l’aide d’une constante chaîne de caractères
Exemple :
char Chaine[21]=’’Bonjour’’ ;
char nom[ ] = ‘’Pierre’’ ;
4. Lecture et affichage de chaînes
scanf(″%s″, <var ch>) ;
printf(″%s″, <chaine>);
Exemple:
#include <stdio.h>
int main(void)
{ char nom[26] ;
printf(‘’Entrer votre nom’’) ;
scanf(‘’%s‘’, nom) ; /* lire la chaîne nom */
puts(‘‘La chaine saisie est ‘’ );
printf(‘’%s‘’, nom) ; /* afficher la chaîne nom*/
return 0 ;
}

II. Manipulation de chaînes de caractères


La bibliothèque C dispose de nombreuses fonctions reparties dans plusieurs bibliothèques permettant de
manipuler les chaînes de caractères.
1. Fonctions de lecture et d’affichage de stdio
En plus des fonctions scanf et printf, on dispose des fonctions gets et puts définies dans le
fichier d’entête stdio.h pour la lecture et l’écriture des chaînes de caractères.
gets(<chaine>) : permet de lire une ligne à partir du clavier, une ligne consistant en une suite de
caractères terminée par le caractère de retour à la ligne ‘\n’, ce caractère est remplacé par le caractère
terminal nul ‘\0’ en mémoire.
puts(<chaine>) : affiche une chaîne de caractères à l’écran, le caractère terminal nul ‘\0’ est remplacé par
le caractère ‘\n’ à l’affichage.
Exemple :
#include <stdio.h>
int main(void)
{ char nom[26] ;
puts(‘’Entrer votre nom’’) ;
gets(nom) ; /* lire la chaîne nom */
puts(‘‘La chaine saisie est ‘’ );
puts( nom) ; /* afficher la chaîne nom*/
return 0 ;
}
Ce programme donnera :
Entrer votre nom

36
Cours de C L1ELN-L1GE-L1EEA

Pierre ↵
La chaine saisie est
Pierre

2. Fonctions de manipulation de string


Fonction Syntaxe Action
Strcpy strcpy(ch1,ch2) copie la chaîne ch2 dans la chaîne ch1 et retourne ch1.
strncpy strncpy(ch1,ch2,n) copie n caractères de la chaîne ch2 dans la chaîne ch1
Strcat strcat(ch1, ch2) copie la chaîne ch2 à la fin de la chaîne ch1 et retourne ch1.
copie n caractères de la chaîne ch2 à la fin de la chaîne ch1et retourne
strncat strncat(ch1,ch2,n)
ch1.
compare ch1 et ch2 pour l'ordre lexicographique ; retourne une valeur
Strcmp strcmp(ch1,ch2) négative si ch1 est inférieure à ch2, une valeur positive si ch1 est
supérieure à ch2, 0 si elles sont identiques.
strncmp strncmp(ch1,ch2,n) compare les n premiers caractères de ch1 et ch2.
stricmp stricmp(ch1,ch2) Agit respectivement comme strcmp et strncmp mais sans tenir compte
strnicmp strnicmp(ch1,ch2,n) de la différence entre majuscules et minuscules.

Strchr strchr(ch,c)
retourne un pointeur sur la 1ière occurrence de c dans ch, et NULL si c n'y
figure pas.

strrchr strrchr(ch,c)
retourne un pointeur sur la dernière occurrence de c dans ch, et NULL si c
n'y figure pas.
retourne un pointeur sur la première occurrence de ch2 dans ch1, et NULL
Strstr strchr(ch1,ch2)
si ch2 n'y figure pas.
Strlen strlen (ch) retourne la longueur de la chaîne ch.
3. Fonctions de classification et conversion de caractères de ctype
Fonction Syntaxe Action
islower islower(c) Teste si le caractère c est une lettre minuscule
isupper isupper(c) Teste si le caractère c est une lettre majuscule
isalpha isalpha(c) Teste si le caractère c est une lettre (minuscule ou majuscule).
isalnum isalnum(c) Teste si le caractère c est une lettre ou un chiffre.
isdigit isdigit(c) Teste si le caractère c est un chiffre en base 10.
isxdigit isxdigit(c) Teste si le caractère c est un chiffre en base 16.
isspace isspace(c) Teste si le caractère c est un caractère d’espacement (code : 9-13 ou 32)
iscntrl iscntrl(c) Teste si le caractère c est un caractère de contrôle (code : 0-31 ou 127)
toupper toupper(c) Convertit le caractère c en majuscule si possible et retourne le résultat de la conversion.
tolower tolower(c) Convertit le caractère c en minuscule si possible et retourne le résultat de la conversion.

37
Cours de C L1ELN-L1GE-L1EEA

Chapitre VI: Structures


1. Définition
Une structure est une structure de données composée d’un certain nombre d’éléments qui peuvent être
de types différents. Les éléments d’une structure sont appelés champs. Le type d’un champ peut être
n'importe quel autre type, même de type structure. Les structures sont la traduction en C de la notion
algorithmique d’enregistrement.
2. Définition d’un type structure
struct [<nom_structure>]
{
<type1> <champ1>;
<type2> <champ2>;
...............................
};
Exemple :
struct date
{int an, mois, jour};/* structure à trois champs de type int */
Il n'est pas nécessaire de donner un nom à une structure.
3. Déclaration d’une variable structure
On dispose de deux possibilités pour déclarer une variable de type structure :
• faire suivre la définition de la structure par l'identificateur de la variable ;
Exemple : struct
struct employe
{char nom[50],prenom[50];
{char nom[50],prenom[50]; ou, plus simplement :
unsigned int nbre_enfant;
unsigned int nbre_enfant;
}Jean;
} Jean;
Dans le deuxième exemple, le nom de la structure est omis.
• déclarer la structure en lui donnant un nom, puis déclarer les variables avec la syntaxe suivante :
struct <nom_structure> <variable>;
Exemple :
struct date
{int an, mois, jour};
struct employe
{char nom[50], prenom[50];
unsigned int nbre_enfant;
struct date date_embauche; /* variable de type structure date */
};
struct employe Jean, Paul;
4. Initialisation des structures
Les structures s’initialisent comme les tableaux.
Exemple :
struct employe empl = {{′A′, ′L′, ′I′},{′J′, ′e′, ′a′, ′n′},5, {1, 2,1989}};
La norme C99 propose une autre syntaxe d’initialisation des structures :
struct employe empl = {.nom[50]={′A′, ′L′, ′I′},
.prenom[50]= ″Jean″,
.nbre_enfant=5,
.date_embauche= {1, 2,1989}
};
5. Accès aux champs
Les champs d'une structure sont accédés à l’aide de l’opérateur de sélection de membre(.), suivi du nom
du champ à accéder.

38
Cours de C L1ELN-L1GE-L1EEA

Exemple :
En considérant l’exemple précédent,
[Link][1] accède à la deuxième lettre du nom de l’employé Jean
Jean.nbre_enfant accède au nombre d’enfants de Jean
[Link] accède à la date d’embauche de Jean
[Link] accède au mois de la date d’embauche de Jean
6. Alias de type structure
L’alias d’un type structure se définit par la syntaxe suivante :
typedef struct [<nom_structure>]
{
<type1> <champ1>;
<type2> <champ2>;
...............................
}<alias>;
Une fois l’alias défini, le mot clé struct n’est plus utilisé lors de la déclaration d’une variable de la
structure.
Exemple :
typedef struct employe
{char nom[50], prenom[50];
unsigned int nbre_enfant;
struct date date_embauche; /* variable de type structure date */
}EMPLOYE;
EMPLOYE Jean ;
7. Utilisation globale des structures
Jusqu’à présent, les structures sont utilisées en travaillant individuellement sur chacun de leurs champs à
l’aide de l’opérateur de sélection de champs (.). Il est possible cependant d’affecter une structure à une
autre, d’avoir l’adresse d’une structure ou passer en paramètre de fonction une structure.

39
Cours de C L1ELN-L1GE-L1EEA

Chapitre VII: LES POINTEURS


I. Généralités
1. Structure de la mémoire d'un ordinateur
Un ordinateur comporte un processeur et de la mémoire connectés par un bus.

Structure d’un ordinateur et de la mémoire


La mémoire est constituée de petites cases de 8 bits appelées octets dont on peut écrire la valeur
et venir la relire plus tard. Chaque octet de la mémoire est identifié de manière univoque par un numéro
appelé adresse.
Dans la figure ci-dessus, une portion de mémoire est représentée couvrant les adresses 1000 à 1028.
Chaque ligne de mémoire contient donc 4 octets. En particulier, la première ligne contient les octets
d’adresse 1000 (extrême gauche), 1001 (centre gauche), 1002 (centre droite), et 1003(extrême droite).
Par convention, les adresses sont représentées en notation hexadécimale.
On représentera de manière simplifiée la mémoire de l’ordinateur comme une série de cases
contenant chacune un octet.

Représentation simplifiée du début de la mémoire d'un ordinateur


2. Adresse et valeur d’une variable
En programmation, on devrait donc, pour utiliser des valeurs dans la mémoire, procéder à des
opérations du genre “ranger la valeur 12 à l'adresse 1036”, “prendre la valeur rangée à l'adresse 1024,
l'additionner à la valeur rangée à l'adresse 1036 et ranger le résultat de l'opération à l'adresse 1058”.
Naturellement cela n'est guère pratique car cela nécessiterait de se souvenir de toutes ces adresses.
C'est pourquoi le langage C offre la notion de variable. Les variables ne sont que de simples labels
arbitraires que l'on donne à des cases mémoires pour éviter d'avoir à utiliser leur adresse numérique. On
peut ainsi utiliser des noms plus faciles à retenir plutôt que des adresses numériques.
Lorsqu’on déclare des variables dans un programme C, le compilateur choisit lui-même des
emplacements disponibles dans la mémoire et établit une correspondance entre les noms de variables
choisis et les adresses de ces emplacements. Le compilateur retient les adresses de toutes les variables
déclarées.
Considérons le programme suivant:
#include <stdio.h>
#include <string.h>
int main(void)
{int x, i; float r; short int j,k; char str[24];
x = 3;
strcpy(str,"Hello");
x = x + 1;
return 0;

40
Cours de C L1ELN-L1GE-L1EEA

}
En supposant qu’un float prend 8 octets de place mémoire, un int 4 octets, un short 2 octets, et une
chaîne caractères, n octets, le compilateur pourrait organiser les données en mémoire comme suit:

Quand on utilise la variable x dans le programme, en fait on utilise l'adresse 1000 (et implicitement les
3 suivantes, 1001, 1002 et 1003 si un int occupe bien 32bits), on dit que l'adresse de la variable x est
1000. Par exemple lorsque l'on écrit l’instruction :
− x = 3, ce que le programme fait à l’exécution, c’est aller écrire à l’adresse 1000 en mémoire la
valeur 3 codée en binaire sur 4 octets.
− x=x+1, ce que le programme fait à l’exécution, c’est aller prendre la valeur de 4 octets stockée à
l’adresse 1000, la donner au processeur qui lui ajoute la valeur 1, et la réécrire à la même
adresse 1000.
Ainsi chaque variable qu’on déclare est caractérisée par :
− son adresse c’est à dire l’adresse en mémoire à partir de laquelle elle est stockée
− sa valeur c’est à dire ce qui est stocké à cette adresse.
3. Accès aux variables
On peut accéder au contenu (valeur) d’une variable de deux manières:
• Grâce au nom de la variable

• Grâce à l’adresse de la variable, en utilisant une variable spéciale.


Par exemple si A une variable contenant la valeur 10 et P une variable qui contient l'adresse de
A, A et P peuvent se présenter en mémoire comme suit:

II. Notion de pointeur


1. Définition
Un pointeur est une variable qui peut contenir l'adresse d’autres variables d’un type donné.
Si un pointeur P contient l'adresse d'une variable A, on dit que P pointe sur A et on représente cela
schématiquement comme suit:
P A

• 10

41
Cours de C L1ELN-L1GE-L1EEA

Les pointeurs en C ont un grand nombre d'intérêts :


• Ils constituent le moyen de réaliser le passage par variable en C.
• Ils permettent la gestion dynamique de la mémoire (allocation dynamique, tableaux
dynamiques,..)
• Ils permettent de créer des structures récursives.
• ….
2. Déclaration de pointeurs
Un pointeur se déclare en précisant le type des variables pointées :
<type_pointé> *<nom_pointeur> ;
Cette syntaxe déclare la variable <nom_pointeur> dont la valeur est l’adresse d’une variable de type
<type_pointé>. <type_pointé> peut être aussi bien un type de base ( int, char...) qu'un
type complexe ( struct...).
<type_pointé>* est le type pointeur sur <type_pointé>.
Exemple :
int *pi ; /* pi est un pointeur vers un int */
short int *psi ; /* psi est un pointeur vers un short int */
double *pd ; /* pd est un pointeur vers un double*/
Les pointeurs sont généralement codés sur le même nombre d’octets que les entiers int (2 ou 4 octets).
Les pointeurs sont affichés avec le format ″%p″.
La déclaration d’un pointeur ne réserve pas l’espace mémoire associé à l’objet pointé.
3. Opérateurs d’adresse et d’indirection
L’opérateur d’adresse & appliqué à une variable permet d’obtenir l’adresse de celle-ci. Sa
syntaxe est : &<nom_variable>
L'adresse d'une variable d’un programme peut changer d’une exécution à l’autre car le compilateur
alloue les blocs de mémoire qui sont libres, et ceux-ci ne sont pas les mêmes à chaque exécution.
Exemple :
int i ; int *pi ; /* pointeur vers un int */
pi = &i ; /* pi pointe sur i */
L’opérateur d’indirection * appliqué à un pointeur permet d'accéder au contenu de l'adresse
mémoire pointée par ce pointeur. Sa syntaxe : *<nom_pointeur>
Exemple :
int i, j ; int *pi ; /* pointeur vers un int */
pi = &i ; /* pi pointe sur i */
*pi = 2 ; /* initialisation de la valeur pointée par pi à 2. (i=2)*/
j =*pi + 1 ; /* utilisation de la valeur pointée par pi. (j=3)*/
L’opération consistant à prendre la valeur de la case mémoire pointée par un pointeur constitue un
déréférencement ou indirection.
Remarque :
Si un pointeur p pointe sur une variable x, alors *p peut être utilisé partout où on peut écrire x.
Les opérateurs * et & ont la même priorité que les autres opérateurs unaires (la négation !,
l'incrémentation ++, la décrémentation --). Dans une même expression, les opérateurs unaires *, &, !, ++,
-- sont évalués de droite à gauche.
Exemple :
int x, y, *p;
p = &x;
y = *p+1; /* équivaut à y = x + 1 */
*p = *p + 10; /* équivaut à x = x + 10 */
*p += 2; /* équivaut à x += 2 */
++*P; /* équivaut à ++x */
(*P)++; /* équivaut à x++ ; ici il fallait forcer la priorité*/
4. Pointeurs génériques et pointeur nul
Un pointeur doit préférentiellement être typé. Cependant dans certaines situations (fonctions
d’allocation dynamique,…) il est nécessaire de conserver et manipuler une adresse mémoire sans

42
Cours de C L1ELN-L1GE-L1EEA

nécessairement savoir ou donner d'indication sur le type des données qui seront stockées à cet endroit.
Le C dispose pour de tels cas du type pointeur générique noté void *. Le type void * est
compatible avec tout autre type pointeur : n’importe quel pointeur peut être affecté à une variable de
type void * et réciproquement. On déclare les pointeurs génériques de la façon suivante:
void *ptr;
Exemple :
void* gen ; int *adr ;
gen = adr; /*on ne retient que l’adresse de l’emplacement pointé par adr */
adr = gen /* on associe à une adresse un type de données */
Les pointeurs génériques ne peuvent pas être déréférencés, c'est-à-dire qu'il est illégal de leur appliquer
l’opérateur *. Si l'on veut pouvoir déréférencer un pointeur générique, il faut donner une indication sur
les données contenues en mémoire à l'adresse indiquée par le pointeur en utilisant une conversion
explicite du type du pointeur. Par exemple (int *)ptr convertit le pointeur générique en pointeur sur
int.
Le pointeur nul est une constante nommée NULL qui vaut zéro et est compatible avec tout type
pointeur c’est à dire peut être affectée à tout pointeur. Cette constante est utilisée pour indiquer qu’un
pointeur ne pointe nulle part c’est à dire qu’il ne contient pas d'adresse utilisable. La constante NULL est
définie dans les fichiers d’en tête stdio.h, stdlib.h, stddef.h et alloc.h.
5. Initialisation des pointeurs
Après avoir déclaré un pointeur il faut l'initialiser. Cela est très important car lorsqu’on déclare un
pointeur, celui-ci contient ce que la case où il est stocké contenait avant, c'est-à-dire n'importe quel
nombre ; si donc l’on ne l’initialise pas, il risque de pointer vers une zone dangereuse de la mémoire
(partie du système d'exploitation, …).
Il existe trois manières sûres de donner une adresse valide à un pointeur :
• Prendre l’adresse d’une variable existante.
Exemple : double x, *p; / *déclaration */
p = &x ; /* Initialisation */
p contient maintenant une adresse valide, l’adresse de la variable x.
• Provoquer l’allocation d’un nouvel espace, c’est à dire allouer dynamiquement une variable
anonyme.
Exemple : double *p;
p = (double*)malloc(sizeof(double));/*Initialisation */
• Obtenir une valeur par des expressions valides sur des pointeurs.
Exemple : float x, *p, *q;
p = &x;
q = p + 3;
p = NULL ;
Remarque :
Essayer d'obtenir la valeur de la variable pointée par un pointeur à NULL conduit à une erreur.
int a,*P; /* a entier, p pointeur */
a = *P; /* incorrect */
P = NULL; / P pointe sur rien */
a = *P; /* erreur*/
6. Pointeur et const
• On peut, à l'aide du spécificateur const, s'assurer que le pointeur reste constant :
char * const ptr = tab;
ptr est un pointeur constant sur un caractère et initialisé avec l'adresse tab.
On ne peut donc pas modifier la valeur de ptr.
• Par contre la définition suivante :
const char *ptr;
définit ptr comme un pointeur sur une constante caractère.
On peut donc modifier la valeur de ptr mais pas la valeur pointée par ce dernier.
• La dernière forme, résultante des 2 formes précédentes, déclare un pointeur constant sur une
constante caractère :

43
Cours de C L1ELN-L1GE-L1EEA

const char * const ptr = tab;


on ne peut modifier ni le pointeur ni la valeur pointée.
7. Arithmétique des pointeurs
Une partie de la puissance des pointeurs vient du fait qu'on puisse utiliser l'arithmétique sur le
pointeur lui-même. L’idée de base de l’extension de l'arithmétique sur les pointeurs est la suivante : si
exp exprime l’adresse d’une certaine variable x1 alors exp+1 exprime l’adresse de la variable de
même type que x1 qui se trouverait dans la mémoire immédiatement après x1 et exp-1 l’adresse de celui
qui se trouverait immédiatement avant.
Il devient alors naturel, si exp1 et exp2 sont les adresses respectives de deux variables x1 et x2 de
même type et contigues (x2 après x1), de donner à exp2 - exp1 la valeur 1.
7.1. Affectation par un pointeur sur le même type
Soient p1 et p2 deux pointeurs sur le même type de données, alors l'instruction
p1 = p2;
fait pointer p1 sur la même variable que p2
7.2. Addition externe
C’est l’addition d’un pointeur avec un entier.
Si p est un pointeur sur un type T de taille t octets et i un entier, alors
P+i = adresse contenue dans p + i*t
Autrement dit :
p+i pointe i*t octets après si i est positif
p+i pointe (-i)*t octets avant si i est négatif.
Exemple :
On dispose de neufs emplacements mémoire consécutifs de type T.
p+2
p -3

Remarque :
La valeur de p + i dépend du type pointé par p.
7.3. Incrémentation et décrémentation d'un pointeur
Si p est un pointeur sur un type T de taille t octets, les expressions suivantes sont possibles :
p++, ++p, p--, --p.
Par exemple p++ augmente l'adresse de p de t octets, afin de pointer sur l'élément de type T supposé
suivre dans la mémoire.
7.4. Affectations composées
Si p est un pointeur sur un type T de taille t octets et i un entier, les expressions suivantes sont
possibles : p+=i, p-=i
7.5. Soustraction de deux pointeurs
Soient p1 et p2 deux pointeurs sur un même type T de taille t.
p2-p1 = (adresse contenue dans p2 - adresse contenue dans p1)/t
p1 – p2 fournit le nombre d’emplacements mémoire de taille t compris entre p1 et p2.
La différence deux pointeurs est de type ptrdiff_t défini dans le fichier d’en-tête stddef.h.
Exemple :
On dispose de neufs emplacements mémoire consécutifs de type T et deux pointeurs p1 et p2.
p2
p1

Alors :
p2–p1 = 5 et p1-p2 = -5

44
Cours de C L1ELN-L1GE-L1EEA

7.6. Comparaison de pointeurs


Il est permis de comparer deux pointeurs de même type par <, >, <=, >=, ==, !=.
Par exemple, p < q est vraie si p pointe sur un élément (d’une suite d’emplacements mémoire de même
type) qui précède celui sur lequel pointe q.
7.7. Domaine des opérations arithmétiques
L'addition, la soustraction, l'incrémentation, la décrémentation et la comparaison sur les pointeurs
sont seulement définies à l'intérieur d'une suite d’emplacements mémoire contiguës de même type ;
notamment dans à l’intérieur d’un tableau. Si l'adresse formée par le pointeur sort de la suite, alors le
résultat n'est pas défini.
III. Pointeurs et tableaux
En C, les tableaux sont étroitement liés aux pointeurs ; chaque opération avec des indices de tableau
peut être exprimée à l'aide de pointeurs.
1. Conversion des tableaux
En C, tout identificateur de type tableau de X apparaissant dans une expression est converti en une
valeur constante dont :
• le type est pointeur vers X ;
• la valeur est l’adresse du premier élément du tableau.
Exemple :
int T[20], * p ;
p = T;/*équivaut à p = &T[0].Test converti en un pointeur sur int (int *) dont est la valeur est &T[0]
*/
Remarque :
• L’adresse du premier élément d’un tableau est l’adresse de ce tableau ; donc l’adresse d’un tableau est
contenue dans son identificateur.
• Le nom d’un tableau étant un pointeur constant, on ne peut lui affecter une valeur.
int T1[20], T2[20], *p ;
T2 = T1 ;/* incorrect car affectation d’une constante à une constante */
T2 = p ; /* incorrect */
P = T2 ; /* valide */
La conversion automatique des noms de tableau en pointeur va permet d’utiliser l’arithmétique des
pointeurs pour parcourir les tableaux et d’accéder facilement à leurs éléments.
2. Indexation des composantes d'un tableau
Jusqu’à présent pour accéder à un élément d’un tableau, on disposait de l’opérateur d’indexation
[]; avec les pointeurs on dispose d’une autre possibilité. Considérons les déclarations suivantes :
int T[10], i ;
0 1 2 3 4 5 6 7 8 9

Alors : T est l’adresse du premier élément du tableau T ; T+6


T
et T + i est, d’après l’arithmétique des pointeurs, l’adresse de l’élément de rang i du tableau.
En appliquant l’opérateur d’indirection à T + i, on accède donc à l’élément de rang i du tableau :
T + i est l’adresse de T[i] ;
*(T + i) est la valeur de T[i].

Lors de la compilation, toutes les expressions de la forme T[i] sont traduites en *(T + i).
Exemple :

On voit qu’appliquer l’opérateur d’indexation [] à un tableau T revient, du fait de la conversion


automatique de T vers un pointeur, à appliquer l’opérateur [] à une valeur constante de type pointeur. De
manière générale, on peut l’appliquer à n’importe quelle valeur de type pointeur.
Exemple :
apr _es les d_eclar ati ons :

Avec les déclarations suivantes :

45
Cours de C L1ELN-L1GE-L1EEA

int T[10], *p ;
on peut écrire :
p = &T[4] /* ou p = T +4 */
et utiliser l’opérateur d’indexation sur p, p[0] étant T[4], p[1] étant T[5], …, p[5] étant T[9]. p apparaît
comme un sous tableau de T.
Du fait de la commutativité de l’addition externe, l’opérateur d’indexation est commutatif. En effet
T[i] étant équivalent à *(T+i) et l’addition externe étant commutative, *(T+i) est équivalent à *(i+T) donc
à i[T]. Ainsi, lorsqu’on utilise l’opérateur d’indexation, on peut noter indifféremment l’élément de rang i
d’un tableau T, T[i] ou i[T].
3. Autres applications de l’arithmétique des pointeurs
3.1. Parcours d’un tableau et comparaison des indices
Si p pointe sur l'élément T[i] d'un tableau T, alors après l'instruction
p++; p pointe sur T[i+1]
p+=n; p pointe sur T[i+n]
P--; p pointe sur T[i-1]
p-=n; p pointe sur T[i-n]
La comparaison de deux pointeurs qui pointent dans le même tableau est équivalente à la comparaison
des indices correspondants. Par exemple si p1 pointe sur T[i] et p2 sur T[j], alors p1<p2 si i < j.
Exemple :
Inversion d’un tableau d’entiers, en utilisant l’arithmétique des pointeurs.
Remarque :
Si T est un tableau, les expressions T++,++T,T--,--T, T+=n,T-=n sont incorrectes car T est une constante.
3.2. Différence d’indices
La soustraction de deux pointeurs qui pointent dans le même tableau est équivalente à la
soustraction des indices correspondants ; si p1 et p2 pointent respectivement sur T[i] et T[j], alors p1- p2
est égal à i-j.
4. Comparaison entre pointeurs et tableaux
4.1. Similitude entre les pointeurs et les tableaux
Lorsqu’on a un pointeur, il est possible d'avoir une écriture spécifiquement prévue pour les
tableaux en utilisant notamment l’opérateur d’indexation. De la même manière, lorsqu’on a un tableau,
il est également possible d'avoir une écriture spécifiquement prévue pour les pointeurs en utilisant
l'arithmétique des pointeurs en faisant attention toutefois à ce que l'adresse du tableau ne change pas
puisqu'elle doit être constante.
Exemple :
Arithmétique des pointeurs pour le tableau
int T[10], i;
for ( i=0 ; i<10 ; i++)
*(T+i) = i ;/*Utilisation de l’arithmétique des pointeurs sur un Tableau */
Opérateur d’indexation sur un pointeur
int T[10], i, *pi = T ;
for (i=0 ; i<10 ; i++)
pi[i] = i ;/* Opérateur d’indexation sur un pointeur*/
4.2. Différence entre pointeur et tableau
• Un tableau est un pointeur constant, mais avec une réservation d'une zone mémoire correspondant à la
capacité du tableau pour stocker les données.
• Un pointeur est une variable. On peut donc modifier son contenu. Par contre, il n'y a pas de
réservation mémoire de la variable pointée. Cette réservation doit être réalisée au préalable.
5. Pointeur et tableau multidimensionnel
En C, un tableau multidimensionnel est un tableau dont les éléments sont des tableaux. Aussi
l’arithmétique des pointeurs s’étend aux tableaux multidimensionnels.
5.1. Adresse d’un tableau multidimensionnel
Considérons le tableau à deux dimensions M :
int M[3][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},

46
Cours de C L1ELN-L1GE-L1EEA

{10, 11, 12, 13, 14, 15, 16, 17, 18,19},


{20, 21, 22, 23, 24, 25, 26, 27, 28,29}};
M est un tableau de 3 éléments qui sont des tableaux de 10 entiers ; donc le nom du tableau M
représente l’adresse du premier élément du tableau et pointe sur le tableau M[0] qui a la valeur
{0,1,2,3,4,5,6,7,8,9}.
D’après l’arithmétique des pointeurs, pour tout i (0≤ i ≤ 3) :
M + i est l’adresse du tableau M[i].
Notons que, comme M[i] est un tableau, M[i] a une valeur constante qui est l’adresse de son premier
élément M[i][0]. De plus, bien que M+i et M[i](= &M[i][0]) aient même valeur, ils sont de types
différents ; M+i est de type pointeur sur tableau de int alors que M[i] est de type pointeur sur int.
5.2. Indexation
Chaque élément du tableau M est un tableau. Comment peut-on accéder à l'aide de pointeurs aux
éléments de chaque élément du tableau, c’est à dire aux éléments M[0][0], M[0][1], ... , M[0][9],
M[1][0],…,M[1][9],M[2][0], . . . M[2][9] ? Pour cela il suffit de connaître l’adresse de l’élément
M[i][j].
En mémoire ces éléments sont stockés de la manière suivante :
M[0][0] M[0][1] M[0][9] M[1][0] M[1][1] M[1][9] M[2][0] M[2][1] M[2][9]

.... .... .. .

M ou M[0] M+1ou M[1] M+2 ou M[2]

M[i][j] est l’élément d’indice j du tableau M[i] d’indice i de M. Donc M[i][j] est à l’adresse M[i] +j.
Pour aller à l’adresse du tableau M[i], il faut parcourir 10 emplacements mémoire i fois. D’où l’adresse
de M[i][j] est M + i*10 + j.
Connaissant l’adresse de M[i][j], on peut lui accéder en utilisant l’opérateur d’indirection :
M[i][j] = *((int*)M + i*10 + j)ou
M[i][j] = *(M[0] + i*10 + j)
Exemple:
#include<stdio.h>
#define M 10
#define N 10

int main(void)
{ int T[M][N], i, j, n, m;
/* Lecture de la matrice T */
printf(″ Nombre de ligne :″) ;
scanf(″%d″, &m) ;
printf(″nombre de colonnes :″) ;
scanf(″%d″,&n) ;
for(i = 0 ; i<m ;i++)
for( j = 0; j<n;j++)
scanf(″%d″,(T[0]+i*N+j)); /*ou scanf(″%d″,((int*)T+i*N+j)); */
/* Affichage */
for(i = 0 ; i<m ;i++)
{for( j = 0;j<n;j++)
printf(″%d ″,*(T[0]+i*N+j));
printf(″\n″) ;
}
return 0 ;
}

47
Cours de C L1ELN-L1GE-L1EEA

De manière générale, pour pouvoir travailler à l'aide de pointeurs dans un tableau à deux dimensions, on
a besoin de quatre données:
• l'adresse du premier élément du tableau
• la longueur d'une ligne réservée en mémoire
• le nombre d'éléments effectivement utilisés dans une ligne
• le nombre de lignes effectivement utilisées
IV. Pointeurs et chaînes de caractères
Une chaîne de caractères est un tableau de caractères se terminant par le caractère nul ‘\0’. On
peut donc manipuler toute chaîne de caractères à l’aide d’un pointeur sur char.
1. Conversion de chaînes littérales
Lorsque les chaînes littérales apparaissent dans un contexte autre qu’une déclaration avec
initialisation de tableau de caractères, elles subissent une conversion en pointeur vers char.
1.1. Cas d’affectation
Exemple :
char *C;
C = "Ceci est une chaîne de caractères constante";
C va pointer sur une chaîne littérale stockée quelque part en mémoire.

On peut afficher cette chaîne constante comme chaîne de caractères ou comme pointeur.
printf(″%p″, C) ;/* affiche l’adresse de la chaîne */
printf(″%s″, C) ;/* affiche la chaîne littérale*/
printf(″%p″, ″Bonjour″);/* convertit la chaîne littérale en pointeur et affiche sa valeur */
1.2. Initialisation d’un pointeur sur char
Exemple :
char *B = "Bonjour !";
Remarque :
Il existe une différence importante entre les deux déclarations:
char A[] = "Bonjour !"; /* un tableau */
char *B = "Bonjour !"; /* un pointeur */
A est un tableau qui a exactement la grandeur pour contenir la chaîne de caractères et la terminaison '\0'.
Les caractères de la chaîne peuvent être changés, mais le nom A va toujours pointer sur la même adresse
en mémoire.
B est un pointeur qui est initialisé de façon à ce qu'il pointe sur une chaîne de caractères constante
stockée quelque part en mémoire. Le pointeur peut être modifié et pointer sur autre chose. La chaîne
constante peut être lue, copiée ou affichée, mais pas modifiée.

1.3. Modification
Si on affecte une nouvelle valeur à un pointeur sur une chaîne de caractères constante, on risque
de perdre la chaîne constante.
Exemple :
char *A = "Petite chaîne";
char *B = "Deuxième chaîne un peu plus longue";
A = B;
Maintenant A et B pointent sur la même chaîne; la "Petite chaîne" est perdue:

2. Pointeur sur char et chaîne de caractères


• Utiliser les tableaux de caractères pour déclarer les chaînes de caractères qu’on veut modifier.

48
Cours de C L1ELN-L1GE-L1EEA

• Utiliser les pointeurs sur char pour manipuler des chaînes de caractères constantes (dont le
contenu ne change pas).
• Utiliser de préférence des pointeurs pour effectuer les manipulations à l'intérieur des tableaux de
caractères. En effet les chaînes de caractères ont une marque de fin de chaîne, ce qui permet de
parcourir les chaînes à l’aide des pointeurs sans avoir besoin de connaître leurs longueurs. De
plus un pointeur sur char a l’avantage de pointer sur des chaînes de n’importe quelle
longueur.
V. Pointeurs et structures
Les structures sont des lvalues. Elles possèdent donc une adresse correspondant à l’adresse du
premier membre de la structure. On peut donc manipuler des pointeurs sur des structures.
Exemple :
struct eleve{ char nom[20] ;
int age ;
};
struct eleve *peleve; / * peleve pointe sur la structure eleve*/
1. Opérateur pointeur de membre de structure
On accède à un membre d’une variable structure à l’aide de l’opérateur de sélection de membre
(.). Maintenant si p est un pointeur sur une structure, on peut accéder à un membre de la structure
pointée à l’aide de l’opérateur pointeur de membre, noté ->
Exemple :
struct eleve *peleve;
gets(peleve->nom) ;
peleve->age=20 ;
Si p pointe sur une structure, *p est la structure pointée ; donc p-><nom_membre> est équivalente à
(*p).<nom_membre>.
2. Structure récursive
2.1. Définition
Les structures récursives font référence à elles-mêmes. Elles sont principalement utilisées pour
implanter des structures de données dont la définition formelle est un énoncé récursif.
Par exemple, on peut donner la définition récursive suivante : un arbre binaire est
– soit un symbole conventionnel signifiant structure vide;
– soit un triplet ( valeur , filsg , filsd ) constituée d’une information dont la nature dépend du problème
étudié et de deux descendants qui sont des arbres binaires.
Du point de vue technique il n’est pas possible de déclarer un champ d’une structure comme
ayant le même type que la structure elle-même (une telle structure serait de taille infinie !). On
contourne cette difficulté en utilisant les pointeurs. Ainsi un arbre binaire est représenté par une adresse,
qui est :
– soit l’adresse nulle ;
– soit l’adresse d’une structure constituée de trois champs : une information et deux descendants qui
sont des adresses d’arbres binaires.
Typiquement, la définition d’une telle structure sera donc calquée sur la suivante :
struct noeud
{ <type> valeur;
struct noeud *filsg ;
struct nœud *filsd;
};
Noter que le nom de la structure (noeud) est ici indispensable, pour indiquer le type des objets pointés
par les champs filsg et filsd.
2.2. Structures mutuellement récursives
Considérons le problème suivant : représenter une liste de familles ; chaque famille est une liste
d’individus, avec la particularité que chaque individu fait référence à sa famille.

49
Cours de C L1ELN-L1GE-L1EEA

Les structures famille et individu se pointent mutuellement, chacune intervenant dans la définition de
l’autre. Comment les déclarer?
Le compilateur C tolère qu’on déclare un pointeur vers une structure non encore définie. Plus
précisément, si la structure ayant le nom indiqué n’a pas encore été déclarée, l’expression « struct
nom » est légitime. Elle identifie un type incomplet et on peut l’utiliser à tout endroit où sa taille n’est
pas requise, notamment dans la déclaration d’un pointeur vers une telle structure.
Cela nous permet de déclarer nos structures mutuellement récursives sous la forme :
typedef struct famille {char *nom;
struct individu *membres;
struct famille *famille_suivante;
} FAMILLE;
typedef struct individu {char *prenom;
struct individu *membre_suivant;
struct famille *famille;
} INDIVIDU;
Une autre solution aurait été:
typedef struct famille FAMILLE;
typedef struct individu INDIVIDU;
struct famille {char *nom;
INDIVIDU *membres;
FAMILLE *famille_suivante;
};
struct individu {char *prenom;
INDIVIDU *membre_suivant;
FAMILLE *famille;
};
VI. Tableaux de pointeurs
Les tableaux de pointeurs sont déclarés comme suit :
<type> *<nom_tableau>[<taille>] ;
On déclare ainsi un tableau <nom_tableau> de <taille> pointeurs sur des données de type <type>.
Exemple :
double *T[10];/*tableau de 10 pointeurs sur double*/
Les tableaux de pointeurs sont utilisés pour mémoriser de façon économique des chaînes de caractères
de différentes longueurs.
Exemple :
char *JOUR[] = {"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"};
déclare un tableau JOUR de 7 pointeurs sur char. Chacun des pointeurs est initialisé avec l'adresse de
l'une des 7 chaînes de caractères :

50
Cours de C L1ELN-L1GE-L1EEA

VII. Gestion dynamique de la mémoire


1. Méthodes d’allocation de la mémoire
Dans un programme en C, les variables peuvent être allouées de deux manières différentes :
• Statiquement : Avant l’exécution du programme, le compilateur traite le code source, détermine
l’ensemble des variables et réserve un emplacement mémoire en conséquence. Les variables ainsi
allouées appelées variables statiques.
La durée de vie de ces variables correspond à la durée de vie soit du programme soit de la
fonction qui les a déclarées. Ces variables sont stockées soit dans la zone statique, soit sur la pile.
• Dynamiquement : pendant l’exécution du programme, il est possible d’avoir besoin d’une variable
pour une utilisation relativement brève dans le temps. Une variable est alors créée à la volée et elle
sera détruite quand le besoin ne s’en fera plus. Une variable ainsi créée est une variable dynamique et
est stockée dans la zone mémoire appelée tas.
L'allocation statique de mémoire est considérablement plus efficace car effectuée avant le
commencement du programme. Quand le programme démarre, tout est prêt pour fonctionner
correctement et les variables sont très faciles à atteindre et à manipuler. Par ailleurs le temps de réponse
est plus rapide. Elle est toutefois moins flexible car elle nécessite de connaître, avant l'exécution du
programme, la quantité et le type de mémoire désirés. La taille mémoire nécessaire pour tout le temps
d'exécution du programme peut être très conséquente suivant le nombre de variables. Par ailleurs, il n'est
pas sûr qu'une variable soit utile pendant toute la durée de vie du programme.
Les variables statiques sont des variables nommées que l'on manipule directement, alors que les
variables dynamiques sont des variables non nommées (anonymes) manipulées indirectement au travers
de pointeurs. L'allocation et la désallocation de variables statiques sont gérées automatiquement par le
compilateur ; l'allocation se fait au démarrage du programme (ou à l’entrée d’une fonction) et la
désallocation à la fermeture de ce dernier (à la sortie de la fonction). L'allocation et la désallocation de
variables dynamiques, en revanche, doivent être explicitement gérées par le programmeur.
2. Allocation dynamique de la mémoire
C dispose de plusieurs fonctions permettant de réserver dynamiquement de la mémoire.
2.1. La fonction malloc
a)Syntaxe
malloc(<taille>);
b) Comportement
malloc prend en paramètre la taille en octet <taille> de la zone mémoire désirée et retourne
un pointeur de type void* sur la zone allouée ou 0 (pointeur nul) s'il n'y a pas assez d'espace mémoire
contigu ; autrement dit malloc réserve un emplacement mémoire de taille <taille> octets et fournit
l’adresse de cet emplacement en cas de succès ou NULL (0) en cas d’échec.
<taille> est une expression de type size_t, alias de unsigned int ; donc avec malloc, on ne
peut pas réserver plus de 65535 octets à la fois.
La fonction malloc est déclarée dans le fichier d’en tête stdlib.h.
Exemple : Programme qui lit un tableau de n phrases de différentes longueurs.
#include <stdio.h>
#include <stdlib.h> /* pour malloc */

51
Cours de C L1ELN-L1GE-L1EEA

#include <string.h> /* strlen, strcpy */


#define LG 501
#define NBPHR 50 /* nombre maximal de phrases */
int main(void)
{int n; /* nombre effectif de phrases */
char TEMP[LG];/* Chaîne temporaire */
char *TEXTE[NBPHR];/* Tableau de phrases */
int i;
printf(“ Nombre de phrases à saisir:”);
scanf(“%d”,&n);
for (i=0; i< n; i++)
{gets(TEMP);
TEXTE[i] =(char*)malloc(strlen(TEMP)+1);
if (TEXTE[i]!=NULL)
strcpy(TEXTE[i], TEMP);
else
{printf("ERREUR: Pas assez de mémoire \n");
exit(-1);/* quitter le programme */
}
}
return 0;
}
Remarque:
1° Une zone mémoire fraîchement allouée par malloc n'est pas initialisée et peut contenir
n'importe quoi (données provenant d'anciennes applications ayant utilisé le même espace, ou
données aléatoires). Il est donc nécessaire de l'initialiser avec des valeurs appropriées.
2° Les fonctions d'allocation dynamique retournent des pointeurs sur void. Il faut donc opérer des
conversions de types explicites pour utiliser ces zones mémoire en fonction du type des données qui y
seront mémorisées.
c) Opérateur sizeof
Si l’on doit réserver de la mémoire pour des données d’un type dont la taille est susceptible de
varier d’une machine à l’autre, il est préférable d’utiliser l’opérateur sizeof afin de préserver la
portabilité du programme. Cet opérateur fournit la taille effective en octet de son argument. Syntaxe :
sizeof <var> fournit la taille de la variable <var>
sizeof <const> fournit la taille de la constante <const>
sizeof (<expr>)fournit la taille de l’expression <expr>
sizeof (<type>)fournit la taille d’une expression de type <type>
Exemple : Saisir un tableau de n personnes caractérisées par leurs noms, adresses et âges.
#include <stdio.h>
#include <stdlib.h> /* pour malloc. */
#include <string.h>/*pour strcpy,strlen. */
#define LG 51
Typedef struct { char *nom ;
char *adr ;
int age ;
}PERSONNE;
int main(void)
{ int nb_pop,i;
char nom[LG],adr[LG];
PERSONNE *p ;/* tableau de personnes*/
printf("Nombre de personnes à saisir : ") ;
scanf("%d", &nb_pop) ;

52
Cours de C L1ELN-L1GE-L1EEA

p = (PERSONNE*) malloc(nb_pop*sizeof(PERSONNE));
if (population != NULL)
{
printf("Saisie de la première personne\n") ;
printf("\t\t\t Son nom : ") ;
gets(nom) ;
printf("\t\t\t Son adresse : ") ;
gets(adr) ;
i=0;
while((i<nb_pop)&&((p+i)->nom=(char*)malloc((strlen(nom)+1)*sizeof(char)))&&
((p+i)->adr=(char*)malloc((strlen(adr)+1)*sizeof(char))))
{strcpy((p+i)->nom, nom);
strcpy((p+i)->adr, adr);
printf(“\t\t\t Son age:”);
scanf("%d",&((p+i)->age)) ;
printf("Saisie de la %d ieme personne\n",i+1) ;
printf("\t\t\t Son nom : ") ;
gets(nom) ;
printf("\t\t\t Son adresse : ") ;
gets(adr) ;
i++;
}
printf(“ Nombre de personnes effectivement saisies: %d”,i);
}
else
printf("Pas assez de mémoire pour cette opération\n");
return 0;
}

2.2. La fonction calloc


a) Syntaxe
calloc(<nelt>, <taille>)
b) Comportement
Cette syntaxe alloue et initialise à zéro <nelt> emplacements mémoire de taille <taille>
chacun et fournit l’adresse de la zone en cas des succès et NULL sinon. calloc prend deux paramètres
de type size_t et retourne un pointeur de type void*. calloc est déclarée dans le fichier d’en tête
stdlib.h.
Exemple : Remplir un tableau avec les carrés des n premiers entiers.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{int *tab, i, n ;
printf(“Donner le nombre d’elements :”) ;
scanf(“%d“ , &n) ;
tab =(int*)calloc(n,sizeof(int));
if(tab!=NULL)
{for (i = 0; i<n; i++)
*(tab++)= i*i;/* ou tab[i]=i*i ou *(tab+i)= i*i */
tab-=n;/* pour que tab pointe sur le début du tableau*/
}
return 0;
}

53
Cours de C L1ELN-L1GE-L1EEA

2.3. La fonction de réallocation realloc


a) Syntaxe
realloc(<ptr>, <nlle_tail>);
b) Comportement
realloc essaie de réajuster à une valeur donnée la taille d’un emplacement
préalablement alloué par malloc, calloc ou realloc.
realloc prend en argument le pointeur <ptr> sur la zone mémoire dont il faut modifier la
taille, et la nouvelle taille <nlle_tail> désirée. Si l'opération s'est bien passée, realloc
retourne le pointeur sur la zone mémoire ; sinon realloc retourne NULL.
Lorsque la taille demandée est supérieure à la taille précédente, le contenu de l’emplacement
précédent est conservé. Si la taille demandée est inférieure à la taille précédente, le début de
l’emplacement précédent est conservé jusqu’à la taille demandée.
Il est fort possible que le nouveau pointeur soit différent de l'ancien. En effet, si la zone
mémoire doit être augmentée, et que la zone en cours n'est pas assez grande pour supporter la
nouvelle taille, realloc allouera une nouvelle zone de mémoire contigue et recopiera
complètement l'ancienne zone. Il est donc impératif de ne pas adresser directement par un
pointeur un espace mémoire dont la taille doit ou peut être modifiée par realloc.
La fonction realloc est déclarée dans le fichier d’en tête stdlib.h.
Exemple : Saisie d'une chaîne de longueur variable.
#include <stdio.h>
#include <stdlib.h>
int main( void ) {
int t = 16; /* taille du buffer */
char*buf = (char*)malloc(16);
char *pos = buf; /* position d'ecriture dans le buffer */
while ( (*pos = getchar()) != '\n' ) {
pos++;
if ( pos == buf + t ) {/* on augmente la taille de 16 octets */
buf = (char *) realloc(buf, t + 16);
pos = buf + t; /* le buffer peut être a une autre adresse ! */
t += 16;
}
}
*pos++ = '\0';
/* on ajuste la taille du buffer a la taille réelle de la chaine */
buf = (char *) realloc(buf, pos - buf);
puts(buf);
return 0;
}
Remarque :
1° Si l'opération échoue, realloc retourne NULL, mais ne désalloue pas la zone d'origine. Il est
impératif de ne pas réassigner le même pointeur immédiatement avec le résultat de realloc au
risque de perdre toutes les données en mémoire et de ne jamais pouvoir les libérer.
ptr = realloc(ptr, nlle_taille);
/*S’il y a échec, l’emplacement précédemment pointé par ptr n’est libéré et on ne dispose
plus de son adresse. Il vaut mieux procéder ainsi : */
void *p;
p = realloc(ptr, nlle_taille)
if (p != NULL)
{
.........
}
2° realloc(NULL, taille) équivaut à malloc(taille) et realloc(ptr, 0) est équivaut à free(ptr)

54
Cours de C L1ELN-L1GE-L1EEA

3. Libération de la mémoire dynamique


La libération de la mémoire allouée dynamiquement est à la charge du programmeur. En C, on
dispose de la fonction free pour la désallocation de la mémoire.
a) Syntaxe
free(<ptr>);
b) Comportement
La fonction free annule la réservation d'une zone de mémoire pointée par <ptr>,
allouée par malloc ou calloc ou realloc ou par toute autre fonction réservant de la
mémoire. La fonction free est déclarée dans le fichier d’en tête stdlib.h.
Exemple:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *ptr = NULL, i;
ptr = (int *) malloc(10 * sizeof(int));
for (i=0; i < 10; i++)
ptr[i] = i * 10;
printf("%d\n", i[9]);
free(ptr);
return 0;
}
Remarque :
1° La fonction free ne change pas le contenu du pointeur <ptr> ; il est conseillé d'affecter la valeur
NULL au pointeur immédiatement après avoir libéré le bloc de mémoire qui y était attaché.
2° Si nous ne libérons pas explicitement la mémoire à l'aide free, alors elle est libérée
automatiquement à la fin du programme.
4. Fonctions de manipulation de blocs de mémoire
Il existe une série de fonctions permettant de manipuler des blocs de mémoire de type
quelconque. Elles se distinguent des fonctions de chaînes de caractères de la forme strxxx par le fait
qu’il faut spécifier la taille des blocs intervenants. Ces fonctions sont déclarées dans les fichiers d’entête
string.h et mem.h.
4.1. La fonction memchr
a) Syntaxe
memchr(<src>, <c>, <nb>)
b) Comportement
memchr recherche la première occurrence du caractère <c> dans les <nb> premiers octets du
bloc de mémoire pointé par <src>; elle retourne un pointeur sur la première occurrence de <c> ou le
pointeur nul si <c> n’est pas contenu dans le bloc pointé par <src>.
Exemple :
#include <stdio.h>
#include <string.h>
int main(void) {
char ch[30];
char *ptr;
strcpy(ch, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
ptr = (char *) memchr(ch, 'I', strlen(ch));
if (ptr)
printf("Le caractere 'I' est en position: %d\n", ptr - ch);
else
printf("Le caractere 'I' n'est pas trouve\n");
return 0;
}

55
Cours de C L1ELN-L1GE-L1EEA

4.2. La fonction memcmp


a) Syntaxe
memcmp(<blc1>, <blc2>, <nb>)
b) Comportement
memcmp compare les <nb> premiers octets des blocs de mémoire pointés par <blc1> et
<blc2> considérés comme des caractères non signés (unsigned char); elle retourne un nombre
négatif, zéro, ou un nombre positif selon que <blc1> est respectivement inférieur, égal ou supérieur à
<blc2>
Exemple :
#include <stdio.h>
#include <string.h>
int main(void) {
char *blc1 = "ABCDE";
char *blc2 = "abc";
int etat = memcmp(blc1, blc2, strlen(blc2));
if (etat > 0)
printf("%s > %s\n", blc1, blc2);
else
if (etat == 0)
printf("%s == %s\n", blc1, blc2);
else
printf("%s < %s\n", blc1, blc2);
return 0;
}
4.3. La fonction memcpy
a) Syntaxe
memcpy(<dest>,<src>, <nb>)
b) Comportement
memcpy copie les <nb> premiers octets composant le bloc de mémoire pointé par <src> dans
le bloc de mémoire pointé par <dest> en écrasant le contenu précédent de ce bloc. <dest> doit au
minimum avoir de l’espace pour les <nb> octets à copier ; de plus les deux ne doivent pas se
chevaucher, c’est à dire <src>+<nb> < <dest> ou <dest>+<nb> < <src>.
Exemple :
#include <stdio.h>
#include <string.h>
int main(void)
{ int tab[2][5] = { { 1, 2, 3, 4, 5},{11, 12, 13, 14, 15} };
int temp[2][5];
memcpy(temp, tab, sizeof(tab));/* copie tab dans tmp */
printf("temp[1][4] = %d\n", temp[1][4]);/* affiche : temp[1][4] = 15 */
return 0;
}

4.4. La fonction memmove


a) Syntaxe
memmove(<dest>,<src>, <nb>)
b) Comportement
memmove est identique à memcpy mais les blocs peuvent se chevaucher.
4.5. La fonction memset
a) Syntaxe
memset(<blc>, <c>, <nb>)
b) Comportement
memset positionne tous les <nb> premiers octets de <blc> à la valeur de <c> et retourne le

56
Cours de C L1ELN-L1GE-L1EEA

pointeur <blc>.
Exemple :
#include <stdio.h>
#include <string.h>
int main(void) {
char blc[] = "E.N.A.C.";
printf("bloc avant 'memset': %s\n", blc);
memset(bloc, '*', strlen(blc));
printf("bloc apres 'memset': %s\n", blc);
return 0;
}
5. Tableaux dynamiques
La similitude entre les tableaux et les pointeurs permet de réaliser des tableaux dynamiques,
c’est-à-dire des tableaux dont la taille n’est pas connue au moment de la compilation mais uniquement
lors de l’exécution, lorsque le tableau commence à exister. Pour cela il suffit de :
– remplacer la déclaration du tableau par celle d’un pointeur ;
– allouer l’espace à l’exécution, avant toute utilisation du tableau, par un appel de malloc ;
– dans le cas d’un tableau local, libérer l’espace à la fin de l’utilisation.
Pour tout le reste, l’utilisation de ces tableaux dynamiques est identique à celle des tableaux normaux.
Exemple : Programme calculant dans un tableau la ligne de rang N du triangle de Pascal.
int main(void)
{int n, p, i, N;
int *tab;
printf(“Numero de la ligne:”);
scanf(″%d″, &N) ;
tab = (int*)malloc((N + 1) * sizeof(int));
if (tab = = NULL)
exit(-1) ;
for (n = 0; n <= N; n++)
{/* calcul des coefficients de la ligne n° n*/
tab[0] = tab[n] = 1;
for (p = n - 1; p > 0; p--)
tab[p] += tab[p - 1];
/* affichage des coefficients de la ligne n° n */
for (i = 0; i <= n; i++)
printf("%5d", tab[i]);
printf("\n");
}
free(tab);
getch() ;
return 0 ;
}
6. Règles de base d’utilisation des pointeurs
• Veiller à toujours initialiser les pointeurs que l’on utilise (à NULL, …).
• Tester systématiquement si une demande d’allocation (malloc, calloc,
realloc, ..) a été satisfaite.
• Tester la validité de tout pointeur utilisé.
• Toujours désallouer avec free la mémoire allouée dynamiquement.
• Toujours remettre à 0 (NULL) les pointeurs déalloués par free.

57
Cours de C L1ELN-L1GE-L1EEA

Chapitre VIII: Fonctions en C


I. Généralités

1. Notion de fonction
Dans l’écriture d’un programme, on peut être confronté aux cas suivants :
les différentes parties du programme sont indépendantes ;
des séquences identiques d'instructions apparaissent en plusieurs points du programme;
des séquences identiques d'instructions peuvent être utilisées dans d'autres programmes.
Cela amène des répétions sont:
– lourdes (on doit recopier les même instructions à plusieurs endroits)
– dangereuses (bug du copier-coller)
– cause de difficultés de lecture du programme
D’où la nécessité de regrouper des fois certaines instructions en sous-programmes appelées fonctions.
Une fonction est un ensemble d'instructions nommé réalisant une tâche précise, auquel on passe
généralement des données et qui retourne le plus souvent une valeur.
Les fonctions ont un rôle très important dans :
La structuration des programmes : Elles permettent de structurer le programme en composants
fermés et cohérents. L’analyse descendante par raffinements successifs divise le problème en sous-
parties. Les fonctions seront naturellement des outils adaptées à la description de ces sous-parties
cohérentes. Notons que la définition d’une fonction n’est pas liée au nombre d’utilisations
(fréquence d’appel), mais à la notion de module (entité) ; autrement dit une suite d’instructions
pourra être déclarée comme fonction même si elle n’est exécutée qu’une seule fois.
La factorisation des calculs ou des traitements: Les instructions identiques apparaissant en
plusieurs endroits dans le programme sont regroupées en un seul endroit sous forme de fonction.
La réutilisabilité: Les instructions d'une fonction peuvent être dans une autre fonction sans
l'utilisateur ne sache comment ces instructions sont écrites.
Le paramétrage des programmes : Certaines séquences d’instructions ont de fortes ressemblances,
mais ne diffèrent par la valeur de certains identificateurs ou expressions. Le mécanisme de
paramétrage d’une fonction permettra de considérer une séquence d’instructions comme un schéma
de calcul abstrait dont les paramètres représenteront des valeurs particulières à chaque exécution de
la séquence d’instructions. Le paramétrage permet donc d’appliquer une fonction à des contextes
différents.
La lisibilité et la maintenance des programmes : une conséquence de la structuration des
programmes à l’aide de fonctions est l’augmentation de la lisibilité. De plus les fonctions permettent
de réduire la taille des programmes et de faciliter leurs maintenances.
2. Définition de fonction
Définir une fonction, c’est associer un nom unique à une suite d’instructions. La définition d’une
fonction comporte deux parties :
L’en-tête
Appelée aussi signature ou prototype, spécifie :
− Le nom de la fonction; il suit les mêmes règles que celui d’une variable ;
− Les paramètres formels, leurs types et leurs modes de passage. Certaines fonctions, pour
pouvoir s'exécuter, ont besoins qu'il leur soit communiqué des informations. Au moment où on
définit une fonction, on ne peut pas savoir déjà avec quelles valeurs de chacune de ces
informations elle va s'exécuter. C'est pourquoi on utilise, chaque information, un nom qui
représente l'ensemble des valeurs possibles de l'information que la fonction va recevoir. Ce
nom, c'est le paramètre formel ou muet. C’est une variable dont la valeur et l’adresse sont
connues à l’exécution. Pour chaque paramètre, il faut indiquer son nom, son type, et son mode
de passage. Les définitions des paramètres sont placées entre parenthèses () et séparées par des
virgules. Si la fonction ne possède pas de paramètres, on remplace la liste par le mot-clé void.
− Le type du résultat c'est-à-dire le type de la valeur qu'elle est sensée retourner. Si la fonction
ne renvoie aucune valeur, le type vaut void. Contrairement à d'autres langages, il n'y a pas
en C de notion de procédure; une procédure est implémentée par une fonction ne renvoyant pas
de valeur.

58
Cours de C L1ELN-L1GE-L1EEA

Le corps
Il contient la description, délimitée par des accolades {}, de ce que fait la fonction. Comme une
fonction est un sous-programme, le corps de la fonction débute éventuellement par des déclarations
de variables, qui sont locales à cette fonction. Il se termine par l'instruction de retour à la fonction
appelante, return, dont la syntaxe est :
return( [<expression>]);
La valeur de <expression> est la valeur que retourne la fonction. Son type doit être le même que celui
qui a été spécifié dans l'en-tête de la fonction. Si le type de <expression> est différent du type du
résultat tel qu’il a été déclaré dans l’en-tête, des instructions de conversion sont automatiquement
mises en place par le compilateur.
Si la fonction ne retourne pas de valeur (fonction de type void), l’instruction return;( sans
expression) achève la définition ou elle est simplement omise.
Plusieurs instructions return peuvent apparaître dans le corps d’une fonction ; le retour au
programme appelant sera alors provoqué par le premier return rencontré lors de l'exécution.
On a donc la syntaxe suivante pour la définition d’une fonction :
<type> <nom_fonction>([<type1> <paramètre1>, <type2> <paramètre2>, ..., <typen> <paramètren>])
{
[<déclarations de variables locales> ]
<liste d'instructions>
}
Exemple :
Remarque :
• Lors de la définition d’une fonction, le compilateur enregistre les instructions de la fonction mais
n’exécute aucune instruction.
• Dans l’en-tête d’une fonction, il n’est pas possible de mettre en facteur un type commun à plusieurs
paramètres.
• Dans la première version du langage C, l’en-tête d’une fonction se définissait comme suit :
<type> <fonction>([<paramètre1>, <paramètre2>, ..., <paramètren>] )
type1> <paramètre1>, . . ., typen> <paramètren> ;
{
[<déclarations de variables locales> ]
<liste d'instructions>
}
La norme ANSI autorise toujours cette forme.
• Le programme principal en C est une fonction; c'est la fonction principale appelée main. Elle constitue
le point d’entrée du programme.
• Contrairement à d’autres langages, les fonctions C ne peuvent être imbriquées. Un programme en C
est une suite de fonctions s’appelant entre elles, dont la première à s’exécuter est la fonction main.
3. Appel de fonction
L’appel d’une fonction consiste à demander l’exécution des instructions de la fonction sur un ensemble de données
que l’on fournit en entrée. Pour appeler une fonction, il faut :
Indiquer son nom ;
Donner entre parenthèses ( ) les paramètres effectifs, séparés par des virgules : Quand on appelle
une fonction, on lui transmet les véritables valeurs des informations sur lesquelles elle doit
travailler : ce sont les paramètres effectifs ou réels. D’un appel à l’autre de la fonction les
paramètres effectifs utilisés seront probablement différents. Lorsque la fonction n’a pas besoin de
paramètres, les parenthèses restent vides.
D’où la syntaxe :
<nom_fonction>([<arg1>, <arg2>, ..., <argn>])
Exemple :

L’appel d’une fonction est une instruction élémentaire et l’appel d’une fonction renvoyant est une
expression qui peut apparaître dans toute expression.
Une fonction peut être appelée soit dans la fonction principale, soit dans une autre fonction.
Voici les étapes de l’appel d’une fonction :

59
Cours de C L1ELN-L1GE-L1EEA

• Enregistrement du contexte de la fonction appelante ;


• Débranchement vers la fonction appelée ;
• exécution du corps de la fonction appelée ;
• retour de la fonction appelée; après l’exécution du corps de la fonction appelée, celle-ci
s’achève et la prochaine instruction exécutée est celle qui suit immédiatement l’appel de la
fonction-programme dans le sous-programme appelant
L'appel et le retour de la fonction implique un transfert de contrôle de l'appelant (programme principal
ou autre fonction) vers l'appelé (fonction) puis de l’appelé vers l’appelant.
Appelé
{
.........................
Appelant .........................
{
......................... }
.........................
}

4. Modes de transmission des paramètres

Lors de l’appel d’une fonction, les paramètres effectifs sont mis en correspondance avec les paramètres
formels, un à un et de gauche à droite avec vérification du type des paramètres. On impose que le
nombre de paramètres effectifs soit identique à celui des paramètres formels. On appelle mode de
passage (ou de transmission) de paramètres, la manière dont les paramètres effectifs sont associés aux
paramètres formels. Il existe essentiellement deux manières de passer des paramètres effectifs à une
fonction pour qu’elle puisse les manipuler au travers des paramètres formels :
• Mode de passage par valeur
Il consiste à affecter au nom du paramètre formel la valeur du résultat de l’évaluation du paramètre
effectif ; le paramètre effectif sert donc à fournir une valeur initiale au paramètre formel.
Lors de l’appel d’une fonction ayant un paramètre passé par valeur :
- il y a création d’un emplacement local à la fonction, correspondant au paramètre formel;
- il y a copie de la valeur du paramètre réel dans l’emplacement local ;
- les modifications subies par le paramètre formel dans la fonction sont subies par l’emplacement local ;
- au retour, l’emplacement local est détruit et le paramètre réel conserve sa valeur.
Ce mode de passage a les propriétés suivantes :
- Les paramètres effectifs peuvent être des variables, des constantes ou des expressions ;
- Les modifications du paramètre formel restent locales à la fonction et n’affectent pas le paramètre
réel ;
- Le temps d’exécution élevé par rapport aux autres modes de passage à cause du temps de recopie du
paramètre réel ;
- Le mode de passage par valeur consomme de l’espace mémoire car pendant l’exécution de la fonction,
le paramètre réel existe en deux exemplaires.
Ce mode de passage est déconseillé pour de grandes structures de données.
• Mode de passage par variable
Ce mode consiste à passer non plus la valeur des variables comme paramètre, mais à passer les variables
elles-mêmes.
Ce mode a les propriétés suivantes :
- pas de copie du paramètre réel, d’où rapidité d’exécution et économie de mémoire ;
- Possibilité de modifier le paramètre réel ;
- les paramètres doivent être des lvalues.
a) Mode de passage de paramètres en C
En C, seul le mode de passage par valeur est permis
b) Passage par pointeur
Le seul mode de passage existant en C étant le passage par valeur, pour simuler le passage par variable,
on utilise le passage par pointeur. Le passage par pointeur est en fait un passage par valeur dans lequel
les valeurs passées sont des adresses.

60
Cours de C L1ELN-L1GE-L1EEA

Exemple :

5. Déclaration de fonction
Comme tout objet en C, une fonction doit être déclarée avant son utilisation. Cette déclaration consiste à
donner le prototype de la fonction suivie d’un point virgule :
<type> <nom_fonction>([<type1> <paramètre1>, <type2> <paramètre2>, ..., <typen> <paramètren>]) ;
Les noms de paramètre sont optionnels, mais il est fortement conseillé de les laisser. Cela donne une
bonne indication sur leurs rôles.
Exemple :

La déclaration permet au compilateur de vérifier que le nombre et le type des paramètres utilisés dans la
définition concordent bien avec le prototype, de mettre en place d'éventuelles conversions des
paramètres effectifs, lorsque la fonction est appelée avec des paramètres dont les types ne correspondent
pas aux types indiqués dans le prototype.
La déclaration d’une fonction peut apparaître à l’intérieur d’une fonction l’utilise, auquel cas la
déclaration est locale ou apparaître avant la définition de toute fonction ; dans ce cas la déclaration est
globale.
Pour une plus grande lisibilité, mais aussi pour simplifier la maintenance du code, il est conseillé de
regrouper tous les prototypes d'un module (fichier xxx.c) dans un fichier d’en-tête (<xxx.h>). Ce dernier
n'a plus alors qu'à être inclus dans le code qui utilise ces fonctions. C'est le cas des fonctions prédéfinies
de la bibliothèque standard.
6. La fonction main
L’exécution d’un programme commence par la fonction principale main. Tout se passe comme si le
système d’exploitation appelle cette fonction comme une fonction ordinaire. La fonction main doit
retourner un entier dont la valeur est transmise à l'environnement d'exécution. Cet entier indique si le
programme s'est ou non déroulé sans erreur. La valeur de retour 0 correspond à une terminaison
correcte, toute valeur de retour non nulle correspond à une terminaison sur une erreur. On peut utiliser
comme valeur de retour les deux constantes symboliques EXIT_SUCCESS (égale à 0) et
EXIT_FAILURE (égale à 1) définies dans le fichier d’en-tête stdlib.h.
Lorsqu'elle est utilisée sans arguments, le prototype valide de la fonction main est : int main(void);
Dans les environnements comme UNIX ou MS DOS, un programme C peut recevoir une liste
d'arguments au lancement de son exécution. La ligne de commande qui sert à lancer le programme est,
dans ce cas, composée du nom du fichier exécutable suivi par des paramètres. La fonction main reçoit
tous ces éléments de la part de l'interpréteur de commandes. En fait, la fonction main possède deux
paramètres formels, appelés par convention argc (argument count) et argv (argument vector). argc est
une variable de type int dont la valeur est égale au nombre de mots composant la ligne de commande (y
compris le nom de l'exécutable). Elle est donc égale au nombre de paramètres effectifs de la fonction +
1. argv est un tableau de chaînes de caractères correspondant chacune à un mot de la ligne de
commande. Le premier élément argv[0] contient le nom de la commande (du fichier exécutable), le
second argv[1] contient le premier paramètre…Le second prototype valide de la fonction main est donc :
int main(int argc, char * argv[]);
Exemple :
Le programme suivant calcule le produit de deux entiers, entrés en arguments de l'exécutable.
#include <stdio.h>
#include <stdlib.h> /* pour la fonction atoi */
int main(int argc, char *argv[])
{ int a, b;
if (argc != 3)
{ printf("\nErreur : nombre invalide d'arguments");
printf("\nUsage: %s int int\n",argv[0]);
return(EXIT_FAILURE);
}
a = atoi(argv[1]);
b = atoi(argv[2]);

61
Cours de C L1ELN-L1GE-L1EEA

printf("\nLe produit de %d par %d vaut : %d\n", a, b, a * b);


return(EXIT_SUCCESS);
}
On lance donc l'exécutable avec deux paramètres entiers, par exemple,
[Link] 12 8
Ici, argv sera un tableau de 3 chaînes de caractères argv[0], argv[1] et argv[2] qui, dans notre exemple,
valent respectivement "[Link]", "12" et "8". Enfin, la fonction de la librairie standard atoi, déclarée dans
stdlib.h, prend en argument une chaîne de caractères et retourne l'entier dont elle est l'écriture décimale.
Tout autre prototype de n'est pas du tout portable et ne doit jamais être utilisé (même s'il est accepté par
votre compilateur).

II. Passage de tableau en paramètre


Les tableaux peuvent être des paramètres de fonctions.
1. Paramètre tableau de taille fixe
Lorsqu’ une fonction doit s’appliquer des tableaux de même taille, la taille apparaît dans le type du
paramètre formel correspondant.
Exemple :
# include <stdio.h>
# define NB 10
int main(void)
{ int nbEt; /* Nombre d’étudiants*/
int i ;
float note[NB] ; /* tableau devant contenir les notes d’un étudiant */
void lire_note(float not[NB]) ; /* déclaration de lire_note qui permet d’entrer les notes*/
float cal_moy(float not[NB]) ; /* déclaration de cal_moy qui calcule la moyenne des notes*/
printf(‘’ Entrer le nombre d’etudiants : ‘’) ;
scantf(‘’%f’’, &nbEt) ;
for ( i = 0; i < nbEt; i++)
{ Printf(‘’ Entrer les notes de l’etudiant n° %d\n’’,i+1) ;
lire_note(note); /* appel de lire_note sur le tableau note*/
printf(‘’\n’’) ;
printf(‘’ la moyenne de l’etudiant n° %d est %.2f’’, i+1 , cal_moy(note));
printf(‘’\n\n\n’’) ;
}
return 0 ;
}
void lire_note(float not[NB]) /* définition de lire_note */
{ int i;
printf(‘’ 1ere note: ‘’);
scanft(‘’%f’’,&not[0]);
for (i = 1; i < NB; i++)
{ printf(‘’ %ieme note: ‘’, i+1);
scanft(‘’%f’’,&not[i]);
}
}
float cal_moy(float not[NB]) /* définition de cal_moy */
{ float moy, som = 0;
for (i = 0; i < NB; i++)
som += not[i];

moy = som/NB ;

62
Cours de C L1ELN-L1GE-L1EEA

return moy;
}
2. Paramètre tableau de taille variable
Lorsqu’ une fonction doit s’appliquer des tableaux de tailles différentes, la taille est omise dans le type
du paramètre formel correspondant.
Exemple :
void lire_note(tab[])
{
……………..
}
Cependant dans la pratique, afin d’éviter d’utiliser des variables globales, le nombre d’éléments sera
fournie comme paramètre supplémentaire de la fonction.
Exemple :
# include <stdio.h>
# define NB1 10
#define NB2 15
int main(void)
{ int i ;
float note1[NB1] ;
float note2[NB2] ;
void lire_note(float tab[], int n) ; /* déclaration de lire_note */
lire_note(note1, NB1) ;
lire_note(note2, NB2) ;
return 0 ;
}
void lire_note(float tab[], int n) /* définition de lire_note */
{ int i;
printf(‘’ 1ere note: ‘’);
scanft(‘’%f’’,&not[0]);
for (i = 1; i < n; i++)
{ printf(‘’ %ieme note: ‘’, i+1);
scanft(‘’%f’’,&not[i]);
}
}
3. Mode de transmission des paramètres tableaux
Considérons l’exemple suivant :
# include <stdio.h>
/* échange les éléments d’indices donnés d’un tableau*/
void echange_tableau(double t[], int m, int n)
{double temp = t[m] ;
t[m] = t[n] ;
t[n] = temp;
}
int main (void)
{double tab[] = {11.5,2,13,24,15.03,6};
echange_tableau(t, 2, 4);
/* affichage des éléments du tableau après l’appel*/
for (i = 0 ; i < 5 ;i++)
printf(‘’ tab[%d] = %.2f’’, i, tab[i]);
return 0;
}
A l’exécution, on a:
tab[0] = 11.50 tab[1] = 2.00 tab[2] = 15.03 tab[3] = 24.00 tab[4] = 13.00 tab[5] = 6.00

63
Cours de C L1ELN-L1GE-L1EEA

On constate que le tableau tab a modifié après l’appel de la fonction echange_tableau.


En fait, lorsqu’un tableau est passé comme paramètre effectif à une fonction, il est affecté par les
modifications de la fonction. Ce comportement, qui semble contraire au mode de passage par valeur du
langage s’explique par le lien entre les tableaux et les pointeurs comme on l’a vu dans le chapitre sur les
pointeurs.

II. Classe de stockage d'une variable


1. Portée d’une variable
La portée d'une variable est la zone du programme dans laquelle elle est accessible. La portée d’une
variable dépend de l’endroit où elle déclarée :
• La portée d’une variable déclarée avant la définition de toute fonction s’étend sur tout le programme ;
• La portée d’une variable déclarée dans une fonction ou dans un bloc se limite à cette fonction ou à ce
bloc.
2. Durée de vie d’une variable
La durée de vie d'une variable est le temps pendant lequel elle existe. On distingue :
a) Les variables permanentes (ou statiques)
Une variable permanente occupe un emplacement en mémoire qui reste le même durant toute l'exécution
du programme. Cet emplacement est alloué une fois pour toute lors de la compilation dans une zone
mémoire appelée segment de données. Par défaut les variables permanentes sont initialisées à leur valeur
nulle par le compilateur.
b) Les variables temporaires
Les variables temporaires se voient allouer un emplacement en mémoire de façon dynamique lors de
l'exécution du programme. Elles ne sont pas initialisées par défaut. Leur emplacement en mémoire est
libéré par exemple à la fin de l'exécution d'une fonction secondaire.
3. Variables globales
Une variable globale est une variable déclarée en dehors de toute fonction. Sa portée s’étend sur la
portion de code qui suit sa déclaration. Les variables globales ont une durée de vie permanente.
Exemple :
int n; /* variable globale */
void fonction();
void fonction()
{ n++;
printf("appel numero %d ",n);
return;
}
int main(void)
{ int i;
for (i = 0; i < 5; i++)
fonction();
return 0 ;
}
Le programme affiche :
appel numero 1 appel numero 2 appel numero 3 appel numero 4 appel numero 5
a) Variables externes
Il est parfois nécessaire d’utiliser une variable globale commune à plusieurs fichiers sources. Dans ce
cas, il est indispensable que le compilateur comprenne que deux variables portant le même nom mais
déclarées dans deux fichiers différents correspondent en fait à un seul objet. Pour cela la variable doit
être définie (déclarée avec réservation de mémoire) une seule fois. Dans les autres fichiers qui
l’utilisent, il faut simplement faire référence à cette variable, sous forme d’une déclaration précédée par
le mot clé extern. Cette déclaration n’alloue pas d’espace mémoire ; elle prévient le compilateur de
l’existence d’un objet défini ailleurs et le renseigne sur le type de l’objet.
Exemple :
/* Fichier 1 */
unsigned long n ; /* définition de la variable n */

64
Cours de C L1ELN-L1GE-L1EEA

……………………………………………………………………………………………….
/* Fichier 2 */
extern unsigned long n /* fait référence à la variable n définie dans le fichier 1 */
……………………………………………………………………………………………….
Remarque :
Une déclaration extern ne doit pas comporter d’initialisation puisqu’elle n’alloue pas la variable.
Dans une déclaration extern de tableau, il est inutile d’indiquer la taille de celui-ci puisqu’elle n’alloue
pas le tableau. Par exemple si dans un fichier où on a les définitions suivantes :
unsigned long n = 100 ; /* Allocation et initialisation de la variable n */
int table[100]; /* Allocation d’un tableau de 100 entiers */
on pourra faire référence à ces variables dans un autre fichier en écrivant:
extern unsigned long n;
extern int table[];
b) Variables globales cachées
Par défaut les variables globales peuvent être partagées entre plusieurs fichiers. Cependant il est possible
de rendre inaccessible une variable globale à l’extérieur du fichier où elle a été définie, à l’aide du mot
clé static. Avec cette définition, il est impossible de faire référence à la variable depuis un autre fichier
à l’aide de la déclaration extern.
Exemple :
/* fich1.c */ /* fich2.c */
int tab[10]; extern int tab[10]
static int i; static int i, j;
static char j;
Remarque :
Dans l’ensemble des fichiers qui constituent un programme, chaque variable globale non cachée
• doit avoir un nom unique
• doit faire l’objet d’une et une seule définition
• peut être déclarée externe (y compris dans le fichier où elle est définie) un nombre quelconque de fois.
4. Variables locales
On appelle variable locale une variable déclarée à l'intérieur d'une fonction ou d'un bloc d'instructions
du programme.
La portée d’une variable locale se limite à l’intérieur de la fonction où elle est déclarée.
Lorsque le nom d’une variable locale est identique au nom d’une variable globale, la variable globale est
localement masquée ; dans cette fonction, la variable globale devient inaccessible.
Exemple :
int n = 10; /* variable globale */
void fonction(void);
void fonction( void)
{ int n = 0; /* variable locale masquant la variable globale n */
n++;
printf("appel numero %d ",n);
return;
}
int main(void)
{ int i;
for (i = 0; i < 5; i++)
fonction();
}
Le programme affiche :
appel numero 1 appel numero 1 appel numero 1 appel numero 1 appel numero 1

65
Cours de C L1ELN-L1GE-L1EEA

La durée de vie des variables locales dépend de leur emplacement en mémoire.


a) Variables locales automatiques
Les variables locales automatiques sont allouées automatiquement dans une mémoire appelée pile au
moment de l'exécution ; elles sont détruites de la pile à la fin de la fonction dans laquelle elles sont
définies. Si une variable automatique est initialisée, la valeur sera affectée à chaque appel de la
fonction. Les variables automatiques sont déclarées avec le mot-clé auto; cependant les variables
locales étant par défaut automatiques, ce mot-clé est généralement omis.
b) Variables locales critiques
Les variables critiques sont des variables locales allouées dynamiquement dans un registre du
processeur à l'exécution. Une variable critique est déclarée à l’aide du mot-clé register. Il informe le
compilateur que la variable est fréquemment accédée et qu’il y a lieu d’accélérer son accès en la
plaçant dans un registre. Le nombre de registres étant limité, cette requête ne sera satisfaite que s'il reste
des registres disponibles.
Exemple :
char* strcpy(char* dest, char* srce)
{register char* d = dest, *s = srce ;

while ((*d+ + = *s+ +) !=0) ;


return dest;
}
Remarque :
• Une variable critique est d’un type simple (caractère, nombre ou pointeur)
• Une variable critique n'a pas d'adresse en mémoire, on ne peut donc pas affecter un pointeur à
l'adresse d'une variable critique.
• Cette technique permettant d'accélérer les programmes a aujourd'hui perdu tout son intérêt. Grâce aux
performances des optimiseurs de code intégrés au compilateur, il est maintenant plus efficace de
compiler un programme avec une option d'optimisation que de placer certaines variables dans des
registres.
c) Variables locales statiques (rémanentes)
Les variables locales statiques sont allouées à la compilation dans un segment de données. Ce sont de
variables permanentes. Au cours des appels successifs de la fonction où elles sont déclarées elles
conservent leurs anciennes valeurs. Elle est également initialisée à zéro à la compilation. Les variables
locales statiques sont déclarées avec le mot-clé static.
Exemple :
int n = 10;
void fonction();
void fonction()
{ static int n;
n++;
printf("appel numero %d ",n);
return;
}
int main(void)
{ int i;
for (i = 0; i < 5; i++)
fonction();
return 0 ;
}
Ce programme affiche :
appel numero 1 appel numero 2 appel numero 3 appel numero 4 appel numero 5
On voit que la variable locale n est statique (elle est initialisée à zéro, et sa valeur est conservée d'un
appel à l'autre de la fonction). Par contre, il s'agit bien d'une variable locale, qui n'a aucun lien avec la
variable globale du même nom.
5. Règles de portée des variables
Les variables globales sont accessibles dans tout le programme, y compris les fonctions.

66
Cours de C L1ELN-L1GE-L1EEA

Les variables locales ne sont accessibles que dans la fonction qui les a déclarées.
Les paramètres formels d’une fonction ne sont accessibles que dans la fonction; ce sont donc des
variables locales.
Lorsque le nom d’une variable locale est identique à une variable globale, la variable globale est
localement masquée ; dans cette fonction, la variable globale devient inaccessible.
Une fonction peut donc travailler :
sur des données du programme appelant (variables globales)
sur des données transmises par l'appelant (paramètres en mode entrée)
sur des données lui sont propres (variables locales)
Ainsi on dispose de deux méthodes d’échange d’information entre une fonction appelante et une
fonction appelée :
Par les paramètres
Par les variables globales; mais cette méthode peut conduire à des effets indésirables.
6. Classes d’allocation d’une variable
La classe d’allocation d'une variable permet de spécifier son accès, sa durée de vie et son emplacement
en mémoire. En C, il existe quatre classes d’allocation :
Classe automatique(auto) : Les variables de classe auto sont des variables locales à une fonction ou à
un bloc ; elles sont allouées lors de l’activation de la fonction ou du bloc et disparaissent à la sortie
de la fonction ou du bloc. Elles sont stockées sur une pile. La classe auto est la classe par défaut
des variables locales.
Classe statique(static) : Les variables de classe static sont des variables locales déclarées avec le mot-
clé static ou des variables globales (déclarées avec ou sans static ). Leur durée de vie est
permanente. Elles sont stockées en zone donnée statique créée lors de la compilation.
Classe registre (register) : Les variables de classe register sont des variables critiques. Elles
disparaissent après l’activation de la fonction ou du bloc qui les contient. Elles sont stockées dans
un registre.
Classe externe (extern) : La classe extern déclare des variables globales qui sont définies dans un
autre fichier.
En résumé :
Type de variable Déclaration Portée Durée de vie Classe d’allocation
Globale En dehors de toute La partie du fichier source
fonction suivant sa déclaration Static
En dehors de toute Uniquement la partie du Extern
Globale cachée fonction, avec l’attribut fichier source suivant sa
static déclaration
Permanente
Globale externe En dehors de toute
fonction, avec l’attribut N’importe quel fichier
Static
extern
Locale statique Au début d’une fonction La fonction
Locale automatique Au début d’une fonction La fonction Auto
Temporaire
Locale critique Au début d’une fonction La fonction Register

III. Fonction statique


Les fonctions en C sont naturellement globales; elles peuvent donc être partagées entre plusieurs
fichiers ; cependant il n’est pas nécessaire pour cela, comme c’est le cas des variables, d’utiliser la
déclaration extern.
Comme pour les variables globales, il est possible de rendre inaccessible une fonction à l’extérieur du
fichier où elle a été définie, en la définissant et en la déclarant avec le mot clé static.
Exemple :
/* Déclaration de fonction statique */
static int locale1(void);
/* Définition de fonction statique */

67
Cours de C L1ELN-L1GE-L1EEA

static int locale2(int i, float j)


{
return i*i+j;
}

IV. Fonction à nombre d’argument variable


Le langage C permet de définir des fonctions dont le nombre de paramètres n’est pas fixé et peut varier
d’un appel à un autre. De telles fonctions se définissent en explicitant quelques paramètres fixes, au
moins un, suivis d’une virgule et de trois points de suspension.
<type> <fonction_var>(<paramètre1>, <paramètre1>,...)
Pour permettre d’accéder aux paramètres lors de la définition de la fonction, on dispose d’une structure
de données et d’un jeu de macros définis dans le fichier d’en-tête stdarg.h :
va_list : structure de données représentant la liste des paramètres ;
va_start(<variable>, <paramètre>);: initialise la variable <variable> de type va_list
avec le paramètre variable à partir du dernier paramètre <paramètre> classique de la fonction ;
va_arg(<variable>, <type>);: récupère le paramètre en cours contenu dans <variable> avec le type
<type>et met à jour <variable> pour passer au paramètre suivant.
va_end(<variable>);: détruit la variable <variable> de type va_list

Exemple:
#include <stdio.h>
#include <stdarg.h>
int moyenne( int premier_parametre, ... );
int main( void )
{/* moyenne de 3 entiers (-1 marque la fin de la liste des paramètres).*/
printf( "La moyenne est: %d\n", moyenne( 2, 3, 4, -1 ) );
/* moyenne de 4 entiers. */
printf( "La moyenne est : %d\n", moyenne ( 5, 7, 9, 11, -1 ) );
/* appel avec juste -1 */
printf( "La moyenne est: %d\n", moyenne (-1 ) );
return 0;
}
/* Retourne la moyenne d’une liste variable de paramètres entiers. */
int moyenne( int premier_parametre, ... )
{ int nbre = 0, somme = 0, i = premier_parametre;
va_list varg;
va_start(varg,premier_parametre);/*Initialise varg.*/
while( i != -1 )
{ somme += i;
nbre ++;
i = va_arg(varg, int); /*récupère varg comme entier.*/
}
va_end(varg ); /* détruit varg.*/
return(somme? (somme / nbre) : 0 );
}

V. Fonction récursive
1. Définition
Une fonction est dite récursive si elle comporte, dans son corps, au moins un appel à elle-même.
Exemple : Calcul des coefficients du triangle de Pascal
Les coefficients du triangle de Pascal se définissent par récurrence comme suit :

68
Cours de C L1ELN-L1GE-L1EEA

Ci, j = 1 si j = 1 ou j = i
Ci, j = Ci-1, j-1+ Ci-1, j si j ≠ 1 et j ≠ i
D’où la fonction :
unsigned long int coef(unsigned int i, unsigned int j)
{
unsigned long int k;
if(j = = 1|| j = = i)
k = 1;
else
k = coef(i-1, j-1) + coef(i-1, j);
return k;
}

2. Règles d’écriture de fonctions récursives


Lors de la définition d’une fonction récursive, on doit s’assurer que les points sont observés:
la fonction doit être définie de manière conditionnelle: quand elle est appelée, il doit y avoir une
vérification de conditions d'arrêt. Si elles sont satisfaites, la récursivité doit s'arrêter.
Au moins l'un des cas de l'expression conditionnelle doit mener à une expression évaluable sans
appel récursif.
Chaque fois qu'elle est appelée de manière récursive (par elle-même, donc), un ou plusieurs des
arguments qui lui sont transmis doivent se rapprocher de la condition d'arrêt.
Le nombre d'appel récursif pour parvenir à un résultat doit être fini.
Enfin, et c'est probablement le point le plus important: dans une fonction récursive, la complexité du
problème doit être réduite à chaque nouvel appel récursif.
3. Appel de fonction récursive
Chaque appel à une fonction donne naissance à une activation qui doit mémoriser, entre autres, les
informations sur les valeurs de paramètres, l’adresse de retour et les valeurs des objets locaux. Ces
informations sont collectées dans un enregistrement d’activation qui est stocké dans un tableau
appelé pile d’activation.
Lorsqu’une fonction est récursive, il peut exister plusieurs activations de la même fonction ; les
enregistrements d’activation assurent entre, autres, la mémorisation de différentes valeurs d’un
même paramètre formel.
Exemple :
Considérons la fonction fact et l’appel fact(3) dans un programme.
L'ordinateur qui exécute le programme voit qu'on lui demande de calculer fact(3). Il va en effet stocker
dans la pile le fait qu'on veut cette valeur, mais qu'on ne pourra la calculer qu'après avoir obtenu la
valeur de fact(2). On procède ainsi (on dit qu'on empile les appels ) jusqu'à demander la valeur de
fact(0) :

appelant
{ { {
…………. {
……………….. ……………….. ……………
………………..
{ …1;
…3*fact(2) ; …2*fact(1) ; …1*fact(0) ;
…………. ………………… ………………… ……………
…………………
fact (3) ; } } }
}
………….
}
fact(0)
1 ? 0 1
2 ? 2 ?
3 ? 1 ?
3 ? 3 ?
2 ?
3 ?

69
Cours de C L1ELN-L1GE-L1EEA

Arrivé au bout, il ne reste plus qu'à dépiler les appels, pour de proche en proche pouvoir calculer la
valeur de fact(3) :

0 1 1 1
2 2
1 ? 2 ? 3 6
3 ?
2 ? 3 ?
3 ?

70
Cours de C L1ELN-L1GE-L1EEA

Chapitre 3: LES FICHIERS


I. Généralités
Les données d’un programme stockées en mémoire centrale sont perdues dès la sortie du
programme. Cette volatibilité de la mémoire constitue un handicap dans certaines applications. Par
exemple, si toutes les données d’un programme qui gère les clients d’une banque sont en mémoire
uniquement, on serait amené à ressaisir à chaque exécution du programme les informations sur les
clients ; ce qui n’est pas intéressant. Les fichiers permettent de lever cet handicap en stockant de
manière permanente des informations entre deux exécutions d’un programme. En plus de ne pas être
volatiles, les fichiers ont l’avantage d’être dynamiques c'est-à-dire leurs tailles peuvent augmenter au
cours de leurs utilisations tant qu’il reste de la place sur le support. Leur principal inconvénient est le
temps d’accès à une donnée (de l’ordre de 106 plus lent que la mémoire).

1. Définition
Un fichier est un ensemble d’informations stockées sur une mémoire secondaire (disque dur,
disquette, bande magnétique, CD-ROM, clé, …). Au contenu du fichier est associé un nom, et
éventuellement d’autres informations supplémentaires (dates de création et de modification, droits
d’accès, …).
En C, un fichier est un flot de données c'est-à-dire une suite d’octets connectée à un
périphérique. C fait en sorte que les communications d’un programme avec environnement se font par
l’intermédiaire de fichiers ; pour le programmeur, tous les périphériques, même l’écran et le clavier, sont
des fichiers. Les informations contenues dans un fichier ne sont pas forcement de même type. Un
pointeur fournit (tête de lecture) l’adresse des informations.

Pointeur
2. Types de fichiers
On distingue en C deux types de fichiers suivant la façon dont sont stockées les informations.

2.1. Fichiers binaires

Un fichier binaire contient des données enregistrées sous une forme qui est la copie exacte de
leur codage en mémoire.
Les fichiers binaires ont les propriétés suivantes :
• Ils sont peu encombrants. Par exemple si un entier occupe 4 octets en mémoire, une variable entière
occupera aussi 4 octets dans un fichier binaire quelle que soit sa valeur, d’où un gain de place
• Les opérations de lecture et d’écriture sont rapides car elles ne réalisent aucune conversion.
• Les fichiers binaires sont inexploitables par des programmes externes qui ignorent la structure réelle
du fichier.

2.2. Fichiers texte


Dans un fichier texte, les données sont stockées sous forme de caractères codés généralement
suivant le code ASCII (American Standard Code Information Interchange). Par exemple un float binaire
(i .e. en mémoire) sera transformé en décimal puis le caractère correspondant à chaque chiffre sera écrit.
Les fichiers texte présentent les caractéristiques suivantes :
• Alors que les fichiers binaires sont de simples suites d’octets, les fichiers texte sont constitués de
lignes, chaque ligne est faite d’un nombre variable de caractères qui se terminent par les caractères de
contrôle CR (code 13) et LF (code 10), signifiant un retour en début de ligne. Lors de la lecture d’un
fichier texte, le couple (CR, LF) de marque de fin de ligne sera traduit en le caractère ‘\n’ et

71
Cours de C L1ELN-L1GE-L1EEA

inversement, l’écriture logique (i.e. en mémoire) du caractère ‘\n’ se traduira par l’écriture effective du
couple (CR, LF) sur le fichier.
• Les fichiers texte sont exploibles par des programmes différents de celui qui les a produits.
• Les opérations d’entrée/sortie nécessitent des conversions pour transformer une suite de carctères en
une représentation compatible avec le type de variable à affecter et inversement. Cette conversion
demande un traitetement supplémentaire, de sorte que les opérations d’entrée/sortie sont plus lentes
sur les fichiers texte.
• Les fichiers texte peuvent être encombrants ; par exemple les variables numériques représentées par
des chaînes de caractères occupent, en moyenne, plus d’octets qu’une représentation binaire.

3. Modes d’accès
Le mode d’accès définit la manière dont l’ordinateur va aller chercher les informations
contenues dans un fichier. On distingue deux modes d’accès :
3.1. Accès séquentiel
L’accès séquentiel consiste à traiter les informations séquentiellement c’est- à-dire dans l’ordre
où elles apparaissent (ou apparaîtront) dans le fichier ; on ne peut accéder à une information qu’en
ayant au préalable examiné celle qui précède. Ce mode est possible aussi bien dans les fichiers texte que
dans les fichiers binaires ; dans le cas des fichiers texte, cela signifie qu’on lit le fichier ligne par ligne.
3.2. Accès direct (ou aléatoire)
L’accès direct consiste à se placer directement sur l’information souhaitée sans avoir à parcourir
celles qui la précédent. L’accès direct est possible sur les fichiers binaires.
En réalité, en C, l’accès est séquentiel ; il existe seulement des fonctions permettant de réaliser l’accès
direct.

4. Organisation des fichiers


Avant d’aborder les opérations sur les fichiers en C, voyons rapidement comment les systèmes
de gestion de fichiers organisent les données sur les fichiers.
En général un fichier est constitué de groupements informationnels élémentaires appelés
enregistrements ou articles. Chaque enregistrement est constitué d’occurrences d’un ensemble de
propriétés appelées champs ou rubriques. Cet ensemble de propriétés est appelé enregistrement type du
fichier. Considérons par exemple le fichier des produits d’un magasin. Si un produit est caractérisé par
sa référence, sa désignation, sa couleur et sa quantité en stock, l’enregistrement type du fichier sera
composé des champs référence (entier), désignation (chaîne), couleur (chaîne) et quantité (entier).
101. compas. bleu.12 [Link].18 103. [Link].17 [Link].14 [Link]

Enregistrement ou article

Parmi les champs d’un fichier, il y a la clé d’identification qui permet de distinguer un article des
autres. Dans l’exemple ci-dessus référence est la clé d’identification.
Un fichier dispose aussi de clés d’accès c'est-à-dire de rubriques ou groupes de rubriques à partir
desquels on pourra accéder à un enregistrement. Une clé d’accès est appelée clé primaire s’il s’agit de la
clé d’identification, clé secondaire sinon.
L’organisation d’un fichier est l’ensemble des liaisons entre les enregistrements stockés en
mémoire secondaire. Il existe plusieurs organisations de fichier ; toutes les organisations permettent
l’accès séquentiel.
4.1. Organisation séquentielle
Les enregistrements sont stockés les uns à la suite des autres sur la mémoire secondaire. En
principe, seul l’accès séquentiel est possible.
4.2. Organisation par index
A chaque fichier physique est associé une ou plusieurs tables appelées index stockées sur
mémoire secondaire. Un index est une table qui associe à la valeur d’une clé d’accès l’adresse d’un
enregistrement du fichier ayant cette valeur de clé. Il peut avoir plusieurs index pour un fichier. Un

72
Cours de C L1ELN-L1GE-L1EEA

index est appelé primaire s’il est construit à partir de la clé primaire et secondaire s’il concerne une clé
secondaire.
Exemple :
Deux index associés au fichier produit
Index sur référence : index primaire. Index sur couleur: index secondaire.

Référence adresse couleur adresse


101 1 Bleu 3
102 2 Bleu 5
103 3 Vert 2
104 4 Rouge 1
105 5 Rouge 4

Un index peut être trié sur la clé d’accès ou non. Cette organisation permet les deux modes d’accès,
l’accès direct s’effectue par consultation de l’index. Cette organisation est efficace dans la recherche
d’un ensemble d’enregistrements à partir d’une valeur d’une clé d’accès. L’ajout ou la suppression
d’enregistrements nécessite l’ajout ou la suppression de valeurs dans l’index. La modification de valeur
de la clé d’accès dans le fichier nécessite une modification dans l’index.
4.3. Organisation chaînée
Dans une organisation chaînée, un enregistrement contient l’adresse d’un autre ; ce deuxième
enregistrement est alors considéré comme le suivant du premier, quelle que soit sa place physique sur le
support. A son tour ce deuxième enregistrement contient l’adresse d’un troisième, et ainsi de suite…
L’adresse indiquée dans un enregistrement peut être physique ou logique (par exemple valeur de
l’identifiant).

ADR ADR ADR

II. Opérations globales sur les fichiers en C

1. Tampon d’entrée/ sortie


En C, quelque que soit l’opération que l’on veut effectuer sur un fichier (écriture ou lecture), les
données transitent par une zone mémoire temporaire appelée tampon (buffer en anglais) avant être
rangées définitivement dans le fichier. Les transferts physiques d’octets entre le fichier et un programme
qui le lit ou l’écrit se font par paquets de taille fixe, la taille du tampon. Par défaut, à tout fichier est
associé un tampon de taille prédéfinie mais le programme peut, à l’aide de la fonction setvbuf,
modifier sa taille ou même le supprimer. L’utilisation du tampon réduit le nombre d’opérations
d’entée/sortie physiques beaucoup plus lentes que les opérations d’entée/sortie logiques ; ce qui
augmente la rapidité des entrées/sorties.

73
Cours de C L1ELN-L1GE-L1EEA

MEMOIRE SECONDAIRE

Lecture physique de tout le paquet

MEMOIRE
CENTRALE

Zone mémoire tampon

Lecture logique d’un enregistrement

Zone de variable décrite dans le programme

Utilisation par le programme

Le passage par un tampon introduit un décalage dans la chronologie des opérations d’entrée/ sortie
puisque ce que le programmeur croit être une opération d’écriture dans un fichier n’est en réalité qu’une
opération de recopie dans un tampon en mémoire, dont le contenu sera transféré ultérieurement vers le
fichier.

2. Déclaration de fichier
Pour pouvoir manipuler un fichier, un programme a besoin d’un certain nombre d’informations :
l’adresse du tampon associé au fichier, la position de la tête de lecture, le mode d’accès au fichier, ….
Ces informations sont rassemblées dans une structure système, FILE, définie dans le fichier d’en tête
stdio.h. Les fichiers sont représentés dans un programme par des variables de type FILE * appelées
flots. Donc pour utiliser un fichier dans un programme, il faut déclarer une variable FILE* :
FILE* <Nom_logique > ;
Remarque :
Toutes les fonctions de gestion des fichiers que nous allons voir dans la suite sont déclarées dans le
fichier d’entête stdio.h.

3. Ouverture de fichier
Avant de lire ou d’écrire dans un fichier, il faut l’ouvrir. Pour ouvrir un fichier en C, il est
nécessaire de spécifier le nom logique et le nom physique du fichier puisque l’ouverture associe ces
deux noms. Pour ouvrir un fichier, on utilise de la fonction fopen. Son prototype est :
FILE* fopen(const char* <nom_physique >, const char* <mode>)
Sa syntaxe est :
fopen(<nom_physique>, <mode>) ;
fopen alloue un emplacement en mémoire pour une structure de type FILE , essaie d’établir le lien
entre cet emplacement et le fichier que l’on veut ouvrir (initialise les champs de l’emplacement alloué),
et fournit l’adresse de l’emplacement en cas de succès et NULL si l’opération a échoué.
<nom_physique> est le nom externe (ou chemin) du fichier à ouvrir, fourni sous forme d'une chaîne
de caractères tel que connu par le système d’exploitation.
<mode> est une chaîne de caractères qui spécifie le type d’opération que l’on veut effectuer ce fichier.

74
Cours de C L1ELN-L1GE-L1EEA

"r" ouverture du fichier en lecture seulement ; erreur si le fichier n’existe pas.


ouverture du fichier en écriture seulement ; création du fichier s’il n’existe pas;
"w"
si le fichier existe son ancien contenu est perdu.
"a"
ouverture du fichier en ajout, c'est-à-dire en écriture à la fin du fichier ;
création du fichier s’il n’existe pas.
"r+" ouverture du fichier en lecture et écriture ; erreur si le fichier n’existe pas.

"w+"
ouverture du fichier en lecture et écriture ; création du fichier s’il n’existe pas;
si le fichier existe son ancien contenu est perdu.
"a+" ouverture du fichier en lecture et en ajout ; création du fichier s’il n’existe pas.

A l’ouverture, le pointeur est positionné au début du fichier sauf pour le mode ″a ″et ″a+″.
Sur PC, on peut rajouter au mode t ou b pour respectivement des fichiers texte (option par défaut) et
binaires, ou le définir par défaut en donnant à la variable globale _fmode la valeur O_TEXT ou
O_BINARY.
Ouvrir un fichier en mode texte (t) aura les conséquences suivantes :
- en lecture la séquence (CR, LF) est transformée en LF (‘\n’) en mémoire,
- en écriture LF en mémoire est transformé en (CR, LF) sur disque.
Cela signifie que :
- si on lit en mode binaire un fichier créé en mode texte, on récupérera des caractères CR
supplémentaires ;
- si on écrit en mode texte dans un fichier créé en mode binaire, on placera des caractères CR qui
perturberont une possible lecture ultérieure en mode binaire.
Exemple : Création du fichier des pays ouest africains
#include <stdio.h>
int main(void)
{
FILE *fich;/* déclaration du flot qui sera associé au fichier*/
fich= fopen("c:\pays","w");/* ouverture du fichier (texte)en écriture*/
if ( fich == NULL) /* si la création a échoué, affichage d'un message */
{printf("Erreur de création du fichier\n");/* d’erreur et sortie du */
exit(1); /* programme*/
}
printf("Création effectuée avec succès. . . \n");
return 0;
}

4. Fermeture de fichier
Lorsque le traitement sur un fichier est terminé, il faut le fermer à l’aide de la fonction fclose.
Son prototype et sa syntaxe sont :
int fclose(FILE* <flot>)
fclose(<flot>) ;
<flot> est un pointeur vers une structure de type FILE, qui est presque toujours la valeur retournée
par fopen.
fclose ferme le fichier auquel est connecté <flot> : elle provoque l’écriture physique du tampon sur
le fichier, restitue l’espace alloué par le tampon et le flot <flot>. fclose retourne 0 si la fermeture du
fichier s’est bien passée, EOF en cas d’erreur.
La fermeture d’un fichier est obligatoire pour être sûr d’avoir l’intégrité des données effectivement
transférées sur le fichier. En effet, si oublier de fermer, par appel de fclose, un fichier qui a été ouvert
en lecture n’a en général aucune conséquence, oublier de le faire sur un fichier qui a été ouvert en
écriture peut provoquer la perte de l’information qui y est écrite.

75
Cours de C L1ELN-L1GE-L1EEA

Exemple : Fermeture du fichier des pays ouest africains


#include <stdio.h>
int main(void){
FILE *fich; /* déclaration du flot qui sera associé au fichier*/
fich= fopen("c:\pays","r");/* ouverture du fichier (texte)en lecture*/
if ( fich == NULL) /* si la création a échoué, affichage d'un message */
{printf("Erreur de création du fichier\n");/* d’erreur et sortie du */
exit(1); /* programme*/
}
printf("Ouverture effectuée avec succès. . . \n");
/*Traitements
........................
*/
fclose(fich) ; /* fermeture après traitements */
return 0;
}

5. Vidange du tampon
Le vidange (écriture physique) du tampon sur fichier se fait automatiquement lorsque :
• Le tampon est plein ;
• Le fichier est fermé à l’aide de fclose ;
• Dans le cas de flot connecté à un organe interactif (clavier, écran, …), le caractère de fin de ligne est
tapé par l’utilisateur ou écrit par le programme.
Cependant, on peut demander expressement l’écriture physique immédiate du tampon à l’aide de la
fonction fflush. Son prototype et sa syntaxe sont :
int fflush(FILE* <flot>)
fflush(<flot>);
fflush retourne EOF en cas d’erreur, 0 sinon.
Exemple :
#include <stdio.h>
int main(void){
FILE *fich = fopen("c:\pays", "a");/* ouverure en ajout*/
if (fich !=NULL)
{
/* traitements */
fflush( fich );/* vidange du tampon associé au fichier pays*/
}
fflush( stdin ); /* vidage du tampon associé au flot stdin */
return 0;
}

6. Création de fichier temporaire


La fonction tmpfile de prototype FILE* tmpfile(void) et de syntaxe tmpfile()crée
un fichier binaire en mode ″w+b″ , sans nom externe, qui sera automatiquement détruit à la terminaison
du programme.
Exemple:

76
Cours de C L1ELN-L1GE-L1EEA

#include <stdio.h>
int main(void){
FILE *tempfp;
tempfp = tmpfile();
if (tempfp)
printf("fichier temporaire crée\n");
else
{ printf("impossible de créer le fichier temporaire\n");
exit(1);
}
return 0;
}

7. Définition de la politique de gestion du tampon


Il ya trois choix dans la politique de gestion des tampons :
• Sans tampon : les entrées / sorties logiques coincident avec les entrées/ sorties physiques.
• Tampon de type bloc : les entrées /sorties physiques se font lorsque le tampon atteint la taille du bloc.
• Tampon de type ligne : les entrées/ sorties physiques se font lorsque le tampon contient une ligne
entière.
Par défaut, les fichiers sont asociés à des tampons de type bloc. La fonction setvbuf peut être utilisée
pour changer le type de tampon utilisé. Son prototype et sa syntaxe :
int setvbuf(FILE* <flot>,char* <buf>, int <type>, size_t <taille>)
setvbuf(<flot>,<buf>, <type>, <taille>);
<flot> est le flot ouvert dont on veut changer le type de tampon ;
<type> est le type de tampon choisi, il doit être l’une des trois constantes symboliques définies dans
stdio.h :
_IONBF : sans tampon ;
_IOFBF : tampon de type bloc ;
_IOLBF : tampon de type ligne ;
<buf> est l’adresse du tampon s’il est différent de NULL ; s’il est égal à NULL, setvbuf allouera
l’espace pour le tampon par appel de la fonction malloc;
<taille> est la taille voulue pour le tampon.
Setvbuf retourne 0 en cas de succès.
Setvbuf doit être appeler après l’ouverture du fichier mais avant toute opération de lecture/ écriture.
Exemple :
#include <stdio.h>
int main(void){
FILE *input, *output;
char bufr[512];
input=fopen("[Link]","r+b");/*ouverture en lecture écriture en binaire*/
output = fopen("[Link]", "w");");/*ouverture en écriture en mode texte*/
/* Utiliser maintenant l’emplacement de bufr comme tampon pour [Link]*/
if (setvbuf(input, bufr, _IOFBF, 512) != 0)
printf("Erreur de changement de tampon pour le fichier d’entrée\n");
else
printf("Le tampon a été redéfini pour le fichier d’entrée \n");
/*Utiliser un tampon ligne pour le fichier [Link] en utilisant
l’espace obtenu par appel indirect à malloc */
if (setvbuf(output, NULL, _IOLBF, 132) != 0)
printf("Erreur de changement de tampon pour le fichier d’entrée\n");
else
printf("Le tampon a été redéfini pour le fichier d’entrée \n");

/* Traitements des fichiers */

/* Fermeture des fichiers */


fclose(input);
fclose(output);
return 0;
}

77
Cours de C L1ELN-L1GE-L1EEA

8. Détection de fin de fichier


La fonction feof teste l’indicateur de fin de fichier et retourne une valeur non nulle si on est en
fin de fichier, 0 sinon.
int feof(FILE* <flot>)
La fin du fichier est atteinte, en C, lorsque l’on a passé le dernier octet.

Exemple :
#include <stdio.h>
int main(void){
FILE *fich;
char car;
fich = fopen("c:\pays", "r");
car = fgetc(fich); /* lecture d'un caractère dans le flot ; cf IV.1 */
if (feof(fich)) /* teste la fin du fichier */
printf("fin du fichier atteinte\n");
fclose(fich);
return 0;
}

9. Détection d’erreur d’entrée/ sortie


int ferror(FILE* <flot>)
renvoie une valeur non nulle s’il ya erreur, 0 sinon. Elle doit être appelée immédiatement après une
opération d’entrée / sortie pour savoir si l’opération en question a échoué.
Exemple :
#include <stdio.h>
int main(void) {
FILE *fich = fopen("c:\pays", "w");
char car ;
car= getc(fich);/*erreur car tentative de lecture dans un fichier ouvert
en écriture seule */
if (ferror(fich)) /* teste si une erreur s'est produite */
printf("Erreur de lecture sur le fichier\n");
fclose(fich);
return 0;
}

III. Les flots prédéfinis


Par défaut, lorsqu’un programme débute, il existe trois flots prédéfinis de type texte qui sont
automatiquement ouverts ; ils sont conectés aux organes d’entrée/ sortie usuels :
• stdin : flot d’entrée standard connecté au clavier ;
• stdout : flot de sortie standard connecté à l’écran ;
• stderr : flot de sortie d’erreur connecté à l’écran.

78
Cours de C L1ELN-L1GE-L1EEA

IV. Opérations d’entrée/sortie en mode texte


En lagage C, diverses fonctions et macros existent permettant la lecture, l’écriture et le
positionnement dans des fichiers. Ces opérations peuvent être regroupées en deux catégories : les
opérations en mode texte et les opérations en mode binaire. Nous allons voir ici certaines des fonctions
et macros en mode texte qui conviennent plus aux fichiers texte.

1. Ecriture/Lecture carctère par caractère


Ces macros et fonctions permettent de lire ou d'écrire des caractères dans les fichiers.

int fputc(int <c>, FILE *<flot>)


La fonction fputc écrit le caractère <c> dans le fichier <flot>, retourne le caractère écrit ou
EOF en cas d'échec. Le pointeur du fichier est automatiquement positionné derrière l’octet écrit.
Syntaxe : fputc (<c>, <flot>)

int putc(int <c>, FILE *<flot>)


putc fait la même chose que fputc, mais putc est une macro.
Syntaxe : putc (<c>, <flot>)

int putchar(int <c>)


Envoie le caractère <c> sur la sortie standard (stdout). Putchar est une macro. putchar
(<c>) équivaut à putc(<c>, stdout).

int ungetc ( int <c>, FILE *<flot>);


Remet dans le tampon du fichier le dernier caractère lu.

int fgetc(FILE *<flot>)


La fonction fgetc retourne le prochain caractère présent sur <flot> ou EOF si la fin de fichier
est atteinte ou si une erreur survient. Le pointeur du fichier est automatiquement positionné
derrière l’octet lu.
Syntaxe : fgetc(<flot>)

int getc(FILE *<flot>)


getc fait la même chose que fgetc, mais getc est une macro.
Syntaxe : getc(<flot>)

int getchar(void)
La macro getchar retourne le prochain caractère présent sur l'entrée standard (stdin).
getchar() équivaut à getc(stdin).

Exemple : Ecriture du fichier des pays caractère par caractère à partir d’une lecture caractère par
caractère du clavier, puis affichage sur l’écran caractère par caractère.
#include <stdio.h>
int main(void) {
FILE *fich ;
char car ;
if((fich=fopen("[Link]","w"))==NULL)/*Ouverture du fichier en création*/

79
Cours de C L1ELN-L1GE-L1EEA

{printf("Erreur de création du fichier\n");


exit(1);
}
puts(" Entrer le nom des pays, taper ENTER pour terminer la saisie") ;
while((car=getchar()) !=’\n’)
fputc( car, fich) ; /* ou putc(car, fich)*/
fclose(fich) ;
puts("Affichage sur l’écran caractère par caractère ") ;
if((fich=fopen("[Link]","r"))==NULL)/*Ouverture du fichier en création*/
{printf("Erreur de lecture du fichier\n");
exit(1);
}
while (!feof(fich)) /* tant que la fin du fichier n'est pas atteinte */
{car=fgetc(fich); /* ou car = getc(fich)*/
putchar(car) ;
}
fclose(fich);
return 0;
}

2. Ecriture/Lecture ligne par ligne


Ces fonctions lisent et écrivent des chaînes de caractères dans les fichiers.

int fputs(char *<chaine>, FILE *<flot>)


La fonction fputs écrit la chaîne <chaine> dans le fichier <flot>, retourne EOF en cas d'échec
et une valeur non négative (le dernier caractère écrit) dans les autres cas. Le pointeur du fichier
est automatiquement positionné après le dernier octet écrit.
Syntaxe : fputs(<chaine>, <flot>)

int puts(char *<chaine>)


La fonction puts envoie les caractères de <chaine> sur la sortie standard (stdout), plus un '\n'
final. puts (<chaine>) équivaut à fputs(<chaine>, stdout) + fputc(‘\n’)

Char *fgets (char *<chaine>, int <taille>, FILE *<flot>)


La fonction fgets lit des carctères depuis <flot>, les place dans l’espace pointé par <chaine> ;
elle s’arrête lorsqu’elle a lu <taille>-1, ou lorsqu’elle a rencontré le caractère de ligne ‘\n’
(dans ce cas ce caractère est copié dans la chaîne) ou le caractère EOF. Le caractère nul
‘\0’ est ajouté à la fin de la chaîne <chaine>. fgets retourne <chaine> ou NULL en cas
d'échec. Le pointeur du fichier est automatiquement positionné après le dernier octet lu.
Syntaxe : fgets (<chaine>, <taille>, <flot>)

80
Cours de C L1ELN-L1GE-L1EEA

char *gets(char *<chaine>)


gets lit une ligne de texte (terminée par '\n') depuis l'entrée standard (stdin), l'enregistre dans
<chaine> jusqu'à atteindre un caractère de fin de ligne, ou EOF, qu'il remplace par '\0'. Il
n'ya pas de vérification de débordement de <chaine>.

Exemple : Ecriture du fichier des pays (pays par ligne) à partir du clavier, puis affichage sur l’écran.
#include <stdio.h>
#include<string.h>
#define LGMAX 31
int main(void)
{ FILE * fich;
char pays[LGMAX] ;/* nom du pays */
char nomfic[LGMAX];/*Nom du fichier donné ici par l’utilisateur*/
/* création du fichier*/
printf("Nom du fichier \x85 cr\x82er : ");/* 85 code de à en hexadéc.*/
gets(nomfic);
if((fich = fopen(nomfic, "w"))!=NULL)
{ printf("Pour sortir de la boucle, entrer une cha\x8Cne vide\n");
printf("Entrer un nom de pays : ");
gets(pays);
while (strcmp(pays, "")!=0)
{strcat(pays, "\n");/* ajout du caractère \n au nom saisi*/
fputs(pays,fich);
printf("Entrer un nom de pays : ");
gets(pays);
}
fclose(fich) ;
}
/* Affichage des pays */
if((fich = fopen(nomfic, "r")) !=NULL)
{ printf("Liste des pays\n");
while((fgets(pays,LGMAX , fich))!=NULL)
puts (pays);
fclose(fich) ;
}
return 0;
}

3. Ecriture/Lecture formatées
int fprintf (FILE *<flot>), const char*<format>[,<var1>,...,<varn>])
La fonction fprintf écrit dans le fichier <flot> la chaîne de caractère <format> en remplaçant
éventuellement les formateurs (%xx) contenus dans <format> par les valeurs des expressions

81
Cours de C L1ELN-L1GE-L1EEA

<var1>, . . ., <varn>, converties en chaînes de caractères. fprintf renvoie le nombre de


caractères effectivement écrits ou EOF en cas d’erreur.
Syntaxe : fprintf(<flot>),<format>[,<var1>,...,<varn>])

int printf(const char *<format>, ...)


printf(<format>[,<var1>,...,<varn>])équivaut à
fprintf(stdout,<format>[,<var1>,...,<varn>])

int sprintf(const char*<dest>,const char*<format>,<var1>,..,<varn>)


sprintf fait la même chose que fprintf sauf que les caractères produits ne sont pas ajoutés à un
fichier mais sont concaténés ensemble dans la chaîne <dest>.

int fscanf(FILE*<flot>), const char *<format>,<ref1>,...,<refn>)


La fonction fscanf lit les caractères contenus dans le fichier <flot> et les convertit en un type
correspondant au format <format> et les enregistre dans les emplacements référencés par
<ref1>,..., <refn>. La lecture est interrompue quand les données lues ne correspondent pas au
format spécifié ou quand la fin de fichier est rencontrée. fscanf renvoie le nombre de caractères
convertis et affectés ou EOF en cas d’erreur.
Syntaxe : fscanf(<flot>),<format>,<ref1>,...,<refn>)

int scanf(char *format, ...)


scanf(<flot>),<format>,<ref1>,...,<refn>)équivaut à
fscanf(stdin, <format>,<ref1>,...,<refn>)

int sscanf(const char*<source>,const char*<format>,<ref1>,..,<refn>)


sscanf fait la même chose que avec la différence que les caractères lus proviennent non pas
d’un fichier mais de la chaîne de caractères <source>.

Exemple : Création du fichier des pays avec leurs capitales et superficies, puis affichage sur l’écran.
#include <stdio.h>
#define LGMAX 31
int main(void)
{ FILE * fich;
char pays[LGMAX], capitale[LGMAX];
char nomfic[LGMAX];
long superf ;

printf("Nom du fichier a creer : ");


gets(nomfic);
if((fich = fopen(nomfic, "w"))!=NULL)
{
printf("Pour sortir de la boucle, entrer une cha\x8Cne vide\n");
printf("Entrer le nom du pays : ");
gets(pays);
while (strcmp(pays, "")!=0)
{ printf("Entrer sa capitale : ");
gets(capitale);
printf("Entrer sa superficie : ") ;

82
Cours de C L1ELN-L1GE-L1EEA

scanf("%ld", &superf) ;
fprintf(fich, " %s %s %ld\n", pays, capitale, superf);
printf("Entrer le nom du pays : ");
gets(pays);
}
fclose(fich);
}
printf("Liste des pays\n");
if((fich= fopen(nomfic, "r"))!=NULL)
{ fscanf(fich,"%s %s %ld", pays, capitale, &superf);
while(!feof(fich))
{printf("Pays:%s capitale:%s superficie:%ld\n",pays,capitale,superf);
fscanf(fich,"%s %s %ld", pays, capitale, &superf);
}
fclose(fich) ;
}
return 0;
}

V. Opérations d’entrée/sortie en mode binaire


Ces fontions permettent de manipuler les fichiers sans transcodage des informations transférées.
Elles sont particulièrement adaptées aux fichiers binaires.

1. Ecriture binaire
int fwrite(const void*<ptr>, size_t<tail>, size_t<nb>, FILE *<flot>)
fwrite écrit dans le flot <flot> <nb>objets ayant chacun une taille de <tail>octets et placés les uns à
la suite des autres dans une zone pointée par <ptr> et retourne le nombre d'objets (et non le nombre
d'octets) réellement écrits ou 0 en cas d'erreur ou de fin de fichier.
Syntaxe : fwrite(<ptr>, <tail>, <nb>, <flot>)

2. Lecture binaire
int fread(void*<ptr>, size_t <tail>, size_t <nb>, FILE *<flot>)
fread essaie de lire dans le flot <flot> <nb> objets ayant chacun une longueur de <tail> octets et
les range les uns à la suite des autres dans la zone pointée par <ptr>. Cette fonction retourne le nombre
d'objets réellement lus ou la valeur 0 si la fin du fichier est rencontrée ou s'il y a une erreur de lecture.
Syntaxe : fread(<ptr>, <taille>, <nb>, <flot>)

Exemple : Création du fichier des pays avec leurs capitales et superficies, puis affichage sur l’écran ; un
pays est modélisé par une structure.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf;
} PAYS;
int main(void)
{ FILE* fpays;
PAYS pays;

83
Cours de C L1ELN-L1GE-L1EEA

char nomfic[LGMAX];
printf("entrer le nom du fichier:");
if((fpays=fopen(gets(nomfic),"wb"))==NULL) exit(1);
printf("Pour sortir de la boucle, entrer une cha\x8Cne vide\n");
printf("Entrer le nom du pays : ");
gets([Link]);
while (strcmp([Link], "")!=0)
{printf("\tEntrer sa capitale : ");
gets([Link]);
printf("\tEntrer sa superficie : ") ;
scanf("%ld", &[Link]) ;
fwrite(&pays,sizeof pays,1,fpays);
printf("Entrer le nom du pays : ");
fflush(stdin);
gets([Link]);
}
fclose(fpays);
if((fpays=fopen(nomfic,"rb"))==NULL) exit(1);
while( fread(&pays, sizeof pays, 1, fpays)==1)
printf("Pays:%s capitale:%s superficie:%ld\n",[Link],
[Link], [Link]);
fclose(fpays);
return 0;
}
3. Positionnement dans des fichiers
Les fonctions suivantes permettent de se déplacer dans un fichier.

int fseek(FILE *<flot>, long <deplacement>, int <origine>)


fseek permet de déplacer de <deplacement>(peut être<0)octets dans le flot <flot>, en
partant de <origine>. <origine> peut être:
• SEEK_SET (égal à 0) : début du fichier ;
• SEEK_CUR (égaleà 1) : position courante ;
• SEEK_END (égal à 2) : fin du fichier.
fseek retourne 0 en cas de succès et une valeur non nulle sinon.
Syntaxe : fseek(<flot>, <deplacement>, <origine>)

long ftell(FILE *<flot>)


ftell retourne la position courante dans le flot <flot>, en nombre d'octets à partir du début du
fichier.
Syntaxe : ftell(<flot>)

void rewind(FILE*<flot>)
rewind positionne le pointeur du fichier en début de fichier.

84
Cours de C L1ELN-L1GE-L1EEA

rewind(<flot>) équivaut à fseek(<flot>, 0L, SEEK_SET).

int fgetpos(FILE* <flot>, fpos_t*<ptr>)


fgetpos place dans <ptr> la position courante dans le fichier <flot>.
fgetpos retourne 0 en cas de succès et une valeur nan nulle sinon.
Syntaxe : fgetpos(<flot>, <ptr>)

int fsetpos(FILE* <flot>, const fpos_t*<ptr>)


fsetpos positionne le pointeur du flot <flot> à l’emplacement correspondant à <ptr>. La valeur
de <ptr> provient d’un appel précédent à fgetpos. Après un appel à lé prochaine opération peut
être aussi bien une écriture qu’une lecture
fsetpos retourne 0 en cas de succès et une valeur nan nulle sinon.
Syntaxe : fsetpos(<flot>, <ptr>).

Exemple : On suppose qu’en plus du nom, de la capitale et de la superficie, on veut stocker la


population et la densité de chaque pays. On suppose le fichier des pays créé et les champs nom, capitale
et superficie saisis (cf exemple précédent). On suppose enfin disposer d’un autre fichier contenant les
numéros (dans le fichier des pays) et les populations des pays. On veut écrire un programme qui
renseigne les champs population et densité dans le fichier à partir du fichier des poupulations.
include<stdio.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct population{
int position;/* position du pays dans le fichier pays,commençant à 1*/
long popul;/* population du pays */
};
FILE* fpays, *fpopul;
int main(void)
{ PAYS pays;
struct population pop;
char nomfic[LGMAX];
int pos;/* position du pays*/
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb+"))==NULL) exit(1);
printf("Fichier des populations : ");
if((fpopul=fopen(gets(nomfic),"rb"))==NULL)exit(1);
while (fread(&pop, sizeof pop,1, fpopul)==1)
{pos=[Link];
fseek(fpays,(pos-1)*sizeof(PAYS),SEEK_SET);
fread(&pays, sizeof(PAYS),1, fpays) ;

85
Cours de C L1ELN-L1GE-L1EEA

[Link]=[Link];
[Link]=[Link]/[Link];
fseek(fpays,(pos-1)*sizeof(PAYS),SEEK_SET);
fwrite(&pays,sizeof pays,1,fpays);
}
rewind(fpays);/* positionnement en début de fichier*/
printf("affichage du fichier des pays compl\x82t\x82:\n);
while( fread(&pays, sizeof pays, 1, fpays)==1)
printf("%ld- Pays:%s capitale:%s superficie:%ldpopulatio:%ld
densit\x82:%ld\n",ftell(fapys)/sizeof(PAYS),[Link],
[Link],[Link],[Link],[Link]);
fclose(fpays);
fclose(fpopul);
return 0;

}
Remarque :
Bien qu’elles sont plus adaptées aux fichiers binaires, les fonctions de positionnement peuvent
aussi s’utiliser sur les fichiers texte.
La principale application des fonctions de positionnement est la réalisation de l’accès direct à
une composante (enregistrement) d’un fichier à partir de la donnée de son rang dans le fichier.

VI. Autres opérations

1. Destruction de fichier
int remove (const char* <nom>)
Supprime le fichier <nom> et retourne 0 en cas de succès et -1 sinon.
2. Renommage de fichier
int rename (const char* <ancien>, const char*<nouveau>)
Renomme le fichier <ancien> en <nouveau> et retourne 0 en cas de succès et -1 sinon.

VII. Traitements usuels sur les fichiers.

Les traitements usuels sur les fichiers sont de deux types :


• Traitements élémentaires : rechercher une composante, ajouter une composante, mettre à jour une
composante, supprimer une compsoante, . . .
• Traitements globaux : lister un fichier, cloner un fichier (créér une copie), trier un fichier, fusionner
de fichiers, éclater un fichier, nombre d’éléments du fichier ….

A. Listage d’un fichier


Principe :
• Ouvrir le fichier en lecture, ce qui positionne le pointeur en début de fichier ;
• Lire le premier enregistrement
• Et tant qu’on n’a pas lu au-delà du dernier octet :
– Affichage les informations sur l’enregistrement
– Lire l’enregistrement suivant.
Exemple : Lister le fichier crée en IV.3.
#include <stdio.h>
#define LGMAX 31

86
Cours de C L1ELN-L1GE-L1EEA

int main(void)
{ FILE * fich;
char pays[LGMAX], capitale[LGMAX], nomfic[LGMAX];
long superf, compt=0;
printf("Nom du fichier a lister : ");
gets(nomfic);
if((fich= fopen(nomfic, "r"))==NULL) exit(1);
printf("Liste des pays\n");
fscanf(fich,"%s %s %ld", pays, capitale, &superf);
while(!feof(fich))
{compt++ ;
printf("Pays:%s capitale:%s superficie:%ld\n",pays,capitale,superf);
fscanf(fich,"%s %s %ld", pays, capitale, &superf);
}
fclose(fich) ;
printf("Le fichier contient %ld pays",compt) ;
return 0;
}
B. Copie de fichier

1. Dans un tableau
Exemple 1 : Copier le nom, la capitale et la superficie des pays contenus dans le fichier texte crée dans
le paragraphe IV.3 dans trois tableaux respectivement.
#include <stdio.h>
#include <string.h>
#define TAILLE 100
#define LGMAX 31
int main(void)
{ FILE * fich;
char nom[LGMAX], capitale[LGMAX], nomfic[LGMAX] ;
char tabNom[TAILLE][LGMAX],tabCapit[TAILLE][LGMAX];
long superf, compt=0, tabSuperf[TAILLE];
printf("Nom du fichier: ");
if((fich= fopen(gets(nomfic), "r"))==NULL) exit(1);
fscanf(fich, "%s %s %ld",nom, capitale, &superf);
while(!feof(fich))
{strcpy(tabNom[compt],nom);strcpy(tabCapit[compt],capitale);
tabSuperf[compt]=superf; compt;
fscanf(fich, "%s %s %ld",nom, capitale, &superf);
}
fclose(fich);
return 0;
}
Pour avoir l’ensemble des informations d’un fichier dans un seul tableau, il faut que les éléments qu’il
contient soient de même type.
Exemple 2 : Charger dans un tableau le fichier crée au paragraphe V.3.
#include <stdio.h>
#include <string.h>
#define TAILLE 100
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
int main(void)
{ FILE* fpays ;
PAYS tabPays[TAILLE],pays;
char nomfic[LGMAX];

87
Cours de C L1ELN-L1GE-L1EEA

int compt=0;
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
fread(&pays, sizeof(PAYS), 1, fpays);
while (!feof(fpays))
{ strcpy(tabPays[compt].nom,[Link]);
strcpy(tabPays[compt].capitale,[Link]);
tabPays[compt].superf=[Link] ;
tabPays[compt].densite=[Link] ;
compt++ ;
fread(&pays, sizeof(PAYS), 1, fpays);
}
fclose(fpays) ;
/* Traitements sur le tableau (tris, insertion, modifications, ...)
Puis sauvegarde du tableau dans le fichier*/
if((fpays=fopen(nomfic, "wb"))==NULL) exit(1);
fwrite(tabPays, compt*sizeof(PAYS), 1, fpays);
/* ou fwrite(tabPays, sizeof(PAYS), compt, fpays);*/
fclose(fpays);
return 0 ;
}

2. Dans une liste chaînée


Exemple : charger le fichier crée au paragraphe V.3 dans une liste chaînée.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TAILLE 100
#define LGMAX 31
typedef struct pays {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
struct pays *voisin;
} PAYS;
int main(void)
{ FILE* fpays ;
PAYS *listPays=NULL, *p, *p_svt, *p_prec, pays;
char nomfic[LGMAX];
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
fread(&pays, sizeof(PAYS), 1, fpays);
while (!feof(fpays))
{ p = (PAYS*)malloc(sizeof(PAYS));/* on suppose il y a succès */
strcpy(p->nom,[Link]); strcpy(p->capitale,[Link]);
p->superf=[Link] ; p->densite= [Link] ;
if(listPays==NULL)
{p->voisin=listPays ; listPays=p ;}
else
{p_prec=listPays ; p_svt=listPays->voisin ;
while(p_svt !=NULL) /* on veut se positioner en fin de liste*/
{p_prec=p_svt ; p_svt=p_svt->voisin ;}
p->voisin=p_svt ; /* p_svt=NULL*/
p_prec->voisin=p ;
}
fread(&pays, sizeof(PAYS), 1, fpays);

88
Cours de C L1ELN-L1GE-L1EEA

}
fclose(fpays) ;
/* Traitements sur la liste (tris, insertion, modifications, ...)
Puis sauvegarde de la liste dans le fichier*/
if((fpays=fopen(nomfic, "wb"))==NULL) exit(1);
p=listPays;
while(p!=NULL)
{fwrite(p, sizeof(PAYS), 1, fpays);p=p->voisin;}
fclose(fpays);
return 0 ;
}

3. Dans un autre fichier


Cf. paragraphe D.
C. Recherche dans un fichier

1. Recherche dans un fichier d’organisation séquentielle

1.1 Recherche linéaire dans un fichier


Principe :
• Ouvrir le fichier en lecture ;
• Parcourir le fichier, en voyant si chaque enregistrement lu satisfait les critères de recherche. On peut
s’arrêter de parcourir dès qu’on trouve un enregistrement répondant aux critères (recherche de la
première occurrence) ou continuer le parcours jusqu’à la fin du fichier (recherche de toutes les
occurrences).
Exemple 1 : À partir du fichier des pays créé dans le paragraphe V.3., écrire un programme qui affiche
tous les pays ayant la densité maximale.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays ;
int main(void)
{ PAYS pays;
char nomfic[LGMAX];
long max=0, compt=0;
printf("Fichier des pys:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
/* recherche de la plus grande densité */
while (fread(&pays, sizeof(PAYS), 1, fpays)==1)
if ([Link]>max)
max= [Link];
printf("La densit\x82 maximale est %ld\n", max) ;
/* recherche des pays ayant cette densité maximale*/
rewind(fpays);/* se positionner en début de fichier*/
while (fread(&pays, sizeof(PAYS), 1, fpays)==1)
if ([Link]==max)
{compt++;
printf("%ld- %s densit\x82 :%ld\n",compt,[Link],max);
}
fclose(fpays);
return 0;
}
Exemple 2 : À partir du fichier des pays créé dans le paragraphe V.3., écrire un programme qui affiche
le premier pays ayant la densité maximale.
#include<stdio.h>
#include<string.h>
#define LGMAX 31

89
Cours de C L1ELN-L1GE-L1EEA

typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays ;
int main(void)
{ PAYS pays;
char nomfic[LGMAX];
long max=0, ok=0;
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
/* recherche de la plus grande densité */
/* cf exemple ci dessus*/
/* recherche des pays ayant cette densité maximale*/
rewind(fpays) ; /* se positionner en début de fichier*/
while (fread(&pays, sizeof(PAYS), 1, fpays)==1&&!ok)
if ([Link]==max)
ok=1 ;
printf(" Pays ayant la densité maximale: %s\n", [Link]) ;
fclose(fpays);
return 0;
}

1.2 Recherche dichotomique


La recherche dichotomique suppose que le fichier est trié. De plus, puisque la recherche se fait
directement sur le fichier, le fichier doit être à accès direct.
Principe :
• Ouvrir le fichier en lecture
• Se positionner sur l’enregistrement médian et le comparer à l’enregistrement recherché :
– Si l’enregistrement médian satisfait aux critères, la recherche est terminée ;
– Si l’enregistrement médian est « supérieur » à l’enregistrement recherché, appliquer le principe
sur la moitié gauche du fichier ;
– Si l’enregistrement médian est « inférieur » à l’enregistrement recherché, appliquer le principe
sur la moitié droite du fichier ;
Exemple : Recherche dichotomique sur le fichier des pays supposé trié par ordre croissant sur le nom.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays ;
int main(void)
{ PAYS pays;
char nomfic[LGMAX],nom[LGMAX];
int nb_enr=0, debut, milieu, fin, trouve=0;
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
/* nombre d'enregistrements */
while (fread(&pays, sizeof(PAYS), 1, fpays)==1)
nb_enr++;
if (nb_enr>0)
{printf(" Pays \x85 chercher:");
gets(nom);
debut=1; fin=nb_enr;
while(debut<=fin && !trouve)
{milieu=(debut+fin)/2;
fseek(fpays, milieu*sizeof(PAYS),SEEK_SET);
fread(&pays, sizeof(PAYS), 1, fpays);
if (strcmp([Link], nom)>0)
fin= milieu-1;

90
Cours de C L1ELN-L1GE-L1EEA

else
if (strcmp([Link], nom)<0)
debut= milieu+1;
else
trouve=1;
}
if(!trouve)
printf("%s n'est pas un pays ouest africain\n", nom);
else
printf("Voici les informations sur%s\n\tcapitale:%s\n\tsuperficie
:%ld\n\tpopulation:%ld\n\tdensit\x82:%ld\n",[Link],[Link],
[Link],[Link],[Link]);
}
else
printf("Fichier vide");
fclose(fpays);
return 0;
}

2. Recherche dans un fichier d’organisation indexée


Rappelons que l’organisation indexée utilise :
• Un fichier de données, en général non trié ;
• Au moins un fichier d’index généralement trié selon les clés des enregistrements, chargé en mémoire
centrale en début de programme dans un index ;
• Un index, c'est-à-dire une table de couple (clé, adresse) associant à chaque valeur de la clé l’adresse
de l’enregistrement ayant cette valeur. L’index peut être un tableau ou une liste chaînée. L’index est
généralement trié selon les clés des enregistrements. L’index est sauvegardé dans le fichier en fin de
programme.
Principe :
• Recherche de la clé sur l’index (généralement recherche dichotomique) ;
• Si clé trouvée, se positionner sur l’enregistrement dans le fichier de données.
Exemple : Rechercher dans le fichier des pays, indexé sur le nom, le pays dont le nom est donné (voir ci
dessous un exemple de création d’un fichier et son fichier d’index).
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#define DIM 100
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct strindex{
char nom[LGMAX];
int num;
}index[DIM];/* index sur le nom sous forme de tableau*/
FILE* fpays, *fipays;
int main(void)
{ PAYS pays;
struct strindex ipays ;
char nomfic[LGMAX],nom[LGMAX];
int nb_enr=0, debut, milieu, fin, pos=-1;
/* chargement du fichier d’index*/
printf("Fichier d’index:");
if((fipays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
while (fread(&ipays, sizeof(struct strindex), 1, fipays)==1)
{index[nb_enr].num=[Link];
strcpy(Index[nb_enr].nom, [Link]).
nb_enr++;
}
fclose(fipays);
/* recherche dichotomique sur l’index qui est trié*/
printf(" Pays \x85 chercher:");

91
Cours de C L1ELN-L1GE-L1EEA

gets(nom);
debut=0; fin=nb_enr-1;
while(debut<=fin && pos==-1)
{milieu=(debut+fin)/2;
if (strcmp(index[milieu].nom, nom)>0)
fin= milieu-1;
else
if (strcmp(index[milieu].nom, nom)<0)
debut= milieu+1;
else
pos=milieu;
}
if(pos==-1)
printf("%s n'est pas un pays ouest africain\n", nom);
else
{/* se positionner sur l’enregistrement qui est au rang pos*/
printf("Fichier de données : ") ;
if(fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
fseek(fpays, (index[pos].num)*sizeof(PAYS),SEEK_SET);
fread(&pays, sizeof(PAYS), 1, fpays);
printf("Voici les informations sur %s\n\tcapitale:%s\n\tsuperficie:
%ld\n\tpopulation:%ld\n\tdensit\x82:%ld\n",[Link],[Link],
[Link],[Link],[Link]);
fclose(fpays) ;
}
return 0 ;
}
Remarque :
On pouvait rechercher directement le nom du pays dans le fichier d’index en faisant comme l’exemple
précédent.
Exemple de programme de création d’un fichier et de son fichier d’index
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#define DIM 100
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct strindex{
char nom[LGMAX];
int num;
} index [DIM];/* index sur le nom,sous forme de tableau*/
FILE* fpays, *fipays;
int main(void)
{ PAYS pays;
struct strindex ipays ;
char nomfic[LGMAX], nomind[LGMAX];
int nb_enr=0,i, pos=0;
/* création du fichier et saisie de l’index*/
printf("Fichier de pays:");
if((fpays=fopen(gets(nomfic),"wb"))==NULL) exit(1);
printf("Pour sortir de la boucle, entrer une cha\x8Cne vide\n");
printf("Entrer le nom du pays : ");
gets([Link]);
while (strcmp([Link], "")!=0)
{ printf("\tEntrer sa capitale : ");
gets([Link]);
printf("\tEntrer sa superficie : ") ;
scanf("%ld", &[Link]) ;
printf("\tEntrer sa population : ") ;
scanf("%ld", &[Link]) ;
[Link]=[Link]/[Link];
fwrite(&pays,sizeof pays,1,fpays);

92
Cours de C L1ELN-L1GE-L1EEA

pos=0;
while(pos<nb_enr&& strcmp(index[pos].nom,[Link])<=0)
pos++ ;
/* décalage des enregistrement pour libérer la position d’insertion*/
for(i=nb_enr ; i> pos ; i--)
{index[i].num=index[i-1].num;
strcpy(index[i].nom, index[i-1].nom);
}
strcpy(index[pos].nom,[Link]);/* insertion du nom du pays*/
index[pos].num=nb_enr;
nb_enr++;
fflush(stdin);
printf("Entrer le nom du pays : ");
gets([Link]);
}
fclose(fpays);
/* sauvegarde de l’index dans le fichier d’index*/
printf("Fichier d'index:");
if((fipays=fopen(gets(nomind),"wb"))==NULL) exit(1);
fwrite(index,nb_enr*sizeof(struct strindex),1, fipays);
fclose(fipays);
return 0 ;
}

D. Mise à jour des fichiers

1. Mise à jour d’un fichier d’organisation séquentielle


Comme il est impossible de lire et d’écrire en même temps dans un fichier séquentiel, les mises
à jour directes sur fichier doivent se faire à l’aide d’un fichier supplémentaire.
1.1 Insertion d’un nouvel enregistrement
Principe :
• Ouvrir le fichier à mettre à jour en lecture
• Ouvrir le fichier d’aide en écriture
• Copier dans le fichier d’aide tous les enregistrements du fichier à mettre à jour qui précèdent le nouvel
enregistrement
• Ecrire le nouvel enregistrement dans le fichier d’aide
• Copier dans le fichier d’aide le reste des enregistrements du fichier à mettre à jour
• Fermer les deux fichiers
• Ouvrir le fichier d’aide en lecture et le fichier à mettre à jour en écriture
• Copier le fichier d’aide dans le fichier à mettre à jour
• Fermer et détruire le fichier d’aide
Remarque :
Si l’insertion se fait en fin de fichier (ajout), il suffit d’ouvrir le fichier à mettre à jour en ajout ("a").
Exemple 1: Considérons le fichier texte contenant une liste d’étudiants et organisé comme suit:
– Le fichier commence par une description du groupe d’élèves sur un nombre fixe de caractères ;
– Ensuite il y a le nombre d’élèves
– Ensuite il y a la liste des élèves ; chaque élève étant connu par son nom, son prénom, son sexe, et
son niveau d’étude.
Ecrire un programme qui saisit ce fichier. Il s’agit d’écrire la description, puis les élèves en les comptant
enfin le nombre d’élève.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
int main(void)
{FILE* fel;
char des[LGMAX+5], nom[LGMAX], prenom[LGMAX],sexe;
int nb_el=0, niv;
if((fel=fopen("c:\[Link]","w"))==NULL) exit(1);
printf("Descriptif du groupe:");

93
Cours de C L1ELN-L1GE-L1EEA

do
gets(des);/* ldescriptif a ici 35 caractères*/
while(strlen(des)!=LGMAX+4);
fputs(des,fel);/* écriture du descriptif*/
while(printf("entrer le nom d'un \x82l\x8Ave:"),strlen(gets(nom)))
{printf("\tson pr\x82nom:"); gets(prenom);
printf("\tson sexe:"); fflush(stdin); scanf("%c", &sexe);
printf("\tson niveau:"); fflush(stdin); scanf("%d", &niv);
fprintf(fel,"%s %s %c %d\n",nom,prenom,sexe,niv);
nb_el++;/* compte les élèves saisis*/
}
fclose(fel);
/*Insertion du nombre d'élèves, juste après le descriptif */
if((fel=fopen("c:\[Link]","r"))==NULL) exit(1);
if((fel1=fopen("c:\[Link]","w"))==NULL) exit(1);
fgets(des,LGMAX+5, fel);
fprintf(fel1,"%s %d\n",des, nb_el);/* copie du descriptif et écriture du
nombre d'élève dans le fichier d'aide*/
/* copie des autres éléments du fichier [Link]*/
fscanf(fel,"%s %s %c %d",nom,prenom,&sexe,&niv);
while(!feof(fel))
{ fprintf(fel1,"%s %s %c %d\n",nom,prenom,sexe,niv);
fscanf(fel,"%s %s %c %d",nom,prenom,&sexe,&niv);
}
fclose(fel); fclose(fel1);
/* recopie de [Link] dans [Link]*/
if((fel1=fopen("c:\[Link]","r"))==NULL) exit(1);
if((fel=fopen("c:\[Link]","w"))==NULL) exit(1);
fgets(des,LGMAX+5,fel1);
fscanf(fel1,"%d", &nb_el);
fprintf(fel,"%s %d\n",des, nb_el);
fscanf(fel1,"%s %s %c %d",nom,prenom,&sexe,&niv);
while(!feof(fel1))
{fprintf(fel,"%s %s %c %d\n",nom,prenom,sexe,niv);
fscanf(fel1,"%s %s %c %d",nom,prenom,&sexe,&niv);
}
fclose(fel); fclose(fel1);remove("c:\[Link]");
return 0;
}
Exemple 2 : Supposons, trié selon le nom le fichier des pays créé au paragraphe V.3. Ecrire un
programme qui inserre dans le fichier un pays sans perturber l’ordre.
include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays, *ftmp ;
int main(void)
{ PAYS pays, pays_ins;
char nomfic[LGMAX];
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","wb"))==NULL) exit(1);
printf(" \nPays \x85 inserrer:"); gets(pays_ins.nom);
printf("\tSa capitale : ") ; gets(pays_ins.capitale) ;
printf("\tSa superficie : ") ; scanf("%ld",&pays_ins.superf) ;
printf("\tSa population : ") ; scanf("%ld",&pays_ins.popul) ;
pays_ins.densite= pays_ins.popul/pays_ins.superf;

94
Cours de C L1ELN-L1GE-L1EEA

while(fread(&pays,sizeof(PAYS),1, fpays)==1&&strcmp([Link],
pays_ins.nom) <=0)
fwrite(&pays, sizeof(PAYS),1,ftmp);
fwrite(&pays_ins, sizeof(PAYS),1,ftmp);
if(!feof(fpays))
{ fwrite(&pays, sizeof(PAYS),1,ftmp);
while(fread(&pays,sizeof(PAYS),1, fpays)==1)
fwrite (&pays, sizeof(PAYS),1,ftmp);
}
fclose(fpays) ; fclose(ftmp) ;
if((fpays=fopen(nomfic,"wb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","rb"))==NULL) exit(1);
while(fread(&pays,sizeof(PAYS),1, ftmp)==1)
fwrite(&pays, sizeof(PAYS),1,fpays);
fclose(fpays) ; fclose(ftmp) ; remove("c:\tmp");
return 0;}
1.2 Modification d’un enregistrement
Principe :
• Ouvrir le fichier à mettre à jour en lecture
• Ouvrir le fichier d’aide en écriture
• Copier dans le fichier d’aide tous les enregistrements du fichier à mettre qui précèdent
l’enregistrement à modifier
• Ecrire l’enregistrement modifié dans le fichier d’aide
• Copier dans le fichier d’aide le reste des enregistrements du fichier à mettre à jour
• Fermer les deux fichiers
• Ouvrir le fichier d’aide en lecture et le fichier à mettre à jour en écriture
• Copier le fichier d’aide dans le fichier à mettre à jour
• Fermer et détruire le fichier d’aide
Exemple : Programme qui, à partir du fichier des pays, modifie un pays donné (le premier trouvé).
include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays, *ftmp;
int main(void)
{ PAYS pays;
char nomfic[LGMAX],nom[LGMAX], reponse;int trouve= 0;
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","wb"))==NULL) exit(1);
printf(" \nPays \x85 modifier:"); gets(nom);
/* Copie dans c:\tmp des pays qui précèdent le pays à modifier*/
while( !feof(fpays) &&!trouve)
{fread(&pays,sizeof(PAYS),1, fpays)
if(!strcmp([Link], nom))
trouve =1;
else
fwrite(&pays,sizeof(PAYS),1, ftmp);
}
if( !trouve)
printf("Pays inconnu\n") ;
else /* saisie des modifications*/
{printf(" Nom à modifier(O/N)? ") ;
fflush(stdin) ;
reponse =getchar() ;

95
Cours de C L1ELN-L1GE-L1EEA

if (reponse==’O’)
{printf("Entrer le nouveau nom : ") ; gets([Link]) ;}
printf(" Capitale à modifier(O/N)? ") ;
fflush(stdin) ;
reponse =getchar() ;
if (reponse==’O’)
{printf("Entrer la nouvelle capitale: ") ; gets([Link]) ;}
printf(" Superficie à modifier(O/N)? ") ;
fflush(stdin) ;
reponse =getchar() ;
if (reponse==’O’)
{printf("Entrer la nouvelle superficie: ");scanf("%ld",&[Link]);}
printf(" Population à modifier(O/N)? ") ;
fflush(stdin) ;
reponse =getchar() ;
if (reponse==’O’)
{printf("Entrer la nouvelle population "); scanf("%ld",&[Link]);}
[Link]=[Link]/[Link] ;
/*Ecriture de l’enregistremment modifié dans c:\tmp et le reste des pays*/
fwrite(&pays,sizeof(PAYS),1, ftmp);
while(!feof(fpays))
{fread(&pays,sizeof(PAYS),1, fpays);
fwrite(&pays,sizeof(PAYS),1, ftmp);
}
fclose(fpays) ; fclose(ftmp) ;
/*copie de c:\tmp dans le fichier de pays*/
if((fpays=fopen(nomfic,"wb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","rb"))==NULL) exit(1);
fread(&pays,sizeof(PAYS),1, ftmp);
while(!feof(ftmp)
{fwrite(&pays,sizeof(PAYS),1, fpays);
fread(&pays,sizeof(PAYS),1, ftmp);
}
}
fclose(fpays) ; fclose(ftmp); remove("c:\tmp");
return 0;
}

1.3 Suppression d’un enregistrement


Principe :
• Recopier un à un les enregistrements dans le fichier d’aide sauf celui à supprimer.
• Ensuite, recopier le fichier d’aide dans le fichier à modifier.
Exemple : Programme qui supprime tous les pays dont la densité est inférieure à un seuil donné.
include<stdio.h>
#include<string.h>
#define LGMAX 31
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays, *ftmp;
int main(void)
{ PAYS pays; long seuil ;
char nomfic[LGMAX];
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","wb"))==NULL) exit(1);
printf(" \nSeuil:"); scanf("%ld",&seuil);
/* Copie dans c:\tmp des pays dont la densité est >= à seuil*/
fread(&pays,sizeof(PAYS),1, fpays)

96
Cours de C L1ELN-L1GE-L1EEA

while( !feof(fpays)
{if([Link]>=seuil)
fwrite(&pays,sizeof(PAYS),1, ftmp);
fread(&pays,sizeof(PAYS),1, fpays)
}
fclose(fpays) ; fclose(ftmp);
/* Copie de c:\tmp dans le fichier de pays */
if((fpays=fopen(nomfic,"wb"))==NULL) exit(1);
if((ftmp=fopen("c:\tmp","rb"))==NULL) exit(1);
while( !feof(fpays)
{fread(&pays,sizeof(PAYS),1, ftmp)
fwrite(&pays,sizeof(PAYS),1, fpays);
}
fclose(fpays) ; fclose(ftmp); remove("c:\tmp");
return 0;
}

2. Mise à jour d’un fichier d’organisation indexée


2.1 Insertion d’un nouvel enregistrement
Principe :
• Insérer la clé de l’enregistrement dans l’index avec décalage des éléments
• Insérer l’enregistrement en fin de fichier
Exemple : Inserrer dans le fichier des pays, indexé sur le nom, un pays donné. Le fichier d’index est
trié.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#define DIM 100
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct strindex{
char nom[LGMAX];
int num;
} index [DIM];/* index sur le nom,sous forme de tableau*/
FILE* fpays, *fipays;
int main(void)
{ PAYS pays;
Struct strindex ipays ;
char nomfic[LGMAX], nomind[LGMAX];
int nb_enr=0,i, pos=0;
/* chargement du fichier d’index
printf("Fichier d’index:");
if((fipays=fopen(gets(nomind),"rb"))==NULL) exit(1);
while (fread(&ipays, sizeof(struct strindex), 1, fipays)==1)
{index[nb_enr].num=[Link];
strcpy(index[nb_enr].nom, [Link]).
nb_enr++;
}
fclose(fipays);
/* recherche de la position d’insertion*/
printf(" Le nom du pays \x85 inserrer:"); gets([Link]);
while(pos<nb_enr&& strcmp(index[pos].nom,[Link])<=0)
pos++ ;
/* décalage des enregistrements pour libérer la position d’insertion*/
for(i=nbr_enr ; i> pos ; i--)
{index[i].num=index[i-1].num;
strcpy(index[i].nom, index[i-1].nom);
}
strcpy(index[pos].nom,[Link]);/* insertion du nom du pays*/
index[pos].num=nb_enr;
nb_enr++;

97
Cours de C L1ELN-L1GE-L1EEA

/* sauvegarde de l’index dans le fichier d’index*/


if((fipays=fopen(nomind,"wb"))==NULL) exit(1);
fwrite(index,nb_enr*sizeof(struct strindex),1, fipays);
fclose(fipays);
/* Insertion du pays en fin de fichier*/
printf("Fichier de données:");
if((fpays=fopen(gets(nomfic),"ab"))==NULL) exit(1);
printf("\tSa capitale : ") ; gets([Link]) ;
printf("\tSa superficie : ") ; scanf("%ld",&[Link]) ;
printf("\tSa population : ") ; scanf("%ld",&[Link]) ;
[Link]= [Link]/[Link];
fwrite(&pays,sizeof(PAYS),1, fpays);
fclose(fpays) ;
return 0 ;
}
2.2 Modification d’un enregistrement
Principe :
• Rechercher la clé de l’enregistrement dans l’index
• Se positionner sur l’enregistrement et le récupérer
• Modifier l’enregistrement et écrire l’enregistrement modifié.
Exemple : Modifier dans le fichier des pays, indexé sur le nom, la capitale d’un pays donné. Le fichier
d’index est trié.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#idefine DIM 100
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct strindex{
char nom[LGMAX];
int num;
}index[DIM];/* index sur le nom sous forme de tableau*/
FILE* fpays, *fipays;
int main(void)
{ PAYS pays;
Struct strindex ipays ;
char nomfic[LGMAX],nom[LGMAX];
int nb_enr=0, debut, milieu, fin, pos=-1;
/* chargement du fichier d’index
printf("Fichier d’index:");
if((fipays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
while (fread(&ipays, sizeof(struct strindex), 1, fipays)==1)
{index[nb_enr].num=[Link];
strcpy(Index[nb_enr].nom, [Link]).
nb_enr++;
}
fclose(fipays);
/* recherche dichotomique sur l’index qui est trié*/
printf(" Pays \x85 modifier:");
gets(nom);
debut=0; fin=nb_enr-1;
while(debut<=fin && pos==-1)
{milieu=(debut+fin)/2;
if (strcmp(index[milieu].nom, nom)>0)
fin= milieu-1;
else
if (strcmp(index[milieu].nom, nom)<0)
debut= milieu+1;
else
pos=milieu;
}

98
Cours de C L1ELN-L1GE-L1EEA

if(pos==-1)
printf("%s n'est pas un pays ouest africain\n", nom);
else
{/* se positionner sur l’enregistrement qui est au rang pos*/
printf("Fichier de données : ");
if(fpays=fopen(gets(nomfic),"rb+"))==NULL) exit(1);
fseek(fpays, (index[pos].num)*sizeof(PAYS),SEEK_SET);
fread(&pays, sizeof(PAYS), 1, fpays);
printf("Entrer la nouvelle capitale:"); gets([Link]);
fseek(fpays, (index[pos].num)*sizeof(PAYS),SEEK_SET);
/* ou fseek(fpays, -sizeof(PAYS),SEEK_CUR);*/
fwrite(&pays,sizeof(PAYS),1, fpays);/* écriture de l’enrg. Modifié*/
fclose (fpays);
}
return 0 ;
}

2.3 Suppression d’un enregistrement


Principe :
• On fait généralement, dans un premier temps, des suppressions logiques en marquant dans l’index les
enregistrements à supprimer ; l’index contient donc un champ indiquant l’état (marqué pour
suppression ou non) de chaque enregistrement ;
• Puis de temps en temps, on réorganise le fichier de données et l’index en éliminant physiquement les
enregistrements marqués. Pour cela, on recopie dans un fichier d’aide tous les enregistrements sauf
ceux marqués et on réécrit l’index pour supprimer les clés des enregistrements supprimés.
Exemple : Programme qui supprime (logiquement), du fichier des pays indexé sur le nom, un pays
donné et supprime physiquement tous les enregistrements marqués).
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#idefine DIM 100
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
struct strindex{
char nom[LGMAX];
int num;
int marque ;/*vaut 1 si l’enregistrement correspondant est marqué pour
suppresion et 0 sinon */
}index[DIM];/* index sur le nom sous forme de tableau*/
FILE* fpays, *fipays, *ftmp ;
int main(void)
{ PAYS pays;
Struct strindex ipays ;
char nomfic[LGMAX], nomind[LGMAX],nom[LGMAX];
int nb_enr=0, debut, milieu, fin, pos=-1, nbEnr_supp=0, j;
/* chargement du fichier d’index*/
printf("Fichier d’index:");
if((fipays=fopen(gets(nomind),"rb"))==NULL) exit(1);
while (fread(&ipays, sizeof(struct strindex), 1, fipays)==1)
{index[nb_enr].num=[Link];
strcpy(Index[nb_enr].nom, [Link]).
nb_enr++;
}
fclose(fipays);
printf("Pays \x85 supprimer: "); gets(nom);
/* recherche dichotomique sur l’index qui est trié*/
debut=0; fin=nb_enr-1;
while(debut<=fin && pos==-1)
{milieu=(debut+fin)/2;
if (strcmp(index[milieu].nom, nom)>0)
fin= milieu-1;

99
Cours de C L1ELN-L1GE-L1EEA

else
if (strcmp(index[milieu].nom, nom)<0)
debut= milieu+1;
else
pos=milieu;
}
if(pos==-1)
printf("%s n'est pas un pays ouest africain\n", nom);
else
index[pos].marque=1 ; /* marquer l’enregistrement*/
/* purger le fichier de pays des pays marqués*/
printf("Fichier des pays:");
if((fpays=fopen(gets(nomfic),"rb"))==NULL) exit(1);
if((ftmp=fopen("tmp","wb"))==NULL) exit(1);
for(pos=0; pos<nb_enr;pos++)
{fread(&pays,sizeof pays, 1, fpays) ;
if (index[pos].marque==0)
fwrite(&pays,sizeof pays, 1, ftmp);
else
nbEnr_supp++;
}
fclos(fpays) ; fclose(ftmp) ;
if((fpays=fopen(nomfic),"wb"))==NULL) exit(1);
if((ftmp=fopen("tmp","rb"))==NULL) exit(1);
nb_enr=0;/* recopie de ftmp dans fpays et reindexation*/
for(i=0; i<nb_enr-nbEnr_supp;i++)
{fread(&pays,sizeof pays, 1, ftmp);
fwrite(&pays,sizeof pays, 1, fpays);
pos=0;
while(pos<nb_enr&& strcmp(index[pos].nom,[Link])<=0)
pos++ ;
/* décalage des enregistrements pour libérer la position d’insertion*/
for(j=nbr_enr ; j> pos ; j--)
{index[j].num=index[j-1].num;
strcpy(index[j].nom, index[j-1].nom);
}
strcpy(index[pos].nom,[Link]);/*insertion du nom du pays dans l’index*/
index[pos].num=nb_enr;
nb_enr++;
}
fclos(fpays) ; fclose(ftmp);
/* sauvegarde de l’index dans le fichier d’index*/
if((fipays=fopen(nomind),"wb"))==NULL) exit(1);
fwrite(index, sizeof(struct strindex), nb_enr,fipays);
fclos(fipays) ;
return 0 ;
}
E. Fusion de deux fichiers triés
Principe :
Fusionner deux fichiers triés, c’est constituer un nouveau fichier trié qui comprenne tous les éléments
des deux fichiers ; pour cela :
• On prend le plus petit des deux éléments accessibles (c'est-à-dire en tête dans chaque fichier) et on
ajoute au fichier de sortie ;
• On passe à l’élément suivant du fichier dont on a pris l’élément de tête ;
• On répète ce processus jusqu’à épuisement d’un des deux fichiers ;
• On recopie alors le reste de l’autre fichier.
Exemple : Fusionner les fichiers des pays ouest africains et des pays du magreb supposés triés sur le
nom.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#define PAYSOUEST "[Link]"/*fichier physique des pays ouest africain*/
#define PAYSMAGR "[Link]" /* fichier physique des pays magrébins*/

100
Cours de C L1ELN-L1GE-L1EEA

#define FUSION "[Link]" /* fichier de fusion*/


typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
/* Fusion de f1 et f2 dans f*/
Void fusion(FILE*f1,FILE*f2,FILE*f,int*l)/**l nombre d'éléments fusionnés*/
{PAYS pays1, pays2;
*l=0;
fread(&pays1,sizeof pays1,1,f1);fread(&pays2,sizeof pays2,1,f2);
while (!feof(f1)&&!feof(f2))
{if(strcmp([Link],[Link])<0)
{fwrite(&pays1,sizeof pays1,1,f);(*l)++;
fread(&pays1,sizeof pays1,1,f1);
}
else
{fwrite(&pays2,sizeof pays2,1,f);(*l)++;
fread(&pays2,sizeof pays2,1,f2);
}
}
while (!feof(f1))
{fwrite(&pays1,sizeof pays1,1,f);(*l)++;
fread(&pays1,sizeof pays1,1,f1);
}

while (!feof(f2))
{fwrite(&pays2,sizeof pays2,1,f);(*l)++;
fread(&pays2,sizeof pays2,1,f2);
}
}

int main(void)
{FILE* fpaysOue, *fpaysMagr, *ffusion;
int nb_pays; fpaysOue=fopen(PAYSOUE,"rb");fpaysMagr=fopen(PAYSMAGR,"rb");
ffusion=fopen(FUSION,"wb");
fusion(fpaysoue,fpaysMagr,ffusion,&nb_pays);
fclose(ffusion); fclose(fpaysOue);fclose(fpaysMagr);
ffusion=fopen(FUSION,"rb");
/* Affichage */
while(fread(&pays,sizeof(PAYS),1, ffusion)>0)
printf("Pays:%s Capitale:%s Superf:%ld Popul:%ld densit\x82:%ld\n\n",
[Link],[Link], [Link], [Link], [Link]);
printf("\n Total pays fusionn\x82:%d",nb_pays);
fclose(ffusion);
return 0;
}

F. Tri de fichier

1. Tri direct dans un fichier d’organisation séquentielle


Sur les fichiers d’organisation séquentielle, les algorithmes de tri utilisant les insertions (par
exemple le tri par insertion) et ceux qui déplacent les éléments sans les positionner à leur place
définitive (par exemple tri par bulle) sont en général trop longs. Les méthodes de tri généralement
utilisées sont le tri par sélection et le tri par fusion.
1.1 Tri direct par sélection
Principe :
• Supprimer du fichier initial le minimum et le recopier dans un fichier temporaire
• Réitérer ce principe tant qu’il reste des éléments dans le fichier initial.
• A la fin, recopier le fichier temporaire dans le fichier initial.
Exemple : Trier le fichier des pays par ordre alphabétique par la méthode par sélection.
#include<stdio.h>

101
Cours de C L1ELN-L1GE-L1EEA

#include<string.h>
#define LGMAX 31
#define NOMFIC "d:\[Link]" /* fichier physique des pays*/
#define TEMP "d:\[Link]" /* fichier temporaire */
#define AIDE "d:\[Link]" /* fichier d’aide pour supprimer le minimum */
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
FILE* fpays, *ftmp, *faide;
int main(void)
{ PAYS pays, paysmin;
long nb_pays =0; /* nombre de pays */
long i,j,pos, posmin ;
/* Nombre de pays dans le fichier des pays */
fpays=fopen(NOMFIC,"rb");/* on n’a pas fait le test de succès*/
fread(&pays, sizeof pays, 1, fpays) ;
while(!feof(fpays))
{nb_pays++;
fread(&pays, sizeof pays, 1, fpays);
}
fclose(fpays);
/* Début du tri */
ftmp=fopen(TEMP,"wb");
i=1;
while(i<=nb_pays)/* minimum des (nb_pays-i+1)éléments*/
{fpays=fopen(NOMFIC, "rb");
fread(&pays, sizeof pays , 1, fpays);
pos=1; posmin=pos;
strcpy([Link], [Link]) ;
strcpy([Link], [Link]) ;
[Link]=[Link] ; [Link]= [Link] ;
while(fread(&pays, sizeof pays , 1, fpays)>0)
{pos++;
if(strcmp([Link], [Link])<0)
{strcpy([Link], [Link]) ;
strcpy([Link], [Link]) ;
[Link]=[Link] ; [Link]= [Link] ;
[Link]= [Link] ;
posmin=pos;
}
}
fwrite(&paysmin, sizeof pays,1,ftmp);/*écriture du minimum dans TEMP*/
/*suppression du minimum du fichier des pays*/
fclose(fpays);/*Fermeture et ouverture pour se positionner en début*/
fpays=fopen(NOMFIC,"rb");
faide=fopen(AIDE,"wb");
j=1; /* copie des pays dans AIDE sauf le pays au rang posmin*/
while(fread(&pays, sizeof pays , 1, fpays)>0)
{if(j!=posmin)
fwrite(&pays, sizeof pays , 1, faide);
j++ ;
}
fclose(fpays) ;fclose(faide);
fpays=fopen(NOMFIC,"wb");
faide=fopen(AIDE,"rb");/*copie de AIDE dans NOMFIC */
while(fread(&pays, sizeof pays , 1, faide)>0)
fwrite(&pays, sizeof pays , 1, fpays);
fclose(fpays) ;fclose(faide);
i++;

102
Cours de C L1ELN-L1GE-L1EEA

}
fclose(ftmp);
fpays=fopen(NOMFIC,"wb");/* copier le fichier trié TMP dans NOMFIC*/
ftmp=fopen(TEMP,"rb");
while(fread(&pays, sizeof pays , 1, ftmp)>0)
fwrite(&pays, sizeof pays , 1, fpays);
fclose(fpays) ;fclose(ftmp);
remove(AIDE) ; remove(TEMP) ;/* fin du tri*/
/*Affichage des pays par ordre alphabétique */
fpays=fopen(NOMFIC,"rb");
while(fread(&pays,sizeof(PAYS),1, fpays)>0)
printf("Pays:%s Capitale:%s Superf:%ld Popul:%ld densit\x82:%ld\n\n",
[Link],[Link], [Link], [Link], [Link]);
fclose(fpays) ;
return 0;
}

1.1 Tri direct par fusion


Principe :
On appelle monotonie une suite d'éléments ordonnés et longueur d’une monotonie le nombre d’éléments
de la suite. Un fichier de n enregistrements à trier est considéré comme un fichier de n monotonies de
longueur 1. Pour le trier :
• On les copie alternativement dans deux fichiers, on obtient deux fichiers de n/2 monotonies de
longueur 1 ;
• On fusionne alors ces fichiers, monotonie par monotonie, dans le fichier initial et on obtient un fichier
de n/2 monotonies de longueur 2 ;
• Ce processus alterne une phase d’éclatement qui répartit les monotonies existantes dans deux fichiers,
et une phase de fusion qui regroupe ces monotonies dans un seul fichier. A chaque passage
(combinaison éclatement-fusion), on divise par deux le nombre de monotonies (et on multiplie par
deux la longueur des monotonies). On réitère ce processus jusqu’à ce qu’on obtient une monotonie
unique (donc de longueur le nombre d’éléments du fichier initial).
L’algorithme utilise en principe trois fichiers ; mais pour accélérer le tri, on utilise un fichier
supplémentaire pour permettre de fusionner deux fichiers et éclater sur deux fichiers.
Exemple : Trier le fichier des pays par ordre alphabétique par la méthode de fusion.
#include<stdio.h>
#include<string.h>
#define LGMAX 31
#define NOMFIC "[Link]" /* fichier physique des pays*/
typedef struct {
char nom[LGMAX],capitale[LGMAX];
long superf, popul,densite;
} PAYS;
/*copie de f alternativement sur f1 et f2 et calcul du nombre d’éléments de
f*/
void repartir( FILE * f, FILE * f1, FILE * f2, int * l){
/* Les fichiers sont ouverts et en début*/
PAYS pays;
*l=0;
while(!feof(f)){
fread(&pays, sizeof(pays), 1, f); if(feof(f)) break;
fwrite(&pays, sizeof(pays), 1, f1); ++(*l);
fread(&pays, sizeof(pays), 1, f);if(feof(f)) break;
fwrite(&pays, sizeof(pays), 1, f2);++(*l);
}
/* Les fichiers sont ouverts et en fin de fichier */
}

void fusionMonotonie( FILE * f1, FILE * f2, FILE * f, int l){


/* fusion de 2 monotonies de longueurs l, la dernière est éventuellement
plus courte. */
int i, j ;
PAYS pays1, pays2;

103
Cours de C L1ELN-L1GE-L1EEA

int fin1, fin2;


fin1=feof(f1); fin2 = feof(f2);
i = 1; fin1 = !fread(&pays1, sizeof(pays1), 1, f1);
j = 1; fin2 = !fread(&pays2, sizeof(pays2), 1, f2);
while (!(fin1 || fin2))
{ if( strcmp([Link], [Link])<0)
{ fwrite(&pays1, sizeof(pays1), 1, f);++i;
if(i<=l) fread(&pays1, sizeof(pays1), 1, f1);
fin1 = feof(f1) || (i>l);
}
else
{ fwrite(&pays2, sizeof(pays2), 1, f);++j;
if(j<=l) fread(&pays2, sizeof(pays2), 1, f2);
fin2 = feof(f2) || (j>l);
}
}

while (!fin1)
{ fwrite(&pays1, sizeof(pays1), 1, f);++i;
if(i<=l) fread(&pays1, sizeof(pays1), 1, f1);
fin1 = feof(f1) || (i>l);
}
while (!fin2)
{ fwrite(&pays2, sizeof(pays2), 1, f);++j;
if(j<=l) fread(&pays2, sizeof(pays2), 1, f2);
fin2 = feof(f2) || (j>l);
}
}

void fusion (char *nf1, char *nf2, char *ng1, char*ng2, int l){
/*fusion de toutes les monotonies de longueur l de f1 avec les monotonies
de même rang de f2, et distribution alternativement sur g1 et g2. */
FILE * f1, *f2, *g1, *g2;
f1 = fopen(nf1,"rb"); f2 = fopen(nf2,"rb");
g1 = fopen(ng1,"wb"); g2 = fopen(ng2,"wb");
do{
fusionMonotonie(f1,f2,g1,l);
fusionMonotonie(f1,f2,g2,l);
}while (!(feof(f1) && feof(f2)) );
fclose(f1);fclose(f2);fclose(g1);fclose(g2);
}

void triFusion( char* fNom)


{ /* tri par fusion du fichier fNom */
FILE * f, *f1, *f2, *g1, *g2 ;
int l, n;
f = fopen(fNom, "rb");
f1 = fopen("[Link]","wb"); f2 = fopen("[Link]","wb");
repartir(f,f1,f2,&n);fclose(f);
fclose(f1);fclose(f2);remove(fNom);
g1 = fopen("[Link]","wb"); g2 = fopen("[Link]","wb");
l=1;
while(1){
fusion ("[Link]","[Link]","[Link]","[Link]",l);
l=l*2;
if(l>=n){rename("[Link]",fNom); break;}
fusion ("[Link]","[Link]","[Link]","[Link]",l);
l=l*2;
if(l>=n){rename("[Link]",fNom); break;}
}
}

int main(void)
{ FILE* fpays, *ftmp, *faide;

104
Cours de C L1ELN-L1GE-L1EEA

PAYS pays, paysmin;


long nb_pays =0; /* nombre de pays */
long i,j,pos, posmin ;
char* fnom;
/* création -----------------------------------*/
fpays=fopen(NOMFIC,"wb");
printf("Pour sortir de la boucle, entrer une cha\x8Cne vide\n");
printf("Entrer le nom du pays : ");
gets([Link]);
while (strcmp([Link], "")!=0)
{ printf("\tEntrer sa capitale : ");
gets([Link]);
printf("\tEntrer sa superficie : ") ;
scanf("%ld", &[Link]) ;
printf("\tEntrer sa population : ") ;
scanf("%ld", &[Link]) ;
[Link]=[Link]/[Link];
fwrite(&pays,sizeof pays,1,fpays);
fflush(stdin);
printf("Entrer le nom du pays : ");
gets([Link]);
}
fclose(fpays);
/*Affichage des pays avant tri */
fpays=fopen(NOMFIC,"rb");
while(fread(&pays,sizeof(PAYS),1, fpays)>0)
printf("Pays:%s Capitale:%s Superf:%ld Popul:%ld densit\x82:%ld\n\n",
[Link],[Link], [Link], [Link], [Link]);
fclose(fpays) ;
strcpy(fnom, NOMFIC);
triFusion(fnom);
fpays=fopen(fnom,"rb");
/*Affichage des pays après tri */
fpays=fopen(NOMFIC,"rb");
while(fread(&pays,sizeof(PAYS),1, fpays)>0)
printf("Pays:%s Capitale:%s Superf:%ld Popul:%ld densit\x82:%ld\n\n",
[Link],[Link], [Link], [Link], [Link]);
fclose(fpays) ;
getch();
return 0;
}

2. Tri dans un fichier d’organisation indexée


Sur un fichier indexé, le tri se fait sur l’index (qui est soit un tableau soit une liste), les
enregistrements eux étant stockés dans le fichier de données dans l’ordre de leur création.

G. Stratégies de traitement des fichiers

Il existe globalement deux manières de traiter les fichiers:


• l’une consiste à s’en tenir au fichier proprement dit, c'est-à-dire à modifier directement (ou presque)
les informations sur le disque dur. C’est la stratégie utilisée ici pour décrire la plupart des traitements
usuels.
• l’autre stratégie consiste à passer par un ou plusieurs tableaux (ou listes chaînées). En fait, le principe
fondamental de cette approche est de commencer, avant toute autre chose, par recopier l’intégralité du
fichier de départ en mémoire vive. Ensuite, on ne manipule que cette mémoire vive. Et lorsque le
traitement est terminé, on recopie à nouveau dans l'autre sens, depuis la mémoire vive vers le fichier
d’origine. C’est la stratégie utilisée ici pour les traitements usuels dans les fichiers d’organisatin
indexée. Les avantages de cette technique sont nombreux :
√ la rapidité : les accès en mémoire vive sont des milliers de fois plus rapides (nanosecondes) que
les accès aux mémoires de masse (millisecondes au mieux pour un disque dur). En basculant le

105
Cours de C L1ELN-L1GE-L1EEA

fichier du départ dans un tableau, on minimise le nombre ultérieur d'accès disque, tous les
traitements étant ensuite effectués en mémoire.
√ la facilité de programmation : bien qu’il faille écrire les instructions de recopie du fichier dans le
tableau, c’est largement plus facile de faire um même traitement avec un tableau qu’avec des
fichiers.
Cependant cette recopie en mémoire peut s'avérer problématique dans le cas d'immenses fichiers car
cela peut demander des ressources de dimensions considérables.
De plus lorsque le fichier contient des données de type non homogènes (chaînes, numériques, etc.), il
va falloir déclarer plusieurs tableaux, dont le maniement au final peut être aussi lourd que celui du
fichier de départ. Ce dernier inconvénient peut être levé en utilisant des structures (struct).

106

Vous aimerez peut-être aussi