Читайте файл рядок за рядком, використовуючи ifstream в C ++


612

Вміст file.txt:

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

Де 5 3пара координат. Як обробляти цей рядок даних за рядком в C ++?

Я можу отримати перший рядок, але як я можу отримати наступний рядок файлу?

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

Відповіді:


916

Спочатку зробіть ifstream:

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

Два стандартні методи:

  1. Припустимо, що кожен рядок складається з двох чисел і читається лексемою за лексемою:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Рядок на основі рядків з використанням потокових потоків:

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

Вам не слід змішувати (1) і (2), оскільки аналіз на основі лексеми не піднімає нові рядки, тому ви можете виявити помилкові порожні рядки, якщо використання getline()після вилучення на основі лексеми призведе до кінця лінія вже.


1
@EdwardKarak: Я не розумію, що означає "кома як маркер". Коми не представляють цілих чисел.
Керрек СБ

8
ОП використовував пробіл для розмежування двох цілих чисел. Мені хотілося знати, якщо в той час (infile >> a >> b) буде працювати, якщо ОП використовує як роздільник комами, тому що це сценарій у моїй власній програмі
Едвард Карак

30
@EdwardKarak: Ага, коли ви сказали "маркер", ви мали на увазі "роздільник". Правильно. З комою ви б сказали:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Керрек СБ

11
@KerrekSB: Так. Я помилявся. Я не знав, що це може зробити. У мене може бути якийсь власний код для переписування.
Позначте H

4
Для пояснення while(getline(f, line)) { }конструкції та щодо поводження з помилками, будь ласка, подивіться цю (мою) статтю: gehrcke.de/2011/06/… (я думаю, мені не потрібно, щоб сумнівна сумління розміщувала це тут, це навіть трохи попередньо датує цю відповідь).
Доктор Ян-Філіп Геркке

175

Використовуйте ifstreamдля читання даних з файлу:

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

Якщо вам справді потрібно читати рядок за рядком, то зробіть це:

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

Але вам, мабуть, просто потрібно витягнути координатні пари:

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

Оновлення:

У своєму коді ви використовуєте ofstream myfile;, проте oв ofstreamстендах для output. Якщо ви хочете прочитати з файлу (вводу), використовуйте ifstream. Якщо ви хочете як читати, так і писати, використовуйте fstream.


8
Ваше рішення дещо вдосконалено: ваша змінна лінія не відображається після зчитування файлу на відміну від другого рішення Kerrek SB, яке також є хорошим і простим рішенням.
DanielTuzes

3
getlineзнаходиться в string Престолі , так що не забувайте#include <string>
mxmlnkn

55

Читання файлів по рядку в C ++ може бути здійснено різними способами.

[Швидкий] цикл із std :: getline ()

Найпростіший підхід - відкрити std :: ifstream та циклічно за допомогою викликів std :: getline (). Код чистий і легкий для розуміння.

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

[Швидко] Використовуйте файл_description_source Boost

Інша можливість полягає у використанні бібліотеки Boost, але код стає дещо докладнішим. Продуктивність досить схожа на код вище (Loop with 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();
        }
    }
}

[Найшвидший] Використовуйте код C

Якщо продуктивність є критичною для вашого програмного забезпечення, ви можете розглянути можливість використання мови C. Цей код може бути в 4-5 разів швидшим, ніж версії C ++, наведені вище, див. Орієнтир нижче

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

Бенчмарк - який з них швидший?

Я зробив декілька показників ефективності з кодом вище, і результати цікаві. Я перевірив код з файлами ASCII, що містять 100 000 рядків, 1 000 000 рядків і 10 000 000 рядків тексту. Кожен рядок тексту в середньому містить 10 слів. Програма складена з -O3оптимізацією, і її вихід передається /dev/null, щоб видалити змінну часу реєстрації з вимірювання. І останнє, але не менш важливе, кожен фрагмент коду записує кожен рядок з printf()функцією для узгодженості.

Результати показують час (у мс), який кожен фрагмент коду потребував для читання файлів.

Різниця в ефективності між двома підходами C ++ мінімальна і не повинна змінювати практику. Продуктивність коду С - це те, що робить тест вражаючим і може бути зміною гри з точки зору швидкості.

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

введіть тут опис зображення


1
Що станеться, якщо ви вилучите синхронізацію C ++ з C на консольних виходах? Ви могли б бути вимір відомого недоліку поведінки за замовчуванням std::coutпроти printf.
користувач4581301

2
Дякуємо, що викликали цю проблему. Я переробив тести, і продуктивність залишається такою ж. Я відредагував код, щоб використовувати printf()функцію у всіх випадках для узгодженості. Я також намагався використовувати std::coutу всіх випадках, і це абсолютно не мало значення. Як я нещодавно описав у тексті, вихід програми виходить на те, /dev/nullщоб час на друк рядків не вимірювався.
HugoTeixeira

6
Groovy. Дякую. Цікаво, де сповільнення.
user4581301

4
Привіт @HugoTeixeira Я знаю, що це стара тема, я намагався повторити ваші результати, і я не міг помітити жодної суттєвої різниці між c і c ++ github.com/simonsso/readfile_benchmarks
Сімсон

За замовчуванням синхронізовані потоки вхідних програм C ++ cstdio. Ви повинні спробувати з налаштуванням std::ios_base::sync_with_stdio(false). Я думаю, ви отримали б набагато кращі показники (Це не гарантується, хоча це визначено реалізацією при відключенні синхронізації).
Fareanor

11

Оскільки ваші координати належать разом як пари, чому б не написати структуру для них?

struct CoordinatePair
{
    int x;
    int y;
};

Потім ви можете написати перевантажений оператор вилучення для istreams:

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

    return is;
}

А потім ви можете прочитати файл координат прямо у такий вектор:

#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
Що станеться, коли intв потоці неможливо прочитати два лексеми operator>>? Як можна змусити його працювати з аналізатором зворотного відстеження (тобто, коли operator>>не вдалося, повернути потік до попереднього кінця позиції, повернути помилкове чи щось подібне)?
fferri

Якщо неможливо прочитати два intлексеми, то isпотік оцінюватиметься, falseі цикл читання закінчується в цій точці. Ви можете виявити це всередині operator>>, перевіривши повернене значення окремих прочитаних. Якщо ви хочете відкати потік, ви б зателефонували is.clear().
Мартін Бродхерст

в operator>>цьому правильніше сказати, is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;оскільки в іншому випадку ви припускаєте, що вхідний потік знаходиться в режимі пропуску пробілів.
Дарко Веберич

7

Розширення на прийняту відповідь, якщо вхідний:

1,NYC
2,ABQ
...

ви все одно зможете застосувати таку ж логіку, як ця:

#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

Хоча не потрібно закривати файл вручну, але це добре зробити, якщо область застосування змінної файлу більша:

    ifstream infile(szFilePath);

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

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

Не впевнений, що це заслуговує на голосування проти. ОП попросили спосіб отримати кожен рядок. Ця відповідь робить це і дає чудову пораду про те, щоб файл закрився. Для простої програми це може не знадобитися, але як мінімум ВЕЛИКА звичка формуватись. Це може бути покращено, додавши в кілька рядків коду для обробки окремих рядків, які він витягує, але загалом це найпростіший відповідь на питання про ОП.
Ксандор

2

Ця відповідь призначена для візуальної студії 2017, і якщо ви хочете прочитати з текстового файлу, яке розташування є відносно вашої компільованої консольної програми.

спочатку помістіть свій текстовий файл (test.txt у цьому випадку) у папку з рішеннями. Після компіляції зберігайте текстовий файл у одній папці з applicationName.exe

C: \ Користувачі \ "ім'я користувача" \ джерело \ репос \ "рішенняНазви" \ "рішенняНазви"

#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

Це загальне рішення для завантаження даних у програму C ++ і використовує функцію readline. Це можна змінити для файлів CSV, але роздільник - це пробіл.

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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.