Конкретно меня интересует istream& getline ( istream& is, string& str );
. Есть ли у конструктора ifstream возможность указать ему преобразовывать все кодировки новой строки в '\ n' под капотом? Я хочу иметь возможность вызывать getline
и изящно обрабатывать все окончания строк.
Обновление : чтобы уточнить, я хочу иметь возможность писать код, который компилируется практически где угодно и будет вводить данные практически из любого места. Включая редкие файлы, в которых '\ r' без '\ n'. Сведение к минимуму неудобств для пользователей программного обеспечения.
Эту проблему легко решить, но мне все еще любопытно, как в стандарте гибко обрабатывать все форматы текстовых файлов.
getline
читает целую строку до '\ n' в строку. '\ N' потребляется из потока, но getline не включает его в строку. Пока это нормально, но может быть '\ r' непосредственно перед '\ n', который включается в строку.
В текстовых файлах есть три типа окончаний строк : '\ n' - это обычное окончание на машинах Unix, '\ r' (я думаю) использовалось в старых операционных системах Mac, а Windows использует пару, '\ r' после "\ n".
Проблема в том, что getline
в конце строки остается '\ r'.
ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
// BUT, there might be an '\r' at the end now.
}
Изменить Спасибо Нилу за то, что он указал, что f.good()
это не то, что я хотел. !f.fail()
это то, что я хочу.
Я могу удалить его вручную (см. Редактирование этого вопроса), что легко для текстовых файлов Windows. Но меня беспокоит, что кто-то загрузит файл, содержащий только '\ r'. В этом случае я предполагаю, что getline будет использовать весь файл, думая, что это одна строка!
.. и это даже без Юникода :-)
.. может быть, у Boost есть хороший способ использовать по одной строке из любого типа текстового файла?
Редактировать Я использую это для обработки файлов Windows, но я все еще чувствую, что не должен! И это не будет развиваться для файлов, содержащих только '\ r'.
if(!line.empty() && *line.rbegin() == '\r') {
line.erase( line.length()-1, 1);
}
Ответы:
Как указал Нил, «среда выполнения C ++ должна правильно работать с любым соглашением о завершении строки для вашей конкретной платформы».
Однако люди перемещают текстовые файлы между разными платформами, так что этого недостаточно. Вот функция, которая обрабатывает все три окончания строк («\ r», «\ n» и «\ r \ n»):
std::istream& safeGetline(std::istream& is, std::string& t) { t.clear(); // The characters in the stream are read one-by-one using a std::streambuf. // That is faster than reading them one-by-one using the std::istream. // Code that uses streambuf this way must be guarded by a sentry object. // The sentry object performs various tasks, // such as thread synchronization and updating the stream state. std::istream::sentry se(is, true); std::streambuf* sb = is.rdbuf(); for(;;) { int c = sb->sbumpc(); switch (c) { case '\n': return is; case '\r': if(sb->sgetc() == '\n') sb->sbumpc(); return is; case std::streambuf::traits_type::eof(): // Also handle the case when the last line has no line ending if(t.empty()) is.setstate(std::ios::eofbit); return is; default: t += (char)c; } } }
А вот и тестовая программа:
int main() { std::string path = ... // insert path to test file here std::ifstream ifs(path.c_str()); if(!ifs) { std::cout << "Failed to open the file." << std::endl; return EXIT_FAILURE; } int n = 0; std::string t; while(!safeGetline(ifs, t).eof()) ++n; std::cout << "The file contains " << n << " lines." << std::endl; return EXIT_SUCCESS; }
источник
t
он пуст перед установкой eofbit. Разве этот бит не следует устанавливать независимо от того, были ли прочитаны другие символы?std::get_line
которое игнорирует пустую последнюю строку. Я использовал следующий код в случае eof для имитацииstd::get_line
поведения:is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
Среда выполнения C ++ должна правильно работать с любым соглашением о конце строки для вашей конкретной платформы. В частности, этот код должен работать на всех платформах:
#include <string> #include <iostream> using namespace std; int main() { string line; while( getline( cin, line ) ) { cout << line << endl; } }
Конечно, если вы имеете дело с файлами с другой платформы, все ставки отключены.
Поскольку две наиболее распространенные платформы (Linux и Windows) обе завершают строки символом новой строки, а Windows предшествует ему возвратом каретки, вы можете проверить последний символ
line
строки в приведенном выше коде, чтобы увидеть, является ли он\r
и если да удалите его перед обработкой для конкретного приложения.Например, вы можете предоставить себе функцию стиля getline, которая выглядит примерно так (не проверено, использование индексов, substr и т. Д. Только в педагогических целях):
ostream & safegetline( ostream & os, string & line ) { string myline; if ( getline( os, myline ) ) { if ( myline.size() && myline[myline.size()-1] == '\r' ) { line = myline.substr( 0, myline.size() - 1 ); } else { line = myline; } } return os; }
источник
safegetline
- важная часть решения. Но если эта программа компилируется в Windows, нужно ли мне также открывать файл в двоичном формате? Компиляторы Windows (в текстовом режиме) позволяют '\ n' вести себя как '\ r' '\ n'?ifstream f("f.txt", ios_base :: binary | ios_base::in );
Вы читаете файл в двоичном или в текстовом режиме? В режиме ТЕКСТ пара возврат каретки / перевод строки, CRLF , интерпретируется как ТЕКСТ конца строки или символа конца строки, но в ДВОИЧНОМ вы получаете только ОДИН байт за раз, что означает, что любой символ ДОЛЖЕНигнорироваться и оставляться в буфере для извлечения как другого байта! Возврат каретки в пишущей машинке означает, что тележка пишущей машинки, в которой находится печатающий рычаг, достигла правого края бумаги и возвращается к левому краю. Это очень механическая модель механической пишущей машинки. Тогда перевод строки означает, что рулон бумаги немного повернут вверх, чтобы бумага оказалась в положении, чтобы начать новую строку набора. Насколько я помню, одна из младших цифр в ASCII означает переход на один символ вправо без ввода, мертвый символ и, конечно, \ b означает возврат: переместить автомобиль на один символ назад. Таким образом, вы можете добавлять специальные эффекты, такие как нижний (введите подчеркивание), зачеркивание (введите минус), приблизительные различные акценты, отмените (введите X), без необходимости использования расширенной клавиатуры, просто отрегулировав положение автомобиля вдоль линии перед вводом строки. Таким образом, вы можете использовать напряжение ASCII размером в байты для автоматического управления пишущей машинкой без компьютера. Когда появляется автоматическая пишущая машинка,АВТОМАТИЧЕСКИЙ означает, что как только вы дойдете до самого дальнего края бумаги, вагон вернется влево и будет применен перевод строки, то есть предполагается, что вагон возвращается автоматически по мере продвижения рулона вверх! Таким образом, вам не нужны оба управляющих символа, только один, \ n, новая строка или перевод строки.
Это не имеет ничего общего с программированием, но ASCII старше и ЭЙ! похоже, что некоторые люди не думали, когда начали писать тексты! Платформа UNIX предполагает электрическую автоматическую печатную машину; модель Windows более полная и позволяет управлять механическими машинами, хотя некоторые управляющие символы становятся все менее и менее полезными в компьютерах, например, колокольчик, 0x07, если я хорошо помню ... Некоторые забытые тексты, должно быть, изначально были записаны с помощью управляющих символов для пишущих машинок с электрическим управлением, и это увековечило модель ...
На самом деле правильным вариантом было бы просто включить \ r, перевод строки, без необходимости возврата каретки, то есть автоматический, следовательно:
char c; ifstream is; is.open("",ios::binary); ... is.getline(buffer, bufsize, '\r'); //ignore following \n or restore the buffer data if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c); ...
будет наиболее правильным способом обработки всех типов файлов. Однако обратите внимание, что \ n в режиме ТЕКСТ на самом деле является парой байтов 0x0d 0x0a, но 0x0d ЕСТЬ просто \ r: \ n включает \ r в режиме ТЕКСТ, но не в ДВОИЧНОМ , поэтому \ n и \ r \ n эквивалентны ... или должно быть. На самом деле это очень простая отраслевая путаница, типичная отраслевая инерция, поскольку принято говорить о CRLF на ВСЕХ платформах, а затем они попадают в различные двоичные интерпретации. Строго говоря, файлы, включающие ТОЛЬКО 0x0d (возврат каретки) как \ n (CRLF или перевод строки), искажаются в ТЕКСТЕ.режим (пишущая машинка: просто верните машину и зачеркните все ...) и представляют собой двоичный формат, не ориентированный на строку (либо \ r, либо \ r \ n, что означает строчную ориентацию), поэтому вы не должны читать как текст! Код должен выйти из строя, возможно, с каким-то пользовательским сообщением. Это зависит не только от ОС, но и от реализации библиотеки C, что усугубляет путаницу и возможные варианты ... (особенно для прозрачных слоев перевода UNICODE, добавляя еще одну точку артикуляции для сбивающих с толку вариантов).
Проблема с предыдущим фрагментом кода (механическая пишущая машинка) заключается в том, что он очень неэффективен, если после \ r (текста автоматической пишущей машинки) нет символов \ n. Затем он также принимает режим BINARY, в котором библиотека C вынуждена игнорировать текстовые интерпретации (языковой стандарт) и отдавать чистые байты. Не должно быть никакой разницы в фактических текстовых символах между обоими режимами, только в управляющих символах, поэтому, вообще говоря, чтение BINARY лучше, чем режим TEXT . Это решение эффективно для BINARYрежим типичных текстовых файлов ОС Windows независимо от вариантов библиотеки C и неэффективен для текстовых форматов других платформ (включая веб-переводы в текст). Если вы заботитесь об эффективности, лучше всего использовать указатель на функцию, протестировать элементы управления строкой \ r vs \ r \ n, как вам нравится, затем выбрать лучший пользовательский код getline в указателе и вызвать его из Это.
Между прочим, я помню, что нашел и несколько текстовых файлов \ r \ r \ n ... которые переводятся в двухстрочный текст, как это все еще требуется некоторым потребителям печатного текста.
источник
Одним из решений было бы сначала выполнить поиск и заменить все окончания строк на '\ n' - точно так же, как, например, Git по умолчанию.
источник
Вам не повезло, кроме написания собственного обработчика или использования внешней библиотеки. Проще всего проверить, чтобы
line[line.length() - 1]
это не \ r. В Linux это излишне, поскольку большинство строк оканчиваются символом '\ n', что означает, что вы потеряете немало времени, если это будет в цикле. В Windows это тоже лишнее. Однако как насчет классических файлов Mac, заканчивающихся на \ r? std :: getline не будет работать для этих файлов в Linux или Windows, потому что '\ n' и '\ r' '\ n' оканчиваются на '\ n', что устраняет необходимость проверять наличие '\ r'. Очевидно, что такая задача, которая работает с этими файлами, не будет работать. Конечно, существуют многочисленные системы EBCDIC, с чем большинство библиотек не решится взяться.Проверка на "\ r", вероятно, лучшее решение вашей проблемы. Чтение в двоичном режиме позволит вам проверить все три общих окончания строк ('\ r', '\ r \ n' и '\ n'). Если вас интересуют только Linux и Windows, поскольку окончания строк Mac в старом стиле не должны существовать надолго, проверьте наличие только '\ n' и удалите завершающий символ '\ r'.
источник
Если известно, сколько элементов / номеров в каждой строке, можно прочитать одну строку, например, с 4 цифрами как
string num; is >> num >> num >> num >> num;
Это также работает с окончаниями других строк.
источник