Подтвердить что ты не робот

Какой предпочтительный шаблон для чтения строк из файла на С++?

Я видел как минимум два способа чтения строк из файла в учебниках на С++:

std::ifstream fs("myfile.txt");
if (fs.is_open()) {
  while (fs.good()) {
    std::string line;
    std::getline(fs, line);
    // ...

и

std::ifstream fs("myfile.txt");
std::string line;
while (std::getline(fs, line)) {
  // ...

Конечно, я могу добавить несколько проверок, чтобы убедиться, что файл существует и открыт. Помимо обработки исключений, есть ли причина предпочитать более подробный первый шаблон? Какая ваша стандартная практика?

4b9b3361

Ответ 1

while (std::getline(fs, line))
{}

Это не только правильно, но и предпочтительнее , потому что оно идиоматично.

Я предполагаю, что в первом случае вы не проверяете fs после std::getline() как if(!fs) break; или что-то подобное. Потому что, если вы этого не сделаете, то первый случай полностью ошибочен. Или, если вы это сделаете, второй вариант все же предпочтительнее, поскольку он более краток и ясен в логике.

Функция good() должна использоваться после попытки чтения из потока; его использовали, чтобы проверить, была ли попытка успешной. В первом случае вы этого не сделаете. После std::getline() вы считаете, что чтение было успешным, даже не проверяя, что возвращает fs.good(). Кроме того, вы полагаете, что если fs.good() возвращает true, std::getline будет успешно читать строку из потока. Вы идете точно в обратном направлении: факт состоит в том, что если std::getline успешно читает строку из потока, то fs.good() вернет true.

Документация на cplusplus говорит о good(), что

Функция возвращает true, если не установлены ни один из флагов ошибок потока (eofbit, failbit и badbit).

То есть, когда вы пытаетесь прочитать данные из входного потока, и если попытка была неудачной, только тогда установлен флаг сбоя, а good() возвращает false как признак сбоя.

Если вы хотите ограничить область действия переменной line только внутри цикла, вы можете написать цикл for как:

for(std::string line; std::getline(fs, line); )
{
   //use 'line'
}

Примечание: это решение пришло мне в голову после прочтения решения @john, но я считаю его лучше, чем его версия.


Прочитайте подробное объяснение здесь, почему вторая предпочтительна и идиоматична:

Или прочитайте этот красиво написанный блог от @Jerry Coffin:

Ответ 2

Подумайте об этом как о расширенном комментарии к "отличному ответу" Наваза.

Что касается вашего первого варианта,

while (fs.good()) {
  std::string line;
  std::getline(fs, line);
  ...

У этого есть несколько проблем. Проблема номер 1 как та, что условие while находится в неправильном месте и является излишним. Он не в том месте, потому что fs.good() указывает, было ли последнее действие, выполненное в файле, в порядке. Некоторое условие должно быть в отношении предстоящих действий, а не предыдущих. Невозможно узнать, будет ли предстоящее действие над файлом в порядке. Какие предстоящие действия? fs.good() не читает ваш код, чтобы увидеть, что это за предстоящее действие.

Проблема номер два заключается в том, что вы игнорируете статус возврата из std::getline(). Это нормально, если вы сразу же проверяете статус с помощью fs.good(). Итак, немного исправляя это,

while (true) {
  std::string line;
  if (std::getline(fs, line)) {
    ...
  }
  else {
     break;
  }
}

В качестве альтернативы вы можете сделать if (! std::getline(fs, line)) { break; }, но теперь у вас есть break в середине цикла. Yech. Гораздо лучше сделать условия выхода частью самой постановки цикла, если это вообще возможно.

Сравните это с

std::string line;
while (std::getline(fs, line)) {
  ...
}

Это стандартная идиома для чтения строк из файла. Подобная идиома существует в C. Эта идиома очень старая, очень широко используется и очень широко рассматривается как правильный способ читать строки из файла.

Что делать, если вы пришли из магазина, который запрещает условные операции с побочными эффектами? (Есть много и много стандартов программирования, которые делают именно это.) Существует способ обойти это, не прибегая к разрыву в середине подхода цикла:

std::string line;
for (std::getline(fs, line); fs.good(); std::getline(fs, line)) {
  ...
}

Не такой уродливый, как подход к перерыву, но большинство согласятся, что это не так красиво, как стандартная идиома.

Моя рекомендация - использовать стандартную идиому, если некоторые идиотские стандарты не запретили ее использование.

Добавление
Что касается for (std::getline(fs, line); fs.good(); std::getline(fs, line)): это уродливо по двум причинам. Один из них - это очевидный фрагмент реплицированного кода.

Менее очевидно, что вызов getline, а затем good ломает атомарность. Что делать, если какой-либо другой поток также читает из файла? Это не так важно сейчас, потому что С++ I/O в настоящее время не является потокобезопасным. Это будет на предстоящем С++ 11. Нарушение атомарности просто для того, чтобы обеспечить соблюдение стандартов стандартов, - это рецепт катастрофы.

Ответ 3

На самом деле я предпочитаю другой способ

for (;;)
{
  std::string line;
  if (!getline(myFile, line))
    break;
  ...
}

Для меня это лучше читается, и строка правильно определена (т.е. внутри цикла, где она используется, а не вне цикла)

Но из двух вы написали, что второе верно.

Ответ 4

Первый освободил и перераспределил строку в каждом цикле, потратив время. Второй раз записывает строку в уже существующее пространство, удаляя освобождение и перераспределение, делая ее на самом деле быстрее (и лучше), чем первая.

Ответ 5

Попробуйте это = >

// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
  string line;
  ifstream myfile ("example.txt");
  if (myfile.is_open())
  {
    while ( myfile.good() )
    {
      getline (myfile,line);
      cout << line << endl;
    }
    myfile.close();
  }

  else cout << "Unable to open file"; 

  return 0;
}