Introduction aux fichiers
===
###### tags: `algo` `bourg` `c++` `iut` `cm`
Le cours en vidéo:
{%youtube wlkeWseayPs %}
En C++, la lecture et l'écriture dans un fichier nécessitent l'utilisation d'une librairie orientée objet (principe que nous nous verrons au semestre 2). Nous allons ici vous donner quelques techniques pour tout de même pouvoir utiliser les fichiers en masquant les aspects liés à la programmation objet. Tout d'abord, il est important de noter que la librairie dont vous aurez besoin s'appelle `fstream` (à importer donc en début de programme).
## Ecriture dans un fichier
En début d'année nous avons découvert le flot lié à la sortie standard (console) `cout`. En utilisant le même opérateur, à savoir `<<`, nous allons voir que nous pouvons écrire dans un fichier, à condition de créer un flot dédié (une sorte d'équivalent du `cout`, mais vers un fichier).
Pour créer un flot associé à une sortie fichier, il faut déclarer un objet de type `ofstream`. La création de cet objet se fait à partir de deux informations:
- le nom du fichier dans lequel on voudra écrire (qui est une chaine de caractères donc)
- le mode d'ouverture de ce fichier, défini par une constante prédéfinie.
La syntaxe de création d'une telle variable est inhabituelle pour vous (c'est de l'objet), mais elle s'apparente à une déclaration-initialisation d'une variable de type ``ofstream`` via un appel à une sorte de fonction sans nom et ayant deux paramètres:
````C++
(char nom_fichier[], int mode)
````
En ce qui concerne le mode d'ouverture (le deuxième paramètre de la fonction), il peut prendre plusieurs valeurs:
- `ios::out` pour une ouveture en écriture standard
- `ios::app` pour une ouverture en ajout de données, autrement dit, on écrira dans le fichier à la suite de ce qui existe déjà.
- `ios::trunc` pour une ouverture en écrasant ce qui existe dans le fichier, si celui-ci existe déjà.
On peut combiner certains modes entre eux via l'opérateur `|`. Si vous demandez l'ouverture d'un flot avec un nom de fichier qui n'existe pas sur votre disque dur, celui-ci est créé.
:::warning
L'appel à la fonction est un peu particulier, puisqu'il s'agit d'une syntaxe orientée objet que vous verrez au semestre prochain. Concrèment, c'est une sorte de déclaration-initialisation que vous devez faire, dont l'initialisation se fait par appel de fonction. Si vous voulez écrire par exemple dans un fichier qui s'appelle *score.txt* en écrasant le contenu actuel du fichier, vous allez écrire le code suivant et déclarer une variable ``sortie`` de type ``ofstream``:
````C++
ofstream sortie("score.txt", ios::trunc);
````
:::
Maintenant que vous avez créé votre flux vers votre fichier (grâce à votre variable ``sortie``, qui est donc une sorte d'équivalent du ``cout`` pour les fichiers), vous allez pouvoir écrire du texte dans votre fichier. Il suffit pour cela d'utiliser l'opérateur `<<` sur le flux de sortie (comme vous le faisiez pour le flot console `cout`):
````C++
sortie << "bonjour les amis" << endl;
````
Une fois que vous avez fini d'écrire dans le fichier, il faut rendre celui-ci disponible à nouveau en fermant le flux qui lui est associé via la fonction `close`. La syntaxe sera la suivante:
````C++
sortie.close();
````
:::warning
Là encore, l'appel à la fonction close est un appel de fonction orienté objet. Ne soyez donc pas surpris par la syntaxe un peu inhabituelle.
:::
Pour résumer, voici un exemple de code qui permet de créer un nouveau fichier appelé `"bonjour.txt"`, et dans lequel on écrira `bonjour monsieur` suivi des entiers de 1 à 10 séparés par des espaces.
````C++
#include <fstream>
using namespace std;
int main(){
ofstream sortie("bonjour.txt",ios::out);
sortie << "bonjour monsieur" << endl;
for (int i=1;i<=10;i++)
sortie << i << " " ;
sortie.close();
return 0;
}
````
## Lecture depuis un fichier
Lire des données depuis un fichier pour ensuite les utiliser dans votre programme, voilà une action qui risque de vous être utile dans de nombreuses circonstances ! Les flots de lecture depuis un fichier sont de type ``ifstream``, et leur ouverture/fermeture est similaire à celle des flots en écriture. La seul différence est le mode ``ios::in`` qui est utilisé pour la lecture. A la différence des flots de sortie, il est nécessaire que le fichier à lire existe au préalable. Dans le cas contraire, la création du flot renverra la valeur ``false``. En général, il est de bonne pratique de tester cette valeur pour être sur que le fichier a bien été ouvert et qu'on a pas fait une faute de frappe dans le nom du fichier. Concrètement, cela donne les lignes suivantes:
````C++
ifstream entree("bonjour.txt",ios::in);//declaration du flot
if (!entree) //si le flot vaut false
cout << "Probleme d'ouverture " << endl;
else {
//lecture du fichier ici (cf. ci-dessous)
entree.close();
}
````
Si l'ouverture du fichier a réussi, un "curseur virtuel" sera placé en début de fichier. La lecture des caractères du fichier commencera à partir de ce curseur.
Pour lire les informations du fichier, il existe deux alternatives.
- Pour une lecture mot à mot et formatée, vous pouvez utiliser l'opérateur ``>>``, comme avec le flot ``cin``. Nous vous rappelons le handicap de cette méthode, qui est que la lecture s'arrête à chaque fois qu'il trouve un délimiteur blanc (espace, retour chariot, tabulation...). Autrement dit, vous ne pouvez pas lire de ligne entière avec cette méthode. A chaque appel à l'opérateur ``>>``, le curseur virtuel se déplace juste après le délimiteur. Et la prochaine lecture commencera à partir de la nouvelle position du curseur.
- Pour une lecture ligne par ligne, vous pouvez utiliser la fonction ``getline(char ligne[], int taille)``. Cette fonction lit une ligne entière et s'arrête dès qu'elle rencontre un retour chariot, ou alors après avoir lu ``taille`` caractères. Elle prend en paramètre la chaine de caractères dans laquelle ce qui est lu va être stocké, et le nombre maximum de caractères à lire. Attention, cette fonction est orientée objet, son appel est un peu particulier, on doit le faire précéder du flot d'entrée suivi d'un ``.`` Par ex: ``entree.getline(ligne,100);``
- Il existe une version améliorée de ``getline`` avec un troisième paramètre, ``getline(char ligne[], int taille, char delim)``, qui arrête la lecture lorsque le caractère ``delim`` est rencontré ou bien si la taille est dépassée. Cela peut être pratique si les informations que vous lisez dans votre fichier sont toutes séparées par des virgules par exemple.
Quelle que soit la méthode employée, lorsque l'on fait des lectures de mot/ligne répétées, on a souvent besoin de savoir quand est-ce que le curseur se trouve à la fin du fichier. Pour cela, il existe la fonction ``eof()`` qui retourne le booléen correspondant (vrai si on est à la fin du fichier et faux sinon). On l'utilise souvent dans une boucle ``while``. Comme pour ``getline`` et ``close``, la syntaxe de l'appel à cette fonction est orientée objet. Vous trouverez ci-dessous un exemple qui compte le nombre de lignes d'un fichier:
````C++=
int main(void){
int cpt=0;
ifstream entree("bonjour.txt",ios::in);
char ligne[100];
if (!entree)
cout << "Probleme d'ouverture \n";
else {
while (!entree.eof()){
entree.getline(ligne,100);
cpt++;
}
cout << "Nombre de lignes: " << cpt << endl;
entree.close();
}
return 0;
}
````
Si on avait voulu compter le nombre de mots du fichier, l'utilisation de ``>>`` aurait été préconisée, et la ligne $9$ aurait été remplacée par ``entree>>ligne;``