JavaRush /Курси /C++ SELF /Літерали та суфікси в C++: числа, символи й escape

Літерали та суфікси в C++: числа, символи й escape

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

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
}

Міні-таблиця: що сьогодні для нас найважливіше:

Запис Приклад Ідея
цілий літерал
42
«зазвичай int» (у межах нашої моделі)
unsigned-літерал
42u
«точно без мінуса»
long long-літерал
42ll
«хочу великий цілий тип»
дійсний літерал
3.14
за замовчуванням 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, або усвідомлене приведення типу.

1
Опитування
Ініціалізація, const/constexpr, рівень 7, лекція 5
Недоступний
Ініціалізація, const/constexpr
Ініціалізація, const/constexpr
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ