FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
- Comment tester l'existence d'un fichier ?
- Comment savoir si la lecture / écriture dans un fichier a réussi ?
- Comment écrire à la suite d'un fichier existant ?
- Comment détecter la fin de fichier lors de la lecture ?
- Comment calculer la taille d'un fichier ?
- Comment fonctionnent les tests d'ouverture de fichier if ( fichier ) et if ( !fichier ) ?
- Comment faire pour lire un fichier ligne par ligne ?
- Comment lire l'intégralité d'un fichier texte dans un buffer ?
- Comment compter le nombre de lignes d'un fichier ?
- Pourquoi n'ai-je pas le nombre de caractères attendus avec mon fichier ?
- Comment effectuer des lectures / écritures binaires dans un fichier ?
- [Exemple] Comment manipuler un nom de fichier avec string ?
- Pourquoi la lecture de mon fichier ne fonctionne-t-elle plus après une lecture complète ?
- Pourquoi je n'arrive plus à rien lire après avoir atteint la fin de mon fichier, même après un « file.seekg (0, file.beg); » ?
Le C++ standard ne permet pas de tester de manière fiable qu'un fichier existe (celui-ci peut exister sans qu'il soit possible de le lire). Une solution portable consiste à utiliser la fonction exists de la bibliothèque boost::filesystem. Il est en revanche possible de tester si un fichier existe et est accessible en lecture ou non, ce qui est suffisant dans la plupart des cas. Il est cependant important de noter qu'il est possible que le fichier soir créé / effacé entre le moment où l'on teste son accès et le moment où l'on crée / accède au fichier.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <fstream> #include <string> #include <iostream> bool is_readable( const std::string & file ) { std::ifstream fichier( file.c_str() ); return !fichier.fail(); } void Exemple() { using std::cout; if ( is_readable( "fichier.txt" ) ) { cout << "Fichier existant et lisible.\n"; } else { cout << "Fichier inexistant ou non lisible.\n"; } } |
On aurait également pu écrire return fichier, qui fait appel à la conversion implicite en void*, et qui n'est rien d'autre qu'une syntaxe allegée pour fichier.fail(). Pour plus d'informations à ce sujet, consultez Comment fonctionnent les tests d'ouverture de fichier if ( fichier ) et if ( !fichier ) ?.
En cas d'échec de lecture / écriture, des indicateurs d'erreurs du flux sont positionnés et il suffit alors de tester l'état de ce dernier de la même manière que dans le cas du test de saisie avec cin ou du test d'ouverture de fichier.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | void test_lecture() { ifstream file( "fichier.txt" ); if ( !file ) { cerr << "Erreur d'ouverture\n"; return; } string line; if ( ! ( file >> line ) ) { cerr << "Erreur de lecture\n"; return; } cout << "Ligne lue : " << line; } void test_ecriture() { ofstream file( "fichier.txt" ); if ( !file ) { cerr << "Erreur de création\n"; return; } file << "Une ligne\n"; if ( ! file ) { cerr << "Erreur d'écriture\n"; return; } cout << "L'écriture a réussi\n"; } |
La valeur par défaut du mode d'ouverture du fichier dans le constructeur de std::ofstream est ios_base::out combiné à ios_base::trunc (pour truncate = tronquer). Ce dernier a pour conséquence d'écraser le contenu original du fichier ouvert. Pour éviter cela, il suffit de spécifier un autre mode d'ouverture, par exemple ios_base::app (pour append = ajouter à la suite):
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <fstream> void AjouterUneLigne() { // std::ios_base::out est automatiquement ajouté par le // constructeur de std::ofstream std::ofstream file( "fichier.txt", std::ios_base::app ); file << "Une ligne\n"; } int main() { // création du fichier et écriture d'une ligne AjouterUneLigne(); // ouverture du fichier existant et rajout d'une nouvelle ligne AjouterUneLigne(); // "fichier.txt" contient 2 lignes } |
Si une lecture échoue parce que la fin de fichier a été atteinte, alors les indicateurs d'erreur du flux sont positionnés et il est possible de détecter la fin de fichier simplement en testant la réussite de la lecture. Pour avoir plus de précisions sur l'origine de l'échec, il est possible d'utiliser istream::eof() pour savoir si l'erreur est due à une fin de fichier, comme cela est fait dans la question Comment vérifier les valeurs saisies avec cin ?.
Il convient cependant d'être prudent avec cette fonction car elle ne signale la fin de fichier qu'une fois qu'elle a été atteinte. Ainsi le code suivant est erroné:
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | ifstream file( "fichier.txt" ); // compter le nombre de lignes int count = 0; while ( ! file.eof() ) { string line; file >> line; ++count; } |
Lorsque la dernière ligne sera lue, eof() renverra false si cette dernière ligne est terminée par un retour chariot car la lecture se sera arrêtée d'elle même sur ce caractère de fin de ligne (comportement de getline()) au lieu d'être arrêtée par la fin de fichier. Ainsi, l'exemple précédent va compter une ligne en trop dans un tel cas.
Voici maintenant le code corrigé :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ifstream file( "fichier.txt" ); // compter le nombre de lignes int count = 0; while ( true ) { string line; getline( file, line ); if ( file.eof() ) { break; } ++count; } |
Ce code fonctionne, mais une telle écriture est plus contraignante que le test direct de la réussite de lecture (qui de plus ne se limite pas à l'erreur fin de fichier). Aussi on préfère ne pas utiliser eof() et simplement écrire :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | ifstream file( "fichier.txt" ); // compter le nombre de lignes int count = 0; string line; while ( getline( file, line ) ) { ++count; } |
Il n'y a pas de fonction faisant partie du standard C++ qui permette d'obtenir la taille d'un fichier (sans l'ouvrir). Il est cependant possible et facile d'y arriver en ouvrant le fichier, en se positionnant à sa fin, et en récupérant la nouvelle position courante.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | long GetFileSize( std::ifstream & Fichier ) { // sauvegarder la position courante long pos = Fichier.tellg(); // se placer en fin de fichier Fichier.seekg( 0 , std::ios_base::end ); // récupérer la nouvelle position = la taille du fichier long size = Fichier.tellg() ; // restaurer la position initiale du fichier Fichier.seekg( pos, std::ios_base::beg ) ; return size ; } |
Pour que l'expression suivante compile :
Code c++ : | Sélectionner tout |
1 2 3 4 5 | std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios if ( fichier ) // ce test échoue si le fichier n'est pas ouvert { // OK } |
Code c++ : | Sélectionner tout |
operator void*( ) const;
Code c++ : | Sélectionner tout |
1 2 3 4 5 | std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios if ( fichier.operator void*() != 0 ) // ce test échoue si le fichier n'est pas ouvert { // OK } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | std::ifstream fichier( "fichier.txt" ); // ou n'importe quel objet dérivant de std::ios if ( !fichier ) // ce test est vrai si l'ouverture échoue { // ERREUR } |
Code c++ : | Sélectionner tout |
bool operator!() const;
La classe std::istream dont hérite std::ifstream possède une fonction getline() mais cette dernière n'est pas pratique et est à utiliser avec précaution car elle demande un pointeur sur un tableau de char (char *) à remplir. Aussi est-il préférable d'utiliser std::getline (déclaré dans <string>) qui permet d'extraire une ligne d'un istream sous la forme d'une string. Il n'est donc plus nécessaire de se préoccuper de la taille de celle-ci. Le petit programme ci-dessous lit un fichier ligne par ligne et l'affiche à l'écran.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <string> #include <fstream> #include <iostream> int main() { // le constructeur de ifstream permet d'ouvrir un fichier en lecture std::ifstream fichier( "fichier.txt" ); if ( fichier ) // ce test échoue si le fichier n'est pas ouvert { std::string ligne; // variable contenant chaque ligne lue // cette boucle s'arrête dès qu'une erreur de lecture survient while ( std::getline( fichier, ligne ) ) { // afficher la ligne à l'écran std::cout << ligne << std::endl; } } } |
Pour charger l'intégralité d'un fichier en mémoire, il est judicieux d'utiliser un std::stringstream comme buffer ce qui permet de s'affranchir d'une allocation dynamique et de toute manipulation de pointeur.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <fstream> #include <iostream> #include <sstream> int main() { // le constructeur de ifstream permet d'ouvrir un fichier en lecture std::ifstream fichier( "fichier.txt" ); if ( fichier ) // ce test échoue si le fichier n'est pas ouvert { std::stringstream buffer; // variable contenant l'intégralité du fichier // copier l'intégralité du fichier dans le buffer buffer << fichier.rdbuf(); // nous n'avons plus besoin du fichier ! fichier.close(); // manipulations du buffer... std::cout << "Taille du buffer : " << buffer.str().size() << '\n'; } } |
Voici deux exemples qui illustrent comment arriver à ce résultat :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // utiliser ignore #include <fstream> #include <limits> int main() { std::ifstream file( "fichier.txt" ); if ( file ) { int lines = 0; while ( file.ignore( std::numeric_limits<int>::max(), '\n' ) ) { ++lines; } } } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // utiliser std::count et std::istreambuf_iterator #include <fstream> #include <algorithm> int main() { std::ifstream file( "fichier.txt" ); if ( file ) { int lines = std::count( std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>(), '\n' ); } } |
Sous Windows, vous pouvez être surpris de lire un peu moins de caractères dans un fichier texte, ou d'en avoir écris plus que ce que vous avez demandé. Vous constatez cela en comparant la taille de votre flux (ou la taille théorique de ce que vous avez écris) avec la taille réelle du fichier indiquée par Windows. Si votre programme n'est pas en cause, alors l'explication est simple : il s'agit du traitement particulier opéré par ofstream et ifstream lorsqu'ils travaillent sur des fichiers ouverts en mode texte, ce qui est le cas par défaut.
Ceci n'a généralement pas d'influence, sauf sous DOS et Windows où un saut de ligne est matérialisé par la séquence des deux caractères '\r' et '\n'. Pour assurer une meilleure portabilité du code, le langage C++ (ainsi que le langage C) considère qu'une fin de ligne est identifiée par un unique caractère '\n'.
Pour cette raison, en mode texte sous Windows, l'objet ifstream va ignorer le caractère '\r' lorsqu'il rencontre une fin de ligne, et ofstream va insérer un '\r' avant chaque '\n' qu'on lui a demandé d'écrire.
Ainsi, le fait que le fichier sur disque contiennent des caractère '\r' que vous n'avez pas lu / écris devrait expliquer votre différence de taille constatée. Pour lire ces caractères / ne pas générer leur écriture, il faut travailler en mode binaire en spécifiant le flag std::ios_base::binary.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | #include <fstream> using namespace std; int main() { ofstream file_txt( "fichier_txt.txt" ); file_txt << "a\n" "b\n" "c\n"; ofstream file_bin( "fichier_bin.txt", ios_base::binary ); file_bin << "a\n" "b\n" "c\n"; } |
Il est possible d'effectuer une lecture ou une écriture en mode texte dans un flux std::fstream, via les opérateurs >> et <<. Mais il est également possible d'effectuer des lectures / écritures binaires via les fonctions membres read() et write(), un peu à la façon de fread() et fwrite().
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <fstream> #include <iostream> std::ofstream FileOut("Toto.txt", std::ios_base::binary); int xout = 24; FileOut.write(reinterpret_cast<const char*>(&xout), sizeof(int)); FileOut.close(); std::ifstream FileIn("Toto.txt", std::ios_base::binary); int xin; FileIn.read(reinterpret_cast<char*>(&xin), sizeof(int)); FileIn.close(); std::cout << xin << std::endl; // Affiche "24"; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | template<typename T> std::ostream& write(std::ostream& os, const T& value) { return os.write(reinterpret_cast<const char*>(&value), sizeof(T)); } template<typename T> std::istream & read(std::istream& is, T& value) { return is.read(reinterpret_cast<char*>(&value), sizeof(T)); } // Nos appels deviennent donc write(FileOut, xout); read(FileIn, xin); |
Si vous devez sérialiser des données plus complexes (gestion des données dynamiques, des conteneurs, des objets polymorphes, etc.), il existe des mécanismes plus élaborés et plus efficaces, comme par exemple boost::serialization ou CArchive dans les MFC.
Le code suivant illustre des opérations courantes effectuées sur un nom de fichier :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <string> #include <iostream> int main() { using namespace std; string file_name = "fichier.txt"; size_t ext_pos = file_name.find_last_of( '.' ); if ( ext_pos != string::npos ) { // 3 manières d'extraire l'entension string ext1 = file_name.substr( ext_pos ); string ext2( file_name, ext_pos ); string ext3; ext3.assign( file_name, ext_pos, file_name.size() - ext_pos ); // 3 manières d'extraire le nom string name1 = file_name.substr( 0, ext_pos ); string name2( file_name, 0, ext_pos ); string name3 = file_name; name3.resize( ext_pos ); // remplacer le nom string file_name2 = file_name; // copie pour préserver l'original file_name2.replace( 0, ext_pos, "remplace" ); // remplacer l'extension string file_name3 = file_name; // copie pour préserver l'original file_name3.replace( ext_pos + 1, file_name3.size() - 1, "dat" ); // insérer un mot entre le nom du fichier et son extension string file_name4 = file_name; // copie pour préserver l'original file_name4.insert( ext_pos, "-modif" ); // afficher les résultats cout << ext1 << '\n'; // ".txt" cout << ext2 << '\n'; // ".txt" cout << ext3 << '\n'; // ".txt" cout << name1 << '\n'; // "fichier" cout << name2 << '\n'; // "fichier" cout << name3 << '\n'; // "fichier" cout << file_name2 << '\n'; // "remplace.txt" cout << file_name3 << '\n'; // "fichier.dat" cout << file_name4 << '\n'; // "fichier-modif.txt" } } |
Une fois la fin de fichier atteinte par une première lecture, le flux se met dans un état invalide, ce qui empêche la réussite de toute lecture ultérieure.
Afin de pouvoir lire à nouveau le même flux, il faut donc :
- le remettre dans un état valide (avec la fonction clear()) ;
- le rembobiner (avec la fonction seekg()).
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <fstream> #include <iostream> #include <limits> int CountLines(std::ifstream& File) { int Count = 0; while (File.ignore(std::numeric_limits<int>::max(), '\n')) ++Count; return Count; } int main() { std::ifstream File("fichier.txt"); std::cout << CountLines(File) << std::endl; File.clear(); File.seekg(0, std::ios::beg); std::cout << CountLines(File) << std::endl; return 0; } |
Les flux gèrent une notion d'erreur, et en particulier, quand on essaye de lire au-delà de la fin d'un fichier, un flag indiquant cela est positionné. Cette situation arrive très souvent, puisque c'est ainsi qu'on détecte qu'on est arrivé à la fin. Une fois en état d'erreur, la plupart des opérations sur les flux ne font plus rien.
En C++98, la seule manière de sortir de cet état est d'appeler file.clear(); avant de faire d'autres opérations, dont seekg.
En C++11, seekg peut fonctionner directement si l'erreur était liée à une fin de fichier, et enlève alors ce flag.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.