Чому це відбувається?
Це мало стосується вкладеного вами вкладу, а швидше до поведінки за замовчуванням std::getline()
. Коли ви надали вхід для імені ( std::cin >> name
), ви не тільки подали наступні символи, але й неявний новий рядок був доданий до потоку:
"John\n"
Новий рядок завжди додається до вашого входу, коли ви вибираєте Enterабо Returnподаєте з терміналу. Він також використовується у файлах для переходу до наступного рядка. Новий рядок залишається в буфері після вилучення в name
до наступної операції вводу / виводу, де вона або відкидається, або витрачається. Коли потік управління досягне std::getline()
, новий рядок буде відкинутий, але вхід припиниться негайно. Причина цього відбувається в тому, що функція за замовчуванням цієї функції диктує, що вона повинна (вона намагається прочитати рядок і зупиняється, коли знаходить новий рядок).
Оскільки цей провідний новий рядок гальмує очікувану функціональність вашої програми, випливає, що ми повинні якось пропустити наше ігнорування. Один із варіантів - дзвонити std::cin.ignore()
після першого вилучення. Він відкине наступний доступний символ, щоб новий рядок вже не був у дорозі.
std::getline(std::cin.ignore(), state)
Поглиблене пояснення:
Це перевантаження того, std::getline()
що ви викликали:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Ще одне перевантаження цієї функції сприймає роздільник типу charT
. Символ розмежувача - символ, який представляє межу між послідовностями введення. Ця конкретна перевантаження встановлює роздільник для символу нового рядка input.widen('\n')
за замовчуванням, оскільки його не надано.
Тепер це декілька умов, за якими std::getline()
припиняється введення даних:
- Якщо потік вилучив максимальну кількість символів, яку
std::basic_string<charT>
може містити а
- Якщо символ кінцевого файлу (EOF) знайдений
- Якщо роздільник був знайдений
Третя умова - це та, з якою ми маємо справу. Ваш внесок у state
представлений таким чином:
"John\nNew Hampshire"
^
|
next_pointer
де next_pointer
наступний символ для розбору Оскільки символ, що зберігається на наступній позиції в послідовності введення, є роздільником, std::getline()
він спокійно відкине цей символ, приріст next_pointer
до наступного наявного символу та зупинить введення. Це означає, що решта символів, які ви надали, все ще залишаються в буфері для наступної операції вводу / виводу. Ви помітите, що якщо ви виконаєте інше зчитування з рядка в state
, ваш видобуток дасть правильний результат, як останній дзвінок, щоб std::getline()
відмінити роздільник.
Можливо, ви помітили, що зазвичай ви не стикаєтеся з цією проблемою під час вилучення за допомогою форматованого оператора введення ( operator>>()
). Це пояснюється тим, що вхідні потоки використовують пробіли як роздільники для введення, а маніпулятор std::skipws
1 увімкнено за замовчуванням. Потоки відкидають провідний пробіл від потоку, коли починають виконувати форматовані введення. 2
На відміну від операторів форматованого вводу, std::getline()
це функція введення неформатоване введення. І всі неформатизовані функції введення мають дещо спільний наступний код:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Вищевказане - це дозорний об'єкт, який інстанціюється у всіх форматованих / неформатованих функціях вводу / виводу в стандартній реалізації C ++. Об'єкти Sentry використовуються для підготовки потоку для вводу-виводу та визначення того, знаходиться він у відмовному стані. Ви виявите лише те, що у неформатованих функціях введення другий аргумент конструктора дозорного є true
. Цей аргумент означає, що провідні пробіли не будуть відкинуті з початку послідовності введення. Ось відповідна цитата зі Стандарту [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Якщо noskipws
дорівнює нулю і не is.flags() & ios_base::skipws
дорівнює нулю, функція витягує та відкидає кожен символ до тих пір, поки наступним наявним символом введення c
є символ пробілу. [...]
Оскільки вищевказана умова є помилковою, об'єкт, що надійшов, не відкидає пробіл. Причина noskipws
, яку задає true
ця функція, полягає в тому, що суть std::getline()
полягає в тому, щоб прочитати в std::basic_string<charT>
об’єкт сирі, неформатовані символи .
Рішення:
Не можна зупинити цю поведінку std::getline()
. Що вам доведеться зробити, це відкинути новий рядок самостійно перед std::getline()
запуском (але це зробити після вилученого форматування). Це можна зробити, використовуючи, ignore()
щоб відкинути решту вхідних даних, поки ми не досягнемо нового нового рядка:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Вам потрібно буде включити <limits>
для використання std::numeric_limits
. std::basic_istream<...>::ignore()
це функція, яка відкидає задану кількість символів, поки вона не знайде роздільник або не досягне кінця потоку ( ignore()
також відкидає роздільник, якщо він знайде його). max()
Функція повертає найбільшу кількість символів , що потік може прийняти.
Ще один спосіб відкинути пробіл - це використовувати std::ws
функцію, яка є маніпулятором, призначеним для вилучення та вилучення провідних пробілів з початку вхідного потоку:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Яка різниця?
Різниця полягає в тому, що ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 нерозбірливо відкидає символи, поки він або не відкидає count
символи, не знайде роздільник (вказаний другим аргументом delim
) або не потрапить у кінець потоку. std::ws
використовується лише для викидання символів пробілів з початку потоку.
Якщо ви змішуєте відформатований вхід з неформатованим входом і вам потрібно відкинути залишковий пробіл, використовуйте std::ws
. В іншому випадку, якщо вам потрібно очистити недійсний вхід, незалежно від того, що це таке, використовуйте ignore()
. У нашому прикладі нам потрібно лише очистити пробіл, оскільки потік спожив ваш вхід "John"
для name
змінної. Залишилось лише символ нового рядка.
1: std::skipws
це маніпулятор, який повідомляє вхідному потоку відкидати провідну пробіл під час виконання форматованого введення. Це можна вимкнути за допомогою std::noskipws
маніпулятора.
2: Вхідні потоки за замовчуванням вважають пробіли певними символами, такі як пробіл, символ нового рядка, канал форми, повернення каретки тощо.
3: Це підпис о std::basic_istream<...>::ignore()
. Ви можете викликати його з нульовими аргументами, щоб відкинути один потік із потоку, один аргумент для відхилення певної кількості символів або два аргументи для відкидання count
символів або до тих пір, доки це не відбудеться delim
. Ви зазвичай використовуєте std::numeric_limits<std::streamsize>::max()
як значення, count
якщо ви не знаєте, скільки символів є перед роздільником, але ви хочете їх відкинути.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
також слід працювати, як очікувалося. (Окрім наведених нижче відповідей).