1. Літерали та їхній тип: значення прямо в коді
Коли ви пишете програму, значення в ній майже завжди походять із двох джерел: ті, що надходять із зовнішнього світу, наприклад введення користувача, і ті, що ви буквально записуєте в коді. Оце друге і є літералами.
Літерал — це значення, записане безпосередньо у вихідному коді програми: число, символ або рядок. Наприклад, 10, 3.14, 'A', "Hello". І головна думка цієї лекції така: літерал — це не просто «42», а «42 певного типу». Саме від цього типу залежить, як працюватимуть вирази й перетворення.
Невеликий приклад, щоб відчути цю ідею: тип важливий, навіть якщо ви його не бачите.
#include <iostream>
int main() {
std::cout << (5 / 2) << '\n'; // 2
std::cout << (5 / 2.0) << '\n'; // 2.5
}
Тут змінюється лише один символ (.0), але зміст обчислення вже інший. Саме про це йдеться, коли ми говоримо про літерали.
2. Числові літерали: без суфіксів і з суфіксами
Числа без суфіксів: що компілятор «має на увазі»
Якщо ви пишете 42, компілятор намагається вибрати «найприродніший» цілий тип. На базовому рівні для наших поточних завдань корисно памʼятати просту модель:
- ціле число без крапки зазвичай сприймається як int;
- число з крапкою зазвичай сприймається як double.
Це не повна картина стандарту, але для впевненого старту — чудова робоча схема.
Дійсний літерал за замовчуванням — це double, тому 3.14 зазвичай сприймається саме так. Цілий літерал 42 найчастіше поводиться як int. Якщо ви хочете явно керувати типом, можете або додати суфікс, або виконати приведення через static_cast.
Перевірмо, «хто є хто», на простому прикладі виведення, без typeid, щоб поки не забігати наперед:
#include <iostream>
int main() {
int a = 42; // 42 — ціле, добре лягає в int
double b = 3.14; // 3.14 — дійсне, за замовчуванням double
std::cout << a << '\n'; // 42
std::cout << b << '\n'; // 3.14
}
Поки що все просто. Але щойно зʼявляються межі діапазонів або змішування signed/unsigned (це буде окремою темою пізніше), стає важливо вміти «підказати» компілятору, що саме ви маєте на увазі.
Суфікси для цілих: u і ll
Суфікс у літерала — це як наліпка «обережно: крихке» на коробці. Вміст той самий, але оточення — компілятор і читач коду — починає поводитися уважніше. Суфікси допомагають уникнути несподіванок і зайвих неявних перетворень. У цій лекції нас цікавлять два найуживаніші: u (unsigned) і ll (long long).
42u: літерал як unsigned
Коли ви пишете 42u, ви прямо кажете: «це беззнакове число». Найпростіший практичний сенс тут такий: ви підкреслюєте, що значення не буває відʼємним. Часто так записують лічильники, кількості або розміри. Але важливо памʼятати: беззнаковість — це не «число безпечніше», а «число влаштоване інакше».
#include <iostream>
int main() {
unsigned attempts = 3u;
std::cout << attempts << '\n'; // 3
}
42ll: літерал як long long
Суфікс ll корисний, коли ви точно розумієте, що значення може бути великим, і хочете одразу це показати. Наприклад, якщо ви працюєте з грошима в копійках або центах чи з великим лічильником.
#include <iostream>
int main() {
long long big = 42ll;
std::cout << big << '\n'; // 42
}
Міні-таблиця: що сьогодні для нас найважливіше:
| Запис | Приклад | Ідея |
|---|---|---|
| цілий літерал | |
«зазвичай int» (у межах нашої моделі) |
| unsigned-літерал | |
«точно без мінуса» |
| long long-літерал | |
«хочу великий цілий тип» |
| дійсний літерал | |
за замовчуванням double |
Дійсні літерали: пастка «я ж поклав у double!»
Дійсні літерали виглядають дружньо: є крапка — отже, це «дріб». І справді, 3.14 — це double. Але пастка зазвичай не в тому, який тип має сам літерал, а в тому, як він бере участь у виразах і перетвореннях.
Класичний приклад:
#include <iostream>
int main() {
int a = 5;
int b = 2;
double r = a / b;
std::cout << r << '\n'; // 2
}
Чому 2, а не 2.5? Тому що a / b обчислюється як int / int, тобто цілочисельно. І лише після цього результат (2) перетворюється на double.
Виправлення просте: зробити принаймні один операнд дійсним літералом.
#include <iostream>
int main() {
int a = 5;
int b = 2;
double r = a / 2.0;
std::cout << r << '\n'; // 2.5
}
Так, 2.0 — це теж літерал, і саме він змінює тип обчислення.
3. Символ і рядок: 'A' проти "A"
Одна з найтиповіших несподіванок для початківців: чому 'A' і "A" виглядають майже однаково, але поводяться по-різному? Тому що це принципово різні речі.
- Символьний літерал 'A' — це один символ (тип char).
- Рядковий літерал "A" — це рядок (на практиці — масив символів, але на початку достатньо сприймати це просто як «рядок»).
Різниця особливо помітна, коли ви намагаєтеся зберегти значення у змінних відповідного типу:
#include <iostream>
#include <string>
int main() {
char c = 'A';
std::string s = "A";
std::cout << c << '\n'; // A
std::cout << s << '\n'; // A
}
Зовні все однаково, але за суттю — ні. Символ — це одна «цеглинка» тексту. Рядок — це вже «слово або фраза», навіть якщо там лише одна така цеглинка.
Практична порада: якщо ви зберігаєте одну літеру — наприклад, роздільник, знак операції або код відповіді, — беріть char і пишіть '...'. Якщо ж ви зберігаєте текст, хай навіть короткий, беріть std::string і пишіть "...".
4. Escape-послідовності: як записувати «незручні» символи
Іноді потрібно записати символ, який незручно або неможливо просто «намалювати» в коді: переведення рядка, табуляцію, лапки всередині лапок або зворотний слеш. Для цього й існують escape-послідовності: записи на кшталт '\n', "\t", "\"" тощо.
\n і \t у виведенні
Почнімо з найпотрібніших у консольних програмах: \n і \t.
#include <iostream>
int main() {
std::cout << "A\tB\tC\n"; // A B C
std::cout << "Line1\nLine2\n";
// Line1
// Line2
}
Тут \t — це табуляція, зручна для колонок, а \n — переведення рядка.
Важливо побачити головну думку: escape-послідовності працюють і в символьних, і в рядкових літералах, але результат має різний тип.
- '\n' — це один символ (char).
- "\n" — це рядок, хоча візуально й здається «одним символом».
Тому, якщо функція або змінна очікує char, передавайте '\n'. Якщо очікує рядок — "\n".
Лапки та зворотний слеш: \", \', \\
Вивести лапку всередині рядка — класичний міні-квест. Рядок починається і закінчується "...", отже внутрішню лапку треба екранувати: \". Із зворотним слешем так само: він сам є «службовим», тому, щоб отримати символ \, потрібно написати \\.
Практичні приклади:
#include <iostream>
int main() {
std::cout << "He said: \"Hi\".\n"; // He said: "Hi".
std::cout << "Path: C:\\temp\\file.txt\n"; // Path: C:\temp\file.txt
}
Тепер — те саме на рівні char, коли вам потрібен один символ:
#include <iostream>
int main() {
char quote = '\"';
char slash = '\\';
std::cout << quote << '\n'; // "
std::cout << slash << '\n'; // \
}
Зверніть увагу: '\'' — одинарна лапка як символ — теж існує. Але на перших кроках найчастіше вам потрібна саме подвійна лапка в рядку (\") або слеш (\\).
5. Практика: міні-застосунок MiniCheck
Зберімо невелику консольну програму «MiniCheck»: вона запитує ціну та кількість, обчислює підсумок і виводить «чек». Це хороший полігон для літералів: тут є числа, символи-розділювачі, рядки з лапками й табуляцією, а також константи.
Заголовок і охайне виведення
Зверніть увагу на '\n' і '\t':
#include <iostream>
#include <string>
int main() {
const std::string shop_name = "MiniCheck";
std::cout << "=== " << shop_name << " ===\n"; // === MiniCheck ===
std::cout << "item\tprice\tqty\n"; // item price qty
}
Введення й розрахунок у цілих типах
Зберігатимемо ціну в «центах», щоб поки що не працювати з дробами (про точність double буде окрема тема курсу). Для «центів» підійде цілий тип, а щоб одразу показати, що числа можуть бути великими, використаємо long long і суфікс ll для нульової ініціалізації. Для кількості — unsigned і суфікс u.
#include <iostream>
#include <string>
int main() {
std::string item;
long long price_cents = 0ll;
unsigned qty = 0u;
std::cout << "Item name: ";
std::cin >> item;
std::cout << "Price (cents): ";
std::cin >> price_cents;
std::cout << "Quantity: ";
std::cin >> qty;
long long total = price_cents * static_cast<long long>(qty);
std::cout << "Total cents: " << total << '\n';
}
Тут ми зробили дві важливі речі:
- По-перше, показали суфікси 0ll і 0u як «підказку щодо типу».
- По-друге, використали static_cast<long long>(qty), щоб множення точно відбувалося у великому цілочисельному типі: ми явно показуємо свій намір.
Лапки навколо тексту: \" у реальному завданні
Додамо охайний рядок із лапками навколо назви товару:
#include <iostream>
#include <string>
int main() {
std::string item;
long long price_cents = 0ll;
unsigned qty = 0u;
std::cin >> item >> price_cents >> qty;
std::cout << "You bought: \"" << item << "\"\n"; // You bought: "cola"
}
\" тут — escape-послідовність, яка дає змогу зробити лапки частиною виведеного тексту, а не завершити рядковий літерал.
Як компілятор обробляє escape в рядку
Коли ви пишете "A\nB", у вихідному коді це виглядає як символи A, \, n, B. Але компілятор перетворює \n на один спеціальний символ — переведення рядка. Це корисно уявляти, щоб не дивуватися, чому довжина рядка не дорівнює кількості видимих символів у коді.
flowchart LR
A["Вихідний код: \"A\\nB\""] --> B["Компілятор розпізнає escape-послідовність"]
B --> C["У памʼяті: 'A', потім символ нового рядка, потім 'B'"]
C --> D["Під час виведення: A (новий рядок) B"]
Та сама ідея працює для \\, \", \t та інших послідовностей.
6. Типові помилки
Помилка № 1: плутанина між 'A' і "A".
Початківці часто сприймають обидва варіанти просто як «літеру A», але в C++ це різні типи й різні сутності. 'A' — це один символ char, а "A" — рядок. Якщо ви намагаєтеся зберігати одну літеру в std::string, це не критична помилка. Але якщо ви намагаєтеся зберегти рядок у char, програма або не скомпілюється, або почне поводитися дивно.
Помилка № 2: плутанина між '\n' і "\n".
Обидва варіанти повʼязані з переведенням рядка, але перший — це один символ, а другий — рядок. У виведенні через std::cout обидва зазвичай працюють, але щойно ви почнете передавати значення у функції або складати рядки, різниця стане принциповою. Хороша звичка тут проста: для одиночних символів використовуйте одинарні лапки, а для тексту — подвійні.
Помилка № 3: забули екранувати лапку або зворотний слеш.
Рядок "He said: "Hi"" не працює, тому що внутрішня лапка завершує рядковий літерал завчасно. Потрібно писати \". Із зворотним слешем так само: щоб вивести \, пишіть \\.
Помилка № 4: використання суфікса «навмання», аби компілятор перестав скаржитися.
Іноді хочеться поставити u або ll просто тому, що «так спрацювало». Це погана стратегія: суфікс — частина змісту. Якщо ви пишете u, то справді погоджуєтеся на беззнакову арифметику. Якщо пишете ll, то заявляєте, що хочете працювати в long long. Спочатку поясніть це собі словами: «чи може бути мінус?», «чи може число бути великим?». І лише потім закріплюйте це суфіксом.
Помилка № 5: очікування, що 3.14 — це «просто число», а не double.
У виразах тип літерала впливає на тип обчислень. Якщо ви випадково залишили всі операнди цілими, то a / b буде цілочисельним, навіть якщо результат ви кладете в double. Перевіряйте, чи є у формулі хоча б один дійсний літерал, наприклад 2.0, або усвідомлене приведення типу.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ