Article     Discussion     Modifier     Historique     Forums     Salon IRC

Entrées/Sorties sur les fichiers en C plus plus standard

Un article de Games Creators Network.


Le C++ permet via les flots de manipuler les entrées/sorties facilement et intuitivement, et ce quelque soit leur type : E/S classiques (ecran et clavier via cout et cin) mais aussi fichiers, périphériques (un périphérique étant d'ailleurs sous beaucoup de systemes représenté par un fichier)... cependant, que ce soit par ignorance ou par habitude, de nombreux programmeurs C++ continuent à utiliser les possibilités d'entrée/sortie du C pour accéder aux fichiers (via les fonctionsfopen, etc...). Ce tutoriel se propose de présenter la lecture/écriture avec les fichiers en C++ de manière standard, en utilisant les flots.


Sommaire

[modifier] Généralités

Tout d'abord, quelques indications d'ordre général (la lecture de cette section n'est pas forcément nécessaire pour la compréhension de la suite du tutoriel). Afin d'ouvrir des fichiers pour y lire ou y écrire, il va falloir utiliser les classes ifstream (Input FileStream) et ofstream (Output File Stream), classes dérivées de istream et ostream (les classes de base pour les flots). Pour les utiliser, il va falloir inclure l'entête fstream (sans l'extension .h afin de respecter au mieux la dernière norme C++). Par ailleurs, étant donné qu'il s'agit de classes standards, on va se placer dans l'espace de nom std via cette instruction :

using namespace std;

Si votre compilateur n'est pas à la norme, ou si vous préférez ne pas coder en respectant la norme, vous pouvez simplement inclure l'entête en utilisant fstream.h . D'autre part, ce tutoriel sera divisé en deux parties distinctes : la première visant à expliquer la lecture/ecriture dans les fichiers ASCII classiques, la seconde dans les fichiers binaires. Cette différenciation n'est en fait réellement importante que sur les systèmes différenciant ces deux types de fichiers. C'est par exemple le cas des systèmes Microsoft (Windows, MS-DOS). Sous ces systèmes, en effet, les retours à la lignes ne sont pas codés par un simple caractère '\n' (LF) mais par la séquence de caractères '\r\n' (CRLF). Cependant, afin d'assurer une certaine portabilité dans les entrées/sorties d'un système à l'autre, lorsqu'on écrit ou affiche un caractère '\n' sur ce type particulier de système, C++ (et la plupart des langages modernes) le traduit en séquence '\r\n'. Le phénomène inverse est également vrai : si on lit (dans un fichier ou au clavier) la séquence de caractères '\r\n', C++ le traduit en simple caractère '\n'. Cela permet de lire et écrire les données de façon efficace sans avoir à modifier le code en cas de changement de système. Cette traduction de caractères est appréciable lorsqu'il s'agit d'opérer sur des fichiers ASCII (donc des fichiers composés de lignes différenciables, générallement lisibles par un être humain normallement constitué : un fichier texte, une page html, un fichier de configuration...), cependant elle devient extrémement génante lorsqu'il s'agit de lire/écrire dans des fichiers binaires (des fichiers composés d'octets ininterrompus sans séparation de ligne distincte, générallement totallement illisibles, humainement parlant : fichiers exécutables, fichiers images, musiques...), car alors elle pourrait empécher la lecture (ainsi que l'écriture) du caractère '\r' quand celui-ci est suivi du caractère '\n', sans que celui-ci ait pourtant la signification d'une fin de ligne, puisqu'il s'agit justement d'un fichier binaire. C'est pourquoi, quand on souhaite ouvrir un fichier binaire, il faut procéder d'une façon spécifique, afin d'assurer une lecture/écriture des données efficace, et c'est ce qui explique cette différenciation dans ce tutoriel. Par ailleurs, de nombreuses fonctions membres des classes ifstream et ofstream (et des classes associées aux flots en général) sont plus adaptées aux entrées/sorties sur les fichiers binaires que d'autres (car elles permettent souvent une lecture caractère par caractère), et c'est une raison de plus de différencier la manipulation sur les fichiers ASCII de celle sur les fichiers binaires.


[modifier] Lecture/Ecriture sur les fichiers ASCII

Afin de pouvoir ouvrir un fichier, il faut commencer par créer un objet ifstream (pour la lecture) ou ofstream (pour l'écriture). Commençons par l'écriture. Notre code de départ ressemblera donc à ceci :

#include <fstream>
 
using namespace std;
 
int main()
{
  ofstream fichier;
 
  return (0);
}

Il s'agit ensuite d'ouvrir un fichier, via la fonction membreopen. Cette fonction prend deux arguments en paramètres : le premier est le nom du fichier a ouvrir, et le second le mode d'ouverture. Ce mode d'ouverture est codé via la cumulation de modificateurs binaires. Cela signifie que quand on souhaite indiquer plusieurs propriétés d'ouvertures, il faut cumuler les modificateurs (il s'agit de constantes) via l'opérateur binaire OU (symbole "|"). De plus, ces modificateurs proviennent tous de la classe ios, ce qui explique qu'on fasse précéder leur nom du préfixe ios::(puisqu'ils sont tous dans l'espace de nom de cette classe). Ici, on va ouvrir un fichier exemple nommé test.txt, et spécifier deux propriétés d'ouverture : la première est ios::out, pour indiquer qu'on souhaite ouvrir le fichier en écriture (c'est une propriété obligatoire quand on utilise ofstream), la seconde est ios::trunc, qui va indiquer qu'on souhaite effacer entièrement le fichier lors de l'ouverture si celui-ci existe déjà. Une étape importante aprés l'ouverture du fichier est de vérifier que l'ouverture s'est bien passée, via la fonction membre bad(). Celle-ci ne prend aucun argument, et renvoie une valeur de typebool : false en cas d'ouverture réussie, true en cas de problème à l'ouverture. Une erreur peut se produire par exemple en cas de problème de permission sur le fichier désigné. Enfin, il ne faut pas oublier aprés avoir écrit les données voulues dans le fichier de le fermer via la fonction membre close, qui ne prend aucun argument non plus. Le code de notre programme sera donc semblable à celui-ci :

#include <fstream>
 
using namespace std;
 
int main()
{
  ofstream fichier;
 
  // Cumulation des modes d'ouverture via l'opérateur binaire "ou"
  fichier.open("test.txt", ios::out | ios::trunc);
 
  if (fichier.bad())
    return (1);   // Erreur a l'ouverture, on quitte...
 
  /*
  ...
  ici, on écrit les données dans le fichier
  ...
  */
 
  // Fermeture du fichier
  fichier.close();
 
  return (0);
}

Voyons maintenant comment écrire dans le fichier. Cela se fait de manière extrémement simple. Puisque ofstream est comme on l'a dit est un flot d'écriture (qu'on a connecté a un fichier), il suffit de l'utiliser comme par exemplecout, qui est lui aussi un flot d'écriture (connecté a la sortie standard), c'est à dire avec l'opérateur de décalage binaire , surchargé dans les classes de gestions des flots pour la lecture/l'écriture dans les flots en question. On va donc procéder de cette manière :

fichier << "Hello world !" << endl;

Cela reviendra donc à écrire "Hello World !" dans le fichier, plus un retour à la ligne (pour rappel, endl sert a effectuer un retour a la ligne et à vider le buffer d'écriture du flot). On peut donc, de cette manière, écrire dans le fichier exactement de la même manière qu'on écrit sur l'écran aveccout.

Pour la lecture dans les fichiers ASCII, la procédure à suivre diffère peu de celle étudiée ci-dessus. On va cette fois créer un objet ifstream. Puis, on va ouvrir le fichier de la même manière, à l'aide de la fonction membre open, en spécifiant cette fois-ci une seule propriété d'ouverture : ios::in, pour spécifier qu'on souhaite ouvrir le fichier en lecture (une propriété d'ouverture obligatoire lorsqu'on utilise ifstream). La vérification des erreurs s'effectue aussi de la même manière, à l'aide de la fonction bad(). On va également, aprés avoir lu les données souhaitées dans le fichier, le fermer avec close. Enfin, pour lire les données, on va procéder comme pour tout flot de lecture, comme par exemple cin, via l'opérateur de décalage binaire . Cela va revenir à lire une ligne dans le fichier. Voici par exemple un programme trés simple qui va ouvrir un fichier test.txt, et lire trois nombres entiers à l'intérieur du fichier en question :

#include <fstream>
 
using namespace std;
 
int main()
{
  ifstream fichier;
  int nb_1, nb_2, nb_3;
 
  fichier.open("test.txt", ios::in);
 
  if (fichier.bad())
    return (1);   // Erreur a l'ouverture, on quitte...
 
  fichier >> nb_1 >> nb_2 >> nb_3;
 
  /*
  Maintenant, les trois variables nb_1, nb_2 et
  nb_3 contiennent les valeurs des trois entiers
  situés dans le fichier.
  */
 
  // Fermeture du fichier
  fichier.close();
 
  return (0);
}

Bien entendu, les trois nombres ne seront lus que s'il existe entre eux un caractère de séparation (retour à la ligne, espace...), exactement comme si on les lisait à partir de cin.

Voici enfin deux choses complémentaires à savoir concernant la lecture et l'écriture dans les fichiers ASCII. Tout d'abord, l'utilisation de la fonction open n'est pas obligatoire : on peut également ouvrir un fichier via les constructeurs des classes ifstream et ofstream, avec les mêmes arguments que la fonction open. Par ailleurs, les modes d'ouverture ios::out et ios::in, respectivement obligatoires quand on utilise ofstream et ifstream, sont en fait facultatifs si on ne spécifie aucun autre mode d'ouverture : le second argument de la fonction open (ou du contructeur de la classe) est par défaut égal à ce mode d'ouverture. Enfin, même si la lecture/écriture dans les fichiers ASCII a été présentée de manière assez classique, il est aussi possible de l'effectuer via toutes les autres fonctions liées aux flots, telles que getline (pratique pour la lecture de chaînes de caractères) ou encore read et write (qui vont quand à elles surtout être utilisées pour les fichier binaires, comme on va le voir plus bas).


[modifier] Lecture/Ecriture sur les fichiers binaires

La méthode utilisée pour lire et écrire dans des fichiers binaires n'est pas trés différente de celle concernant les fichiers ASCII. Tout d'abord, on va ouvrir le fichier de la même manière. Cependant, il va falloir spécifier (pour la lecture comme pour l'écriture) qu'on souhaite ouvrir le fichier en mode binaire en ajoutant aux modes d'ouverture le modificateur ios::binary. Si vous programmez sur un système ne différenciant pas les fichiers ASCII des fichiers binaires, ce modificateur n'est pas obligatoire, mais il estfortement conseillé de le mettre quand même, pour le cas où votre programme serait compilé sur un système faisant cette différenciation (pour plus d'informations à ce sujet, lisez la section II - Généralités de ce tutoriel). La fermeture du fichier et la vérification des éventuelles erreurs se fera bien sûr de la même manière, avec les fonctions membres close() et bad(). Pour la lecture, on utilisera cette fois la fonction membreread, qui prend deux arguments : le premier est un pointeur de type char*, pointant vers l'espace mémoire où stocker les octets lus dans le fichier. Le second argument est le nombre d'octets à lire dans le fichier. Pour l'écriture, on va utiliser write, qui fonctionne de la même manière que read, avec deux arguments : un pointeur de type char* pointant vers les données à écrire dans le fichier, et le nombre d'octets à écrire. Par ailleurs, la fonction read, lorsque la fin du fichier est atteinte, va renvoyer la valeur booléenne false, sans quoi elle renverra true, et effectuera un décalage de un caractère dans le fichier. Cela permet d'assurer une lecture complète du fichier avec une boucle de ce type :

while (fichier.read(&buffer; , 1))

Voici maintenant pour illustrer la lecture et l'écriture dans les fichiers binaires un programme simple se contentant d'ouvrir un fichier binaire test, de le lire octet par octet, et à chaque fois d'écrire l'octet lu dans un second fichier binaire test2 (cela revient donc à copier le fichier test vers test2) :

#include <fstream>
 
using namespace std;
 
int main()
{
  ifstream fichier_in;
  ofstream fichier_out;
  char octet;
 
  if (fichier_in.bad())
    return (1);   // Erreur a l'ouverture, on quitte...
 
  if (fichier_out.bad())
    return (1);   // Erreur a l'ouverture, on quitte...
 
  fichier_in.open("test", ios::in | ios::binary);
 
  // Suppression du fichier de sortie s'il existe déjà avec ios::trunc
  fichier_out.open("test2", ios::out | ios::binary | ios::trunc);
 
  while (fichier_in.read(&octet; , 1))   // Pour chaque octet du fichier...
    fichier_out.write(&octet; , 1);   //...inscrire l'octet lu dans le fichier de sortie
 
  // Fermeture des fichiers
  fichier_in.close();
  fichier_out.close();
 
  return (0);
}


[modifier] Pour finir

Voici maintenant deux dernières fonctions membres à connaître quand on manipule les fichiers en C++. La première permet de se situer à un endroit précis du fichier ouvert, pour écrire ou lire ensuite à partir de cet endroit précis (qu'il s'agisse d'un fichier ouvert en mode binaire ou non). Si on utilise ifstream (donc en lecture de fichier), le nom de cette fonction est seekg, et elle prend deux arguments : le premier est le numéro de l'octet où se placer, et le second l'endroit du fichier à partir duquel se situe ce numéro. Cette seconde valeur est là encore un modificateur provenant de la classe ios, qui peut être égal à ios::beg (octet indiqué depuis le début du fichier), ios::cur (octet indiqué depuis la position courante dans le fichier), ou ios::end (octet indiqué depuis la fin du fichier). Par défaut, ce second argument est égal à ios::beg. Si on utilise non pas ifstream mais ofstream, le nom de la fonction est seekp, et son fonctionnement est identique à celui de seekg. La seconde et dernière fonction membre à connaître est respectivement tellg pour ifstream et tellp pour ofstream, qui renvoient la position courante dans le fichier (le numero de l'octet courant depuis le début du fichier).

Voici par ailleurs une petite liste récapitulant tous les modificateurs de mode d'ouverture du second argument de la fonction membre open (le seul à n'avoir pas été abordé est ios::app, assez utile) :

  • ios::in : spécifie qu'on ouvre le fichier en lecture. Obligatoire - mais par défaut - quand on utilise un objet ifstream.
  • ios::out : spécifie qu'on ouvre le fichier en écriture. Obligatoire - mais par défaut - quand on utilise un objet ofstream.
  • ios::trunc : lorsqu'on ouvre le fichier en écriture, spécifie qu'il doit être effacé s'il existe déjà, pour laisser un fichier vide.
  • ios::app : lorsqu'on ouvre le fichier en écriture, spécifie que s'il existe déjà, il ne doit pas être effacé mais qu'on doit se placer a la fin, pour écrire des données à la suite du fichier déjà existant.
  • ios::binary : ouverture en mode binaire (lire la section II à ce propos).

À noter que si on utilise ofstream, donc si on utilise le modificateur ios::out, il faut impérativement indiquer un des deux modificateurs ios::trunc ou ios::app, pour spécifier la procédure à suivre au cas où le fichier existerait déjà.

Enfin, une dernière remarque : il existe également une troisième classe, permettant l'écriture et la lecture de façon simultanée dans un même fichier. Cette classe est fstream. Si on souhaite l'utiliser, il faut alors préciser les deux modificateurs d'ouverture ios::in et ios::out en même temps, afin d'indiquer clairement qu'on souhaite lire et écrire dans le fichier. Cependant, étant donné que cette classe est rarement utilisée et que son fonctionnement est identique à celui de ifstream et ofstream, elle n'a pas été abordée.

[modifier] Conclusion

Vous savez normalement maintenant comment manipuler les fichiers binaires et ASCII en C++, de façon standard en utilisant les flots. C'est une manière puissante et assez intuitive de lire/écrire dans des fichiers quelconques. Si un passage du tutoriel vous paraît difficile à comprendre ou si vous avez une remarque, vous pouvez bien sûr m'écrire.

Ce document a été publié sur la version 3 du G.C.N. par Benjamin.

  • Auteur Original : Benjamin
  • Date de publication : 28 Août 2004

 

Rechercher
Installer l'extension de recherche Plus d'informations

 

Comprendre
Tu me dis, j'oublie. Tu m'enseignes, je me souviens. Tu m'impliques, j'apprends. - Benjamin Franklin

 

Partager
La connaissance est la seule chose qui s'accroit lorsqu'on la partage. - Sacha Boudjema

 

Créer
L'imagination est plus importante que la connaissance. - Albert Einstein

 

 

Le wiki en images Le wiki en images Image du mois: «Snowball: un prototype de jeu développé avec NeL.