Ouvrir et fermer un fichier



Pour lire et écrire dans des fichiers, nous allons nous servir de fonctions situées dans la bibliothèque stdio que nous avons déjà utilisée.


Oui, cette bibliothèque-là contient aussi les fonctions printf et scanf que 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.


Toutes les bibliothèques que je vous ai fait utiliser jusqu'ici (stdlib.hstdio.hmath.hstring.h…) sont ce qu'on appelle des bibliothèques standard. Ce sont des bibliothèques automatiquement livrées avec votre IDE qui ont la particularité de fonctionner sur tous les OS. Vous pouvez donc les utiliser partout, que vous soyez sous Windows, Linux, Mac ou autre.


Les bibliothèques standard ne sont pas très nombreuses et ne permettent de faire que des choses très basiques, comme ce que nous avons fait jusqu'ici. Pour obtenir des fonctions plus avancées, comme ouvrir des fenêtres, il faudra télécharger et installer de nouvelles bibliothèques. Nous verrons cela bientôt !


Assurez-vous donc, pour commencer, que vous incluez bien au moins les bibliothèques stdio.h et stdlib.h en 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 fichier fopen qui 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 vaut NULL, 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 de NULL donc), 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 fonction fclose.


Nous allons dans un premier temps apprendre à nous servir de fopen et fclose. 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 fonction fopen :

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 sur FILE ! C'est un pointeur sur une structure de type FILE. Cette structure est définie dans stdio.h. Vous pouvez ouvrir ce fichier pour voir de quoi est constitué le type FILE, 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 aux define ?


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é stdio ne 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 fonction fopen. Elle renvoie un FILE*. 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 de FILE au début de notre fonction (par exemple la fonction main) :

int main(int argc, char *argv[])

{

    FILE* fichier = NULL;


    return 0;

}



Le pointeur est initialisé à NULL dès le début. Je vous rappelle que c'est une règle fondamentale que d'initialiser ses pointeurs à NULL dè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.


Vous noterez qu'il n'est pas nécessaire d'écrire struct FILE* fichier = NULL. Les créateurs de stdio ont donc fait un typedef comme je vous ai appris à le faire il n'y a pas longtemps.
Notez que la forme de la structure peut changer d'un OS à l'autre (elle ne contient pas forcément les mêmes sous-variables partout). Pour cette raison, on ne modifiera jamais le contenu d'un FILE directement (on ne fera pas fichier.element par exemple). On passera par des fonctions qui manipulent le FILE à notre place.


Maintenant, nous allons appeler la fonction fopen et récupérer la valeur qu'elle renvoie dans le pointeur fichier. Mais avant ça, il faut que je vous explique comment se servir du second paramètre, le paramètre modeOuverture. 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 avez juste l'intention de lire un fichier, il est conseillé de mettre "r". Certes, le mode "r+" aurait fonctionné lui aussi, mais en mettant "r" vous vous assurez que le fichier ne pourra pas être modifié, ce qui est en quelque sorte une sécurité.


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

Le code suivant ouvre le fichier test.txt en mode "r+" (lecture / écriture) :

int main(int argc, char *argv[])

{

    FILE* fichier = NULL;


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


    return 0;

}


Le pointeur fichier devient alors un pointeur sur test.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 fichier test.txt comme moi dans le même dossier que le .exe (fig. suivante).







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 .niveau pour 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 fichier test.txt est 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 fichier readme.txt situé dans C:\Program Files\Notepad++.


J'ai dû mettre deux antislashs \ à chaque fois comme vous l'avez remarqué. En effet, si j'en avais écrit un seul, votre ordinateur aurait cru que vous essayiez d'insérer un symbole spécial comme \n ou \t. Pour écrire un antislash dans une chaîne, il faut donc l'écrire deux fois ! Votre ordinateur comprend alors que c'est bien le symbole \ que vous vouliez utiliser.


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 pointeur fichier devrait contenir l'adresse de la structure de type FILE qui sert de descripteur de fichier. Celui-ci a été chargé en mémoire pour vous par la fonction fopen().


À 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 vaut NULL, l'ouverture a échoué. S'il vaut autre chose que NULL, 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 fonction fclose qui 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 un int qui indique si elle a réussi à fermer le fichier. Cet int vaut :

  • 0 : si la fermeture a marché ;
  • EOF : si la fermeture a échoué. EOF est un define situé dans stdio.h qui 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 le fclose a 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 le else ici 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 petits fclose oubliés.

Créé avec HelpNDoc Personal Edition: Produire des livres électroniques facilement