Fil d'Ariane
Mis à jour le mardi 25 juillet 2017
  • 40 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Lire et écrire dans des fichiers

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

Le défaut avec les variables, c'est qu'elles n'existent que dans la mémoire vive. Une fois votre programme arrêté, toutes vos variables sont supprimées de la mémoire et il n'est pas possible de retrouver ensuite leur valeur. Comment peut-on, dans ce cas-là, enregistrer les meilleurs scores obtenus à son jeu ? Comment peut-on faire un éditeur de texte si tout le texte écrit disparaît lorsqu'on arrête le programme ?

Heureusement, on peut lire et écrire dans des fichiers en langage C. Ces fichiers seront écrits sur le disque dur de votre ordinateur : l'avantage est donc qu'ils restent là, même si vous arrêtez le programme ou l'ordinateur.

Pour lire et écrire dans des fichiers, nous allons avoir besoin de réutiliser tout ce que nous avons appris jusqu'ici : pointeurs, structures, chaînes de caractères, etc.

Ouvrir et fermer un fichier

Pour lire et écrire dans des fichiers, nous allons nous servir de fonctions situées dans la bibliothèquestdioque nous avons déjà utilisée.
Oui, cette bibliothèque-là contient aussi les fonctionsprintfetscanfque nous connaissons bien ! Mais elle ne contient pas que ça : il y a aussi d'autres fonctions, notamment des fonctions faites pour travailler sur des fichiers.

Assurez-vous donc, pour commencer, que vous incluez bien au moins les bibliothèquesstdio.hetstdlib.hen haut de votre fichier.c:

#include <stdlib.h>
#include <stdio.h>

Ces bibliothèques sont tellement fondamentales, tellement basiques, que je vous recommande d'ailleurs de les inclure dans tous vos futurs programmes, quels qu'ils soient.

Bien. Maintenant que les bonnes bibliothèques sont incluses, nous allons pouvoir attaquer les choses sérieuses. Voici ce qu'il faut faire à chaque fois dans l'ordre quand on veut ouvrir un fichier, que ce soit pour le lire ou pour y écrire.

  1. On appelle la fonction d'ouverture de fichierfopenqui nous renvoie un pointeur sur le fichier.

  2. On vérifie si l'ouverture a réussi (c'est-à-dire si le fichier existait) en testant la valeur du pointeur qu'on a reçu. Si le pointeur vautNULL, c'est que l'ouverture du fichier n'a pas fonctionné, dans ce cas on ne peut pas continuer (il faut afficher un message d'erreur).

  3. Si l'ouverture a fonctionné (si le pointeur est différent deNULLdonc), alors on peut s'amuser à lire et écrire dans le fichier à travers des fonctions que nous verrons un peu plus loin.

  4. Une fois qu'on a terminé de travailler sur le fichier, il faut penser à le « fermer » avec la fonctionfclose.

Nous allons dans un premier temps apprendre à nous servir defopenetfclose. Une fois que vous saurez faire cela, nous apprendrons à lire le contenu du fichier et à y écrire du texte.

fopen: ouverture du fichier

Dans le chapitre sur les chaînes, nous nous sommes servis des prototypes des fonctions comme d'un « mode d'emploi ». C'est comme ça que les programmeurs font en général : ils lisent le prototype et comprennent comment ils doivent utiliser la fonction. Je reconnais néanmoins que l'on a toujours besoin de quelques petites explications à côté quand même !

Voyons justement le prototype de la fonctionfopen:

FILE* fopen(const char* nomDuFichier, const char* modeOuverture);

Cette fonction attend deux paramètres :

  • le nom du fichier à ouvrir ;

  • le mode d'ouverture du fichier, c'est-à-dire une indication qui mentionne ce que vous voulez faire : seulement écrire dans le fichier, seulement le lire, ou les deux à la fois.

Cette fonction renvoie… un pointeur surFILE! C'est un pointeur sur une structure de typeFILE. Cette structure est définie dansstdio.h. Vous pouvez ouvrir ce fichier pour voir de quoi est constitué le typeFILE, mais ça n'a aucun intérêt en ce qui nous concerne.

Pourquoi le nom de la structure est-il tout en majuscules (FILE) ? Je croyais que les noms tout en majuscules étaient réservés aux constantes et auxdefine?

Cette « règle », c'est moi qui me la suis fixée (et nombre d'autres programmeurs suivent la même, d'ailleurs). Ça n'a jamais été une obligation. Force est de croire que ceux qui ont programméstdione suivaient pas exactement les mêmes règles !
Cela ne doit pas vous perturber pour autant. Vous verrez d'ailleurs que les bibliothèques que nous étudierons ensuite respectent les mêmes règles que moi, à savoir ici mettre juste la première lettre d'une structure en majuscule.

Revenons à notre fonctionfopen. Elle renvoie unFILE*. Il est extrêmement important de récupérer ce pointeur pour pouvoir ensuite lire et écrire dans le fichier.
Nous allons donc créer un pointeur deFILEau début de notre fonction (par exemple la fonctionmain) :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;

    return 0;
}

Le pointeur est initialisé àNULLdès le début. Je vous rappelle que c'est une règle fondamentale que d'initialiser ses pointeurs àNULLdès le début si on n'a pas d'autre valeur à leur donner. Si vous ne le faites pas, vous augmentez considérablement le risque d'erreur par la suite.

Maintenant, nous allons appeler la fonctionfopenet récupérer la valeur qu'elle renvoie dans le pointeurfichier. Mais avant ça, il faut que je vous explique comment se servir du second paramètre, le paramètremodeOuverture. En effet, il y a un code à envoyer qui indiquera à l'ordinateur si vous ouvrez le fichier en mode de lecture seule, d'écriture seule, ou des deux à la fois.
Voici les modes d'ouverture possibles.

  • "r": lecture seule. Vous pourrez lire le contenu du fichier, mais pas y écrire. Le fichier doit avoir été créé au préalable.

  • "w": écriture seule. Vous pourrez écrire dans le fichier, mais pas lire son contenu. Si le fichier n'existe pas, il sera créé.

  • "a": mode d'ajout. Vous écrirez dans le fichier, en partant de la fin du fichier. Vous ajouterez donc du texte à la fin du fichier. Si le fichier n'existe pas, il sera créé.

  • "r+": lecture et écriture. Vous pourrez lire et écrire dans le fichier. Le fichier doit avoir été créé au préalable.

  • "w+": lecture et écriture, avec suppression du contenu au préalable. Le fichier est donc d'abord vidé de son contenu, vous pouvez y écrire, et le lire ensuite. Si le fichier n'existe pas, il sera créé.

  • "a+": ajout en lecture / écriture à la fin. Vous écrivez et lisez du texte à partir de la fin du fichier. Si le fichier n'existe pas, il sera créé.

Pour information, je ne vous ai présenté qu'une partie des modes d'ouverture. Il y en a le double, en réalité ! Pour chaque mode qu'on a vu là, si vous ajoutez un"b"après le premier caractère ("rb","wb","ab","rb+","wb+","ab+"), alors le fichier est ouvert en mode binaire. C'est un mode un peu particulier que nous ne verrons pas ici. En fait, le mode texte est fait pour stocker… du texte comme le nom l'indique (uniquement des caractères affichables), tandis que le mode binaire permet de stocker… des informations octet par octet (des nombres, principalement). C'est sensiblement différent.
Le fonctionnement est de toute façon quasiment le même que celui que nous allons voir ici.

Personnellement, j'utilise souvent"r"(lecture),"w"(écriture) et"r+"(lecture et écriture). Le mode"w+"est un peu dangereux parce qu'il vide de suite le contenu du fichier, sans demande de confirmation. Il ne doit être utilisé que si vous voulez d'abord réinitialiser le fichier.
Le mode d'ajout ("a") peut être utile dans certains cas, si vous voulez seulement ajouter des informations à la fin du fichier.

Si vous écrivez une fonctionchargerNiveau(pour charger le niveau d'un jeu, par exemple), le mode"r"suffit. Si vous écrivez une fonctionenregistrerNiveau, le mode"w"sera alors adapté.

Le code suivant ouvre le fichiertest.txten mode"r+"(lecture / écriture) :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;

    fichier = fopen("test.txt", "r+");

    return 0;
}

Le pointeurfichierdevient alors un pointeur surtest.txt.

Où doit être situétest.txt?

Il doit être situé dans le même dossier que votre exécutable (.exe).
Pour les besoins de ce chapitre, créez un fichiertest.txtcomme moi dans le même dossier que le.exe(fig. suivante).

Le fichier doit être placé dans le même dossier que l'exécutable

Comme vous le voyez, je travaille actuellement avec l'IDE Code::Blocks, ce qui explique la présence d'un fichier de projet.cbp(au lieu de.sln, si vous avez Visual C++ par exemple). Bref, ce qui compte c'est de bien voir que mon programme (tests.exe) est situé dans le même dossier que le fichier dans lequel on va lire et écrire (test.txt).

Le fichier doit-il être de type.txt?

Non. C'est vous qui choisissez l'extension lorsque vous ouvrez le fichier. Vous pouvez très bien inventer votre propre format de fichier.niveaupour enregistrer les niveaux de vos jeux par exemple.

Le fichier doit-il être obligatoirement dans le même répertoire que l'exécutable ?

Non plus. Il peut être dans un sous-dossier :

fichier = fopen("dossier/test.txt", "r+");

Ici, le fichiertest.txtest dans un sous-dossier appelédossier. Cette méthode, que l'on appelle chemin relatif est plus pratique. Comme ça, cela fonctionnera peu importe l'endroit où est installé votre programme.

Il est aussi possible d'ouvrir un autre fichier n'importe où ailleurs sur le disque dur. Dans ce cas, il faut écrire le chemin complet (ce qu'on appelle le chemin absolu) :

fichier = fopen("C:\\Program Files\\Notepad++\\readme.txt", "r+");

Ce code ouvre le fichierreadme.txtsitué dansC:\Program Files\Notepad++.

Le défaut des chemins absolus, c'est qu'ils ne fonctionnent que sur un OS précis. Ce n'est pas une solution portable, donc. Si vous aviez été sous Linux, vous auriez dû écrire un chemin à-la-linux, tel que :

fichier = fopen("/home/mateo/dossier/readme.txt", "r+");

Je vous recommande donc d'utiliser des chemins relatifs plutôt que des chemins absolus. N'utilisez les chemins absolus que si votre programme est fait pour un OS précis et doit modifier un fichier précis quelque part sur votre disque dur.

Tester l'ouverture du fichier

Le pointeurfichierdevrait contenir l'adresse de la structure de typeFILEqui sert de descripteur de fichier. Celui-ci a été chargé en mémoire pour vous par la fonctionfopen().
À partir de là, deux possibilités :

  • soit l'ouverture a réussi, et vous pouvez continuer (c'est-à-dire commencer à lire et écrire dans le fichier) ;

  • soit l'ouverture a échoué parce que le fichier n'existait pas ou était utilisé par un autre programme. Dans ce cas, vous devez arrêter de travailler sur le fichier.

Juste après l'ouverture du fichier, il faut impérativement vérifier si l'ouverture a réussi ou non. Pour faire ça, c'est très simple : si le pointeur vautNULL, l'ouverture a échoué. S'il vaut autre chose queNULL, l'ouverture a réussi.
On va donc suivre systématiquement le schéma suivant :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;

    fichier = fopen("test.txt", "r+");

    if (fichier != NULL)
    {
        // On peut lire et écrire dans le fichier
    }
    else
    {
        // On affiche un message d'erreur si on veut
        printf("Impossible d'ouvrir le fichier test.txt");
    }

    return 0;
}

Faites toujours cela lorsque vous ouvrez un fichier. Si vous ne le faites pas et que le fichier n'existe pas, vous risquez un plantage du programme par la suite.

fclose: fermer le fichier

Si l'ouverture du fichier a réussi, vous pouvez le lire et y écrire (nous allons voir sous peu comment faire).
Une fois que vous aurez fini de travailler avec le fichier, il faudra le « fermer ». On utilise pour cela la fonctionfclosequi a pour rôle de libérer la mémoire, c'est-à-dire supprimer votre fichier chargé dans la mémoire vive.

Son prototype est :

int fclose(FILE* pointeurSurFichier);

Cette fonction prend un paramètre : votre pointeur sur le fichier.
Elle renvoie unintqui indique si elle a réussi à fermer le fichier. Cetintvaut :

  • 0: si la fermeture a marché ;

  • EOF: si la fermeture a échoué.EOFest undefinesitué dansstdio.hqui correspond à un nombre spécial, utilisé pour dire soit qu'il y a eu une erreur, soit que nous sommes arrivés à la fin du fichier. Dans le cas présent cela signifie qu'il y a eu une erreur.

A priori, la fermeture se passe toujours bien : je n'ai donc pas l'habitude de tester si lefclosea marché. Vous pouvez néanmoins le faire si vous le voulez.

Pour fermer le fichier, on va donc écrire :

fclose(fichier);

Au final, le schéma que nous allons suivre pour ouvrir et fermer un fichier sera le suivant :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;

    fichier = fopen("test.txt", "r+");

    if (fichier != NULL)
    {
        // On lit et on écrit dans le fichier
        
        // ...
        
        fclose(fichier); // On ferme le fichier qui a été ouvert
    }

    return 0;
}

Je n'ai pas mis leelseici pour afficher un message d'erreur si l'ouverture a échoué, mais vous pouvez le faire si vous le désirez.

Il faut toujours penser à fermer son fichier une fois que l'on a fini de travailler avec. Cela permet de libérer de la mémoire.
Si vous oubliez de libérer la mémoire, votre programme risque à la fin de prendre énormément de mémoire qu'il n'utilise plus. Sur un petit exemple comme ça ce n'est pas flagrant, mais sur un gros programme, bonjour les dégâts !

Oublier de libérer la mémoire, ça arrive. Ça vous arrivera d'ailleurs très certainement. Dans ce cas, vous serez témoins de ce que l'on appelle des fuites mémoire. Votre programme se mettra alors à utiliser plus de mémoire que nécessaire sans que vous arriviez à comprendre pourquoi. Bien souvent, il s'agit simplement d'un ou deux « détails » comme des petitsfcloseoubliés.

Différentes méthodes de lecture / écriture

Maintenant que nous avons écrit le code qui ouvre et ferme le fichier, nous n'avons plus qu'à insérer le code qui le lit et y écrit.

Nous allons commencer par voir comment écrire dans un fichier (ce qui est un peu plus simple), puis nous verrons ensuite comment lire dans un fichier.

Écrire dans le fichier

Il existe plusieurs fonctions capables d'écrire dans un fichier. Ce sera à vous de choisir celle qui est la plus adaptée à votre cas.
Voici les trois fonctions que nous allons étudier :

  • fputc: écrit un caractère dans le fichier (UN SEUL caractère à la fois) ;

  • fputs: écrit une chaîne dans le fichier ;

  • fprintf: écrit une chaîne « formatée » dans le fichier, fonctionnement quasi-identique àprintf.

fputc

Cette fonction écrit un caractère à la fois dans le fichier. Son prototype est :

int fputc(int caractere, FILE* pointeurSurFichier);

Elle prend deux paramètres.

  • Le caractère à écrire (de typeint, ce qui comme je vous l'ai dit revient plus ou moins à utiliser unchar, sauf que le nombre de caractères utilisables est ici plus grand). Vous pouvez donc écrire directement'A'par exemple.

  • Le pointeur sur le fichier dans lequel écrire. Dans notre exemple, notre pointeur s'appellefichier. L'avantage de demander le pointeur de fichier à chaque fois, c'est que vous pouvez ouvrir plusieurs fichiers en même temps et donc lire et écrire dans chacun de ces fichiers. Vous n'êtes pas limités à un seul fichier ouvert à la fois.

La fonction retourne unint, c'est un code d'erreur. CetintvautEOFsi l'écriture a échoué, sinon il a une autre valeur.
Comme le fichier a normalement été ouvert avec succès, je n'ai pas l'habitude de tester si chacun de mesfputca réussi, mais vous pouvez le faire encore une fois si vous le voulez.

Le code suivant écrit la lettre'A'danstest.txt(si le fichier existe, il est remplacé ; s'il n'existe pas, il est créé). Il y a tout dans ce code : ouverture, test de l'ouverture, écriture et fermeture.

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        fputc('A', fichier); // Écriture du caractère A
        fclose(fichier);
    }
 
    return 0;
}

Ouvrez votre fichiertest.txt. Que voyez-vous ?
C'est magique, le fichier contient maintenant la lettre'A'comme le montre la fig. suivante.

Le fichier contient désormais la lettre 'A'
fputs

Cette fonction est très similaire àfputc, à la différence près qu'elle écrit tout une chaîne, ce qui est en général plus pratique que d'écrire caractère par caractère.
Cela dit,fputcreste utile lorsque vous devez écrire caractère par caractère, ce qui arrive fréquemment.

Prototype de la fonction :

char* fputs(const char* chaine, FILE* pointeurSurFichier);

Les deux paramètres sont faciles à comprendre.

  • chaine: la chaîne à écrire. Notez que le type ici estconst char*: en ajoutant le motconstdans le prototype, la fonction indique que pour elle la chaîne sera considérée comme une constante. En un mot comme en cent : elle s'interdit de modifier le contenu de votre chaîne. C'est logique quand on y pense :fputsdoit juste lire votre chaîne, pas la modifier. C'est donc pour vous une information (et une sécurité) comme quoi votre chaîne ne subira pas de modification.

  • pointeurSurFichier: comme pourfputc, il s'agit de votre pointeur de typeFILE*sur le fichier que vous avez ouvert.

La fonction renvoieEOFs'il y a eu une erreur, sinon c'est que cela a fonctionné. Là non plus, je ne teste en général pas la valeur de retour.

Testons l'écriture d'une chaîne dans le fichier :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        fputs("Salut les Zér0s\nComment allez-vous ?", fichier);
        fclose(fichier);
    }
 
    return 0;
}

La fig. suivante présente le fichier une fois modifié par le programme.

Le fichier contient désormais notre chaîne
fprintf

Voici un autre exemplaire de la fonctionprintf. Celle-ci peut être utilisée pour écrire dans un fichier. Elle s'utilise de la même manière queprintfd'ailleurs, excepté le fait que vous devez indiquer un pointeur deFILEen premier paramètre.

Ce code demande l'âge de l'utilisateur et l'écrit dans le fichier (résultat fig. suivante) :

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int age = 0;
 
    fichier = fopen("test.txt", "w");
 
    if (fichier != NULL)
    {
        // On demande l'âge
        printf("Quel age avez-vous ? ");
        scanf("%d", &age);
 
        // On l'écrit dans le fichier
        fprintf(fichier, "Le Monsieur qui utilise le programme, il a %d ans", age);
        fclose(fichier);
    }
 
    return 0;
}
Écriture dans un fichier avec fprintf

Vous pouvez ainsi facilement réutiliser ce que vous savez deprintfpour écrire dans un fichier ! C'est pour cette raison d'ailleurs que j'utilise le plus souventfprintfpour écrire dans des fichiers.

Lire dans un fichier

Nous pouvons utiliser quasiment les mêmes fonctions que pour l'écriture, le nom change juste un petit peu :

  • fgetc: lit un caractère ;

  • fgets: lit une chaîne ;

  • fscanf: lit une chaîne formatée.

Je vais cette fois aller un peu plus vite dans l'explication de ces fonctions : si vous avez compris ce que j'ai écrit plus haut, ça ne devrait pas poser de problème.

fgetc

Tout d'abord le prototype :

int fgetc(FILE* pointeurDeFichier);

Cette fonction retourne unint: c'est le caractère qui a été lu.
Si la fonction n'a pas pu lire de caractère, elle retourneEOF.

Mais comment savoir quel caractère on lit ? Si on veut lire le troisième caractère, ainsi que le dixième caractère, comment doit-on faire ?

En fait, au fur et à mesure que vous lisez un fichier, vous avez un « curseur » qui avance. C'est un curseur virtuel bien entendu, vous ne le voyez pas à l'écran. Vous pouvez imaginer que ce curseur est comme la barre clignotante lorsque vous éditez un fichier sous Bloc-Notes. Il indique où vous en êtes dans la lecture du fichier.

Nous verrons peu après comment savoir à quelle position le curseur est situé dans le fichier et également comment modifier la position du curseur (pour le remettre au début du fichier par exemple, ou le placer à un caractère précis, comme le dixième caractère).

fgetcavance le curseur d'un caractère à chaque fois que vous en lisez un. Si vous appelezfgetcune seconde fois, la fonction lira donc le second caractère, puis le troisième et ainsi de suite. Vous pouvez donc faire une boucle pour lire les caractères un par un dans le fichier.

On va écrire un code qui lit tous les caractères d'un fichier un à un et qui les écrit à chaque fois à l'écran. La boucle s'arrête quandfgetcrenvoieEOF(qui signifie « End Of File », c'est-à-dire « fin du fichier »).

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int caractereActuel = 0;
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        // Boucle de lecture des caractères un à un
        do
        {
            caractereActuel = fgetc(fichier); // On lit le caractère
            printf("%c", caractereActuel); // On l'affiche
        } while (caractereActuel != EOF); // On continue tant que fgetc n'a pas retourné EOF (fin de fichier)
 
        fclose(fichier);
    }
 
    return 0;
}

La console affichera tout le contenu du fichier, par exemple :

Coucou, je suis le contenu du fichier test.txt !
fgets

Cette fonction lit une chaîne dans le fichier. Ça vous évite d'avoir à lire tous les caractères un par un. La fonction lit au maximum une ligne (elle s'arrête au premier\nqu'elle rencontre). Si vous voulez lire plusieurs lignes, il faudra faire une boucle.

Voici le prototype defgets:

char* fgets(char* chaine, int nbreDeCaracteresALire, FILE* pointeurSurFichier);

Cette fonction demande un paramètre un peu particulier, qui va en fait s'avérer très pratique : le nombre de caractères à lire. Cela demande à la fonctionfgetsde s'arrêter de lire la ligne si elle contient plus de X caractères.
Avantage : ça nous permet de nous assurer que l'on ne fera pas de dépassement de mémoire ! En effet, si la ligne est trop grosse pour rentrer danschaine, la fonction aurait lu plus de caractères qu'il n'y a de place, ce qui aurait probablement provoqué un plantage du programme.

Nous allons d'abord voir comment lire une ligne avecfgets(nous verrons ensuite comment lire tout le fichier).

Pour cela, on crée une chaîne suffisamment grande pour stocker le contenu de la ligne qu'on va lire (du moins on l'espère, car on ne peut pas en être sûr à 100 %). Vous allez voir là tout l'intérêt d'utiliser undefinepour définir la taille du tableau :

#define TAILLE_MAX 1000 // Tableau de taille 1000
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    char chaine[TAILLE_MAX] = ""; // Chaîne vide de taille TAILLE_MAX
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        fgets(chaine, TAILLE_MAX, fichier); // On lit maximum TAILLE_MAX caractères du fichier, on stocke le tout dans "chaine"
        printf("%s", chaine); // On affiche la chaîne
 
        fclose(fichier);
    }
 
    return 0;
}

Le résultat est le même que pour le code de tout à l'heure, à savoir que le contenu s'écrit dans la console :

Coucou, je suis le contenu du fichier test.txt !

La différence, c'est qu'ici on ne fait pas de boucle. On affiche toute la chaîne d'un coup.

Vous aurez sûrement remarqué maintenant l'intérêt que peut avoir un#definedans son code pour définir la taille maximale d'un tableau par exemple. En effet,TAILLE_MAXest ici utilisé à deux endroits du code :

  • une première fois pour définir la taille du tableau à créer ;

  • une autre fois dans lefgetspour limiter le nombre de caractères à lire.

L'avantage ici, c'est que si vous vous rendez compte que la chaîne n'est pas assez grande pour lire le fichier, vous n'avez qu'à changer la ligne dudefineet recompiler. Cela vous évite d'avoir à chercher tous les endroits du code qui indiquent la taille du tableau. Le préprocesseur remplacera tous lesTAILLE_MAXdans le code par leur nouvelle valeur.

Comme je vous l'ai dit,fgetslit au maximum toute une ligne à la fois. Elle s'arrête de lire la ligne si elle dépasse le nombre maximum de caractères que vous autorisez.

Oui mais voilà : pour le moment, on ne sait lire qu'une seule ligne à la fois avecfgets. Comment diable lire tout le fichier ? La réponse est simple : avec une boucle !

La fonctionfgetsrenvoieNULLsi elle n'est pas parvenue à lire ce que vous avez demandé.
La boucle doit donc s'arrêter dès quefgetsse met à renvoyerNULL.

On n'a plus qu'à faire unwhilepour boucler tant quefgetsne renvoie pasNULL:

#define TAILLE_MAX 1000
 
int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    char chaine[TAILLE_MAX] = "";
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        while (fgets(chaine, TAILLE_MAX, fichier) != NULL) // On lit le fichier tant qu'on ne reçoit pas d'erreur (NULL)
        {
            printf("%s", chaine); // On affiche la chaîne qu'on vient de lire
        }
 
        fclose(fichier);
    }
 
    return 0;
}

Ce code source lit et affiche tout le contenu de mon fichier, ligne par ligne.

La ligne de code la plus intéressante est celle duwhile:

while (fgets(chaine, TAILLE_MAX, fichier) != NULL)

La ligne duwhilefait deux choses : elle lit une ligne dans le fichier et vérifie sifgetsne renvoie pasNULL. Elle peut donc se traduire comme ceci : « Lire une ligne du fichier tant que nous ne sommes pas arrivés à la fin du fichier ».

fscanf

C'est le même principe que la fonctionscanf, là encore.
Cette fonction lit dans un fichier qui doit avoir été écrit d'une manière précise.

Supposons que votre fichier contienne trois nombres séparés par un espace, qui sont par exemple les trois plus hauts scores obtenus à votre jeu :15 20 30.

Vous voudriez récupérer chacun de ces nombres dans une variable de typeint.
La fonctionfscanfva vous permettre de faire ça rapidement.

int main(int argc, char *argv[])
{
    FILE* fichier = NULL;
    int score[3] = {0}; // Tableau des 3 meilleurs scores
 
    fichier = fopen("test.txt", "r");
 
    if (fichier != NULL)
    {
        fscanf(fichier, "%d %d %d", &score[0], &score[1], &score[2]);
        printf("Les meilleurs scores sont : %d, %d et %d", score[0], score[1], score[2]);
 
        fclose(fichier);
    }
 
    return 0;
}
Les meilleurs scores sont : 15, 20 et 30

Comme vous le voyez, la fonctionfscanfattend trois nombres séparés par un espace ("%d %d %d"). Elle les stocke ici dans notre tableau de trois blocs.

On affiche ensuite chacun des nombres récupérés.

Se déplacer dans un fichier

Je vous ai parlé d'une espèce de « curseur » virtuel tout à l'heure. Nous allons l'étudier maintenant plus en détails.

Chaque fois que vous ouvrez un fichier, il existe en effet un curseur qui indique votre position dans le fichier. Vous pouvez imaginer que c'est exactement comme le curseur de votre éditeur de texte (tel Bloc-Notes). Il indique où vous êtes dans le fichier, et donc où vous allez écrire.

En résumé, le système de curseur vous permet d'aller lire et écrire à une position précise dans le fichier.

Il existe trois fonctions à connaître :

  • ftell: indique à quelle position vous êtes actuellement dans le fichier ;

  • fseek: positionne le curseur à un endroit précis ;

  • rewind: remet le curseur au début du fichier (c'est équivalent à demander à la fonctionfseekde positionner le curseur au début).

ftell: position dans le fichier

Cette fonction est très simple à utiliser. Elle renvoie la position actuelle du curseur sous la forme d'unlong:

long ftell(FILE* pointeurSurFichier);

Le nombre renvoyé indique donc la position du curseur dans le fichier.

fseek: se positionner dans le fichier

Le prototype defseekest le suivant :

int fseek(FILE* pointeurSurFichier, long deplacement, int origine);

La fonctionfseekpermet de déplacer le curseur d'un certain nombre de caractères (indiqué pardeplacement) à partir de la position indiquée parorigine.

  • Le nombredeplacementpeut être un nombre positif (pour se déplacer en avant), nul (= 0) ou négatif (pour se déplacer en arrière).

  • Quant au nombreorigine, vous pouvez mettre comme valeur l'une des trois constantes (généralement desdefine) listées ci-dessous :

    • SEEK_SET: indique le début du fichier ;

    • SEEK_CUR: indique la position actuelle du curseur ;

    • SEEK_END: indique la fin du fichier.

Voici quelques exemples pour bien comprendre comment on jongle avecdeplacementetorigine.

  • Le code suivant place le curseur deux caractères après le début :

fseek(fichier, 2, SEEK_SET);
  • Le code suivant place le curseur quatre caractères avant la position courante :

fseek(fichier, -4, SEEK_CUR);

Remarquez quedeplacementest négatif car on se déplace en arrière.

  • Le code suivant place le curseur à la fin du fichier :

fseek(fichier, 0, SEEK_END);

Si vous écrivez après avoir fait unfseekqui mène à la fin du fichier, cela ajoutera vos informations à la suite dans le fichier (le fichier sera complété).
En revanche, si vous placez le curseur au début et que vous écrivez, cela écrasera le texte qui se trouvait là. Il n'y a pas de moyen d'« insérer » de texte dans le fichier, à moins de coder soi-même une fonction qui lit les caractères d'après pour s'en souvenir avant de les écraser !

Mais comment puis-je savoir à quelle position je dois aller lire et écrire dans le fichier ?

C'est à vous de le gérer. Si c'est un fichier que vous avez vous-mêmes écrit, vous savez comment il est construit. Vous savez donc où aller chercher vos informations : par exemple les meilleurs scores sont en position 0, les noms des derniers joueurs sont en position 50, etc.

Nous travaillerons sur un TP un peu plus tard dans lequel vous comprendrez, si ce n'est pas déjà le cas, comment on fait pour aller chercher l'information qui nous intéresse. N'oubliez pas que c'est vous qui définissez comment votre fichier est construit. C'est donc à vous de dire : « je place le score du meilleur joueur sur la première ligne, celui du second meilleur joueur sur la seconde ligne, etc. »

rewind: retour au début

Cette fonction est équivalente à utiliserfseekpour nous renvoyer à la position 0 dans le fichier.

void rewind(FILE* pointeurSurFichier);

L'utilisation est aussi simple que le prototype. Vous n'avez pas besoin d'explication supplémentaire !

Renommer et supprimer un fichier

Nous terminerons ce chapitre en douceur par l'étude de deux fonctions très simples :

  • rename: renomme un fichier ;

  • remove: supprime un fichier.

La particularité de ces fonctions est qu'elles ne nécessitent pas de pointeur de fichier pour fonctionner. Il suffira simplement d'indiquer le nom du fichier à renommer ou supprimer.

rename: renommer un fichier

Voici le prototype de cette fonction :

int rename(const char* ancienNom, const char* nouveauNom);

La fonction renvoie 0 si elle a réussi à renommer, sinon elle renvoie une valeur différente de 0. Est-il nécessaire de vous donner un exemple ? En voici un :

int main(int argc, char *argv[])
{
    rename("test.txt", "test_renomme.txt");

    return 0;
}

remove: supprimer un fichier

Cette fonction supprime un fichier sans demander son reste :

int remove(const char* fichierASupprimer);

Cette fonction tombe à pic pour la fin du chapitre, je n'ai justement plus besoin du fichiertest.txt, je peux donc me permettre de le supprimer :

int main(int argc, char *argv[])
{
    remove("test.txt");

    return 0;
}
Exemple de certificat de réussite
Exemple de certificat de réussite