Académique Documents
Professionnel Documents
Culture Documents
OBJECTIFS SPECIFIQUES :
1.1 LA STRUCTURE
1.1.1 DEFINITION
Une structure est un moyen de regrouper des variables de types différents sous un seul nom.
Cela permet de créer des enregistrements complexes.
• Chaque élément déclaré à l’intérieur de la structure est appelé un champ ou membre.
1
• Le nom donné à la structure est appelé étiquette de la structure.
2
1.1.1.2 REPRESENTATION EN MEMOIRE
3
1.1.1.2.1 ALIGNEMENT DE LA MEMOIRE
Les compilateurs C peuvent introduire des octets de bourrage 1pour garantir que
chaque membre de la structure commence à une adresse mémoire compatible avec
son type de données. La taille totale de la structure peut être plus grande en raison de
l'alignement de la mémoire.
Par exemple, si l'alignement de la mémoire pour les entiers est sur 4 octets, le
compilateur pourrait ajouter 2 octets de bourrage après le tableau de caractères `nom`
(50 octets) et 2 octets de bourrage après l'entier `age` (4 octets) pour s'assurer que la
`moyenne` commence à une adresse qui est un multiple de 4.
Ainsi, la représentation exacte en mémoire dépend des spécificités de la plateforme,
du compilateur et des options de compilation.
1.1.1.2.2 ACCES AUX MEMBRES DE LA STRUCTURE
Lorsque vous accédez aux membres d'une structure en C, le compilateur utilise l'offset
de chaque membre 2par rapport au début de la structure pour accéder directement à
la bonne position en mémoire. Par exemple, pour accéder à la valeur de l'âge dans
une instance de la structure `Etudiant` :
struct Etudiant etudiant;
int valeurAge = etudiant.age;
Le compilateur sait que l'âge est situé à un certain offset par rapport au début de la
structure et génère le code nécessaire pour accéder directement à cette position en
mémoire.
1Les "octets de bourrage" sont des octets supplémentaires ajoutés à une structure de données pour
garantir un alignement correct des membres de la structure en mémoire. Les architectures matérielles
ont souvent des exigences d'alignement pour améliorer l'efficacité des accès aux données. Ainsi, les
compilateurs insèrent des octets de bourrage entre les membres d'une structure afin de s'assurer que
chaque membre commence à une adresse mémoire qui est une multiple de la taille de son type. Bien
que cela puisse augmenter la taille totale de la structure, cela permet d'optimiser les performances
d'accès aux données, en particulier sur des architectures spécifiques.
2 L'offset d'un membre dans une structure représente la distance, en octets, entre le début de la
structure et le début du membre spécifié. C'est l'index ou la position relative d'un membre par rapport
au début de la structure. 'offset est important lors de l'accès aux membres d'une structure, car il permet
au compilateur de calculer où se trouve chaque membre en mémoire par rapport au début de la structure
4
a. INITIALISATION ET AFFICHAGE
Objectif : Créer une variable de la structure et afficher ses valeurs.
Dans la fonction main, nous allons créer une variable de type struct Etudiant nommée
etudiant1 et la déclarer puis l’initialiser avec des valeurs spécifiques, par la suite, accéder aux
membres de la structure accéder aux membres en utilisant l’opérateur (point) et afficher les
informations correspondantes. Nous utiliserons la fonction strcpy pour attribuer une nouvelle
valeur à la chaîne de caractères nom de la structure etudiant2. Avant l'appel à strcpy, la chaîne
de caractères nom de etudiant2 était initialement vide car la structure etudiant2 a été déclarée
sans initialisation explicite. Enfin, les informations de cette structure seront affichées à l'aide
de la fonction printf.
Implémentation sans typedef
#include <stdio.h>
// Définition de la structure
struct Etudiant {
char nom[50];
int age;
float moyenne;
};
int main() {
// Déclaration et initialisation d'une variable de la structure Etudiant
struct Etudiant etudiant1 = {"SANI NGATCHUI", 20, 18.5};
// Affichage des informations
printf("Nom: %s\n", etudiant1.nom);
printf("Age: %d\n", etudiant1.age);
printf("Moyenne: %.2f\n", etudiant1.moyenne);
// Autre manière d'initialiser
struct Etudiant etudiant2;
strcpy(etudiant2.nom, "SILINOU");
etudiant2.age = 20;
etudiant2.moyenne = 17.5;
// Accès aux membres et Affichage des informations de la première structure
printf("Informations de l'étudiant 1 :\n");
printf("Nom: %s\n", etudiant1.nom);
printf("Age: %d\n", etudiant1.age);
printf("Moyenne: %.2f\n\n", etudiant1.moyenne);
// Accès aux membres et Affichage des informations de la deuxième structure
printf("Informations de l'étudiant 2 :\n");
printf("Nom: %s\n", etudiant2.nom);
printf("Age: %d\n", etudiant2.age);
printf("Moyenne: %.2f\n", etudiant2.moyenne);
return 0;
}
5
typedef struct {
char nom[50];
int age;
float moyenne;
} Etudiant;
int main() {
// Déclaration et initialisation d'une variable de la structure
Etudiant
Etudiant etudiant1 = {"SANI NGATCHUI", 20, 18.5};
// Affichage des informations
printf("Nom: %s\n", etudiant1.nom);
printf("Age: %d\n", etudiant1.age);
printf("Moyenne: %.2f\n", etudiant1.moyenne);
// Autre manière d'initialiser
Etudiant etudiant2;
strcpy(etudiant2.nom, "SILINOU");
etudiant2.age = 20;
etudiant2.moyenne = 17.5;
// Accès aux membres et Affichage des informations de la première
structure
printf("Informations de l'étudiant 1 :\n");
printf("Nom: %s\n", etudiant1.nom);
printf("Age: %d\n", etudiant1.age);
printf("Moyenne: %.2f\n\n", etudiant1.moyenne);
return 0;
}
Si nous voulons afficher les informations de plusieurs étudiants, il faudrait créer un tableau de
structures Etudiant et initialiser chaque élément du tableau avec des données spécifiques.
6
Ensuite, écrire une boucle for qui va parcourir le tableau et afficher les informations pour
chaque étudiant.
#include <stdio.h>
// Définition de la structure
typedef struct {
char nom[50];
int age;
float moyenne;
} Etudiant;
int main() {
// Déclaration et initialisation d'un tableau de structures Etudiant
Etudiant etudiants[3] = {
{"SANI NGATCHUI", 20, 19.7},
{"NJOYA SAMUEL", 22, 19.5},
{"FOMO NDANDA", 21, 19.2}
};
// Affichage des informations pour chaque étudiant
for (int i = 0; i < 3; ++i) {
printf("Étudiant %d:\n", i + 1);
printf("Nom: %s\n", etudiants[i].nom);
printf("Age: %d\n", etudiants[i].age);
printf("Moyenne: %.2f\n", etudiants[i].moyenne);
printf("\n");
}
return 0;
}
int main() {
// Initialisation d'une structure Etudiant
Etudiant etudiant1 = {"SANI NGATCHUI", 20, 18.5};
// Modification des valeurs
strcpy(etudiant1.nom, "NJOYA SAMUEL");
etudiant1.age = 21;
etudiant1.moyenne = 19.5;
// Affichage des informations après modification
printf("Nom: %s\n", etudiant1.nom);
7
printf("Age: %d\n", etudiant1.age);
printf("Moyenne: %.2f\n", etudiant1.moyenne);
return 0;
}
Exercice d'Application : Gestion des Étudiants
Objectif : Écrire un programme qui permet à l'utilisateur de saisir les informations de plusieurs
étudiants, puis affiche ces informations à l’écran.
Instructions :
1. Déclaration de la structure :
- Définissez une structure nommée `Etudiant` avec les membres suivants :
- nom (tableau de caractères de taille 50),
- age (entier),
- moyenne (nombre à virgule flottante).
2. Programme principal :
- Demandez à l'utilisateur de saisir le nombre d'étudiants (n).
- Déclarez un tableau de structures `Etudiant` avec une taille égale à n.
- Utilisez une boucle pour permettre à l'utilisateur de saisir les informations de chaque
étudiant (nom, âge, moyenne).
- Utilisez une autre boucle pour afficher les informations de chaque étudiant après leur saisie.
3. Exemple d'exécution :
- Le programme doit demander à l'utilisateur de saisir le nombre d'étudiants.
- Ensuite, pour chaque étudiant, le programme doit permettre à l'utilisateur de saisir le nom,
l'âge et la moyenne.
- Enfin, le programme doit afficher les informations de tous les étudiants.
Remarques :
- Assurez-vous de gérer correctement les espaces dans les noms des étudiants lors de la
saisie.
- Vous pouvez ajouter des vérifications pour vous assurer que les données saisies sont valides
(par exemple, l'âge ne peut pas être négatif).
Note 1.1 : Nous pouvons écrire la structure dans un autre fichier hors du code main () principal.
Pour l’appeler, vous devez séparer la déclaration de la structure de sa définition. Vous déclarez
la structure dans un fichier d'en-tête (header file), et vous définissez la structure dans un fichier
source (source file). Ensuite, vous incluez le fichier d'en-tête dans le fichier source où vous
souhaitez utiliser la structure.
Fichier d'en-tête (etudiant.h)
#ifndef ETUDIANT_H
#define ETUDIANT_H
// Définition de la structure
8
typedef struct {
char nom[50];
int age;
float moyenne;
} Etudiant;
#endif // ETUDIANT_H
Les structures sont utilisées pour créer des objets complexes en regroupant plusieurs types
de données sous un seul nom, ce qui facilite la gestion et la manipulation de données
structurées. Les pointeurs sont utilisés pour des tâches avancées, telles que la gestion de la
mémoire dynamique, la manipulation d'adresses mémoire, et la création de structures de
données complexes.
Interaction entre Structures et Pointeurs
▪ Allocation Dynamique : Les pointeurs sont souvent utilisés avec des structures pour
allouer dynamiquement de la mémoire. Par exemple, l'allocation dynamique d'une
structure en C se ferait avec malloc ou calloc.
▪ Accès aux Éléments : Les pointeurs permettent d'accéder aux éléments d'une structure
de manière dynamique en utilisant l'opérateur de flèche (->). Par exemple, si p est un
pointeur vers une structure Point, p->x accède à la variable x de cette structure.
9
▪ Passage par Référence : Les pointeurs peuvent être utilisés pour passer des structures
par référence à des fonctions, permettant des modifications directes sur la structure
d'origine.
a. POINTEURS ET STRUCTURES
Objectif : Utiliser des pointeurs pour accéder et manipuler les membres d'une structure.
En nous servant de notre Structure Etudiant, nous allons utiliser un pointeur pour accéder aux
membres de la structure, ce qui va nous offrir une manière alternative d'interagir avec les
données de la structure. Le code ci-dessous illustre cela.
#include <stdio.h>
// Définition de la structure
typedef struct {
char nom[50];
int age;
float moyenne;
} Etudiant;
int main() {
// Initialisation d'une structure Etudiant
Etudiant etudiant1 = {"SANI NGATCHUI", 20, 18.5};
return 0;
}
10
est effectuée automatiquement par le système d'exploitation. La désallocation de la mémoire
allouée dynamiquement doit être effectuée manuellement par le programmeur.
Exemple : Le code ci-dessous crée dynamiquement un tableau d'entiers de taille 10 en utilisant
la fonction `malloc`, puis remplit ce tableau avec des valeurs correspondant à leurs indices à
l'aide d'une boucle. Enfin, il libère la mémoire allouée dynamiquement à l'aide de la fonction
`free` pour éviter les fuites de mémoire.
// Allocation de mémoire dynamique
int* tableau = (int*)malloc(10 * sizeof(int));
11
// Définition de la structure
typedef struct {
int x;
int y;
} Point;
// Allocation dynamique pour un objet de type Point
Point *dynamicPoint = (Point*)malloc(sizeof(Point));
Ici, `dynamicPoint` est un pointeur vers une structure `Point`, et `malloc` alloue de la mémoire
pour un objet de taille équivalente à celle de la structure `Point`.
Cette fois, `dynamicArray` pointe vers le début d'un bloc de mémoire contenant un tableau de
5 structures `Point`.
12
La fonction `calloc` est similaire à `malloc`, mais elle initialise la mémoire allouée à zéro. Son
utilisation est la même que `malloc`.
Point *dynamicPoint = (Point*)calloc(1, sizeof(Point));
Allocation Dynamique de Mémoire avec realloc :
La fonction `realloc` permet de redimensionner une zone de mémoire précédemment allouée.
// Redimensionnement du tableau à 10 éléments
Point *resizedArray = (Point*)realloc(dynamicArray, 10 * sizeof(Point));
Remarque importante : Après toute utilisation, il est impératif de libérer la mémoire allouée
avec `free`. Ne pas le faire entraînerait des fuites de mémoire.
free(dynamicPoint);
free(resizedArray);
int main() {
// Allocation dynamique d'une structure Etudiant avec pointeur
Etudiant *etudiant1 = (Etudiant *)malloc(sizeof(Etudiant));
// Vérification de l'allocation
if (etudiant1 != NULL) {
// Utilisation de la structure via le pointeur
strcpy(etudiant1->nom, "BEYAS");
etudiant1->age = 21;
etudiant1->moyenne = 18.7;
// Affichage des informations
printf("Nom : %s, Age : %d, Moyenne : %.2f\n", etudiant1->nom,
etudiant1->age, etudiant1->moyenne);
13
}
return 0;
}
Au final, l'allocation et la désallocation de mémoire en langage C avec des structures et des
pointeurs impliquent l'utilisation de `malloc`, `calloc`, `realloc` pour l'allocation, suivi de `free`
pour la libération de la mémoire allouée. Cela offre une gestion dynamique de la mémoire,
particulièrement utile pour des structures dont la taille peut varier pendant l'exécution du
programme. On peut donc dire que, les structures sont utilisées pour organiser des données,
tandis que les pointeurs permettent une manipulation plus avancée de la mémoire, notamment
avec les structures. Ensemble, ils offrent des moyens puissants de créer, organiser et
manipuler des données complexes en programmation.
1.1.4.1 PORTEE
Le choix d'utiliser une structure à l'intérieur ou à l'extérieur de la fonction main () dépend
généralement de la portée que vous souhaitez donner à cette structure dans votre programme.
Dans les exemples précédents, nous avons toujours placé notre définition de structure en
dehors de toute fonction. Cependant, sachez que celle-ci peut être circonscrite à un bloc de
sorte de limiter sa portée, comme pour les définitions de variables et les déclarations de
variables et fonctions.
Dans l’exemple ci-dessous, la structure Etudiant ne peut être utilisée que dans le bloc de la
fonction main() et les éventuels sous-blocs qui la composent.
#include <stdio.h>
int main(void) {
typedef struct {
char nom[50];
int age;
float moyenne;
} Etudiant;
Etudiant etudiant;
return 0;
}
Tableau comparatif illustrant les avantages de placer une structure à l'intérieur ou à l'extérieur
de la fonction main() :
14
Notez que le choix entre les deux dépendent du contexte spécifique de votre programme et
des principes de conception que vous souhaitez suivre. Il n'y a pas de solution unique, et la
meilleure approche dépend des besoins et de la structure de votre application.
1.1.4.2 DECLARATIONS
Jusqu’à présent, nous avons parlé de définitions de structures, toutefois, comme pour les
variables et les fonctions, il existe également des déclarations de structures. La déclaration
consiste à spécifier le nom de la structure et à indiquer au compilateur qu'elle existe, mais
sans fournir tous les détails de sa composition. La définition consiste à inclure la spécification
complète des membres de la structure et de leur type. Elle alloue également l'espace mémoire
nécessaire pour chaque instance de la structure.
L'inconvénient majeur d'utiliser des structures sans déclaration préalable est lié à la
déclaration et à l'allocation de la mémoire. En C, lorsque vous déclarez une structure, le
compilateur a besoin de connaître la taille de cette structure pour allouer l'espace mémoire
nécessaire. Si la structure contient des références à elle-même (comme dans le cas des
structures interdépendantes ou avec des membres pointeurs vers elles-mêmes), le
compilateur ne peut pas déterminer la taille de la structure tant que la définition complète de
la structure n'est pas connue.
15
Pour éviter cela, la plupart des programmeurs C utilisent des déclarations préalables
(prototypes) pour informer le compilateur de l'existence de la structure avant sa définition
complète. Par exemple, vous pourriez déclarer la structure avant la fonction main comme suit:
Une déclaration de structure est en fait une définition sans le corps de la structure.
// Déclaration préalable de la structure (sans la définition complète)
typedef struct Node Node;
int main() {
// Utilisation de la structure récursive
Node node1, node2, node3;
return 0;
}
// Définition complète de la structure
struct Node {
int data;
Node* next; // Membre pointeur vers la même structure
};
L’intérêt de cette déclaration vise à résoudre deux types de problèmes : les structures
interdépendantes3 et les structures comportant un ou des membres qui sont des pointeurs
vers elle-même4.
3Les structures interdépendantes se réfèrent à des situations où une structure contient des membres
qui sont du même type que la structure elle-même. Cela crée une dépendance récursive.
4 Les structures comportant un ou des membres qui sont des pointeurs vers elle-même se
réfèrent à une situation où une structure a un membre qui est un pointeur vers la même structure. La
principale différence avec les structures interdépendantes réside dans le fait que le membre pointeur
n'est pas obligatoirement du même type que la structure elle-même.
16
• Pointeur (ou référence) : Une référence vers le nœud suivant dans la liste.
Voici à quoi ressemble généralement la structure d'un nœud en C :
#include <stdio.h>
// Définition de la structure Node
typedef struct Node {
int data; // Données du nœud
struct Node* next; // Pointeur vers le nœud suivant
} Node;
int main() {
// Utilisation de la structure Node
Node myNode;
myNode.data = 42;
myNode.next = NULL;
// Affichage des données du nœud
printf("Data: %d\n", myNode.data);
printf("Next: %p\n", (void*)myNode.next);
return 0;
}
1.1.5.2 STRUCTURE GLOBALE DE LA LISTE CHAINEE
Une liste chaînée est simplement une séquence de nœuds connectés les uns aux autres. Le
premier nœud s'appelle généralement la tête de liste, et le dernier nœud peut avoir un pointeur
vers `NULL` pour indiquer la fin de la liste. Visuellement, une liste chaînée ressemble à ceci :
H -> [data1|next] -> [data2|next] -> [data3|next] -> ... -> [dataN|NULL]
▪ H : Tête de liste (pointe vers le premier nœud).
▪ data1, data2, data3, ..., dataN : Données stockées dans chaque nœud.
▪ next :Pointeur vers le nœud suivant.
17
chainées sont des structures de données dynamiques. Cela signifie qu'elles peuvent être
modifiées à la volée, sans avoir à redimensionner la mémoire allouée à la liste.
❖ Liste vide
La liste vide est représentée par un seul nœud. Le nœud a un pointeur vers NULL, ce qui
signifie qu'il n'y a pas d'autre nœud dans la liste.
❖ Liste non vide
La liste non vide est représentée par deux nœuds. Le premier nœud est la tête de la liste. Il
contient la donnée 7777 et un pointeur vers le deuxième nœud. Le deuxième nœud est le
dernier nœud de la liste. Il contient un pointeur vers NULL, ce qui signifie qu'il n'y a pas d'autre
nœud après lui.
❖ Légende
La légende de l'image explique les différents éléments d'une liste chainée.
▪ Tête de liste : le nœud qui pointe vers le début de la liste.
▪ Donnée : la valeur stockée dans un nœud.
▪ Lien (Pointeur) : un pointeur vers le nœud suivant dans la liste.
• Accès Séquentiel : L'accès aux éléments d'une liste chaînée se fait séquentiellement
à partir de la tête. Pour accéder à un élément spécifique, vous devez traverser la liste
depuis la tête jusqu'à cet élément.
• Insertion : Pour insérer un nouvel élément dans une liste chaînée, vous ajustez les
pointeurs dans les nœuds environnants pour inclure le nouveau nœud.
• Suppression : Pour supprimer un nœud, vous ajustez les pointeurs dans les nœuds
environnants pour contourner le nœud à supprimer, puis vous libérez la mémoire du
nœud.
Pour trouver un élément de la liste, vous devez parcourir tous les éléments jusqu'à ce que
vous trouviez celui que vous cherchez. Par exemple, pour trouver l'élément "Faire vos devoirs"
dans cet exemple, vous devriez parcourir la liste de la première à la deuxième virgule, car il
s'agit du deuxième élément de la liste.
18
Si vous mémorisez la liste, vous devez vous souvenir de l'ordre de tous les éléments. Pour
trouver un élément de la liste, vous devez simplement vous rappeler de son ordre. Par
exemple, pour trouver l'élément "faire vos devoirs", vous devriez vous rappeler que c'est le
deuxième élément de la liste.
Les listes chaînées sont un moyen de stocker des données en mémoire de manière similaire
à une liste écrite sur un papier. Chaque élément de la liste chaînée est appelé un nœud. Un
nœud contient deux informations :
- La valeur que vous voulez stocker
- Une référence vers le nœud suivant
La référence vers le nœud suivant est un pointeur qui indique l'emplacement du nœud suivant
dans la liste.
Imaginez que vous ayez une liste chaînée de nombres. Le premier nœud pourrait contenir le
nombre 1, le deuxième nœud pourrait contenir le nombre 2, et ainsi de suite. La référence vers
le nœud suivant serait le pointeur vers le nœud suivant dans la liste.
Pour trouver un élément dans une liste chaînée, vous devez commencer par le premier nœud.
Si le nœud contient la valeur que vous cherchez, vous avez trouvé l'élément. Sinon, vous
devez suivre la référence vers le nœud suivant. Vous continuez à suivre les références jusqu'à
ce que vous trouviez l'élément que vous cherchez ou jusqu'à ce que vous arriviez à la fin de
la liste.
CODE DU PROBLEME
// Structure représentant un nœud de la liste chaînée
typedef struct node {
char *value;
struct node *next;
} node;
// Fonction pour créer un nouveau nœud
node *create_node(char *value) {
node *new_node = malloc(sizeof(node));
new_node->value = value;
new_node->next = NULL;
return new_node;
}
// Fonction pour ajouter un nœud à la fin d'une liste chaînée
void append_node(node **head, node *new_node) {
if (*head == NULL) {
*head = new_node;
} else {
node *current_node = *head;
while (current_node->next != NULL) {
current_node = current_node->next;
}
current_node->next = new_node;
}
19
}
20
1.1.5.6 INCONVENIENTS DES LISTES CHAINEES
▪ Accès Séquentiel : L'accès direct à un élément nécessite une traversée séquentielle
depuis le début de la liste.
▪ Utilisation de la Mémoire : Chaque nœud nécessite un espace supplémentaire pour
stocker le pointeur, ce qui peut entraîner une surcharge mémoire par rapport à un
tableau statique.
Les listes chaînées sont largement utilisées dans de nombreuses applications et offrent une
flexibilité considérable en termes d'ajout et de suppression d'éléments. Elles sont
particulièrement utiles lorsque la taille de la structure de données peut changer fréquemment
pendant l'exécution du programme.
21
CHAPITRE 2 LES FICHIERS
L'utilisation et la manipulation de fichiers occupent une place cruciale dans le domaine de la
programmation en langage C. Les fichiers constituent un moyen essentiel de stocker et
d'organiser des données de manière persistante, permettant ainsi aux programmes de
communiquer avec le monde extérieur. Que ce soit pour la lecture de fichiers existants,
l'écriture de nouveaux contenus ou la modification de données déjà enregistrées, la gestion
des fichiers en langage C revêt une importance fondamentale. Cette interaction avec les
fichiers offre aux programmeurs une puissante flexibilité pour créer des applications robustes
et polyvalentes. La bibliothèque standard du langage C propose une panoplie de fonctions
dédiées à la manipulation de fichiers, permettant aux développeurs de concevoir des
programmes capables de lire et d'écrire des informations structurées de manière efficace.
Dans cette exploration du monde des fichiers en langage C, nous plongerons dans les
différentes techniques pour ouvrir, fermer, lire et écrire dans des fichiers. Nous aborderons
également les concepts fondamentaux tels que les modes d'ouverture, les pointeurs de
fichiers, ainsi que la gestion appropriée des erreurs liées à la manipulation des fichiers. En
comprenant ces aspects essentiels, les programmeurs seront mieux équipés pour créer des
applications qui tirent pleinement parti de la gestion de fichiers en langage C.
2.1 DEFINITION
En informatique, un fichier est un ensemble d’informations stockées sur un support, réuni sous
un même nom et manipulé comme une unité.
En informatique, une extension de fichier est un suffixe de nom de fichier fait pour identifier
son format. Elle est généralement composée de trois ou quatre caractères, séparés du nom
de fichier par un point. Par exemple, le fichier "exemple.txt" a l'extension ".txt".
Les extensions de fichiers sont utilisées par les systèmes d'exploitation et les applications pour
identifier le type de données contenues dans un fichier. Elles permettent de déterminer quel
22
programme est capable d'ouvrir et d'afficher un fichier, ainsi que l'icône à utiliser pour le
représenter.
Chaque fichier possède une organisation interne déterminée par le format des données qu'il
contient. Ces formats sont divers et spécifiques à chaque type de données, comprenant, par
exemple :
- Audio : Ogg, MP3, MP4, FLAC, Wave, etc. ;
- Vidéo : Ogg, WebM, MP4, AVI, etc. ;
- Documents : ODT, DOC et DOCX, XLS et XLSX, PDF, Postscript, etc.
Afin d'aider l'utilisateur à identifier le contenu d'un fichier, ces formats sont généralement
indiqués par une extension, un suffixe ajouté au nom du fichier. Il est crucial de noter que cette
extension est purement indicative et facultative, n'affectant en rien le contenu du fichier. Son
unique but est d'assister l'utilisateur dans la reconnaissance du type de contenu d'un fichier.
Les extensions de fichiers peuvent être définies par les développeurs de logiciels ou par des
organisations standardisées. Il existe des centaines de milliers d'extensions de fichiers
différentes, chacune correspondant à un type de données spécifique. Elles sont un élément
important de l'organisation des fichiers sur un ordinateur. Elles permettent de faciliter la
recherche et l'ouverture des fichiers, ainsi que de garantir que les fichiers sont ouverts avec le
programme approprié.
Par exemple, les systèmes d'exploitation (ou OS) DOS, Windows, OS/2, Mac OS et Unix
possèdent tous un système de fichiers, où les fichiers sont enregistrés à des emplacements
précis selon une structure hiérarchique (ou arborescente). Un fichier est placé dans un
répertoire (appelé dossier sous Windows) ou dans un sous-répertoire, à l'emplacement
souhaité dans l'arborescence.
Le système de fichiers définit les conventions de nommage des fichiers, notamment le nombre
maximal de caractères, les caractères autorisés et, dans certains systèmes, la taille du suffixe
du nom de fichier. Il détermine en outre le format permettant de déterminer le chemin d'accès
à un fichier à travers la structure de répertoires.
23
L’exemple de schéma d'arborescence de fichiers ci-dessous montre un diagramme de la
structure d'un dossier nommé "Tom". Le dossier contient quatre sous-dossiers : "Data",
"Thesis", "Notes.txt" et "Tools". Le dossier "Data" contient deux fichiers texte : "One.txt" et
"Two.txt". Le dossier "Tools" contient trois fichiers texte : "Format", "Stats" et "Old".
2.1.3 HIERARCHIE
• Chaque fichier doit avoir un nom univoque
o éventuellement plusieurs…
• L’espace de nom est structuré en hiérarchie :
o notions de répertoire et de chemin
o notion de répertoire racine (point d’entrée de la hiérarchie)
o notions de chemin relatif et de répertoire courant
24
Figure 2.2. Exemple de schéma d'arborescence de fichiers
Le langage C fournit une bibliothèque standard de fonctions pour accéder aux fichiers. Ces
fonctions permettent de lire, d'écrire, de créer, de modifier et de supprimer des fichiers. Pour
faciliter la localisation et la gestion des fichiers, ceux-ci sont classés et organisés sur leur
25
support conformément à un système de fichiers. Ce système permet à l'utilisateur de structurer
ses fichiers dans une arborescence de dossiers et de les localiser à partir d'un chemin d'accès
spécifique.
Le chemin d'accès d'un fichier est une suite de caractères décrivant sa position dans le
système de fichiers. Il se compose au minimum du nom du fichier visé et au maximum de la
suite de tous les dossiers qu'il est nécessaire de traverser pour l'atteindre depuis le répertoire
racine. Les dossiers sont séparés par un caractère spécifique : "/" sous Unix et "\" sous
Windows.
La racine d'un système de fichier est le point de départ de l'arborescence des fichiers et
dossiers. Sous Unix, c'est le répertoire "/", tandis que sous Windows, chaque lecteur constitue
une racine, comme par exemple "C :". Un chemin d'accès absolu commence par la racine,
permettant d'atteindre le fichier depuis n'importe quelle position dans l'arborescence. Un
chemin d'accès relatif, ne commençant pas par la racine, n'est valide que depuis un point
spécifique dans la hiérarchie des fichiers.
Ainsi, pour accéder à un fichier nommé "texte.txt" dans le dossier "Documents" situé dans le
dossier "utilisateur" à la racine, le chemin d'accès absolu serait /utilisateur/documents/texte.txt
sous Unix et C:\utilisateur\documents\texte.txt sous Windows (supposant qu'il soit sur le
lecteur C:). Cependant, si nous sommes déjà dans le dossier "utilisateur", un chemin relatif
comme documents/texte.txt ou documents\texte.txt serait utilisable. L’explication ci-dessus est
représentée par la démarche ci-dessous.
NB : le chemin absolu est spécifié depuis la racine du système, tandis que le chemin relatif
est défini par rapport au répertoire de travail actuel. L'utilisation de l'un ou l'autre dépend du
contexte et des besoins spécifiques de localisation des fichiers ou répertoires dans une
hiérarchie de fichiers.
26
2.1.6 METADONNEES
Les métadonnées sont des données qui décrivent d'autres données. Elles fournissent des
informations supplémentaires sur les données réelles, telles que leur contenu, leur format, leur
provenance, leur utilisation ou leur signification. Elles peuvent être utilisées à diverses fins,
notamment :
Les métadonnées peuvent être structurées ou non structurées. Les métadonnées structurées
sont organisées dans un format défini, tel que XML ou JSON. Les métadonnées non
structurées ne sont pas organisées dans un format défini, comme le texte libre ou les images.
Exemples de métadonnées
Pour une image : le titre, la description, les mots-clés, la date de prise de vue, le lieu de prise
de vue, le format du fichier.
Pour une base de données : les tables, les colonnes, les types de données, les contraintes.
Les métadonnées sont un élément essentiel de la gestion des données. Elles peuvent être
utilisées pour améliorer la découverte, l'accès, la gestion et l'analyse des données.
En outre, le système de fichiers conserve généralement des métadonnées pour chaque fichier,
incluant des informations telles que sa taille, ses droits d'accès, la date de dernier accès, la
date de dernière modification, etc. Ces données supplémentaires contribuent à une gestion
plus complète des fichiers.
27
2.1.7 LES FLUX : UN PEU DE THEORIE
La bibliothèque standard fournit différentes fonctions pour manipuler les fichiers, toutes
déclarées dans l’en-tête <stdio.h>. Toutefois, celles-ci manipulent non pas des fichiers,
mais des flux de données en provenance ou à destination de fichiers. Ces flux peuvent être
de deux types :
▪ des flux de textes qui sont des suites de caractères terminées par un caractère de fin
de ligne (\n) et formant ainsi des lignes ;
▪ des flux binaires qui sont des suites de multiplets.
La bibliothèque standard fournit également trois flux par défaut lors du démarrage d'un
programme : stdin (entrée standard), stdout (sortie standard) et stderr (sortie d'erreur
standard). Ces flux facilitent les interactions avec l'utilisateur et la gestion des erreurs. Stdin
est utilisé pour récupérer les informations fournies par l'utilisateur, stdout pour transmettre des
informations à l'utilisateur (affichées généralement dans le terminal), et stderr pour transmettre
des messages d'erreurs ou des avertissements, privilégiant une temporisation rapide pour une
transmission efficace des informations d'erreur à l'utilisateur.
28
Le mode
Le mode est une chaîne de caractères composée d’une ou plusieurs lettres qui décrit le type
du flux et la nature des opérations qu’il doit réaliser.
Cette chaîne commence obligatoirement par la seconde de ces informations. Il existe six
possibilités reprises dans le tableau ci-dessous.
Par défaut, les flux sont des flux de textes. Pour obtenir un flux binaire, il suffit d’ajouter la lettre
b à la fin de la chaîne décrivant le mode.
EXEMPLE
Le code ci-dessous tente d’ouvrir un fichier nommé « texte.txt » en lecture seule dans le
dossier courant. Notez que dans le cas où il n’existe pas, la fonction fopen() retournera un
pointeur nul (seul le mode r permet de produire ce comportement).
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = fopen("texte.txt", "r");
if (fp == NULL)
{
printf("Le fichier texte.txt n'a pas pu être ouvert\n");
return EXIT_FAILURE;
}
printf("Le fichier texte.txt existe\n");
return 0;
}
Le code est correct, mais il serait prudent de s'assurer que l’on ferme le fichier après l'avoir
ouvert. D’où intervient la fonction fclose() pour éviter des fuites de mémoire. Cela garantit que
le fichier est correctement fermé avant que le programme ne se termine.
29
La fonction fclose() termine l’association entre un flux et un fichier. S’il reste des données
temporisées, celles-ci sont écrites. La fonction retourne zéro en cas de succès et EOF en cas
d’erreur.
NB : EOF est une constante définie dans l’en-tête <stdio.h> et est utilisée par les fonctions
déclarées dans ce dernier pour indiquer soit l’arrivée à la fin d’un fichier (nous allons y venir)
soit la survenance d’une erreur. La valeur de cette constante est toujours un entier négatif.
Nous pouvons désormais compléter l’exemple précédent comme suit.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = fopen("texte.txt", "r");
if (fp == NULL)
{
printf("Le fichier texte.txt n'a pas pu être ouvert\n");
return EXIT_FAILURE;
}
printf("Le fichier texte.txt existe\n");
if (fclose(fp) == EOF)
{
printf("Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
Veillez qu’à chaque appel à la fonction fopen() corresponde un appel à la fonction fclose().
Les fonctions putc() et fputc() écrivent un caractère dans un flux. Il s’agit de l’opération
d’écriture la plus basique sur laquelle reposent toutes les autres fonctions d’écriture. Ces deux
fonctions retournent soit le caractère écrit, soit EOF si une erreur est rencontrée. La fonction
putchar(), quant à elle, est identique aux fonctions putc() et fputc() si ce n’est qu’elle écrit dans
le flux stdout.
30
NB : Techniquement, putc() et fputc() sont identiques, si ce n’est que putc() est en fait le plus
souvent une macrofonction. Étant donné que nous n’avons pas encore vu de quoi il s’agit,
préférez utiliser la fonction fputc() pour l’instant.
L’exemple ci-dessous écrit le caractère « C » dans le fichier « texte.txt ». Étant donné que nous
utilisons le mode w, le fichier est soit créé s’il n’existe pas, soit vidé de son contenu s’il existe
(revoyez le tableau des modes à la section précédente si vous êtes perdus).
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = fopen("texte.txt", "w");
if (fp == NULL)
{
printf("Le fichier texte.txt n'a pas pu être ouvert\n");
return EXIT_FAILURE;
}
if (fputc('C', fp) == EOF)
{
printf("Erreur lors de l'écriture d'un caractère\n");
return EXIT_FAILURE;
}
if (fclose(fp) == EOF)
{
printf("Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
La fonction fputs() écrit une ligne dans le flux flux. La fonction retourne un nombre positif ou
nul en cas de succès et EOF en cas d’erreurs. La fonction puts() est identique si ce n’est
qu’elle ajoute automatiquement un caractère de fin de ligne et qu’elle écrit sur le flux stdout.
Maintenant que nous savons comment écrire une ligne dans un flux précis, nous allons pouvoir
diriger nos messages d’erreurs vers le flux stderr afin que ceux-ci soient affichés le plus
rapidement possible.
L’exemple suivant écrit le mot « Bonjour » suivi d’un caractère de fin de ligne au sein du fichier
« texte.txt ».
31
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = fopen("texte.txt", "w");
if (fp == NULL)
{
fputs("Le fichier texte.txt n'a pas pu être ouvert\n", stderr);
return EXIT_FAILURE;
}
if (fputs("Bonjour\n", fp) == EOF)
{
fputs("Erreur lors de l'écriture d'une ligne\n", stderr);
return EXIT_FAILURE;
}
if (fclose(fp) == EOF)
{
fputs("Erreur lors de la fermeture du flux\n", stderr);
return EXIT_FAILURE;
}
return 0;
}
La norme garantit qu’une ligne peut contenir jusqu’à 254 caractères (caractère de fin de ligne
inclus). Aussi, veillez à ne pas écrire de ligne d’une taille supérieure à cette limite.
Les fonctions getc() et fgetc() sont les exacts miroirs des fonctions putc() et fputc() : elles
récupèrent un caractère depuis le flux fourni en argument. Il s’agit de l’opération de lecture la
plus basique sur laquelle reposent toutes les autres fonctions de lecture. Ces deux fonctions
retournent soit le caractère lu, soit EOF si la fin de fichier est rencontrée ou si une erreur est
rencontrée. La fonction getchar(), quant à elle, est identique à ces deux fonctions si ce n’est
qu’elle récupère un caractère depuis le flux stdin.
NB : Comme putc(), la fonction getc() est le plus souvent une macrofonction. Utilisez donc
plutôt la fonction fgetc() pour le moment.
L’exemple ci-dessous lit un caractère provenant du fichier texte.txt.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = fopen("texte.txt", "r");
32
if (fp == NULL)
{
fprintf(stderr, "Le fichier texte.txt n'a pas pu être
ouvert\n");
return EXIT_FAILURE;
}
int ch = fgetc(fp);
if (ch != EOF)
printf("%c\n", ch);
if (fclose(fp) == EOF)
{
fprintf(stderr, "Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
La fonction fgets() lit une ligne depuis le flux flux et la stocke dans le tableau tampon. Cette
dernière lit au plus un nombre de caractères égal à taille diminué de un afin de laisser la place
pour le caractère nul, qui est automatiquement ajouté. Dans le cas où elle rencontre un
caractère de fin de ligne : celui-ci est conservé au sein du tableau, un caractère nul est ajouté
et la lecture s’arrête.
La fonction retourne l’adresse du tableau tampon en cas de succès et un pointeur nul si la fin
du fichier est atteinte ou si une erreur est survenue.
L’exemple ci-dessous réalise donc la même opération que le code précédent, mais en utilisant
la fonction fgets().
NB : Étant donné que la norme nous garantit qu’une ligne peut contenir jusqu’à 254 caractères
(caractère de fin de ligne inclus), nous utilisons un tableau de 255 caractères pour les contenir
(puisqu’il est nécessaire de prévoir un espace pour le caractère nul).
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char buf[255];
FILE *fp = fopen("texte.txt", "r");
if (fp == NULL)
{
fprintf(stderr, "Le fichier texte.txt n'a pas pu être
ouvert\n");
33
return EXIT_FAILURE;
}
if (fgets(buf, sizeof buf, fp) != NULL)
printf("%s\n", buf);
if (fclose(fp) == EOF)
{
fprintf(stderr, "Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
*s = '\0';
}
NB : Le flux stdin étant le plus souvent mémorisé par lignes, ceci vous explique pourquoi nous
lisions les caractères restant après un appel à scanf() jusqu’à rencontrer un caractère de fin
de ligne : pour vider le tampon du flux stdin.
La fonction fscanf() retourne le nombre de conversions réussies (voire zéro, si aucune n’est
demandée ou n’a pu être réalisée) ou EOF si une erreur survient avant qu’une conversion n’ait
eu lieu.
34
Comme pour les flux de texte, il vous est possible de recourir aux fonctions putc() et fputc().
Dans le cas d’un flux binaire, ces fonctions écrivent un multiplet (sous la forme d’un int converti
en unsigned char) dans le flux spécifié.
La fonction fwrite() écrit le tableau référencé par ptr composé de nombre éléments de taille
multiplets dans le flux flux. Elle retourne une valeur égale à nombre en cas de succès et une
valeur inférieure en cas d’échec.
L’exemple suivant écrit le contenu du tableau tab dans le fichier binaire.bin. Dans le cas où un
int fait 4 octets, 20 octets seront donc écrits.
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int tab[5] = { 1, 2, 3, 4, 5 };
const size_t n = sizeof tab / sizeof tab[0];
FILE *fp = fopen("binaire.bin", "wb");
if (fp == NULL)
{
fprintf(stderr, "Le fichier binaire.bin n'a pas pu être
ouvert\n");
return EXIT_FAILURE;
}
if (fwrite(&tab, sizeof tab[0], n, fp) != n)
{
fprintf(stderr, "Erreur lors de l'écriture du tableau\n");
return EXIT_FAILURE;
}
if (fclose(fp) == EOF)
{
fprintf(stderr, "Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
35
Lors de la lecture depuis un flux binaire, les fonctions fgetc() et getc() permettent de récupérer
un multiplet (sous la forme d’un unsigned char converti en int) depuis un flux.
La fonction fread() est l’inverse de la fonction fwrite() : elle lit nombre éléments de taille
multiplets depuis le flux flux et les stocke dans l’objet référencé par ptr. Elle retourne une valeur
égale à nombre en cas de succès ou une valeur inférieure en cas d’échec.
Dans le cas où nous disposons du fichier binaire.bin produit par l’exemple de la section
précédente, nous pouvons reconstituer le tableau tab.
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int tab[5] = { 0 };
const size_t n = sizeof tab / sizeof tab[0];
FILE *fp = fopen("binaire.bin", "rb");
if (fp == NULL)
{
fprintf(stderr, "Le fichier binaire.bin n'a pas pu être
ouvert\n");
return EXIT_FAILURE;
}
if (fread(&tab, sizeof tab[0], n, fp) != n)
{
fprintf(stderr, "Erreur lors de la lecture du tableau\n");
return EXIT_FAILURE;
}
if (fclose(fp) == EOF)
{
fprintf(stderr, "Erreur lors de la fermeture du flux\n");
return EXIT_FAILURE;
}
return 0;
}
Résultat
tab[0] = 1
tab[1] = 2
tab[2] = 3
36
tab[3] = 4
tab[4] = 5
2.1.11 RESUME
L’extension d’un fichier fait partie de son nom et est purement indicative ;
Les fichiers sont manipulés à l’aide de flux afin de s’abstraire des disparités entre systèmes
d’exploitation et de recourir à la temporisation ;
Trois flux de texte sont disponibles par défaut : stdin, stdout et stderr ;
La fonction fopen() permet d’associer un fichier à un flux, la fonction fclose() met fin à cette
association ;
Les fonctions fputc(), fputs() et fprintf() écrivent des données dans un flux de texte ;
Les fonctions fgetc(), fgets() et fscanf() lisent des données depuis un flux de texte ;
Les fonctions fputc() et fwrite() écrivent un ou plusieurs multiplets dans un flux binaire ;
Les fonctions fgetc() et fread() lisent un ou plusieurs multiplets depuis un flux binaire.
EN LANGAGE C
1. Écrivez un programme en langage C qui crée un fichier texte nommé "donnees.txt" et y écrit
les entiers de 1 à 10, un entier par ligne.
2. Ensuite, lisez le contenu du fichier "donnees.txt" et affichez les entiers à l'écran.
3. Ajoutez une fonction au programme qui calcule la somme des entiers lus depuis le fichier
et affiche le résultat.
BINAIRES EN LANGAGE C
1. Créez une structure en langage C appelée "Etudiant" avec les champs suivants : nom
(chaîne de caractères), numéro d'étudiant (entier), et moyenne (nombre à virgule flottante).
2. Écrivez une fonction qui génère un tableau de structures "Etudiant" avec des données
fictives pour trois étudiants.
3. Écrivez ces structures dans un fichier binaire appelé "etudiants.dat".
4. Ensuite, lisez le contenu du fichier binaire "etudiants.dat" et affichez les informations des
étudiants à l'écran.
5. Ajoutez une fonction au programme qui calcule la moyenne générale des étudiants et affiche
le résultat.
37
2.2.3 EXERCICE 3 : GESTION DYNAMIQUE DE LA MEMOIRE
Objectif : Écrire un programme en langage C qui demande à l'utilisateur de saisir la taille d'un
tableau, alloue dynamiquement de la mémoire pour ce tableau, puis demande à l'utilisateur de
remplir le tableau avec des entiers. Enfin, le programme doit afficher la somme des éléments
du tableau et libérer la mémoire allouée.
TEXTE
Écrire un programme qui permet de lire un fichier texte et d’en afficher le contenu à l’écran, un
mot par ligne.
EXPLICATIONS
Pour réaliser cet exercice, il faudra utiliser les fonctions fopen(), fgets() et printf().
▪ La fonction fopen() permet d’ouvrir un fichier. Nous allons utiliser le mode r pour lire le
fichier en lecture seule.
▪ La fonction fgets() permet de lire une ligne depuis un fichier. Nous allons utiliser cette
fonction pour lire les mots du fichier un par un.
▪ La fonction printf() permet d’afficher des données à l’écran. Nous allons utiliser cette
fonction pour afficher les mots du fichier, un par ligne.
EXPLICATIONS
Pour réaliser cet exercice, il faudra utiliser les fonctions fopen(), fgets() et atoi().
▪ La fonction fopen() permet d’ouvrir un fichier. Nous allons utiliser le mode r pour lire le
fichier en lecture seule.
▪ La fonction fgets() permet de lire une ligne depuis un fichier. Nous allons utiliser cette
fonction pour lire les mots du fichier un par un.
▪ La fonction atoi() permet de convertir une chaîne de caractères en entier. Nous allons
utiliser cette fonction pour convertir chaque mot en entier.
38
CHAPITRE 3 LES LISTES CHAINEES
3.1 INTRODUCTION
Les listes chainées sont des structures de données dynamiques qui permettent de stocker des
données de manière non contiguë en mémoire. Elles sont composées d'éléments, appelés
nœuds, qui sont reliés entre eux par des pointeurs. Elles permettent de stocker et organiser
des données de manière dynamique, facilitant l'ajout, la suppression et la modification
d'éléments sans nécessiter une allocation statique de mémoire. Deux types courants de listes
chaînées sont les listes chaînées simples et les listes chaînées doubles, chacune avec ses
propres caractéristiques et avantages. Ces structures offrent une flexibilité accrue par rapport
aux tableaux statiques, car elles permettent l'insertion et la suppression efficaces d'éléments
sans nécessiter une allocation de mémoire préalable. En C, la mise en œuvre de listes
chaînées se base sur des nœuds qui contiennent les données ainsi que des pointeurs vers
les éléments suivants ou précédents de la liste.
La file est une structure de données, qui permet de stocker les données dans l'ordre FIFO
(First In First Out) - en français Premier Entré Premier Sorti). La récupération des données
sera faite dans l'ordre d'insertion.
L'image ci-dessus montre un diagramme d'une file qui permet de stocker des données dans
un ordre FIFO (premier entré, premier sorti). Les données sont stockées dans une liste
chaînée, où chaque élément de la liste contient les données elles-mêmes et un pointeur vers
39
l'élément suivant dans la liste. Le premier élément de la file est appelé le premier entré (First
In). Le dernier élément de la file est appelé le dernier entré (Last In).
Dans une file, les éléments sont placés les uns à côtés des autres on sort les éléments du plus
ancien vers le plus récent. Cela correspond à ce qui se passe dans une file d'attente : si on
prend l'exemple d’un garagiste, il reçoit les clients le matin et réceptionne leurs fiches dans
une file d'attente. Pour effectuer les travaux sur le véhicule, le garagiste va récupérer les fiches
en commençant par le premier client arrivé, c'est à dire la première fiche entrée dans la file.
C'est la loi du premier arrivé, premier servi ou FIFO (First In First out). C'est le comportement
classique d'une file d'attente.
FILE
Pour définir un élément de la file, le type struct sera utilisé, comprenant un champ "donnee" et
un pointeur "suivant". Il est essentiel que le pointeur suivant soit du même type que l'élément
lui-même, garantissant ainsi sa capacité à pointer vers l'élément. Ce mécanisme de pointage
via le pointeur suivant facilitera l'accès au prochain élément de la file.
typedef struct ElementListe {
char *donnee;
struct ElementListe *suivant;
}Element;
Pour assurer le contrôle de la file, il est recommandé de sauvegarder certains éléments clés
tels que le premier élément, le dernier élément et le nombre total d'éléments. Pour mettre en
œuvre cette gestion, une autre structure sera utilisée, bien que l'utilisation de variables ne soit
pas exclue. La composition de cette structure est la suivante :
typedef struct ListeRepere{
Element *debut;
Element *fin;
int taille;
} File;
3.2.2.1 INITIALISATION
La fonction initialisation initialise les pointeurs debut et fin avec NULL ainsi que la taille avec
la valeur 0. Cela doit être effectué avant toute autre opération sur la file.
void initialisation (File * suite){
suite->debut = NULL;
suite->fin = NULL;
suite->taille = 0;
}
40
3.2.2.2 INSERTION D'UN ELEMENT DANS LA FILE
L'algorithme d'insertion implique la déclaration d'éléments à insérer, l'allocation de mémoire
pour le nouvel élément, le remplissage des données, la mise à jour des pointeurs debut et fin,
et l'ajustement de la taille de la file.
Etapes
▪ Déclaration d'élément(s) à insérer
▪ Allocation de la mémoire pour le nouvel élément
▪ Remplir le contenu du champ de données
▪ Mettre à jour le pointeur debut vers le 1er élément (le début de file)
▪ Mettre à jour le pointeur fin (ça nous servira pour l'insertion vers la fin de la file)
▪ Mettre à jour la taille de la file
La 1ère image affiche le début de l'insertion, donc la liste a la taille 1 après l'insertion.
Dans la file, l'élément à récupérer c'est le 1er entré. Pour cela, l'insertion se fera toujours à la
fin de la file. Il s'agit de l'ordre normal de l'insertion (1er, 2ème, 3ème ...... etc.).
41
LA FONCTION
Le code ci-dessous représente une fonction "inserer" qui prend en paramètres une file (File) représentée
par une structure, un élément courant (courant) et une donnée (donnee) à insérer dans la file. La
fonction crée un nouveau élément (nouveau_element) en allouant dynamiquement de la mémoire pour
la structure de l'élément ainsi que pour la donnée à stocker. Ensuite, la donnée est copiée dans le
nouveau élément. En fonction de la position du courant (tête de la file ou un élément existant), le
nouveau élément est inséré à la position appropriée dans la file, et les liens sont mis à jour en
conséquence. Enfin, la taille de la file est mise à jour, et la fonction renvoie 0 en cas de succès ou -1 en
cas d'échec (manque de mémoire).
int inserer(File *suite, Element *courant, char *donnee) {
// Création d'un nouveau_element et allocation mémoire
Element *nouveau_element;
if ((nouveau_element = (Element *)malloc(sizeof(Element))) == NULL)
return -1;
// Allocation mémoire pour la donnée
if ((nouveau_element->donnee = (char *)malloc(50 * sizeof(char))) ==
NULL) {
free(nouveau_element); // Libération de la mémoire en cas d'échec
return -1;
}
// Copie de la donnée dans le nouveau_element
strcpy(nouveau_element->donnee, donnee);
42
nouveau_element->suivant = suite->debut;
suite->debut = nouveau_element;
} else {
// Cas où la file n'est pas vide
if (courant->suivant == NULL)
suite->fin = nouveau_element;
// Mise à jour des liens pour insérer le nouveau_element après le
courant
nouveau_element->suivant = courant->suivant;
courant->suivant = nouveau_element;
}
// Mise à jour de la taille de la file
suite->taille++;
return 0;
}
IMPLEMENTATION DE LA FONCTION DANS LE MAIN()
Dans l’exemple ci-dessous, la fonction main initialise une file, ajoute quelques éléments à
l'aide de la fonction enfiler, puis affiche le contenu de la file. Enfin, elle libère la mémoire
allouée pour les éléments de la file. Vous pouvez adapter ce code en fonction de vos besoins
spécifiques.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Structure pour un élément de la file
typedef struct Element {
char *donnee;
struct Element *suivant;
} Element;
// Structure pour la file
typedef struct File {
Element *debut;
Element *fin;
int taille;
} File;
// Prototype de la fonction d'insertion
int inserer(File *suite, Element *courant, char *donnee);
int main() {
// Initialisation de la file
File maFile;
maFile.debut = NULL;
maFile.fin = NULL;
maFile.taille = 0;
// Exemple d'utilisation de la fonction enfiler
char donnee1[] = "Premier element";
char donnee2[] = "Deuxieme element";
char donnee3[] = "Troisieme element";
inserer(&maFile, NULL, donnee1);
inserer (&maFile, maFile.debut, donnee2);
inserer (&maFile, maFile.fin, donnee3);
// Affichage des éléments de la file
Element *actuel = maFile.debut;
printf("Contenu de la file : ");
while (actuel != NULL) {
printf("%s -> ", actuel->donnee);
43
actuel = actuel->suivant;
}
printf("NULL\n");
// Libération de la mémoire allouée pour les éléments
actuel = maFile.debut;
while (actuel != NULL) {
Element *temp = actuel;
actuel = actuel->suivant;
free(temp->donnee);
free(temp);
}
return 0;
}
LA FONCTION
Le code suivant représente une fonction "retirer" qui prend en paramètre une file (File)
représentée par une structure. La fonction vise à retirer l'élément en tête de la file. Tout
d'abord, elle vérifie si la file n'est pas vide (taille égale à zéro), renvoyant -1 en cas contraire.
44
Ensuite, un pointeur "supp_element" est utilisé pour pointer vers l'élément en tête de la file.
Les liens de la file sont mis à jour pour pointer vers l'élément suivant comme nouveau début
de file. Ensuite, la mémoire allouée pour la donnée de l'élément et pour l'élément lui-même est
libérée à l'aide de la fonction "free". Enfin, la taille de la file est réduite et la fonction renvoie 0
pour indiquer le succès de l'opération.
int retirer (File * suite){
Element *supp_element;
if (suite->taille == 0)
return -1;
supp_element = suite->debut;
suite->debut = suite->debut->suivant;
free (supp_element->donnee);
free (supp_element);
suite->taille--;
return 0;
}
IMPLEMENTATION DE LA FONCTION DANS LE MAIN()
Dans ce code, le programme principal initialise une file vide et tente de retirer un élément, affichant un
message indiquant le succès ou l'échec de l’opération (La fonction "retirer" retire l'élément en tête de la
file.).
#include <stdio.h>
#include <stdlib.h>
// Structure de l'élément de la file
typedef struct Element {
// Données de l'élément
int donnee;
// Pointeur vers l'élément suivant dans la file
struct Element *suivant;
} Element;
// Structure de la file
typedef struct File {
// Pointeur vers le début de la file
Element *debut;
// Pointeur vers la fin de la file
Element *fin;
// Taille de la file (nombre d'éléments)
int taille;
} File;
// Déclaration de la fonction retirer
int retirer(File *suite);
int main() {
// Initialisation de la file
File maFile;
maFile.debut = NULL;
maFile.fin = NULL;
maFile.taille = 0;
// Exemple d'utilisation de la fonction retirer
int resultat = retirer(&maFile);
if (resultat == 0) {
printf("Retrait réussi.\n");
} else {
45
printf("La file est vide, impossible de retirer.\n");
}
return 0;
}
La procédure `affiche` ci-dessous prend en entrée un pointeur vers une structure de file (`File`)
et parcourt ses éléments à l'aide d'une boucle `for`. Pour chaque élément, elle affiche la
donnée associée (supposée être une chaîne de caractères) en utilisant la fonction `printf`. La
fonction utilise un pointeur `courant` pour traverser la file, en partant du début, et se déplace
à l'élément suivant à chaque itération. Ainsi, l'affichage résultant est une ligne de texte
montrant les données de chaque élément de la file, dans l'ordre où ils sont stockés, séparés
par des espaces.
void affiche(File *suite){
Element *courant;
int i;
courant = suite->debut;
for(i=0;i<suite->taille;++i){
printf(" %s ", courant->donnee);
courant = courant->suivant;
}
}
IMPLEMENTATION DE LA PROCEDURE DANS LE MAIN()
Le code suivant définit une structure de données de file (`File`) implémentée comme une liste chaînée
de structures élémentaires (`Element`). Il crée une file (`suite`) dans la fonction `main`, ajoute deux
éléments à la file avec des données spécifiques, puis affiche le contenu de la file à l'aide de la fonction
`affiche`.
#include <stdio.h>
46
int i;
courant = suite->debut;
Une pile est une structure de données qui permet de stocker des éléments de manière
ordonnée. Les éléments sont ajoutés et supprimés au sommet de la pile, ce qui signifie que le
dernier élément ajouté est le premier élément à être supprimé. Par analogie, elle peut être
représentée par une pile d'assiettes. Les assiettes sont ajoutées au sommet de la pile, et la
première assiette à être retirée est la dernière à avoir été ajoutée.
Le schéma ci-dessous illustre une pile composée de trois éléments : "Donnée 1", "Donnée 2",
"Donnée 3". "Donnée 1" occupe la position supérieure de la pile, tandis que "Donnée 3" se
trouve à la base. Chaque élément est représenté par un rectangle, relié aux autres par des
47
lignes pour indiquer leurs relations(pointeurs). Pour ajouter un élément, il suffit de le placer au
sommet, et pour le supprimer, il doit être retiré de la base de la pile.
48
la pile, et le champ "taille" indique le nombre d'éléments, quelle que soit l'opération effectuée
sur la pile.
49