Какой предпочтительный шаблон для чтения строк из файла на С++?
Я видел как минимум два способа чтения строк из файла в учебниках на С++:
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)) {
// ...
Конечно, я могу добавить несколько проверок, чтобы убедиться, что файл существует и открыт. Помимо обработки исключений, есть ли причина предпочитать более подробный первый шаблон? Какая ваша стандартная практика?
Ответы
Ответ 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;
}