Lire le fichier ligne par ligne en utilisant ifstream en C ++

612

Le contenu de file.txt est:

5 3
6 4
7 1
10 5
11 6
12 3
12 4

5 3est une paire de coordonnées. Comment traiter ces données ligne par ligne en C ++?

Je peux obtenir la première ligne, mais comment obtenir la ligne suivante du fichier?

ifstream myfile;
myfile.open ("text.txt");
citron
la source

Réponses:

916

Tout d'abord, faites un ifstream:

#include <fstream>
std::ifstream infile("thefile.txt");

Les deux méthodes standard sont:

  1. Supposons que chaque ligne se compose de deux nombres et lisez jeton par jeton:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Analyse basée sur les lignes, en utilisant des flux de chaînes:

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }

Vous ne devez pas mélanger (1) et (2), car l'analyse basée sur les jetons ne gobe pas les nouvelles lignes, vous pouvez donc vous retrouver avec des lignes vides parasites si vous utilisez getline()après que l'extraction basée sur les jetons vous a conduit à la fin d'un ligne déjà.

Kerrek SB
la source
1
@EdwardKarak: Je ne comprends pas ce que signifie "virgule comme jeton". Les virgules ne représentent pas des entiers.
Kerrek SB
8
l'OP a utilisé un espace pour délimiter les deux entiers. Je voulais savoir si while (infile >> a >> b) fonctionnerait si l'OP utilisait a comme virgule comme délimiteur, car c'est le scénario de mon propre programme
Edward Karak
30
@EdwardKarak: Ah, donc quand vous avez dit "jeton", vous vouliez dire "délimiteur". Droite. Avec une virgule, vous diriez:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB
11
@KerrekSB: Huh. J'avais tort. Je ne savais pas que ça pouvait faire ça. Je pourrais avoir mon propre code à réécrire.
Mark H du
4
Pour une explication de la while(getline(f, line)) { }construction et concernant la gestion des erreurs, veuillez consulter cet (mon) article: gehrcke.de/2011/06/… (Je pense que je n'ai pas besoin d'avoir mauvaise conscience en postant ceci ici, il est même légèrement pré- date cette réponse).
Dr Jan-Philip Gehrcke du
175

Utilisez ifstreampour lire les données d'un fichier:

std::ifstream input( "filename.ext" );

Si vous avez vraiment besoin de lire ligne par ligne, procédez comme suit:

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

Mais il vous suffit probablement d'extraire des paires de coordonnées:

int x, y;
input >> x >> y;

Mise à jour:

Dans votre code que vous utilisez ofstream myfile;, cependant le odans ofstreamsignifie output. Si vous souhaitez lire à partir du fichier (entrée), utilisez ifstream. Si vous voulez à la fois lire et écrire, utilisez fstream.

K-ballo
la source
8
Votre solution est un peu améliorée: votre variable de ligne n'est pas visible après la lecture du fichier contrairement à la deuxième solution de Kerrek SB qui est aussi une bonne solution simple.
DanielTuzes
3
getlineest à string voir , alors n'oubliez pas le#include <string>
mxmlnkn
56

La lecture d'un fichier ligne par ligne en C ++ peut se faire de différentes manières.

[Rapide] Boucle avec std :: getline ()

L'approche la plus simple consiste à ouvrir un std :: ifstream et une boucle à l'aide d'appels std :: getline (). Le code est propre et facile à comprendre.

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[Rapide] Utilisez file_description_source de Boost

Une autre possibilité est d'utiliser la bibliothèque Boost, mais le code devient un peu plus détaillé. Les performances sont assez similaires au code ci-dessus (boucle avec std :: getline ()).

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[Le plus rapide] Utilisez le code C

Si les performances sont critiques pour votre logiciel, vous pouvez envisager d'utiliser le langage C. Ce code peut être 4 à 5 fois plus rapide que les versions C ++ ci-dessus, voir référence ci-dessous

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

Benchmark - Lequel est le plus rapide?

J'ai fait des tests de performances avec le code ci-dessus et les résultats sont intéressants. J'ai testé le code avec des fichiers ASCII contenant 100 000 lignes, 1 000 000 lignes et 10 000 000 lignes de texte. Chaque ligne de texte contient en moyenne 10 mots. Le programme est compilé avec -O3optimisation et sa sortie est transmise à /dev/nullafin de supprimer la variable de temps d'enregistrement de la mesure. Enfin, chaque élément de code enregistre chaque ligne avec la printf()fonction pour plus de cohérence.

Les résultats montrent le temps (en ms) que chaque morceau de code a pris pour lire les fichiers.

La différence de performances entre les deux approches C ++ est minime et ne devrait pas faire de différence dans la pratique. La performance du code C est ce qui rend la référence impressionnante et peut changer la donne en termes de vitesse.

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

entrez la description de l'image ici

HugoTeixeira
la source
1
Que se passe-t-il si vous supprimez la synchronisation de C ++ avec C sur les sorties de la console? Vous pourriez être un inconvénient connu mesurez du comportement par défaut de std::coutvs printf.
user4581301
2
Merci d'avoir apporté cette préoccupation. J'ai refait les tests et les performances sont toujours les mêmes. J'ai édité le code pour utiliser la printf()fonction dans tous les cas par souci de cohérence. J'ai également essayé d'utiliser std::coutdans tous les cas et cela n'a fait absolument aucune différence. Comme je viens de le décrire dans le texte, la sortie du programme passe à /dev/nulldonc le temps d'impression des lignes n'est pas mesuré.
HugoTeixeira
6
Sensationnel. Merci. Je me demande où est le ralentissement.
user4581301
4
Salut @HugoTeixeira Je sais que c'est un vieux fil, j'ai essayé de répliquer vos résultats et je n'ai pas vu de différence significative entre c et c ++ github.com/simonsso/readfile_benchmarks
Simson
Par défaut, les flux d'entrée / sortie C ++ sont synchronisés avec cstdio. Vous auriez dû essayer de régler std::ios_base::sync_with_stdio(false). Je suppose que vous auriez obtenu de bien meilleures performances (ce n'est pas garanti car il est défini par l' implémentation lorsque la synchronisation est désactivée).
Fareanor
11

Puisque vos coordonnées appartiennent ensemble par paires, pourquoi ne pas leur écrire une structure?

struct CoordinatePair
{
    int x;
    int y;
};

Ensuite, vous pouvez écrire un opérateur d'extraction surchargé pour les istreams:

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

Et puis vous pouvez lire un fichier de coordonnées directement dans un vecteur comme celui-ci:

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}
Martin Broadhurst
la source
1
Que se passe-t-il lorsqu'il n'est pas possible de lire deux intjetons depuis le flux operator>>? Comment peut-on le faire fonctionner avec un analyseur de retour arrière (c.-à-d. En cas d' operator>>échec, restaurer le flux à la position précédente, retourner retour faux ou quelque chose comme ça)?
fferri
S'il n'est pas possible de lire deux intjetons, alors le isflux sera évalué falseet la boucle de lecture se terminera à ce point. Vous pouvez le détecter operator>>en vérifiant la valeur de retour des lectures individuelles. Si vous souhaitez restaurer le flux, vous appelez is.clear().
Martin Broadhurst
dans le operator>>il est plus correct de dire is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;car sinon vous supposez que votre flux d'entrée est en mode de saut d'espaces.
Darko Veberic
7

Développer la réponse acceptée, si l'entrée est:

1,NYC
2,ABQ
...

vous pourrez toujours appliquer la même logique, comme ceci:

#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();
gsamaras
la source
2

Bien qu'il ne soit pas nécessaire de fermer le fichier manuellement, il est préférable de le faire si la portée de la variable de fichier est plus grande:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();
Vijay Bansal
la source
Pas sûr que cela méritait un vote négatif. OP a demandé un moyen d'obtenir chaque ligne. Cette réponse fait cela et donne une excellente astuce pour s'assurer que le fichier se ferme. Pour un programme simple, il peut ne pas être nécessaire, mais au moins une GRANDE habitude de se former. Il pourrait peut-être être amélioré en ajoutant quelques lignes de code pour traiter les lignes individuelles qu'il tire, mais dans l'ensemble, c'est la réponse la plus simple à la question des PO.
Xandor
2

Cette réponse est pour Visual Studio 2017 et si vous souhaitez lire à partir d'un fichier texte quel emplacement est relatif à votre application console compilée.

placez d'abord votre fichier texte (test.txt dans ce cas) dans votre dossier de solution. Après la compilation, conservez le fichier texte dans le même dossier avec applicationName.exe

C: \ Users \ "nom d'utilisateur" \ source \ repos \ "solutionName" \ "solutionName"

#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}
Universus
la source
1

Il s'agit d'une solution générale pour charger des données dans un programme C ++ et utilise la fonction readline. Cela pourrait être modifié pour les fichiers CSV, mais le délimiteur est un espace ici.

int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}
mjr2000
la source