Lesen Sie die Datei Zeile für Zeile mit ifstream in C ++

612

Der Inhalt von file.txt ist:

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

Wo 5 3ist ein Koordinatenpaar? Wie verarbeite ich diese Daten zeilenweise in C ++?

Ich kann die erste Zeile abrufen, aber wie erhalte ich die nächste Zeile der Datei?

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

Antworten:

916

Machen Sie zuerst eine ifstream:

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

Die zwei Standardmethoden sind:

  1. Angenommen, jede Zeile besteht aus zwei Zahlen und liest Token für Token:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Zeilenbasiertes Parsen mit String-Streams:

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

Sie sollten (1) und (2) nicht mischen, da das tokenbasierte Parsen keine Zeilenumbrüche verschlingt, sodass Sie möglicherweise falsche Leerzeilen erhalten, wenn Sie getline()nach der tokenbasierten Extraktion das Ende von a erreichen Linie bereits.

Kerrek SB
quelle
1
@ EdwardKarak: Ich verstehe nicht, was "Kommas als Token" bedeutet. Kommas stehen nicht für ganze Zahlen.
Kerrek SB
8
Das OP hat ein Leerzeichen verwendet, um die beiden Ganzzahlen abzugrenzen. Ich wollte wissen, ob while (infile >> a >> b) funktionieren würde, wenn das OP a als Komma und Trennzeichen verwenden würde, da dies das Szenario in meinem eigenen Programm ist
Edward Karak
30
@ EdwardKarak: Ah, als du "Token" gesagt hast, hast du "Trennzeichen" gemeint. Recht. Mit einem Komma würden Sie sagen:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB
11
@ KerrekSB: Huh. Ich lag falsch. Ich wusste nicht, dass es das kann. Möglicherweise muss ich einen eigenen Code umschreiben.
Mark H
4
Eine Erklärung des while(getline(f, line)) { }Konstrukts und zur Fehlerbehandlung finden Sie in diesem (meinem) Artikel: gehrcke.de/2011/06/… (Ich glaube, ich brauche kein schlechtes Gewissen, um dies hier zu posten. datiert diese Antwort).
Dr. Jan-Philip Gehrcke
175

Verwenden Sie ifstreamdiese Option , um Daten aus einer Datei zu lesen:

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

Wenn Sie wirklich Zeile für Zeile lesen müssen, gehen Sie folgendermaßen vor:

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

Aber Sie müssen wahrscheinlich nur Koordinatenpaare extrahieren:

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

Aktualisieren:

In Ihrem Code verwenden Sie ofstream myfile;jedoch die oin ofstreamsteht für output. Wenn Sie aus der Datei (Eingabe) lesen möchten, verwenden Sie ifstream. Wenn Sie sowohl lesen als auch schreiben möchten, verwenden Sie fstream.

K-Ballo
quelle
8
Ihre Lösung ist etwas verbessert: Ihre Zeilenvariable ist nach dem Einlesen der Datei nicht sichtbar, im Gegensatz zur zweiten Lösung von Kerrek SB, die ebenfalls eine gute und einfache Lösung darstellt.
DanielTuzes
3
getlineist in string sehen , also vergessen Sie nicht die#include <string>
mxmlnkn
55

Das zeilenweise Lesen einer Datei in C ++ kann auf verschiedene Arten erfolgen.

[Fast] Schleife mit std :: getline ()

Der einfachste Ansatz besteht darin, einen std :: ifstream und eine Schleife mit std :: getline () -Aufrufen zu öffnen. Der Code ist sauber und leicht zu verstehen.

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

[Schnell] Verwenden Sie die Datei file_description_source von Boost

Eine andere Möglichkeit ist die Verwendung der Boost-Bibliothek, aber der Code wird etwas ausführlicher. Die Leistung ist dem obigen Code ziemlich ähnlich (Schleife mit 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();
        }
    }
}

[Am schnellsten] Verwenden Sie C-Code

Wenn die Leistung für Ihre Software entscheidend ist, können Sie die Sprache C verwenden. Dieser Code kann 4-5 mal schneller sein als die obigen C ++ - Versionen, siehe Benchmark unten

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 - Welches ist schneller?

Ich habe einige Leistungsbenchmarks mit dem obigen Code durchgeführt und die Ergebnisse sind interessant. Ich habe den Code mit ASCII-Dateien getestet, die 100.000 Zeilen, 1.000.000 Zeilen und 10.000.000 Textzeilen enthalten. Jede Textzeile enthält durchschnittlich 10 Wörter. Das Programm wird mit -O3Optimierung kompiliert und seine Ausgabe an weitergeleitet /dev/null, um die Protokollierungszeitvariable aus der Messung zu entfernen. Zu guter Letzt protokolliert jeder Code jede Zeile mit der printf()Funktion für Konsistenz.

Die Ergebnisse zeigen die Zeit (in ms), die jeder Code zum Lesen der Dateien benötigt hat.

Der Leistungsunterschied zwischen den beiden C ++ - Ansätzen ist minimal und sollte in der Praxis keinen Unterschied machen. Die Leistung des C-Codes macht den Benchmark beeindruckend und kann die Geschwindigkeit verändern.

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

Geben Sie hier die Bildbeschreibung ein

HugoTeixeira
quelle
1
Was passiert, wenn Sie die Synchronisation von C ++ mit C auf den Konsolenausgängen entfernen? Sie könnten einen bekannten Nachteil des Standardverhaltens werden Mess std::coutvs printf.
user4581301
2
Vielen Dank für diese Sorge. Ich habe die Tests wiederholt und die Leistung ist immer noch die gleiche. Ich habe den Code so bearbeitet, dass die printf()Funktion in allen Fällen aus Gründen der Konsistenz verwendet wird. Ich habe auch versucht, std::coutin allen Fällen zu verwenden, und dies machte absolut keinen Unterschied. Wie ich gerade im Text beschrieben habe, geht die Ausgabe des Programms an, /dev/nullso dass die Zeit zum Drucken der Zeilen nicht gemessen wird.
HugoTeixeira
6
Groovy. Vielen Dank. Ich frage mich, wo die Verlangsamung ist.
user4581301
4
Hallo @HugoTeixeira Ich weiß, dass dies ein alter Thread ist. Ich habe versucht, Ihre Ergebnisse zu replizieren und konnte keinen signifikanten Unterschied zwischen c und c ++ feststellen. Github.com/simonsso/readfile_benchmarks
Simson
Standardmäßig werden C ++ - In-Out-Streams mit synchronisiert cstdio. Sie sollten es mit der Einstellung versucht haben std::ios_base::sync_with_stdio(false). Ich denke, Sie hätten viel bessere Leistungen erzielt (dies ist jedoch nicht garantiert, da es implementierungsdefiniert ist, wenn die Synchronisation ausgeschaltet ist).
Fareanor
11

Da Ihre Koordinaten paarweise zusammengehören, schreiben Sie doch eine Struktur für sie.

struct CoordinatePair
{
    int x;
    int y;
};

Dann können Sie einen überladenen Extraktionsoperator für istreams schreiben:

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

    return is;
}

Und dann können Sie eine Koordinatendatei direkt in einen Vektor wie diesen lesen:

#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
quelle
1
Was passiert, wenn es nicht möglich ist, zwei intToken aus dem Stream einzulesen operator>>? Wie kann man es mit einem Backtracking-Parser zum Laufen bringen (dh wenn dies operator>>fehlschlägt, den Stream auf die vorherige Position zurücksetzen und false oder ähnliches zurückgeben)?
fferri
Wenn es nicht möglich ist, zwei intToken zu lesen , wird der isStream ausgewertet falseund die Leseschleife wird an diesem Punkt beendet. Sie können dies innerhalb erkennen, operator>>indem Sie den Rückgabewert der einzelnen Lesevorgänge überprüfen. Wenn Sie den Stream zurücksetzen möchten, würden Sie anrufen is.clear().
Martin Broadhurst
in der operator>>ist es korrekter zu sagen, is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;da Sie sonst davon ausgehen, dass sich Ihr Eingabestream im Whitespace-Skipping-Modus befindet.
Darko Veberic
7

Erweitern der akzeptierten Antwort, wenn die Eingabe lautet:

1,NYC
2,ABQ
...

Sie können weiterhin dieselbe Logik wie folgt anwenden:

#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
quelle
2

Es ist zwar nicht erforderlich, die Datei manuell zu schließen, aber es ist eine gute Idee, dies zu tun, wenn der Bereich der Dateivariablen größer ist:

    ifstream infile(szFilePath);

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

    if(infile.is_open())
        infile.close();
Vijay Bansal
quelle
Ich bin mir nicht sicher, ob dies eine Abwertung verdient hat. OP fragte nach einem Weg, um jede Zeile zu bekommen. Diese Antwort macht das und gibt einen guten Tipp, um sicherzustellen, dass die Datei geschlossen wird. Für ein einfaches Programm ist es möglicherweise nicht erforderlich, aber es bildet sich zumindest eine GROSSE Angewohnheit. Es könnte vielleicht verbessert werden, indem ein paar Codezeilen hinzugefügt werden, um die einzelnen Zeilen zu verarbeiten, die es zieht, aber insgesamt ist die einfachste Antwort auf die OP-Frage.
Xandor
2

Diese Antwort gilt für Visual Studio 2017 und wenn Sie aus einer Textdatei lesen möchten, welcher Speicherort relativ zu Ihrer kompilierten Konsolenanwendung ist.

Legen Sie zuerst Ihre Textdatei (in diesem Fall test.txt) in Ihrem Lösungsordner ab. Bewahren Sie die Textdatei nach dem Kompilieren mit applicationName.exe im selben Ordner auf

C: \ Benutzer \ "Benutzername" \ Quelle \ Repos \ "Lösungsname" \ "Lösungsname"

#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
quelle
1

Dies ist eine allgemeine Lösung zum Laden von Daten in ein C ++ - Programm und verwendet die Readline-Funktion. Dies könnte für CSV-Dateien geändert werden, aber das Trennzeichen ist hier ein Leerzeichen.

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
quelle