Leggi il file riga per riga usando ifstream in C ++


612

I contenuti di file.txt sono:

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

Dov'è 5 3una coppia di coordinate. Come posso elaborare questi dati riga per riga in C ++?

Sono in grado di ottenere la prima riga, ma come posso ottenere la riga successiva del file?

ifstream myfile;
myfile.open ("text.txt");

Risposte:


916

Innanzitutto, crea un ifstream:

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

I due metodi standard sono:

  1. Supponi che ogni riga sia composta da due numeri e leggi token per token:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Analisi basata su linea, utilizzando flussi di stringhe:

    #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)
    }

Non dovresti mescolare (1) e (2), poiché l'analisi basata su token non ingloba nuove righe, quindi potresti finire con linee vuote spurie se usi getline()dopo l'estrazione basata su token che ti porta alla fine di un linea già.


1
@EdwardKarak: Non capisco cosa significhi "virgole come token". Le virgole non rappresentano numeri interi.
Kerrek SB,

8
l'OP ha usato uno spazio per delimitare i due numeri interi. Volevo sapere se while (infile >> a >> b) avrebbe funzionato se l'OP avesse utilizzato a come virgola un delimitatore, perché quello è lo scenario del mio programma
Edward Karak,

30
@EdwardKarak: Ah, quindi quando hai detto "token" intendevi "delimitatore". Giusto. Con una virgola, diresti:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB,

11
@KerrekSB: Huh. Mi sbagliavo. Non sapevo che potesse farlo. Potrei avere un mio codice da riscrivere.
Mark H

4
Per una spiegazione del while(getline(f, line)) { }costrutto e per quanto riguarda la gestione degli errori, dai un'occhiata a questo (mio) articolo: gehrcke.de/2011/06/… (Penso di non aver bisogno di cattiva coscienza pubblicando questo qui, anche se leggermente pre data questa risposta).
Dr. Jan-Philip Gehrcke,

175

Utilizzare ifstreamper leggere i dati da un file:

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

Se hai davvero bisogno di leggere riga per riga, allora fai questo:

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

Ma probabilmente devi solo estrarre le coppie di coordinate:

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

Aggiornare:

Nel tuo codice utilizzi ofstream myfile;, tuttavia oin ofstreamindica output. Se vuoi leggere dal file (input) usa ifstream. Se vuoi leggere e scrivere, usa fstream.


8
La tua soluzione è un po 'migliorata: la tua variabile di linea non è visibile dopo la lettura del file in contrasto con la seconda soluzione di Kerrek SB che è anche una buona e semplice soluzione.
Daniel Tuzes,

3
getlineè in string vista , quindi non dimenticare di#include <string>
mxmlnkn il

56

La lettura di un file riga per riga in C ++ può essere eseguita in diversi modi.

[Fast] Loop con std :: getline ()

L'approccio più semplice è aprire uno std :: ifstream e un loop usando le chiamate std :: getline (). Il codice è pulito e di facile comprensione.

#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();
}

[Veloce] Usa il file_description_source di Boost

Un'altra possibilità è utilizzare la libreria Boost, ma il codice diventa un po 'più dettagliato. Le prestazioni sono abbastanza simili al codice sopra (Loop con 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();
        }
    }
}

[Più veloce] Usa il codice C.

Se le prestazioni sono fondamentali per il tuo software, puoi prendere in considerazione l'uso del linguaggio C. Questo codice può essere 4-5 volte più veloce rispetto alle versioni C ++ sopra, vedi benchmark sotto

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);

Indice di riferimento: quale è più veloce?

Ho fatto alcuni benchmark delle prestazioni con il codice sopra e i risultati sono interessanti. Ho testato il codice con file ASCII che contengono 100.000 righe, 1.000.000 di righe e 10.000.000 di righe di testo. Ogni riga di testo contiene in media 10 parole. Il programma viene compilato con l' -O3ottimizzazione e il suo output viene inoltrato /dev/nullper rimuovere la variabile del tempo di registrazione dalla misurazione. Ultimo, ma non meno importante, ogni pezzo di codice registra ogni riga con la printf()funzione per coerenza.

I risultati mostrano il tempo (in ms) impiegato da ciascun pezzo di codice per leggere i file.

La differenza di prestazioni tra i due approcci C ++ è minima e non dovrebbe fare alcuna differenza nella pratica. Le prestazioni del codice C sono ciò che rende impressionante il benchmark e può cambiare il gioco in termini di velocità.

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

inserisci qui la descrizione dell'immagine


1
Cosa succede se si rimuove la sincronizzazione di C ++ con C sulle uscite della console? Si potrebbe essere uno svantaggio misurando nota del comportamento predefinito di std::coutvs printf.
user4581301

2
Grazie per aver portato questa preoccupazione. Ho rifatto i test e le prestazioni sono sempre le stesse. Ho modificato il codice per utilizzare la printf()funzione in tutti i casi per coerenza. Ho anche provato a utilizzare std::coutin tutti i casi e questo non ha fatto alcuna differenza. Come ho appena descritto nel testo, l'output del programma passa /dev/nullquindi il tempo di stampa delle linee non viene misurato.
HugoTeixeira,

6
Groovy. Grazie. Mi chiedo dove sia il rallentamento.
user4581301

4
Ciao @HugoTeixeira So che questo è un vecchio thread, ho provato a replicare i tuoi risultati e non ho visto differenze significative tra c e c ++ github.com/simonsso/readfile_benchmarks
Simson

Per impostazione predefinita, i flussi in-out C ++ sono sincronizzati con cstdio. Avresti dovuto provare con l'impostazione std::ios_base::sync_with_stdio(false). Immagino che avresti ottenuto prestazioni molto migliori (non è garantito, poiché è definito dall'implementazione quando la sincronizzazione è disattivata).
Fareanor,

11

Dal momento che le tue coordinate si uniscono come coppie, perché non scrivere una struttura per loro?

struct CoordinatePair
{
    int x;
    int y;
};

Quindi è possibile scrivere un operatore di estrazione sovraccarico per istreams:

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

    return is;
}

E poi puoi leggere un file di coordinate direttamente in un vettore come questo:

#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
}

1
Cosa succede quando non è possibile leggere due inttoken dallo stream in operator>>? Come si fa a farlo funzionare con un parser di backtracking (cioè quando operator>>fallisce, ripristinare il flusso alla posizione precedente e restituire false o qualcosa del genere)?
Fferri,

Se non è possibile leggere due inttoken, lo isstream valuterà falsee il ciclo di lettura terminerà a quel punto. Puoi rilevarlo all'interno operator>>controllando il valore di ritorno delle singole letture. Se desideri ripristinare il flusso, chiameresti is.clear().
Martin Broadhurst,

in operator>>è più corretto dirlo is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;poiché altrimenti si presume che il flusso di input sia in modalità di salto degli spazi bianchi.
Darko Veberic,

7

Espandendo la risposta accettata, se l'input è:

1,NYC
2,ABQ
...

sarai comunque in grado di applicare la stessa logica, in questo modo:

#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();

2

Sebbene non sia necessario chiudere il file manualmente, è consigliabile farlo se l'ambito della variabile file è più grande:

    ifstream infile(szFilePath);

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

    if(infile.is_open())
        infile.close();

Non sono sicuro che ciò meritasse un voto negativo. OP ha chiesto un modo per ottenere ogni riga. Questa risposta lo fa e fornisce un ottimo consiglio per assicurarsi che il file si chiuda. Per un semplice programma potrebbe non essere necessario, ma almeno una GRANDE abitudine da formare. Potrebbe forse essere migliorato aggiungendo alcune righe di codice per elaborare le singole righe che estrae, ma nel complesso è la risposta più semplice alla domanda dei PO.
Xandor,

2

Questa risposta è per Visual Studio 2017 e se si desidera leggere dal file di testo quale posizione è relativa all'applicazione della console compilata.

prima inserisci il tuo file di testo (test.txt in questo caso) nella cartella della soluzione. Dopo la compilazione conservare il file di testo nella stessa cartella con applicationName.exe

C: \ Users \ "username" \ Source \ repos \ "NomeSoluzione" \ "NomeSoluzione"

#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();
}

1

Questa è una soluzione generale per il caricamento di dati in un programma C ++ e utilizza la funzione readline. Questo potrebbe essere modificato per i file CSV, ma il delimitatore è uno spazio qui.

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.