Cours C
Cours C
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"
printf(MSG);
for ( i = DEBUT; i <= FIN ; i++ )
{
printf("%d carré: %d cube: %d\n", i , carre(i) , cube(i) );
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)
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
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.
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
7
Cours de C L1ELN-L1GE-L1EEA
Pour afficher le caractère % tel quel dans un texte, il faut le représenter par %%
8
Cours de C L1ELN-L1GE-L1EEA
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.
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.
10
Cours de C L1ELN-L1GE-L1EEA
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.
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.
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.
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
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.
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
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}.
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
Opérateur Signification
&& ET booléen
|| OU booléen
! NON booléen
15
Cours de C L1ELN-L1GE-L1EEA
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*/
17
Cours de C L1ELN-L1GE-L1EEA
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
19
Cours de C L1ELN-L1GE-L1EEA
20
Cours de C L1ELN-L1GE-L1EEA
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 ;
}
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);
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
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
29
Cours de C L1ELN-L1GE-L1EEA
30
Cours de C L1ELN-L1GE-L1EEA
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
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
où
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.
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
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
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.
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 ;
}
36
Cours de C L1ELN-L1GE-L1EEA
Pierre ↵
La chaine saisie est
Pierre
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
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
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
• 10
41
Cours de C L1ELN-L1GE-L1EEA
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
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
Lors de la compilation, toutes les expressions de la forme T[i] sont traduites en *(T + i).
Exemple :
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
.... .... .. .
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:
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
51
Cours de C L1ELN-L1GE-L1EEA
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;
}
53
Cours de C L1ELN-L1GE-L1EEA
54
Cours de C L1ELN-L1GE-L1EEA
55
Cours de C L1ELN-L1GE-L1EEA
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
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
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
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’’,¬[0]);
for (i = 1; i < n; i++)
{ printf(‘’ %ieme note: ‘’, i+1);
scanft(‘’%f’’,¬[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
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
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
67
Cours de C L1ELN-L1GE-L1EEA
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;
}
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
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.
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.
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.
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.
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).
73
Cours de C L1ELN-L1GE-L1EEA
MEMOIRE SECONDAIRE
MEMOIRE
CENTRALE
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
"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
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;
}
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;
}
77
Cours de C L1ELN-L1GE-L1EEA
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;
}
78
Cours de C L1ELN-L1GE-L1EEA
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
80
Cours de C L1ELN-L1GE-L1EEA
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
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 ;
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;
}
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.
void rewind(FILE*<flot>)
rewind positionne le pointeur du fichier en début de fichier.
84
Cours de C L1ELN-L1GE-L1EEA
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.
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.
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 ;
}
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 ;
}
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;
}
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;
}
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 ;
}
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;
}
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;
}
97
Cours de C L1ELN-L1GE-L1EEA
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 ;
}
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
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
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;
}
103
Cours de C L1ELN-L1GE-L1EEA
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);
}
int main(void)
{ FILE* fpays, *ftmp, *faide;
104
Cours de C L1ELN-L1GE-L1EEA
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