JavaRush /Курси /C++ SELF /Введення: std::cin >> та його обмеження

Введення: std::cin >> та його обмеження

C++ SELF
Рівень 2 , Лекція 3
Відкрита

1. Потік std::cin і читання токенів оператором >>

Коли програма лише друкує, вона схожа на радіо: каже щось у порожнечу й сподівається, що хтось слухає. Але щойно зʼявляється введення, програма стає схожою на діалог: ви даєте дані, а вона відповідає. У C++ введення зазвичай виконують через std::cin. Корисно уявляти cin як потік символів: у ньому міститься все, що користувач надрукував, включно з пробілами та символами нового рядка.

Найважливіше: оператор >> читає не «все підряд», а одну порцію даних за раз. І ця «порція» майже завжди обмежена пробільними символами.

std::cin і оператор >>: читаємо значення у змінну

Щоб щось прочитати, потрібна змінна, у яку це «щось» можна покласти. Логіка така: оголосили змінну, прочитали в неї дані й використали їх далі — наприклад, вивели на екран.

Мінімальний приклад: читаємо одне ціле число.

#include <iostream>

int main() {
    int age = 0;

    std::cin >> age;
    std::cout << "Age = " << age << '\n'; // якщо введено 20, буде: Age = 20
}

Тут важливо звернути увагу на дві речі. По‑перше, std::cin >> age; намагається прочитати наступне значення з вхідного потоку й записати його в age. По‑друге, тип змінної має значення: оскільки age — це int, програма очікує побачити у вхідному потоці число, наприклад 20, -5 або 0.

Що саме читає >>: токен і пробільні розділювачі

Зараз прозвучить слово «токен», але не лякайтеся: це просто шматочок тексту без пробілів. Оператор >> працює за таким принципом: «Пропусти пробіли, а потім прочитай поспіль символи до наступного пробілу». Пробільним символом при цьому вважається не лише ' ' — звичайний пробіл, а й символ нового рядка '\n' та табуляція '\t'.

Уявіть, що користувач увів такий рядок. Символ — це Enter, тобто '\n':

12   34 

Між 12 і 34 тут три пробіли. Для >> це не має значення: він бачить «число», потім «пробіли», а далі — ще одне «число».

#include <iostream>

int main() {
    int a = 0;
    int b = 0;

    std::cin >> a >> b;
    std::cout << "a=" << a << ", b=" << b << '\n'; // якщо введено "12   34", буде: a=12, b=34
}

Зверніть увагу: std::cin >> a >> b; — це два послідовні читання, просто записані в одному рядку. Спочатку читається a, потім — b.

Розділювачі: пробіл, Enter і табуляція однаково «ріжуть» токени

Іноді новачкам здається, що Enter «сильніший» за пробіл або що пробіл тут «просто для краси». Для operator>> усе простіше: будь-який пробільний символ — це межа токена, і крапка.

Уявімо таке введення, де кожне слово стоїть з нового рядка:

Ivan
Ivanov

Або таке, де обидва слова стоять в одному рядку:

Ivan Ivanov

Для коду нижче результат буде однаковим, тому що і пробіл, і Enter — це розділювачі токенів.

#include <iostream>
#include <string>

int main() {
    std::string first;
    std::string last;

    std::cin >> first >> last;
    std::cout << first << " | " << last << '\n'; // Ivan | Ivanov
}

Це добра новина: дані можна вводити як зручно — в одному рядку або в кількох. Але саме через це й виникають обмеження, про які ми поговоримо далі.

Мінісхема: як >> «ріже» введення на токени

Щоб усе це перестало здаватися магією, корисно хоча б раз побачити просту модель. Нехай користувач увів ось так:

23   Ada   Lovelace

Тоді потік символів можна подумки розділити на токени:

flowchart LR
    A["23"] --> B["Ada"] --> C["Lovelace"]

А пробіли та символи нового рядка — це лише розділювачі, які між токенами просто пропускаються.

Практичний висновок дуже простий: якщо ви читаєте int, а потім std::string, то int «зʼїсть» перший токен, а std::string — наступний. Жодних домовленостей на кшталт «вводьте з нового рядка» на рівні >> не існує: він однаково ділить введення за пробілами.

2. Практика: мініанкета й рядки без пробілів

Практичний приклад: робимо мініанкету

Відтепер працюватимемо з тим самим мініпроєктом: консольною анкетою. Він простий, зате на ньому зручно поступово нарощувати навички: сьогодні читаємо токени, а трохи пізніше навчимося читати цілі рядки й акуратно обробляти введення.

Версія 1: читаємо вік та імʼя як одне слово.

#include <iostream>
#include <string>

int main() {
    int age = 0;
    std::string name;

    std::cin >> age >> name;

    std::cout << "User: " << name << '\n';
    std::cout << "Age: " << age << '\n';
}

Якщо ввести:

20 Ivan

то програма виведе:

User: Ivan
Age: 20

Можна ввести й так — через Enter:

20
Ivan

З погляду >> це одне й те саме: токени все одно читаються послідовно.

Введення рядків через >>: читається слово, а не фраза

Тепер ми підійшли до типового сюрпризу: std::cin >> s для std::string читає лише одне слово, тобто токен до пробілу, Enter або табуляції. Якщо ви введете кілька слів, вони не зникнуть — вони просто залишаться у вхідному потоці й будуть прочитані наступними операціями >>.

Покажемо це на нашій анкеті: прочитаємо «місто» як рядок.

#include <iostream>
#include <string>

int main() {
    std::string city;

    std::cin >> city;
    std::cout << "City=[" << city << "]\n";
}

Якщо користувач введе:

New York

то city стане "New", а слово "York" залишиться у вхідному потоці як наступний токен. Програма виведе:

City=[New]

Це не помилка й не «кривий C++». Це звичайна логіка оператора >>: він читає один токен.

Показовий експеримент: «втрачене» слово насправді не втрачене

Слово, яке не потрапило в першу рядкову змінну, зазвичай не зникає. Воно просто чекає своєї черги. Покажімо це чесно: прочитаємо два слова у дві змінні.

#include <iostream>
#include <string>

int main() {
    std::string w1;
    std::string w2;

    std::cin >> w1 >> w2;

    std::cout << "w1=[" << w1 << "], w2=[" << w2 << "]\n";
}

Якщо ввести:

New York

отримаємо:

w1=[New], w2=[York]

Тобто >> чудово підходить для форматів, де вводяться окремі слова: імʼя, прізвище, команда, логін. Але для полів на кшталт «адреса» або «назва книги» він незручний, тому що там майже завжди є пробіли.

Памʼятка: що вважається розділювачем для >>

Коли ви почнете писати невеликі програми, то часто ловитимете себе на думці: «Чому воно зупинилося саме тут?». Зазвичай відповідь є в таблиці нижче:

Символ у введенні Приклад Для >> це… Що відбувається
пробіл ' '
Ivan Ivanov
розділювач рядок через >> прочитає лише Ivan
символ нового рядка '\n' (Enter)
IvanIvanov
розділювач поведінка така сама, як і з пробілом
табуляція '\t'
Ivan\tIvanov
розділювач токени теж розділяються

Висновок простий: >> читає «до найближчого пробільного символу».

Як вибрати формат введення для std::cin >>

Коли ви обираєте std::cin >>, то фактично обираєте і формат: «користувач вводить значення окремими токенами». Це ідеально підходить для завдань, де вхідні дані мають вигляд «два числа», «три числа», «слово і число», «імʼя та прізвище», «команда і параметр».

Наприклад, наша анкета добре поєднується з таким підходом, якщо ми заздалегідь домовилися, що імʼя та прізвище вводяться окремо, а не одним рядком. Тоді застосунок можна акуратно розширити: читаємо імʼя, прізвище й вік.

#include <iostream>
#include <string>

int main() {
    std::string first;
    std::string last;
    int age = 0;

    std::cin >> first >> last >> age;

    std::cout << "Profile: " << first << " " << last << '\n';
    std::cout << "Age: " << age << '\n';
}

Якщо ввести:

Ada Lovelace 36

то все чудово зчитається: три токени → три змінні.

Мікронюанс: >> не «зʼїдає» пробіли як дані

Коли ми кажемо «пробіл — розділювач», важливо розуміти таке: пробіли не потрапляють у рядок, який читає >>. Він читає лише значущі символи токена. Тому std::string word; std::cin >> word; ніколи не прочитає рядок "Hello world" повністю: пробіл як символ усередині токена неможливий за самим визначенням токена.

Для порівняння: якби ви хотіли читати весь рядок цілком, включно з пробілами, потрібен був би інший інструмент. Але тут ми свідомо не забігаємо наперед: поки що наше завдання — впевнено користуватися >> і не чекати від нього того, чого він не обіцяв.

3. Обмеження >>: невідповідність типу й безпечне читання тексту

Коли тип важливіший за ваші бажання: читання може «зламатися»

Друга велика група обмежень повʼязана з невідповідністю типів. Якщо ви читаєте int, а користувач вводить слово, програма не зможе коректно перетворити «котик» на число.

Покажемо сценарій, який виглядає цілком правдоподібно: людина переплутала й увела вік словами.

#include <iostream>
#include <string>

int main() {
    int age = 0;
    std::string name;

    std::cin >> age >> name;

    std::cout << "age=" << age << ", name=" << name << '\n';
}

Якщо ввести:

twenty Ivan

то програма вже не зможе коректно прочитати age як число. Ми поки що не вміємо красиво обробляти таке введення — це буде окрема тема пізніше в курсі. Тому зараз правило просте й чесне: коли ви розвʼязуєте завдання, де формат введення задано, припускається, що користувач вводить дані правильно.

Чому ми читаємо рядки в std::string, а не в char[]

Іноді в старих прикладах із C/C++ можна побачити щось на кшталт char name[20]; std::cin >> name;. Це працює, але небезпечно: якщо користувач введе надто довге слово, можна отримати серйозні проблеми, аж до переповнення буфера.

Ми поки що не вивчали масиви й вказівники, тому тримаймося простого практичного правила: для тексту використовуйте std::string. Вона значно безпечніша як контейнер для тексту.

4. Типові помилки під час роботи з std::cin >>

Помилка № 1: очікувати, що std::cin >> s; прочитає цілу фразу.
Це одна з найчастіших пасток. Новачок вводить New York, а у змінній опиняється лише New. Причина проста: >> читає токен до пробільного розділювача. Якщо за змістом вам потрібен рядок із пробілами, то такий формат введення для >> не підходить.

Помилка № 2: не оголосити змінну до читання.
Іноді хочеться написати щось на кшталт «прочитай вік» без змінної, але std::cin не вміє читати «в нікуди». Спочатку потрібно оголосити змінну потрібного типу — int age = 0;, — а вже потім виконувати std::cin >> age;. Це не формальність: змінна — це місце в памʼяті, куди потрапить результат.

Помилка № 3: переплутати порядок читання й порядок даних.
Якщо програма виконує std::cin >> age >> name;, а користувач вводить Ivan 20, то age спробує стати Ivan, а це не число, і читання піде не за планом. У >> немає телепатії: він читає токени строго за порядком і намагається покласти їх у змінні вказаного типу.

Помилка № 4: думати, що Enter «закінчує введення для програми».
Enter лише додає символ '\n' у вхідний потік. Для >> це такий самий пробільний розділювач, як і звичайний пробіл. Тому «введення з нового рядка» і «введення через пробіл» найчастіше еквівалентні. Це зручно, але іноді ламає очікування, якщо ви подумки будували введення суворо по рядках.

Помилка № 5: вводити не той тип і дивуватися результату.
Якщо очікується int, то введення на кшталт ten не може бути коректно перетворене на число оператором >>. На цьому етапі курсу ми ще не вчимося красиво обробляти таке введення, тому найкраще рішення для навчальних завдань — уважно стежити за форматом даних і вводити значення саме в тому вигляді, у якому їх очікує програма.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ