План навчання: Курс C++ — Продовження (Статті 29–60+)
План навчання: Курс C++ — Продовження (Статті 29–60+)
Цей документ є академічним планом розвитку навчального курсу C++ на платформі
kostyl.dev. Він описує логічну структуру, тематичний зміст та педагогічні вимоги до кожної статті, яка має бути розроблена після існуючих 28 статей (до теми аргументів командного рядка включно). Стиль викладу — академічний, текстоцентричний («text-first»), з активним використанням компонентів Docus для інтерактивності.
Стан курсу на момент планування
Завершені статті (01–28)
| № | Файл | Тема |
|---|---|---|
| 01 | 01.intro-algorithms.md | Вступ до алгоритмів та програмування |
| 02 | 02.code-style.md | Стиль коду та оформлення |
| 03 | 03.ide-setup.md | Налаштування IDE (CLion / VS Code) |
| 04 | 04.data-output.md | Виведення даних (std::cout) |
| 05 | 05.data-types-variables.md | Типи даних та змінні |
| 06 | 06.data-input.md | Введення даних (std::cin) |
| 07 | 07.operators-type-conversion.md | Оператори та перетворення типів |
| 08 | 08.loops.md | Цикли (for, while, do-while) |
| 09 | 09.arrays.md | Масиви |
| 10 | 10.sorting.md | Алгоритми сортування |
| 11 | 11.searching.md | Алгоритми пошуку |
| 12 | 12.functions-basics.md | Функції: основи |
| 13 | 13.functions-scope.md | Функції: область видимості |
| 14 | 14.functions-overloading-templates.md | Перевантаження та шаблони функцій |
| 15 | 15.pointers-basics.md | Вказівники: основи |
| 16 | 16.references.md | Посилання (references) |
| 17 | 17.pointers-const-arrays.md | Вказівники, const та масиви |
| 18 | 18.pointer-arithmetic.md | Арифметика вказівників |
| 19 | 19.dynamic-memory.md | Динамічна пам'ять (new/delete) |
| 20 | 20.void-pointers.md | Узагальнені вказівники (void*) |
| 21 | 21.pointers-to-pointers.md | Вказівники на вказівники |
| 22 | 22.member-access-operator.md | Оператор доступу до члена (->) |
| 23 | 23.foreach-loop.md | Цикл for на основі діапазону |
| 24 | 24.function-pointers.md | Вказівники на функції |
| 25 | 25.lambdas.md | Лямбда-вирази |
| 26 | 26.lambda-captures.md | Захоплення у лямбда-виразах |
| 27 | 27.ellipsis.md | Еліпсис (...) та варіадичні функції |
| 28 | 28.command-line-arguments.md | Аргументи командного рядка |
Загальна структура нових модулів
Курс продовжується трьома великими блоками, що логічно виростають із вивченого:
Модуль A: Користувацькі типи даних
└─ enum, enum class, typedef/using, struct, union
Модуль B: Об'єктно-орієнтоване програмування (ООП)
└─ Класи, конструктори, деструктори, інкапсуляція,
спадкування, поліморфізм, абстрактні класи
Модуль C: Стандартна бібліотека та сучасний C++
└─ std::string, std::vector, std::array, std::map,
std::optional, smart pointers, move semantics,
шаблони класів, обробка виключень
Модуль A: Користувацькі типи даних (Статті 29–33)
Логіка модуля
Після опанування функцій, вказівників і лямбда-виразів студент може будувати
складні алгоритми, але йому бракує засобів для семантично точного іменування
станів і концепцій програми. Цей модуль заповнює прогалину: enum дає назви
станам, struct — групує пов'язані дані, а union забезпечує роботу з
варіантними даними за умов жорстких обмежень пам'яті.
Стаття 29 — Перерахування (enum)
Файл: 29.enum.mdОрієнтовний обсяг: 2 500–3 500 слів
Залежності: 05.data-types-variables.md, 07.operators-type-conversion.md
Навчальні цілі
- Пояснити мотивацію появи перерахувань: проблема «магічних чисел».
- Навчити оголошувати та використовувати
enum. - Показати внутрішнє представлення (
int), явне присвоєння значень. - Пояснити обмеження «незахищених» (
unscoped) перерахувань. - Показати практичні патерни: коди помилок, стани стейт-машини.
Структура статті
- Вступ і Hook: Сценарій функції
readFile(), що повертає-1,-2,-3. Читач бачить код із «магічними числами» і питає: «Що означає-2?» Відповідь — перерахування. - Фундаментальні концепції
- Визначення: що таке перелічуваний тип (enumerated type).
- Синтаксис
enum, ключове слово, тіло, крапка з комою. - Поняття «енумератор» (enumerator) — іменована константа всередині enum.
- Механіка та внутрішнє представлення
- Автоматичне присвоєння цілочисельних значень (з 0).
- Явне присвоєння значень, від'ємні значення, повторювані значення.
- Компілятор не перешкоджає дублюванню — чому це небезпечно.
- Розмір enum в пам'яті (
sizeof); зв'язок ізint. - Компонент
::debugger-viewдля візуалізації значень. - Компонент
::memory-viewдля демонстрації байтів.
- Неявні перетворення та обробка
- Неявна конвертація enum → int (дозволена).
- Заборона int → enum без
static_cast. - Заборона введення через
std::cinбез tricks. - Виведення
std::cout— числовий результат, функціяprintEnum.
- Простір імен перерахувань (проблема)
- Енумератори потрапляють у навколишній простір імен.
- Конфлікт імен між двома enum в одному файлі.
- Конвенція іменування
PREFIX_VALUEяк частковий обхідний шлях.
- Практичні патерни
- Коди повернення функцій замість магічних чисел.
- Стани кінцевого автомата (стейт-машина гри).
- Напрямки руху, сторони карти, режими роботи.
- Компонент
::terminal-previewдля демонстрації роботи програм.
- Практика та резюме
- Рівень 1: Оголосити enum
Directionз чотирма сторонами; вивести їх значення. - Рівень 1: Виправити код із магічними числами за допомогою enum.
- Рівень 2: Написати функцію
getStatusMessage(ParseResult), що повертає рядок. - Рівень 2: Реалізувати простий стейт-автомат гравця (стоїть, іде, біжить).
- Рівень 3: Реалізувати систему статусів HTTP (200, 404, 500) через enum із функцією виводу та обробки помилок.
- Рівень 1: Оголосити enum
Стаття 30 — Класи-перерахування (enum class)
Файл: 30.enum-class.mdОрієнтовний обсяг: 2 000–3 000 слів
Залежності: 29.enum.md
Навчальні цілі
- Пояснити недоліки «незахищених» enum: витік у простір імен, неявна конвертація.
- Ввести
enum class(scoped enumeration) як відповідь C++11. - Показати синтаксис, область видимості, оператор
::. - Розкрити суворі правила типізації: заборона неявних порівнянь різних enum.
- Навчити явної конвертації через
static_cast. - Показати задання базового типу (
enum class X : uint8_t).
Структура статті
- Вступ і Hook: Демонстрація «тихого» баґу — порівняння
Fruit::LEMON == Color::PINKпроходить компіляцію і повертаєtrue. Чому? Обидва мають значення0. Це реальна помилка, яку важко знайти. Рішення —enum class. - Фундаментальні концепції
- Поняття «scoped» vs «unscoped» перерахування.
- Синтаксис
enum class(абоenum struct— синоніми). - Оператор розширення імені (scope resolution operator
::) для доступу.
- Суворі правила типізації
- Неявна конвертація enum class → int заборонена.
- Порівняння двох різних enum class заборонене.
- Порівняння всередині одного enum class — дозволене.
- Приклад помилки компіляції та її пояснення.
- Явна конвертація
static_cast<int>(value)— коли і навіщо.static_cast<MyEnum>(intVal)— небезпека, UB (Undefined Behavior).- Патерн безпечної конвертації через
std::underlying_type_t.
- Задання базового типу
- Синтаксис
enum class Direction : uint8_t { ... }. - Навіщо: економія пам'яті у вбудованих системах, мережеві протоколи.
sizeofпорівняння.
- Синтаксис
- enum class у switch-statement
- Синтаксис
switch(dir)ізcase Direction::North:. - Попередження компілятора про неповний switch.
- Патерн з
defaultта[[fallthrough]].
- Синтаксис
- Порівняльна таблиця:
enumvsenum classВластивість enumenum classОбласть видимості Глобальна Локальна Неявна конвертація в int✅ ❌ Порівняння різних типів ✅ (баґ!) ❌ (захист) Задання базового типу Частково ✅ Рекомендованість у C++11+ ❌ ✅ - Практика та резюме
- Рівень 1: Переписати
enumіз попередньої статті наenum class; виправити звернення. - Рівень 1: Написати
switchдля виведення назви дня тижня. - Рівень 2: Реалізувати enum class
Permission : uint8_t(Read/Write/Execute) та функцію перевірки прав доступу. - Рівень 3: Реалізувати міні-парсер HTTP відповідей:
enum class HttpStatus : uint16_tз категоризацією (isSuccess(),isRedirect(),isError()).
- Рівень 1: Переписати
Стаття 31 — Псевдоніми типів (typedef і using)
Файл: 31.type-aliases.mdОрієнтовний обсяг: 2 000–2 800 слів
Залежності: 05.data-types-variables.md, 14.functions-overloading-templates.md
Навчальні цілі
- Пояснити призначення
typedef: читабельність, підтримка, кросплатформність. - Навчити синтаксису
typedef oldType newName. - Ввести
using newName = oldType(C++11) як переважний синтаксис. - Показати застосування для спрощення складних типів (вказівники на функції).
- Пояснити стандартні псевдоніми (
int8_t,uint32_tтощо).
Структура статті
- Вступ і Hook: Функція, що приймає
std::vector<std::pair<std::string, int>>тричі в прототипі. Читач відчуває біль. Рішення — один псевдонім. - typedef: синтаксис та семантика
typedef double seconds_t;— базовий патерн.- Суфікс
_tяк конвенція. - typedef не створює новий тип, лише псевдонім.
- Порядок: спочатку існуючий тип, потім нове ім'я.
- Чотири причини використання typedef
- Читабельність:
testScore_t GradeTest()протиint GradeTest(). - Підтримка: зміна базового типу в одному місці.
- Кросплатформність:
int8_t,int16_t,uint32_tз<cstdint>. - Спрощення: складні типи (
std::map<std::string, std::vector<int>>).
- Читабельність:
- typedef і вказівники на функції
- Проблема:
bool (*compare)(int, int)— незрозумілий синтаксис. - Рішення:
typedef bool (*Comparator)(int, int);. - Зв'язок із попередньою статтею про вказівники на функції.
- Проблема:
using— сучасний синтаксис (C++11)using seconds_t = double;— еквівалент typedef.- Чому
usingкращий: читабельніший порядок (псевдонім = тип). usingдля шаблонних псевдонімів (template aliases) — перевага перед typedef.
- Стандартні типи з
<cstdint>Тип Розмір Знак Опис int8_t8 біт Зі знаком Малий цілий uint8_t8 біт Без знаку Байт int16_t16 біт Зі знаком Короткий цілий uint32_t32 біт Без знаку Натуральний int64_t64 біт Зі знаком Великий цілий ptrdiff_t— Зі знаком Різниця вказівників size_t— Без знаку Розміри об'єктів - Поширені помилки та анти-патерни
- Надмірне використання псевдонімів: коли це шкодить читабельності.
- typedef в заголовковому файлі — правила включення.
typedefу глобальному просторі імен — ризики забруднення.
- Практика та резюме
- Рівень 1: Створити
using Meters = double; using Seconds = double;; написати функцію швидкості. - Рівень 1: Замінити всі
intу масиві ідентифікаторів наusing StudentID = uint32_t. - Рівень 2: Спростити вказівник на функцію з попередніх статей через
using. - Рівень 3: Розробити «шар абстракції платформи» — оголосити набір
using-псевдонімів, що перемикаються за допомогою#ifdefміжint32_tіint64_t.
- Рівень 1: Створити
Стаття 32 — Структури (struct)
Файл: 32.struct.mdОрієнтовний обсяг: 3 500–5 000 слів
Залежності: 05.data-types-variables.md, 15.pointers-basics.md, 19.dynamic-memory.md
Навчальні цілі
- Пояснити потребу в агрегуванні даних: проблема «Координата з трьох змінних».
- Навчити оголошувати
struct, ініціалізувати агрегатно. - Пояснити доступ до членів (
.та->). - Показати передачу структур у функції (за значенням vs за посиланням).
- Ввести поняття вкладених структур та масивів структур.
- Показати
structз методами (bridge до ООП).
Структура статті
- Вступ і Hook: Студент реалізує студента через
string name; int age; double gpa;. Три окремі змінні для одного поняття. Передати в функцію — три параметри. Масив студентів — три масиви. Рішення —struct. - Фундаментальні концепції
- Визначення: структура як агрегатний тип даних (aggregate type).
- Синтаксис оголошення
struct, члени-дані (data members), крапка з комою після}. - Різниця між оголошенням типу і визначенням змінної.
- Ініціалізація та агрегатна ініціалізація
- Агрегатна ініціалізація:
Student s = {"Alice", 20, 3.9}. - Ініціалізація за ім'ям члена (C++20 designated initializers):
Student s = {.name = "Alice"}. - Значення за замовчуванням у членах (default member initializers).
- Часткова ініціалізація — що отримує неініціалізований член.
- Агрегатна ініціалізація:
- Доступ до членів
- Оператор
.для об'єктів і посилань. - Оператор
->для вказівників (повторення з22.member-access-operator.md). ::debugger-viewдля візуалізації стану структури.
- Оператор
- Структури та пам'ять
- Розмір структури (
sizeof) та вирівнювання (alignment, padding). - Візуалізація через
::memory-view. __attribute__((packed))— коли і чому небезпечно.
- Розмір структури (
- Передача у функції
- Передача за значенням: копія, витрати на копіювання.
- Передача за константним посиланням
const Student&: рекомендований патерн. - Передача за вказівником: коли доречно.
- Повернення структури з функції, NRVO (Named Return Value Optimization).
- Масиви структур та вкладені структури
Student class_students[30];— масив структур.- Вкладена структура:
AddressвсерединіStudent. - Цикл
for-each по масиву структур.
- Структури з методами (перехідний місток до ООП)
- Функція-член всередині struct (
void print() const { ... }). - Структура з конструктором (попередній перегляд).
- Різниця
structтаclassу C++ (лише за замовчуванням видимості).
- Функція-член всередині struct (
- Практика та резюме
- Рівень 1: Оголосити
struct Point3D {double x, y, z;}; написати функцію відстані. - Рівень 1: Ініціалізувати масив
Color[8]та вивести таблицю RGB значень. - Рівень 2: Реалізувати
struct Matrix2x2з операціями множення. - Рівень 2: Написати
struct LinkedListNodeз вказівником на наступний вузол. - Рівень 3: Реалізувати систему обліку студентів:
struct Student,struct Course, функції пошуку, сортування та виведення звіту.
- Рівень 1: Оголосити
Стаття 33 — Об'єднання (union)
Файл: 33.union.mdОрієнтовний обсяг: 2 000–2 800 слів
Залежності: 32.struct.md, 05.data-types-variables.md
Навчальні цілі
- Пояснити семантику
union: один блок пам'яті, кілька інтерпретацій. - Показати, коли
unionкорисний: мережеві пакети, варіантні типи, рендерери. - Пояснити UB при зчитуванні не-активного члена.
- Ввести
std::variant(C++17) як безпечну альтернативу.
Структура статті
- Вступ і Hook: Уявімо сенсор, що може повертати або
float, або статусuint32_t. Як зберегти обидва варіанти без зайвих байтів? - Фундаментальні концепції
- Синтаксис
union, розмір дорівнює розміру найбільшого члена. - Активний член (active member): зміна одного членa змінює інтерпретацію пам'яті.
::memory-viewдля демонстрації перекриття байтів.
- Синтаксис
- Практичні застосування
- Мережеві пакети: доступ до байтів
uint32_t IP адресаякuint8_t[4]. - Floating-point хаки: читання бітів
floatчерезuint32_t. - Анонімні
unionу структурах.
- Мережеві пакети: доступ до байтів
- Undefined Behavior та правила
- Зчитування не-активного члена: UB у C++, але «common initial sequence» виключення.
unionз нетривіальними типами (конструктор/деструктор) — складнощі.
std::variantяк сучасна альтернатива- Синтаксис
std::variant<int, float, std::string>. std::get<>таstd::visit.- Чому перевагу надають variant у сучасному коді.
- Синтаксис
- Практика та резюме
- Рівень 1: Оголосити
union Dataзint i,float f; вивести їх байтові представлення. - Рівень 2: Реалізувати
union IPv4для зберігання IP-адреси як числа та байтів. - Рівень 3: Реалізувати
std::variant-based систему повідомлень для простого сокет-протоколу.
- Рівень 1: Оголосити
Модуль D: Організація коду та препроцесор (Статті 46–50)
Логіка модуля
До цього моменту всі програми курсу жили в одному файлі. Але реальні проекти складаються з десятків і сотень файлів, що компілюються разом. Цей модуль закриває фундаментальну прогалину: студент дізнається, як C++ організує код між файлами, що відбувається до компіляції (препроцесор), і як уникнути конфліктів у великих проектах.
Логічний порядок тем:
Багатофайлові програми → Заголовкові файли → Директиви препроцесора
↓
Header guards / #pragma once → Простори імен
Спочатку — практична потреба (два файли не бачать одне одного), потім інструмент
для її вирішення (заголовки), потім механізм, що стоїть за #include (препроцесор),
потім захист від дублювання (header guards), і нарешті — захист від конфліктів імен (namespace).
Стаття 46 — Багатофайлові програми
Файл: 46.multifile-programs.mdОрієнтовний обсяг: 2 200–3 000 слів
Залежності: 12.functions-basics.md, 03.ide-setup.md
Навчальні цілі
- Пояснити, чому великі програми розбиваються на кілька
.cpp-файлів. - Показати, як компілятор обробляє кожен файл окремо (одиниця трансляції).
- Пояснити роль лінкера: збирання об'єктних файлів у виконуваний.
- Навчити використовувати попередні оголошення (forward declarations) для виклику функцій з інших файлів.
- Показати типові помилки компілятора та лінкера і як їх діагностувати.
- Продемонструвати додавання файлів у CLion, VS Code та компіляцію через
g++.
Структура статті
- Вступ і Hook: Одна програма — 800 рядків в одному файлі. Знайти функцію
calculateTax()серед них — квест. Команда з трьох осіб не може працювати над одним файлом одночасно. Рішення — розбиття на файли. - Як компілятор бачить проект
- Поняття «одиниця трансляції» (translation unit): кожен
.cpp— окремий світ. - Компілятор не читає інші
.cppавтоматично: ізоляція за задумом. - Лінкер збирає об'єктні файли
.o/.objу виконуваний бінарник. - Діаграма:
a.cpp→a.o,b.cpp→b.o→ лінкер →program.
- Поняття «одиниця трансляції» (translation unit): кожен
- Перший приклад: два файли
add.cppіз визначенням функціїadd(int, int).main.cppбез оголошення — помилка компілятора: «ідентифікатор не знайдено».- Виправлення: додати попереднє оголошення
int add(int x, int y);уmain.cpp. ::terminal-previewіз помилкою і виправленим виводом.
- Попереднє оголошення (forward declaration)
- Що це і навіщо: компілятор повинен знати сигнатуру до використання.
- Оголошення ≠ визначення: де живе тіло функції.
- Правило ODR (One Definition Rule): тіло функції — рівно в одному
.cpp. - Типова помилка:
int add(int x, int y);вmain.cpp, але забули додатиadd.cppдо проекту → помилка лінкера.
- Компіляція через командний рядок
g++ main.cpp add.cpp -o program— всі файли одразу.-cпрапор: компіляція без лінкування (g++ -c add.cpp→add.o).- Makefile як наступний крок (не деталізується — bridge до майбутнього).
- Додавання файлів в IDE
- CLion: автоматично підхоплює
.cppу директорії проекту (CMake). - VS Code: необхідно вказати файли в
tasks.json. - Коротко з
::cautionпро поширену помилку: «додав файл, але не в проект».
- CLion: автоматично підхоплює
- Типові помилки та їх діагностика
- «identifier not found» / «was not declared» → забуте оголошення.
- «unresolved external symbol» → файл не підключено до компіляції.
- «multiple definition» → тіло функції випадково потрапило у заголовок, підключений двічі.
::field-groupіз трьома картками: помилка → причина → виправлення.
- Практика та резюме
- Рівень 1: Розділити задану однофайлову програму на
main.cppіmath.cpp; додати оголошення. - Рівень 2: Реалізувати калькулятор із трьох файлів:
main.cpp,operations.cpp,io.cpp; скомпілювати черезg++. - Рівень 3: Написати міні-бібліотеку
stringutilsіз трьох функцій уstringutils.cpp, скомпілювати як окремий об'єктний файл і слінкувати зmain.cpp.
- Рівень 1: Розділити задану однофайлову програму на
Стаття 47 — Заголовкові файли
Файл: 47.header-files.mdОрієнтовний обсяг: 2 500–3 500 слів
Залежності: 46.multifile-programs.md
Навчальні цілі
- Пояснити проблему повторення попередніх оголошень у кожному
.cpp. - Ввести заголовкові файли
.hяк централізоване сховище оголошень. - Показати структуру заголовка: директиви препроцесора + оголошення.
- Пояснити різницю між кутовими дужками
<>і лапками"". - Розкрити причину відсутності
.hу стандартних заголовках (iostream, а неiostream.h). - Сформулювати правила «що можна/не можна класти в заголовок».
- Показати пару
math.h/math.cppяк еталонний патерн організації файлів.
Структура статті
- Вступ і Hook: Проект росте: тепер
add()використовується в п'яти.cpp-файлах. У кожному — ручне оголошення. Змінилася сигнатура — треба виправляти у п'яти місцях. Рішення — один заголовковий файл. - Що таке заголовковий файл
- Файл
.h(або.hpp) — не магія, просто текст, який препроцесор вставляє замість#include. - Мета: централізоване зберігання оголошень для повторного використання.
- Аналогія: «контракт» між постачальником коду (
.cpp) і споживачем.
- Файл
- Структура заголовкового файлу
- Дві обов'язкові частини: header guard + оголошення.
- Що кладемо в заголовок: прототипи функцій, оголошення структур і класів,
using-псевдоніми, константиconstexpr. - Що не кладемо: визначення функцій (крім
inline), визначення змінних,using namespace.
- Пара
.h/.cpp— еталонний патернmath.h: оголошенняint add(int, int);+ header guard.math.cpp:#include "math.h"+ визначення функції.main.cpp:#include "math.h"— і більше жодних ручних оголошень.::terminal-previewз успішною компіляцією і виводом.
<>vs""— де шукає компілятор#include <iostream>→ системні директорії (стандартна бібліотека).#include "math.h"→ спочатку поточна директорія, потім системні.- Чому не можна використовувати
<>для власних заголовків.
- Чому
iostream, а неiostream.h- Коротка історія: C-ера з
.h, стандартизація C++98, перенесення уstd. iostream.h→ застарілий, безstd::.iostream→ актуальний, зstd::.- C-заголовки:
<stdlib.h>→<cstdlib>з переміщенням уstd. - Правило: завжди без
.hдля стандартних заголовків.
- Коротка історія: C-ера з
- Правила написання власних заголовків
::card-groupз правилами: завжди header guard, тільки оголошення, одна відповідальність, ім'я заголовка = ім'я.cpp, не підключати.cppчерез#include.
- Практика та резюме
- Рівень 1: Створити
geometry.h/geometry.cppз функціями периметра та площі; підключити вmain.cpp. - Рівень 2: Виправити проект із «зламаними» оголошеннями, розкиданими по трьох файлах — централізувати у заголовку.
- Рівень 3: Розробити міні-бібліотеку
vector2d(структураVec2, операції додавання, скалярного добутку, нормалізації) зі своїм заголовком і реалізацією; підключити у тестову програму.
- Рівень 1: Створити
Стаття 48 — Директиви препроцесора
Файл: 48.preprocessor-directives.mdОрієнтовний обсяг: 3 000–4 000 слів
Залежності: 47.header-files.md, 05.data-types-variables.md
Навчальні цілі
- Пояснити, що таке препроцесор і коли він запускається (до компіляції).
- Розкрити механіку директиви
#include: текстова підстановка. - Ввести
#defineдля оголошення макросів-об'єктів з підстановкою тексту. - Пояснити макроси без підстановки та їх роль в умовній компіляції.
- Детально розглянути умовну компіляцію:
#ifdef,#ifndef,#if,#elif,#else,#endif. - Показати область видимості директив: файловий рівень, не функціональний.
- Пояснити, чому
#define-константи поступаютьсяconstexpr.
Структура статті
- Вступ і Hook: Програміст пише
#include <iostream>і не замислюється — що за цим стоїть? Насправді це не мова C++, а окрема програма, що запускається першою. Розберемо її роботу від початку до кінця. - Що таке препроцесор
- Препроцесор як окрема фаза: запускається до компілятора, на виході — «чистий» C++-текст.
- Директиви: починаються з
#, не закінчуються;, не є операторами C++. - Препроцесор «не знає» C++: не розуміє функцій, класів, типів — лише текст.
- Діаграма фаз: вихідний код → препроцесор → розширений код → компілятор → об'єктний файл.
- Директива
#include- Механіка: буквальна вставка вмісту файлу на місце рядка
#include. - Демонстрація:
#include "greeting.h"→ що реально бачить компілятор (розгорнутий код). - Ланцюгові підключення:
a.hвключаєb.h— порядок розгортання. ::caution— чому#include "add.cpp"— це помилка дизайну.
- Механіка: буквальна вставка вмісту файлу на місце рядка
- Директива
#define: макроси з підстановкою- Синтаксис:
#define ІДЕНТИФІКАТОР текст_заміна. - Конвенція:
ВЕЛИКИМИ_ЛІТЕРАМИ_З_ПІДКРЕСЛЕННЯМИ. - Приклад:
#define MAX_SIZE 100— препроцесор замінює кожнеMAX_SIZEна100. - Пастка:
#define SQUARE(x) x*x→SQUARE(2+3)=2+3*2+3=11, а не25. - Чому
constexprкраще: типобезпека, область видимості, дебаґінг.
- Синтаксис:
- Макроси без підстановки
#define DEBUG— визначає символ без значення.- Символ стає «прапорцем» для умовної компіляції.
- Типові символи:
DEBUG,NDEBUG,TESTING,PLATFORM_WINDOWS.
- Умовна компіляція
#ifdef SYMBOL/#endif— якщо символ визначено.#ifndef SYMBOL/#endif— якщо символ не визначено.#if,#elif,#else— повноцінні умовні гілки з виразами.- Практичний патерн:
#ifdef DEBUG ... std::cerr << "debug: " ... #endif. - Крос-платформний код:
#ifdef _WIN32/#elif defined(__linux__)/#else. ::terminal-preview— та сама програма зDEBUGі без.
- Область видимості директив
#defineдіє від точки визначення до кінця файлу.#defineуmain.cppне впливає наfunction.cpp— кожен файл компілюється окремо.#defineвсередині функції: препроцесор ігнорує блоки{}— результат той самий.#undef— явне скасування визначення.
- Практика та резюме
- Рівень 1: Написати програму з
#define MAX_STUDENTS 30; замінити наconstexpr; пояснити різницю. - Рівень 2: Реалізувати систему логування через умовну компіляцію:
LOG(msg)виводить повідомлення якщо визначеноDEBUG, і нічого не робить у release. - Рівень 3: Написати крос-платформний заголовок
platform.h, що визначаєusing FileHandle = ...по-різному для Windows та POSIX через#ifdef _WIN32.
- Рівень 1: Написати програму з
Стаття 49 — Header guards і #pragma once
Файл: 49.header-guards.mdОрієнтовний обсяг: 1 800–2 500 слів
Залежності: 48.preprocessor-directives.md, 47.header-files.md
Навчальні цілі
- Продемонструвати конкретну проблему: подвійне підключення заголовка призводить до помилки.
- Пояснити механіку header guards через
#ifndef/#define/#endif. - Навчити складати унікальне ім'я для макросу-захисника.
- Ввести
#pragma onceяк сучасну компактну альтернативу. - Порівняти два підходи: переносимість vs зручність.
- Сформулювати правило: кожен заголовковий файл повинен мати захист.
Структура статті
- Вступ і Hook: Програма не компілюється, хоча кожен файл виглядає бездоганно.
math.hпідключено і вmain.cpp, і вgeometry.h— і обидва підключені вmain.cpp. Два визначення однієї функції. Де помилка? Показати розгорнутий код після препроцесора — і все стає очевидним. - Діагностика проблеми
- Покрокове розгортання
#includeу прикладіmath.h→geometry.h→main.cpp. - Що бачить компілятор: два визначення
getSquareSides()в одному файлі. - Чому помилка виникає саме тут, а не в кожному файлі окремо.
::caution— ця помилка проявляється лише при певній структурі залежностей, тому її легко пропустити.
- Покрокове розгортання
- Header guards: механіка
- Синтаксис:
#ifndef MATH_H/#define MATH_H/.../#endif. - Принцип: перше підключення — визначаємо символ і обробляємо вміст. Друге — символ вже є,
#ifndefне пропускає. - Покрокова демонстрація: той самий приклад, але з header guards — розгортання і результат.
- Конвенція іменування:
FILENAME_H(наприклад,MATH_H,GEOMETRY_H). - Вимога унікальності:
UTILS_Hу двох різних папках — конфлікт. КращеMYPROJECT_UTILS_H.
- Синтаксис:
#pragma once- Синтаксис: єдиний рядок
#pragma onceна початку файлу. - Як це працює: компілятор запам'ятовує шлях до файлу, при повторному підключенні — пропускає.
- Переваги: коротший, немає ризику помилки в імені макросу, читабельніший.
- Обмеження: нестандартна директива (але підтримується GCC, Clang, MSVC — усіма сучасними компіляторами).
- Синтаксис: єдиний рядок
- Порівняння та рекомендація
Критерій Header guards #pragma onceСтандарт C++ ✅ офіційний ⚠️ розширення Підтримка сучасних компіляторів ✅ ✅ Ризик помилки в імені ⚠️ є ❌ відсутній Обсяг коду 3 рядки 1 рядок Поведінка з символічними лінками ✅ коректно ⚠️ залежить від реалізації Рекомендовано для нових проектів ✅ (для сумісності) ✅ (для зручності) - Заголовки зі Стандартної бібліотеки
<iostream>теж має header guard (показати фрагмент_IOSTREAM_).- Захист — не лише для власних файлів, а індустріальна норма.
- Практика та резюме
- Рівень 1: Відтворити проблему подвійного підключення; виправити через header guard; виправити через
#pragma once. - Рівень 2: Аудит проекту з 4 файлів — додати захист до кожного заголовка, який його не має.
- Рівень 3: Розробити заголовок
config.hдля великого проекту з унікальним іменем захисника (MYAPP_CONFIG_H) та наборомconstexpr-констант.
- Рівень 1: Відтворити проблему подвійного підключення; виправити через header guard; виправити через
Стаття 50 — Конфлікти імен і простори імен (namespace)
Файл: 50.namespaces.mdОрієнтовний обсяг: 3 000–4 000 слів
Залежності: 46.multifile-programs.md, 13.functions-scope.md
Навчальні цілі
- Пояснити, що таке конфлікт імен і за яких умов він виникає.
- Ввести поняття простору імен (
namespace) як механізму ізоляції ідентифікаторів. - Навчити оголошувати власний
namespace, розміщувати в ньому функції і типи. - Пояснити оператор
::(scope resolution) для доступу до членів простору імен. - Розкрити директиву
using namespaceі чомуusing namespace std;— антипатерн. - Показати
using-оголошення як безпечну альтернативу. - Ввести анонімні та вкладені простори імен.
Структура статті
- Вступ і Hook: Два розробники написали функцію
log()— один для виводу повідомлення, інший для математичного логарифму. Проект об'єднали. Компілятор не знає, якуlog()викликати. Це конфлікт імен — і простори імен є його вирішенням. - Анатомія конфлікту імен
- Два
.cppз функцієюdoSomething()однакової сигнатури — помилка лінкера. - Два заголовки з однаковою функцією, підключені в один
.cpp— помилка компілятора. - Чому ймовірність конфлікту зростає з розміром проекту та кількістю бібліотек.
::terminal-previewз текстом помилки та поясненням.
- Два
- Простір імен: оголошення та використання
- Синтаксис
namespace MyLib { ... }. - Функції, змінні, структури всередині namespace.
- Доступ через
:::MyLib::add(3, 4). - Той самий ідентифікатор у двох namespace — не конфлікт.
- Синтаксис
- Розміщення namespace у файлах
math.hзnamespace Math { int add(int, int); }.math.cppзnamespace Math { int add(int a, int b) { return a + b; } }.- Або
Math::add(int a, int b) { ... }— альтернативний синтаксис поза блоком.
using namespace— зручність vs небезпекаusing namespace std;— що воно робить: вносить усі імена зіstdу поточний простір.- Чому це антипатерн у заголовках: забруднює простір імен усіх, хто підключить заголовок.
- Чому навіть у
.cppце ризиковано: приховані конфлікти з майбутніми версіями стандарту. ::caution— конкретний приклад:std::distancevs власна функціяdistance.
using-оголошення як безпечна альтернативаusing std::cout;— вносить лише одне конкретне ім'я.- Локальний
usingвсередині функції — безпечний патерн. - Порівняння:
using namespace std;(всі) vsusing std::cout; using std::endl;(вибіркові).
- Анонімні простори імен
namespace { ... }— еквівалентstaticдля файлового рівня.- Функції та змінні в анонімному namespace видимі лише в поточному
.cpp. - Коли використовувати: приватні допоміжні функції, що не повинні «витікати» назовні.
- Вкладені простори імен (C++17)
namespace Outer::Inner { ... }— скорочений синтаксис C++17.- Приклад:
namespace MyApp::Network::Http { ... }. - Доступ:
MyApp::Network::Http::Request.
stdта причини його існування- Коротка ретроспектива: чому весь STL перемістили в
stdпри стандартизації. cout,cin,string,vector— всі живуть уstd.- Правило курсу: завжди писати
std::cout, ніколиusing namespace std;.
- Коротка ретроспектива: чому весь STL перемістили в
- Практика та резюме
- Рівень 1: Оголосити
namespace Geometryз функціямиcircleArea()іrectArea(); викликати через::. - Рівень 2: Відтворити конфлікт імен між двома бібліотеками (власна
log()і математична); усунути через namespace. - Рівень 3: Розробити міні-фреймворк
AppCoreз вкладеними namespaceAppCore::Logger,AppCore::Config,AppCore::Math; кожен у своїй парі.h/.cpp; зібрати вmain.cpp.
- Рівень 1: Оголосити
Стиль коду в матеріалах курсу
Усі приклади коду в статтях курсу дотримуються Java-подібного стилю, описаного в статті 02.code-style.md. Нижче — зведення обов'язкових правил для авторів матеріалів.
Іменування
| Що іменуємо | Стиль | Приклад |
|---|---|---|
| Змінні та параметри | camelCase | studentAge, itemPrice |
| Функції | camelCase | calculateTotal(), printResult() |
Константи (const, constexpr) | SCREAMING_SNAKE_CASE | MAX_SIZE, TAX_RATE |
| Класи, структури, enum | PascalCase | BankAccount, Direction |
Файли .cpp / .h | PascalCase | MathUtils.cpp, Triangle.h |
| Макроси препроцесора | SCREAMING_SNAKE_CASE | MATH_H, DEBUG_MODE |
Форматування
- Відступи — 4 пробіли на кожен рівень вкладеності. Не таби.
- Фігурні дужки — стиль Allman: відкриваюча
{на новому рядку, вирівняна з оголошенням. - Пробіли навколо операторів — навколо
=,+,-,*,/,==,&&тощо. - Пробіл після ключових слів —
if (,while (,for (,switch (. - Без пробілу між іменем функції та дужкою —
add(a, b), неadd (a, b). - Пробіл після коми в списках аргументів —
func(a, b, c). - Один рядок — одна інструкція.
- Порожні рядки між логічними секціями (ввід / обчислення / вивід).
Змінні
Змінні оголошуються на початку блоку (Java-підхід), перед першим використанням — не розкидаються по тілу функції.
Коментарі
Коментарі пояснюють «навіщо», а не «що». Коментарі-заголовки для логічних секцій (// --- Обчислення ---). Очевидні коментарі (// збільшуємо i на 1) не використовуються.
Приклад еталонного оформлення
#include <iostream>
#include <cmath>
const double PI = 3.14159265;
double circleArea(double radius)
{
return PI * radius * radius;
}
int main()
{
double radius;
std::cout << "Enter radius: ";
std::cin >> radius;
double area = circleArea(radius);
std::cout << "Area: " << area << "\n";
return 0;
}