ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

Оператор извлечения из потока и std::getline

В языке программирования C++ в консольной программе с помощью оператора извлечения >> из потока std::wcin получаем данные от пользователя:

https://ru.wikipedia.org/wiki/Iostream

С помощью этого оператора можно извлечь символьную строку в переменную подходящего типа. Если в строке, полученной от пользователя, есть пробелы, то в переменную попадет только часть строки до первого пробела. Поэтому для извлечения из потока строки с пробелами (до символа новой строки L'\n') учебники рекомендуют использовать функцию std::getline. Это азы.

https://en.cppreference.com/w/cpp/string/basic_string/getline
https://ru.cppreference.com/w/cpp/string/basic_string/getline (по-русски)

А вот дальше начинается интересное.

Сравнивая описания этой функции по этим ссылкам, замечаем, что в русской версии почему-то отсутствует очень интересный и важный раздел «Notes». В нем отмечается, что, применяя ввод, использующий в качестве разделителей пробельные символы (пробелы, символы табуляции, символ новой строки и т. п.) (пример такого ввода: int n; std::wcin >> n;), нужно помнить, что пробельный символ-разделитель, до которого извлекаются данные, останется после операции извлечения в потоке ввода. Если после этой операции извлечения мы переключимся на применение функции std::getline для извлечения из потока строк, то первая извлеченная строка будет тем самым оставшимся от предыдущего извлечения пробельным символом. В большинстве случаев такое поведение программы является нежелательным.

Например, следующий фрагмент кода технически отработает без ошибок:
int n; wcin >> n;
wstring line; getline(wcin, line);
Однако, программа с таким кодом не даст возможности пользователю ввести строку для переменной line, так как функция std::getline извлечет из потока std::wcin символ новой строки L'\n', оставшийся там после операции извлечения числа n, сочтет этот символ новой строки данными от пользователя и завершит работу, ничего не поместив в переменную line.

Для исправления этой ситуации в вышеупомянутом разделе «Notes» рекомендуют три способа. 1) Сначала дополнительно вызвать функцию std::getline для извлечения оставшегося от предыдущей операции символа новой строки, а потом уже вызывать эту функцию для получения от пользователя, собственно, запрашиваемой строки:
int n; wcin >> n;
wstring line; getline(wcin, line); getline(wcin, line);

2) Удаление ведущих пробельных символов из потока ввода с помощью функции (манипулятора потока) std::ws (https://en.cppreference.com/w/cpp/io/manip/ws):
int n; wcin >> n;
wstring line; getline(ws(wcin), line);
Тут нужно иметь в виду, что если пользователь введет в начале запрашиваемой строки пробелы, то они тоже будут удалены. Не всегда это удобно.

3) Удаление всех ненужных символов из потока ввода с помощью метода ignore потока ввода (https://en.cppreference.com/w/cpp/io/basic_istream/ignore):
int n; wcin >> n;
wcin.ignore(numeric_limits<streamsize>::max(), L'\n');
wstring line; getline(wcin, line);

* * *

Рассмотрим ситуацию посложнее. Предположим, есть класс, у которого имеются строковое поле line, числовое поле n и метод для получения данных от пользователя getdata, который сначала запрашивает у пользователя строку с помощью функции std::getline, а затем запрашивает число с помощью операции извлечения из потока ввода.

Если программист, использующий этот класс, напишет консольную программу и создаст в ней один объект этого класса, запросив у пользователя данные через метод getdata объекта, то проблем не будет.

Если же программист создаст в программе несколько объектов этого класса и станет вызывать методы этих объектов один за другим, то для первого объекта всё пройдет правильно, а для следующих объектов получится накладка, так как возникнет именно нежелательная ситуация, которая рассматривалась выше: функция std::getline из второго объекта будет вызвана после операции извлечения числового значения из потока ввода для первого объекта.

Автор описываемого класса, по идее, должен написать код метода getdata так, чтобы этих накладок не возникло. То есть метод getdata должен срабатывать и для случая, когда перед вызовом функции std::getline было извлечение из потока ввода числа с помощью оператора извлечения >>, и в случае, когда такого извлечения не было.

Из описанных трех способов решения проблемы в данном случае сработает только второй, с помощью функции std::ws. Пример кода метода getdata:
getline(ws(wcin), line);
wcin >> n;

Но есть еще такой вариант:
if (wcin.peek() == L'\n') wcin.ignore();
getline(wcin, line);
wcin >> n;
То есть с помощью метода peek (https://en.cppreference.com/w/cpp/io/basic_istream/peek) потока ввода мы определяем, есть ли в потоке ввода ненужный символ новой строки. И если есть, то удаляем его. В этом варианте ведущие пробелы во введенной пользователем строке сохраняются (в некоторых случаях это важно).
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments