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 avons longuement utilisées jusqu'ici ! 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.h, stdio.h, math.h, string.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 faire des choses 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 :
Code : C1
2#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 lire ou pour écrire dedans) :
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 à écrire dedans.
fopen : ouverture du fichier
Dans le chapitre sur les chaînes, nous nous sommes servis des prototypes des fonctions comme de "mode d'emploi". C'est comme ça que les programmeurs font en général : ils lisent le prototype et comprennent alors le fonctionnement de la fonction (bon, je reconnais que des fois même eux ont besoin de quelques petites explications à côté
)
Voyons voir justement le prototype de la fonction fopen :
Code : C1FILE* fopen(const char* nomDuFichier, const char* modeOuverture);
Cette fonction attend 2 paramètres :
Cette fonction renvoie... un pointeur sur FILE
Eh bien ça les amis, vous devriez comprendre ce que c'est maintenant
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 je vous le dis de suite
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 defines ?
Cette "règle", c'est moi qui me la suis fixée (et pas mal d'autres programmeurs la suivent d'ailleurs). Ca 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 juste la première lettre en majuscule pour une structure.
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) :
Code : C1
2
3
4
5
6int 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 risquez moultes plantages 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.truc 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'ouvertures possibles :
Et encore, je n'ai pas tout mis là ! Il y a le double de ça en réalité ! Pour chaque mode qu'on a vu là, si vous rajoutez 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. Vous utiliseriez par exemple le mode binaire pour lire et écrire des fichiers Word octet par octet.
Le fonctionnement est quasiment le même de toute façon que ce que nous allons voir ici.
On a déjà fort à faire avec ces 6 modes d'ouverture à retenir
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 juste rajouter 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) :
Code : C1
2
3
4
5
6
7
8int 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 :
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++).
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 :
Code : C1fichier = 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. C'est donc plus pratique.
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) :
Code : C1fichier = 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 2 antislash \ comme vous l'avez remarqué. En effet, si j'en avais mis un seul, votre ordinateur aurait cru que vous essayiez d'insérer un symbole spécial comme \n ou \t. Pour mettre un antislash dans une chaîne, il faut donc l'écrire 2 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 marchent 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 :
Code : C1fichier = 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().
A partir de là, 2 possibilités :
Juste après l'ouverture du fichier, il FAUT absolument vérifier si l'ouverture a réussi ou pas. 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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int 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 ça lorsque vous ouvrez un fichier. Si vous ne le faites pas et que le fichier n'existe pas, vous risquez un plantage du programme ensuite.
fclose : fermer le fichier
Si l'ouverture du fichier a réussi, vous pouvez lire et écrire dedans (nous allons voir de suite après 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 :
Code : C1int 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 :
A priori, la fermeture se passe toujours bien donc je n'ai pas l'habitude de tester si le fclose a marché. Vous pouvez le faire néanmoins si vous le voulez.
Pour fermer le fichier, on va donc écrire :
Code : C1fclose(fichier);
Tout simplement
Au final, le schéma que nous allons suivre pour ouvrir et fermer un fichier sera le suivant :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int 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. Ca 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 trucs comme des petits fclose oubliés. Comme quoi la solution à un problème mystique est parfois toute bête (je dis bien "parfois"
)
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 lit et écrit dedans
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.
Ecrire 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 3 fonctions que nous allons étudier :
fputc
Cette fonction écrit un caractère à la fois dans le fichier. Son prototype est :
Code : C1
int fputc(int caractere, FILE* pointeurSurFichier);
Elle prend 2 paramètres :
La fonction retourne un int, c'est un code d'erreur. Cet int vaut EOF si l'écriture a échoué, sinon il vaut autre chose.
Comme le fichier a été ouvert avec succès normalement, je n'ai pas l'habitude de tester si chacun de mes fputc a réussi, mais vous pouvez le faire encore une fois si vous le voulez.
Le code suivant écrit la lettre 'A' dans test.txt (si le fichier existe, il est remplacé ; si il n'existe pas, il est créé). Il y a tout dans ce code : ouverture, test de l'ouverture, écriture et fermeture.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
fichier = fopen("test.txt", "w");
if (fichier != NULL)
{
fputc('A', fichier); // Ecriture du caractère A
fclose(fichier);
}
return 0;
}
Ouvrez votre fichier "test.txt". Que voyez-vous ?
C'est magique (enfin pas tellement
), le fichier contient maintenant la lettre 'A' !
La preuve en image :
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
Ceci dit, fputc reste utile lorsque vous devez écrire caractère par caractère, ce qui arrive fréquemment
Prototype de la fonction :
Code : C1
int fputs(const char* chaine, FILE* pointeurSurFichier);
Les 2 paramètres sont faciles à comprendre :
La fonction renvoie EOF s'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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
Preuve que ce code fonctionne :
fprintf
Voici un autre exemplaire de la fonction printf. Celle-ci peut être utilisée pour écrire dans un fichier. Elle s'utilise de la même manière que printf d'ailleurs, excepté le fait que vous devez indiquer un pointeur de FILE en premier paramètre.
Ce code demande l'âge de l'utilisateur et l'écrit dans le fichier :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
Vous pouvez ainsi facilement réutiliser ce que vous savez de printf pour écrire dans un fichier, c'est pas génial ça ?
C'est pour cette raison d'ailleurs que j'utilise le plus souvent fprintf pour écrire dans des fichiers, car c'est pratique et pas trop compliqué
Lire dans un fichier
Nous pouvons utiliser quasiment les mêmes fonctions que pour l'écriture, le nom change juste un petit peu :
Je vais aller un peu plus vite cette fois 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 :
Code : C1
int fgetc(FILE* pointeurDeFichier);
Cette fonction retourne un int : c'est le caractère qui a été lu.
Si la fonction n'a pas pu lire de caractère, elle retourne EOF.
Mais comment savoir quel caractère on lit ? Si on veut lire le 3ème caractère, ainsi que le 10è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 hein, vous ne le voyez pas à l'écran
Mais 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 aussi 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 10ème caractère).
fgetc avance le curseur d'un caractère à chaque fois que vous en lisez un. Si vous appelez fgetc une seconde fois, la fonction lira donc le second caractère, puis le troisième et ainsi de suite
Vous pouvez faire une boucle pour lire les caractères un par un dans le fichier donc
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 quand fgetc renvoie EOF (qui signifie End Of File, c'est-à-dire "fin du fichier").
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
int caractereActuel = 0;
fichier = fopen("test.txt", "r");
if (fichier != NULL)
{
caractereActuel = fgetc(fichier); // On initialise caractereActuel
// Boucle de lecture des caractères un à un
while (caractereActuel != EOF) // On continue tant que fgetc n'a pas retourné EOF (fin de fichier)
{
printf("%c", caractereActuel); // On affiche le caractère stocké dans caractereActuel
caractereActuel = fgetc(fichier); // On lit le caractère suivant
}
fclose(fichier);
}
return 0;
}
La console affichera tout le contenu du fichier, par exemple :
Code : Console
Coucou, je suis le contenu du fichier test.txt !
fgets
Cette fonction lit une chaîne dans le fichier. Ca 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 \n qu'elle rencontre). Si vous voulez lire plusieurs lignes, il faudra faire une boucle.
Voici le prototype de fgets :
Code : C1
char* fgets(char* chaine, int nombreDeCaracteresALire, 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 fonction fgets de 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 dans chaine, la fonction aurait lu plus de caractères qu'il n'y a de place, ce qui aurait probablement provoqué un plantage du programme.
Lire une ligne avec fgets
Nous allons d'abord voir comment lire une ligne avec fgets (nous verrons ensuite comment lire tout le fichier).
On crée une chaîne suffisamment grande pour stocker le contenu de la ligne qu'on va lire (du moins on espère
). Vous allez voir là tout l'intérêt d'utiliser un define pour définir la taille du tableau :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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 :
Code : 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 #define dans son code pour définir la taille maximale d'un tableau par exemple. En effet, TAILLE_MAX est ici utilisé à 2 endroits du code :
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 du define et recompiler
Cela vous évite d'avoir à chercher tous les endroits du code qui indiquent la taille du tableau. Le préprocesseur remplacera tous les TAILLE_MAX dans le code par la nouvelle valeur
Lire tout le fichier avec fgets
Comme je vous l'ai dit, fgets lit au maximum toute une ligne à la fois. Elle s'arrête de lire la ligne si elle dépasse le nombre de caractères maximum que vous autorisez.
Oui mais voilà, pour le moment on ne sait lire qu'une seule ligne à la fois avec fgets. Comment diable lire tout le fichier ?
La réponse est simple : avec une boucle
La fonction fgets renvoie NULL si elle n'est pas parvenue à lire ce que vous avez demandé.
La boucle doit donc s'arrêter dès que fgets se met à renvoyer NULL.
On n'a plus qu'à faire un while pour boucler tant que fgets ne renvoit pas NULL, et zou !
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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 c'est celle du while (en fait c'est la seule nouvelle chose par rapport à tout à l'heure
) :
Code : C1
while (fgets(chaine, TAILLE_MAX, fichier) != NULL)
La ligne du while fait 2 choses : elle lit une ligne dans le fichier et vérifie si fgets ne renvoie pas NULL. Elle peut donc se traduire comme ceci : "Lire une ligne du fichier tant qu'on n'est pas arrivés à la fin du fichier".
fscanf
C'est le même principe que la fonction scanf là encore.
Cette fonction lit dans un fichier qui doit avoir été écrit d'une manière précise.
Supposons que votre fichier contienne 3 nombres séparés par un espace, qui sont par exemple les 3 plus hauts scores obtenus à votre jeu :
15 20 30
Vous voudriez récupérer chacun de ces nombres dans une variable de type int.
La fonction fscanf va vous permettre de faire ça rapidement.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
Code : Console
Les meilleurs scores sont : 15, 20 et 30
Comme vous le voyez, la fonction fscanf attend 3 nombres séparés par un espace ("%d %d %d"). Elle les stocke ici dans notre tableau de 3 blocs.
On affiche ensuite chacun des nombres récupérés.
Jusqu'ici, je ne vous avais fait mettre qu'un seul "%d" entre guillemets pour la fonction scanf. Vous découvrez aujourd'hui qu'on peut en mettre plusieurs, les combiner. Si votre fichier est écrit d'une façon bien précise, cela permet d'aller plus vite pour récupérer chacune des valeurs
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étail.
A 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 3 fonctions à connaître :
ftell : position dans le fichier
Cette fonction est très simple à utiliser. Elle renvoie la position actuelle du curseur sous la forme d'un long :
Code : C1long ftell(FILE* pointeurSurFichier);
Le nombre renvoyé indique donc la position du curseur dans le fichier.
fseek : se positionner dans le fichier
Le prototype de fseek est le suivant :
Code : C1int fseek(FILE* pointeurSurFichier, long deplacement, int origine);
La fonction fseek permet de déplacer le "curseur" d'un certain nombre de caractères (indiqué par deplacement) à partir de la position indiquée par origine.
Voici quelques exemples pour bien comprendre comment on jongle avec deplacement et origine
Si vous écrivez après avoir fait un fseek qui mène à la fin du fichier, cela rajoutera vos informations à la suite dans le fichier (ça complètera votre fichier).
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 je sais à quelle position je dois aller lire et écrire dans le fichier ?
Alors ça, c'est vous qui gérez
Si c'est un fichier que vous avez vous-même é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...)
On fera 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...
La fonction fseek peut se comporter bizarrement sur des fichiers ouverts en mode texte. En général, on l'utilise plutôt pour se déplacer dans des fichiers ouverts en mode binaire.
Quand on lit / écrit dans un fichier en mode texte, on le fait généralement caractère par caractère. La seule chose qu'on se permet en mode texte avec fseek c'est de revenir au début ou de se placer à la fin.
En résumé : fseek c'est bien, mais à utiliser plutôt avec des fichiers binaires. On ne se déplace en général pas avec fseek sur des fichiers texte.
rewind : retour au début
Cette fonction est équivalente à utiliser fseek pour nous renvoyer à la position 0 dans le fichier. Si vous avez eu un magnétoscope un jour dans votre vie, eh bien c'est le même nom que la touche qui permet de revenir en arrière.
Le prototype est tout bête :
Code : C1void rewind(FILE* pointeurSurFichier);
L'utilisation est aussi bête que le prototype. Pour la peine, je ne vous donne pas d'exemple cette fois (je commence à avoir les doigts ankylosés à ce moment de l'écriture du chapitre
)
Renommer et supprimer un fichier
Nous terminerons ce chapitre en douceur par l'étude de 2 fonctions très simples :
La particularité de ces fonctions est qu'elles ne nécessitent pas de pointeur de fichier pour fonctionner. Il suffira juste d'indiquer le nom du fichier à renommer / supprimer
Bref, c'est encore plus simple que simple
rename : renommer un fichier
Ca va être vite étudié vous allez voir
Code : C1int rename(const char* ancienNom, const char* nouveauNom);
La fonction renvoie 0 si elle a réussi à renommer, sinon elle renvoie... autre chose que 0
Avez-vous vraiment besoin d'un exemple ?
Bon allez
Code : C1
2
3
4
5
6int main(int argc, char *argv[])
{
rename("test.txt", "test_renomme.txt");
return 0;
}
Ouahouh c'était duuuur
Et voilà mon fichier renommé
remove : supprimer un fichier
Cette fonction supprime un fichier sans demander son reste :
Code : C1int remove(const char* fichierASupprimer);
Faites très attention en utilisant cette fonction ! Elle supprime le fichier indiqué sans demander de confirmation ! Le fichier n'est pas mis dans la corbeille ni rien, il est littéralement supprimé du disque dur. Il n'est pas possible de récupérer un fichier supprimé.
Cette fonction tombe à pic pour la fin du chapitre, je n'ai justement plus besoin du fichier test.txt, je vais donc le supprimer
Code : C1
2
3
4
5
6int main(int argc, char *argv[])
{
remove("test.txt");
return 0;
}
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 avons longuement utilisées jusqu'ici ! 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.h, stdio.h, math.h, string.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
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
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 faire des choses plus avancées, comme ouvrir des fenêtres, il faudra télécharger et installer de nouvelles bibliothèques. Nous verrons cela bientôt
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
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 :
Code : C1
2#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
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
Voici ce qu'il faut faire à chaque fois dans l'ordre quand on veut ouvrir un fichier (que ce soit pour lire ou pour écrire dedans) :
- On appelle la fonction d'ouverture de fichier fopen qui nous renvoie un pointeur sur le fichier.
- 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 marché, dans ce cas on ne peut pas continuer (il faut afficher un message d'erreur).
- Si l'ouverture a marché (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.
- 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 à écrire dedans.
fopen : ouverture du fichier
Dans le chapitre sur les chaînes, nous nous sommes servis des prototypes des fonctions comme de "mode d'emploi". C'est comme ça que les programmeurs font en général : ils lisent le prototype et comprennent alors le fonctionnement de la fonction (bon, je reconnais que des fois même eux ont besoin de quelques petites explications à côté
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
Voyons voir justement le prototype de la fonction fopen :
Code : C1FILE* fopen(const char* nomDuFichier, const char* modeOuverture);
Cette fonction attend 2 paramètres :
- Le nom du fichier à ouvrir.
- Le mode d'ouverture du fichier, c'est-à-dire une indication qui dit si vous voulez juste écrire dans le fichier, juste lire dans le fichier, ou les deux à la fois
Cette fonction renvoie... un pointeur sur FILE
![blink.gif](http://www.siteduzero.com/Templates/images/smilies/blink.gif)
Eh bien ça les amis, vous devriez comprendre ce que c'est maintenant
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
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 je vous le dis de suite
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
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 defines ?
Cette "règle", c'est moi qui me la suis fixée (et pas mal d'autres programmeurs la suivent d'ailleurs). Ca 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
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
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 juste la première lettre en majuscule pour une structure.
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) :
Code : C1
2
3
4
5
6int 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 risquez moultes plantages par la suite
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
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
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
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.truc 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'ouvertures possibles :
- "r" : lecture seule. Vous pourrez lire le contenu du fichier, mais pas écrire dedans. 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 rajouterez 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, et vous écrivez et lisez ensuite dedans. 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éé.
Et encore, je n'ai pas tout mis là ! Il y a le double de ça en réalité ! Pour chaque mode qu'on a vu là, si vous rajoutez 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. Vous utiliseriez par exemple le mode binaire pour lire et écrire des fichiers Word octet par octet.
Le fonctionnement est quasiment le même de toute façon que ce que nous allons voir ici.
On a déjà fort à faire avec ces 6 modes d'ouverture à retenir
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
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 juste rajouter 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) :
Code : C1
2
3
4
5
6
7
8int 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 :
![8805.png](http://www.siteduzero.com/uploads/fr/files/8001_9000/8805.png)
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++).
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
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
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 :
Code : C1fichier = 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. C'est donc plus pratique.
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) :
Code : C1fichier = 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 2 antislash \ comme vous l'avez remarqué. En effet, si j'en avais mis un seul, votre ordinateur aurait cru que vous essayiez d'insérer un symbole spécial comme \n ou \t. Pour mettre un antislash dans une chaîne, il faut donc l'écrire 2 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 marchent 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 :
Code : C1fichier = 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().
A partir de là, 2 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 absolument vérifier si l'ouverture a réussi ou pas. 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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int 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 ça lorsque vous ouvrez un fichier. Si vous ne le faites pas et que le fichier n'existe pas, vous risquez un plantage du programme ensuite.
fclose : fermer le fichier
Si l'ouverture du fichier a réussi, vous pouvez lire et écrire dedans (nous allons voir de suite après 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 :
Code : C1int 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 qu'on est 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 donc je n'ai pas l'habitude de tester si le fclose a marché. Vous pouvez le faire néanmoins si vous le voulez.
Pour fermer le fichier, on va donc écrire :
Code : C1fclose(fichier);
Tout simplement
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
Au final, le schéma que nous allons suivre pour ouvrir et fermer un fichier sera le suivant :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int 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
![langue.png](http://www.siteduzero.com/Templates/images/smilies/langue.png)
Oublier de libérer la mémoire, ça arrive. Ca 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 trucs comme des petits fclose oubliés. Comme quoi la solution à un problème mystique est parfois toute bête (je dis bien "parfois"
![heureux.png](http://www.siteduzero.com/Templates/images/smilies/heureux.png)
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 lit et écrit dedans
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
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.
Ecrire 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 3 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 :
Code : C1
int fputc(int caractere, FILE* pointeurSurFichier);
Elle prend 2 paramètres :
- Le caractère à écrire (de type int, ce qui comme je vous l'ai dit revient plus ou moins à utiliser un char, 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'appelle "fichier". 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 un int, c'est un code d'erreur. Cet int vaut EOF si l'écriture a échoué, sinon il vaut autre chose.
Comme le fichier a été ouvert avec succès normalement, je n'ai pas l'habitude de tester si chacun de mes fputc a réussi, mais vous pouvez le faire encore une fois si vous le voulez.
Le code suivant écrit la lettre 'A' dans test.txt (si le fichier existe, il est remplacé ; si il n'existe pas, il est créé). Il y a tout dans ce code : ouverture, test de l'ouverture, écriture et fermeture.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
fichier = fopen("test.txt", "w");
if (fichier != NULL)
{
fputc('A', fichier); // Ecriture du caractère A
fclose(fichier);
}
return 0;
}
Ouvrez votre fichier "test.txt". Que voyez-vous ?
C'est magique (enfin pas tellement
![langue.png](http://www.siteduzero.com/Templates/images/smilies/langue.png)
La preuve en image :
![8808.png](http://www.siteduzero.com/uploads/fr/files/8001_9000/8808.png)
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
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
Ceci dit, fputc reste utile lorsque vous devez écrire caractère par caractère, ce qui arrive fréquemment
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
Prototype de la fonction :
Code : C1
int fputs(const char* chaine, FILE* pointeurSurFichier);
Les 2 paramètres sont faciles à comprendre :
- chaine : la chaîne à écrire. Notez que le type ici est const char* : en rajoutant le mot const dans le prototype, la fonction indique que pour elle la chaîne sera considérée comme une constante. En 1 mot comme en 100 : elle s'interdit de modifier le contenu de votre chaîne. C'est logique quand on y pense : fputs doit 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 pour fputc, il s'agit de votre pointeur de type FILE* sur le fichier que vous avez ouvert.
La fonction renvoie EOF s'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 :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
Preuve que ce code fonctionne :
![8809.png](http://www.siteduzero.com/uploads/fr/files/8001_9000/8809.png)
fprintf
Voici un autre exemplaire de la fonction printf. Celle-ci peut être utilisée pour écrire dans un fichier. Elle s'utilise de la même manière que printf d'ailleurs, excepté le fait que vous devez indiquer un pointeur de FILE en premier paramètre.
Ce code demande l'âge de l'utilisateur et l'écrit dans le fichier :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
![8810.png](http://www.siteduzero.com/uploads/fr/files/8001_9000/8810.png)
Vous pouvez ainsi facilement réutiliser ce que vous savez de printf pour écrire dans un fichier, c'est pas génial ça ?
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
C'est pour cette raison d'ailleurs que j'utilise le plus souvent fprintf pour écrire dans des fichiers, car c'est pratique et pas trop compliqué
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
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 aller un peu plus vite cette fois 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
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
fgetc
Tout d'abord le prototype :
Code : C1
int fgetc(FILE* pointeurDeFichier);
Cette fonction retourne un int : c'est le caractère qui a été lu.
Si la fonction n'a pas pu lire de caractère, elle retourne EOF.
Mais comment savoir quel caractère on lit ? Si on veut lire le 3ème caractère, ainsi que le 10è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 hein, vous ne le voyez pas à l'écran
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
Mais 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 aussi 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 10ème caractère).
fgetc avance le curseur d'un caractère à chaque fois que vous en lisez un. Si vous appelez fgetc une seconde fois, la fonction lira donc le second caractère, puis le troisième et ainsi de suite
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
Vous pouvez faire une boucle pour lire les caractères un par un dans le fichier donc
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
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 quand fgetc renvoie EOF (qui signifie End Of File, c'est-à-dire "fin du fichier").
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char *argv[])
{
FILE* fichier = NULL;
int caractereActuel = 0;
fichier = fopen("test.txt", "r");
if (fichier != NULL)
{
caractereActuel = fgetc(fichier); // On initialise caractereActuel
// Boucle de lecture des caractères un à un
while (caractereActuel != EOF) // On continue tant que fgetc n'a pas retourné EOF (fin de fichier)
{
printf("%c", caractereActuel); // On affiche le caractère stocké dans caractereActuel
caractereActuel = fgetc(fichier); // On lit le caractère suivant
}
fclose(fichier);
}
return 0;
}
La console affichera tout le contenu du fichier, par exemple :
Code : Console
Coucou, je suis le contenu du fichier test.txt !
fgets
Cette fonction lit une chaîne dans le fichier. Ca 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 \n qu'elle rencontre). Si vous voulez lire plusieurs lignes, il faudra faire une boucle.
Voici le prototype de fgets :
Code : C1
char* fgets(char* chaine, int nombreDeCaracteresALire, 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 fonction fgets de 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 dans chaine, la fonction aurait lu plus de caractères qu'il n'y a de place, ce qui aurait probablement provoqué un plantage du programme.
Lire une ligne avec fgets
Nous allons d'abord voir comment lire une ligne avec fgets (nous verrons ensuite comment lire tout le fichier).
On crée une chaîne suffisamment grande pour stocker le contenu de la ligne qu'on va lire (du moins on espère
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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 :
Code : 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 #define dans son code pour définir la taille maximale d'un tableau par exemple. En effet, TAILLE_MAX est ici utilisé à 2 endroits du code :
- Une première fois pour définir la taille du tableau à créer
- Une autre fois dans le fgets pour 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 du define et recompiler
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
Lire tout le fichier avec fgets
Comme je vous l'ai dit, fgets lit au maximum toute une ligne à la fois. Elle s'arrête de lire la ligne si elle dépasse le nombre de caractères maximum que vous autorisez.
Oui mais voilà, pour le moment on ne sait lire qu'une seule ligne à la fois avec fgets. Comment diable lire tout le fichier ?
La réponse est simple : avec une boucle
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
La fonction fgets renvoie NULL si elle n'est pas parvenue à lire ce que vous avez demandé.
La boucle doit donc s'arrêter dès que fgets se met à renvoyer NULL.
On n'a plus qu'à faire un while pour boucler tant que fgets ne renvoit pas NULL, et zou !
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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 c'est celle du while (en fait c'est la seule nouvelle chose par rapport à tout à l'heure
![heureux.png](http://www.siteduzero.com/Templates/images/smilies/heureux.png)
Code : C1
while (fgets(chaine, TAILLE_MAX, fichier) != NULL)
La ligne du while fait 2 choses : elle lit une ligne dans le fichier et vérifie si fgets ne renvoie pas NULL. Elle peut donc se traduire comme ceci : "Lire une ligne du fichier tant qu'on n'est pas arrivés à la fin du fichier".
fscanf
C'est le même principe que la fonction scanf là encore.
Cette fonction lit dans un fichier qui doit avoir été écrit d'une manière précise.
Supposons que votre fichier contienne 3 nombres séparés par un espace, qui sont par exemple les 3 plus hauts scores obtenus à votre jeu :
15 20 30
Vous voudriez récupérer chacun de ces nombres dans une variable de type int.
La fonction fscanf va vous permettre de faire ça rapidement.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
Code : Console
Les meilleurs scores sont : 15, 20 et 30
Comme vous le voyez, la fonction fscanf attend 3 nombres séparés par un espace ("%d %d %d"). Elle les stocke ici dans notre tableau de 3 blocs.
On affiche ensuite chacun des nombres récupérés.
Jusqu'ici, je ne vous avais fait mettre qu'un seul "%d" entre guillemets pour la fonction scanf. Vous découvrez aujourd'hui qu'on peut en mettre plusieurs, les combiner. Si votre fichier est écrit d'une façon bien précise, cela permet d'aller plus vite pour récupérer chacune des valeurs
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
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étail.
A 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 3 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 fonction fseek de 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'un long :
Code : C1long ftell(FILE* pointeurSurFichier);
Le nombre renvoyé indique donc la position du curseur dans le fichier.
fseek : se positionner dans le fichier
Le prototype de fseek est le suivant :
Code : C1int fseek(FILE* pointeurSurFichier, long deplacement, int origine);
La fonction fseek permet de déplacer le "curseur" d'un certain nombre de caractères (indiqué par deplacement) à partir de la position indiquée par origine.
- Le nombre deplacement peut être un nombre positif (pour se déplacer en avant), nul (= 0) ou négatif (pour se déplacer en arrière).
- Quant au nombre origine, vous pouvez mettre comme valeur l'une des 3 constantes (généralement des defines) 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 avec deplacement et origine
- Le code suivant place le curseur 2 caractères après le début :
Code : C1fseek(fichier, 2, SEEK_SET);
- Le code suivant place le curseur 4 caractères avant la position courante :
Code : C1fseek(fichier, -4, SEEK_CUR);
(remarquez que deplacement est négatif car on se déplace en arrière) - Le code suivant place le curseur à la fin du fichier :
Code : C1fseek(fichier, 0, SEEK_END);
Si vous écrivez après avoir fait un fseek qui mène à la fin du fichier, cela rajoutera vos informations à la suite dans le fichier (ça complètera votre fichier).
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 je sais à quelle position je dois aller lire et écrire dans le fichier ?
Alors ça, c'est vous qui gérez
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
Si c'est un fichier que vous avez vous-même é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...)
On fera un TP un peu plus tard dans lequel vous comprendrez (si ce n'est pas déjà le cas
![langue.png](http://www.siteduzero.com/Templates/images/smilies/langue.png)
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...
La fonction fseek peut se comporter bizarrement sur des fichiers ouverts en mode texte. En général, on l'utilise plutôt pour se déplacer dans des fichiers ouverts en mode binaire.
Quand on lit / écrit dans un fichier en mode texte, on le fait généralement caractère par caractère. La seule chose qu'on se permet en mode texte avec fseek c'est de revenir au début ou de se placer à la fin.
En résumé : fseek c'est bien, mais à utiliser plutôt avec des fichiers binaires. On ne se déplace en général pas avec fseek sur des fichiers texte.
rewind : retour au début
Cette fonction est équivalente à utiliser fseek pour nous renvoyer à la position 0 dans le fichier. Si vous avez eu un magnétoscope un jour dans votre vie, eh bien c'est le même nom que la touche qui permet de revenir en arrière.
Le prototype est tout bête :
Code : C1void rewind(FILE* pointeurSurFichier);
L'utilisation est aussi bête que le prototype. Pour la peine, je ne vous donne pas d'exemple cette fois (je commence à avoir les doigts ankylosés à ce moment de l'écriture du chapitre
![rire.gif](http://www.siteduzero.com/Templates/images/smilies/rire.gif)
Renommer et supprimer un fichier
Nous terminerons ce chapitre en douceur par l'étude de 2 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 juste d'indiquer le nom du fichier à renommer / supprimer
![smile.png](http://www.siteduzero.com/Templates/images/smilies/smile.png)
Bref, c'est encore plus simple que simple
![heureux.png](http://www.siteduzero.com/Templates/images/smilies/heureux.png)
rename : renommer un fichier
Ca va être vite étudié vous allez voir
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
Code : C1int rename(const char* ancienNom, const char* nouveauNom);
La fonction renvoie 0 si elle a réussi à renommer, sinon elle renvoie... autre chose que 0
![langue.png](http://www.siteduzero.com/Templates/images/smilies/langue.png)
Avez-vous vraiment besoin d'un exemple ?
Bon allez
![hihi.png](http://www.siteduzero.com/Templates/images/smilies/hihi.png)
Code : C1
2
3
4
5
6int main(int argc, char *argv[])
{
rename("test.txt", "test_renomme.txt");
return 0;
}
Ouahouh c'était duuuur
![siffle.png](http://www.siteduzero.com/Templates/images/smilies/siffle.png)
Et voilà mon fichier renommé
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
![8811.png](http://www.siteduzero.com/uploads/fr/files/8001_9000/8811.png)
remove : supprimer un fichier
Cette fonction supprime un fichier sans demander son reste :
Code : C1int remove(const char* fichierASupprimer);
Faites très attention en utilisant cette fonction ! Elle supprime le fichier indiqué sans demander de confirmation ! Le fichier n'est pas mis dans la corbeille ni rien, il est littéralement supprimé du disque dur. Il n'est pas possible de récupérer un fichier supprimé.
Cette fonction tombe à pic pour la fin du chapitre, je n'ai justement plus besoin du fichier test.txt, je vais donc le supprimer
![clin.png](http://www.siteduzero.com/Templates/images/smilies/clin.png)
Code : C1
2
3
4
5
6int main(int argc, char *argv[])
{
remove("test.txt");
return 0;
}