C++

План навчання: Курс C++ — Продовження (Статті 29–60+)

План навчання: Курс C++ — Продовження (Статті 29–60+)

Цей документ є академічним планом розвитку навчального курсу C++ на платформі kostyl.dev. Він описує логічну структуру, тематичний зміст та педагогічні вимоги до кожної статті, яка має бути розроблена після існуючих 28 статей (до теми аргументів командного рядка включно). Стиль викладу — академічний, текстоцентричний («text-first»), з активним використанням компонентів Docus для інтерактивності.


Стан курсу на момент планування

Завершені статті (01–28)

ФайлТема
0101.intro-algorithms.mdВступ до алгоритмів та програмування
0202.code-style.mdСтиль коду та оформлення
0303.ide-setup.mdНалаштування IDE (CLion / VS Code)
0404.data-output.mdВиведення даних (std::cout)
0505.data-types-variables.mdТипи даних та змінні
0606.data-input.mdВведення даних (std::cin)
0707.operators-type-conversion.mdОператори та перетворення типів
0808.loops.mdЦикли (for, while, do-while)
0909.arrays.mdМасиви
1010.sorting.mdАлгоритми сортування
1111.searching.mdАлгоритми пошуку
1212.functions-basics.mdФункції: основи
1313.functions-scope.mdФункції: область видимості
1414.functions-overloading-templates.mdПеревантаження та шаблони функцій
1515.pointers-basics.mdВказівники: основи
1616.references.mdПосилання (references)
1717.pointers-const-arrays.mdВказівники, const та масиви
1818.pointer-arithmetic.mdАрифметика вказівників
1919.dynamic-memory.mdДинамічна пам'ять (new/delete)
2020.void-pointers.mdУзагальнені вказівники (void*)
2121.pointers-to-pointers.mdВказівники на вказівники
2222.member-access-operator.mdОператор доступу до члена (->)
2323.foreach-loop.mdЦикл for на основі діапазону
2424.function-pointers.mdВказівники на функції
2525.lambdas.mdЛямбда-вирази
2626.lambda-captures.mdЗахоплення у лямбда-виразах
2727.ellipsis.mdЕліпсис (...) та варіадичні функції
2828.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) перерахувань.
  • Показати практичні патерни: коди помилок, стани стейт-машини.

Структура статті

  1. Вступ і Hook: Сценарій функції readFile(), що повертає -1, -2, -3. Читач бачить код із «магічними числами» і питає: «Що означає -2?» Відповідь — перерахування.
  2. Фундаментальні концепції
    • Визначення: що таке перелічуваний тип (enumerated type).
    • Синтаксис enum, ключове слово, тіло, крапка з комою.
    • Поняття «енумератор» (enumerator) — іменована константа всередині enum.
  3. Механіка та внутрішнє представлення
    • Автоматичне присвоєння цілочисельних значень (з 0).
    • Явне присвоєння значень, від'ємні значення, повторювані значення.
    • Компілятор не перешкоджає дублюванню — чому це небезпечно.
    • Розмір enum в пам'яті (sizeof); зв'язок із int.
    • Компонент ::debugger-view для візуалізації значень.
    • Компонент ::memory-view для демонстрації байтів.
  4. Неявні перетворення та обробка
    • Неявна конвертація enum → int (дозволена).
    • Заборона int → enum без static_cast.
    • Заборона введення через std::cin без tricks.
    • Виведення std::cout — числовий результат, функція printEnum.
  5. Простір імен перерахувань (проблема)
    • Енумератори потрапляють у навколишній простір імен.
    • Конфлікт імен між двома enum в одному файлі.
    • Конвенція іменування PREFIX_VALUE як частковий обхідний шлях.
  6. Практичні патерни
    • Коди повернення функцій замість магічних чисел.
    • Стани кінцевого автомата (стейт-машина гри).
    • Напрямки руху, сторони карти, режими роботи.
    • Компонент ::terminal-preview для демонстрації роботи програм.
  7. Практика та резюме
    • Рівень 1: Оголосити enum Direction з чотирма сторонами; вивести їх значення.
    • Рівень 1: Виправити код із магічними числами за допомогою enum.
    • Рівень 2: Написати функцію getStatusMessage(ParseResult), що повертає рядок.
    • Рівень 2: Реалізувати простий стейт-автомат гравця (стоїть, іде, біжить).
    • Рівень 3: Реалізувати систему статусів HTTP (200, 404, 500) через 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).

Структура статті

  1. Вступ і Hook: Демонстрація «тихого» баґу — порівняння Fruit::LEMON == Color::PINK проходить компіляцію і повертає true. Чому? Обидва мають значення 0. Це реальна помилка, яку важко знайти. Рішення — enum class.
  2. Фундаментальні концепції
    • Поняття «scoped» vs «unscoped» перерахування.
    • Синтаксис enum class (або enum struct — синоніми).
    • Оператор розширення імені (scope resolution operator ::) для доступу.
  3. Суворі правила типізації
    • Неявна конвертація enum class → int заборонена.
    • Порівняння двох різних enum class заборонене.
    • Порівняння всередині одного enum class — дозволене.
    • Приклад помилки компіляції та її пояснення.
  4. Явна конвертація
    • static_cast<int>(value) — коли і навіщо.
    • static_cast<MyEnum>(intVal) — небезпека, UB (Undefined Behavior).
    • Патерн безпечної конвертації через std::underlying_type_t.
  5. Задання базового типу
    • Синтаксис enum class Direction : uint8_t { ... }.
    • Навіщо: економія пам'яті у вбудованих системах, мережеві протоколи.
    • sizeof порівняння.
  6. enum class у switch-statement
    • Синтаксис switch(dir) із case Direction::North:.
    • Попередження компілятора про неповний switch.
    • Патерн з default та [[fallthrough]].
  7. Порівняльна таблиця: enum vs enum class
    Властивістьenumenum class
    Область видимостіГлобальнаЛокальна
    Неявна конвертація в int
    Порівняння різних типів✅ (баґ!)❌ (захист)
    Задання базового типуЧастково
    Рекомендованість у C++11+
  8. Практика та резюме
    • Рівень 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()).

Стаття 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 тощо).

Структура статті

  1. Вступ і Hook: Функція, що приймає std::vector<std::pair<std::string, int>> тричі в прототипі. Читач відчуває біль. Рішення — один псевдонім.
  2. typedef: синтаксис та семантика
    • typedef double seconds_t; — базовий патерн.
    • Суфікс _t як конвенція.
    • typedef не створює новий тип, лише псевдонім.
    • Порядок: спочатку існуючий тип, потім нове ім'я.
  3. Чотири причини використання typedef
    • Читабельність: testScore_t GradeTest() проти int GradeTest().
    • Підтримка: зміна базового типу в одному місці.
    • Кросплатформність: int8_t, int16_t, uint32_t з <cstdint>.
    • Спрощення: складні типи (std::map<std::string, std::vector<int>>).
  4. typedef і вказівники на функції
    • Проблема: bool (*compare)(int, int) — незрозумілий синтаксис.
    • Рішення: typedef bool (*Comparator)(int, int);.
    • Зв'язок із попередньою статтею про вказівники на функції.
  5. using — сучасний синтаксис (C++11)
    • using seconds_t = double; — еквівалент typedef.
    • Чому using кращий: читабельніший порядок (псевдонім = тип).
    • using для шаблонних псевдонімів (template aliases) — перевага перед typedef.
  6. Стандартні типи з <cstdint>
    ТипРозмірЗнакОпис
    int8_t8 бітЗі знакомМалий цілий
    uint8_t8 бітБез знакуБайт
    int16_t16 бітЗі знакомКороткий цілий
    uint32_t32 бітБез знакуНатуральний
    int64_t64 бітЗі знакомВеликий цілий
    ptrdiff_tЗі знакомРізниця вказівників
    size_tБез знакуРозміри об'єктів
  7. Поширені помилки та анти-патерни
    • Надмірне використання псевдонімів: коли це шкодить читабельності.
    • typedef в заголовковому файлі — правила включення.
    • typedef у глобальному просторі імен — ризики забруднення.
  8. Практика та резюме
    • Рівень 1: Створити using Meters = double; using Seconds = double;; написати функцію швидкості.
    • Рівень 1: Замінити всі int у масиві ідентифікаторів на using StudentID = uint32_t.
    • Рівень 2: Спростити вказівник на функцію з попередніх статей через using.
    • Рівень 3: Розробити «шар абстракції платформи» — оголосити набір using-псевдонімів, що перемикаються за допомогою #ifdef між int32_t і int64_t.

Стаття 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 до ООП).

Структура статті

  1. Вступ і Hook: Студент реалізує студента через string name; int age; double gpa;. Три окремі змінні для одного поняття. Передати в функцію — три параметри. Масив студентів — три масиви. Рішення — struct.
  2. Фундаментальні концепції
    • Визначення: структура як агрегатний тип даних (aggregate type).
    • Синтаксис оголошення struct, члени-дані (data members), крапка з комою після }.
    • Різниця між оголошенням типу і визначенням змінної.
  3. Ініціалізація та агрегатна ініціалізація
    • Агрегатна ініціалізація: Student s = {"Alice", 20, 3.9}.
    • Ініціалізація за ім'ям члена (C++20 designated initializers): Student s = {.name = "Alice"}.
    • Значення за замовчуванням у членах (default member initializers).
    • Часткова ініціалізація — що отримує неініціалізований член.
  4. Доступ до членів
    • Оператор . для об'єктів і посилань.
    • Оператор -> для вказівників (повторення з 22.member-access-operator.md).
    • ::debugger-view для візуалізації стану структури.
  5. Структури та пам'ять
    • Розмір структури (sizeof) та вирівнювання (alignment, padding).
    • Візуалізація через ::memory-view.
    • __attribute__((packed)) — коли і чому небезпечно.
  6. Передача у функції
    • Передача за значенням: копія, витрати на копіювання.
    • Передача за константним посиланням const Student&: рекомендований патерн.
    • Передача за вказівником: коли доречно.
    • Повернення структури з функції, NRVO (Named Return Value Optimization).
  7. Масиви структур та вкладені структури
    • Student class_students[30]; — масив структур.
    • Вкладена структура: Address всередині Student.
    • Цикл for-each по масиву структур.
  8. Структури з методами (перехідний місток до ООП)
    • Функція-член всередині struct (void print() const { ... }).
    • Структура з конструктором (попередній перегляд).
    • Різниця struct та class у C++ (лише за замовчуванням видимості).
  9. Практика та резюме
    • Рівень 1: Оголосити struct Point3D {double x, y, z;}; написати функцію відстані.
    • Рівень 1: Ініціалізувати масив Color[8] та вивести таблицю RGB значень.
    • Рівень 2: Реалізувати struct Matrix2x2 з операціями множення.
    • Рівень 2: Написати struct LinkedListNode з вказівником на наступний вузол.
    • Рівень 3: Реалізувати систему обліку студентів: struct Student, struct Course, функції пошуку, сортування та виведення звіту.

Стаття 33 — Об'єднання (union)

Файл: 33.union.mdОрієнтовний обсяг: 2 000–2 800 слів Залежності: 32.struct.md, 05.data-types-variables.md

Навчальні цілі

  • Пояснити семантику union: один блок пам'яті, кілька інтерпретацій.
  • Показати, коли union корисний: мережеві пакети, варіантні типи, рендерери.
  • Пояснити UB при зчитуванні не-активного члена.
  • Ввести std::variant (C++17) як безпечну альтернативу.

Структура статті

  1. Вступ і Hook: Уявімо сенсор, що може повертати або float, або статус uint32_t. Як зберегти обидва варіанти без зайвих байтів?
  2. Фундаментальні концепції
    • Синтаксис union, розмір дорівнює розміру найбільшого члена.
    • Активний член (active member): зміна одного членa змінює інтерпретацію пам'яті.
    • ::memory-view для демонстрації перекриття байтів.
  3. Практичні застосування
    • Мережеві пакети: доступ до байтів uint32_t IP адреса як uint8_t[4].
    • Floating-point хаки: читання бітів float через uint32_t.
    • Анонімні union у структурах.
  4. Undefined Behavior та правила
    • Зчитування не-активного члена: UB у C++, але «common initial sequence» виключення.
    • union з нетривіальними типами (конструктор/деструктор) — складнощі.
  5. std::variant як сучасна альтернатива
    • Синтаксис std::variant<int, float, std::string>.
    • std::get<> та std::visit.
    • Чому перевагу надають variant у сучасному коді.
  6. Практика та резюме
    • Рівень 1: Оголосити union Data з int i, float f; вивести їх байтові представлення.
    • Рівень 2: Реалізувати union IPv4 для зберігання IP-адреси як числа та байтів.
    • Рівень 3: Реалізувати std::variant-based систему повідомлень для простого сокет-протоколу.

Модуль 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++.

Структура статті

  1. Вступ і Hook: Одна програма — 800 рядків в одному файлі. Знайти функцію calculateTax() серед них — квест. Команда з трьох осіб не може працювати над одним файлом одночасно. Рішення — розбиття на файли.
  2. Як компілятор бачить проект
    • Поняття «одиниця трансляції» (translation unit): кожен .cpp — окремий світ.
    • Компілятор не читає інші .cpp автоматично: ізоляція за задумом.
    • Лінкер збирає об'єктні файли .o / .obj у виконуваний бінарник.
    • Діаграма: a.cppa.o, b.cppb.o → лінкер → program.
  3. Перший приклад: два файли
    • add.cpp із визначенням функції add(int, int).
    • main.cpp без оголошення — помилка компілятора: «ідентифікатор не знайдено».
    • Виправлення: додати попереднє оголошення int add(int x, int y); у main.cpp.
    • ::terminal-preview із помилкою і виправленим виводом.
  4. Попереднє оголошення (forward declaration)
    • Що це і навіщо: компілятор повинен знати сигнатуру до використання.
    • Оголошення ≠ визначення: де живе тіло функції.
    • Правило ODR (One Definition Rule): тіло функції — рівно в одному .cpp.
    • Типова помилка: int add(int x, int y); в main.cpp, але забули додати add.cpp до проекту → помилка лінкера.
  5. Компіляція через командний рядок
    • g++ main.cpp add.cpp -o program — всі файли одразу.
    • -c прапор: компіляція без лінкування (g++ -c add.cppadd.o).
    • Makefile як наступний крок (не деталізується — bridge до майбутнього).
  6. Додавання файлів в IDE
    • CLion: автоматично підхоплює .cpp у директорії проекту (CMake).
    • VS Code: необхідно вказати файли в tasks.json.
    • Коротко з ::caution про поширену помилку: «додав файл, але не в проект».
  7. Типові помилки та їх діагностика
    • «identifier not found» / «was not declared» → забуте оголошення.
    • «unresolved external symbol» → файл не підключено до компіляції.
    • «multiple definition» → тіло функції випадково потрапило у заголовок, підключений двічі.
    • ::field-group із трьома картками: помилка → причина → виправлення.
  8. Практика та резюме
    • Рівень 1: Розділити задану однофайлову програму на main.cpp і math.cpp; додати оголошення.
    • Рівень 2: Реалізувати калькулятор із трьох файлів: main.cpp, operations.cpp, io.cpp; скомпілювати через g++.
    • Рівень 3: Написати міні-бібліотеку stringutils із трьох функцій у stringutils.cpp, скомпілювати як окремий об'єктний файл і слінкувати з main.cpp.

Стаття 47 — Заголовкові файли

Файл: 47.header-files.mdОрієнтовний обсяг: 2 500–3 500 слів Залежності: 46.multifile-programs.md

Навчальні цілі

  • Пояснити проблему повторення попередніх оголошень у кожному .cpp.
  • Ввести заголовкові файли .h як централізоване сховище оголошень.
  • Показати структуру заголовка: директиви препроцесора + оголошення.
  • Пояснити різницю між кутовими дужками <> і лапками "".
  • Розкрити причину відсутності .h у стандартних заголовках (iostream, а не iostream.h).
  • Сформулювати правила «що можна/не можна класти в заголовок».
  • Показати пару math.h / math.cpp як еталонний патерн організації файлів.

Структура статті

  1. Вступ і Hook: Проект росте: тепер add() використовується в п'яти .cpp-файлах. У кожному — ручне оголошення. Змінилася сигнатура — треба виправляти у п'яти місцях. Рішення — один заголовковий файл.
  2. Що таке заголовковий файл
    • Файл .h (або .hpp) — не магія, просто текст, який препроцесор вставляє замість #include.
    • Мета: централізоване зберігання оголошень для повторного використання.
    • Аналогія: «контракт» між постачальником коду (.cpp) і споживачем.
  3. Структура заголовкового файлу
    • Дві обов'язкові частини: header guard + оголошення.
    • Що кладемо в заголовок: прототипи функцій, оголошення структур і класів, using-псевдоніми, константи constexpr.
    • Що не кладемо: визначення функцій (крім inline), визначення змінних, using namespace.
  4. Пара .h / .cpp — еталонний патерн
    • math.h: оголошення int add(int, int); + header guard.
    • math.cpp: #include "math.h" + визначення функції.
    • main.cpp: #include "math.h" — і більше жодних ручних оголошень.
    • ::terminal-preview з успішною компіляцією і виводом.
  5. <> vs "" — де шукає компілятор
    • #include <iostream> → системні директорії (стандартна бібліотека).
    • #include "math.h" → спочатку поточна директорія, потім системні.
    • Чому не можна використовувати <> для власних заголовків.
  6. Чому iostream, а не iostream.h
    • Коротка історія: C-ера з .h, стандартизація C++98, перенесення у std.
    • iostream.h → застарілий, без std::. iostream → актуальний, з std::.
    • C-заголовки: <stdlib.h><cstdlib> з переміщенням у std.
    • Правило: завжди без .h для стандартних заголовків.
  7. Правила написання власних заголовків
    • ::card-group з правилами: завжди header guard, тільки оголошення, одна відповідальність, ім'я заголовка = ім'я .cpp, не підключати .cpp через #include.
  8. Практика та резюме
    • Рівень 1: Створити geometry.h / geometry.cpp з функціями периметра та площі; підключити в main.cpp.
    • Рівень 2: Виправити проект із «зламаними» оголошеннями, розкиданими по трьох файлах — централізувати у заголовку.
    • Рівень 3: Розробити міні-бібліотеку vector2d (структура Vec2, операції додавання, скалярного добутку, нормалізації) зі своїм заголовком і реалізацією; підключити у тестову програму.

Стаття 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.

Структура статті

  1. Вступ і Hook: Програміст пише #include <iostream> і не замислюється — що за цим стоїть? Насправді це не мова C++, а окрема програма, що запускається першою. Розберемо її роботу від початку до кінця.
  2. Що таке препроцесор
    • Препроцесор як окрема фаза: запускається до компілятора, на виході — «чистий» C++-текст.
    • Директиви: починаються з #, не закінчуються ;, не є операторами C++.
    • Препроцесор «не знає» C++: не розуміє функцій, класів, типів — лише текст.
    • Діаграма фаз: вихідний код → препроцесор → розширений код → компілятор → об'єктний файл.
  3. Директива #include
    • Механіка: буквальна вставка вмісту файлу на місце рядка #include.
    • Демонстрація: #include "greeting.h" → що реально бачить компілятор (розгорнутий код).
    • Ланцюгові підключення: a.h включає b.h — порядок розгортання.
    • ::caution — чому #include "add.cpp" — це помилка дизайну.
  4. Директива #define: макроси з підстановкою
    • Синтаксис: #define ІДЕНТИФІКАТОР текст_заміна.
    • Конвенція: ВЕЛИКИМИ_ЛІТЕРАМИ_З_ПІДКРЕСЛЕННЯМИ.
    • Приклад: #define MAX_SIZE 100 — препроцесор замінює кожне MAX_SIZE на 100.
    • Пастка: #define SQUARE(x) x*xSQUARE(2+3) = 2+3*2+3 = 11, а не 25.
    • Чому constexpr краще: типобезпека, область видимості, дебаґінг.
  5. Макроси без підстановки
    • #define DEBUG — визначає символ без значення.
    • Символ стає «прапорцем» для умовної компіляції.
    • Типові символи: DEBUG, NDEBUG, TESTING, PLATFORM_WINDOWS.
  6. Умовна компіляція
    • #ifdef SYMBOL / #endif — якщо символ визначено.
    • #ifndef SYMBOL / #endif — якщо символ не визначено.
    • #if, #elif, #else — повноцінні умовні гілки з виразами.
    • Практичний патерн: #ifdef DEBUG ... std::cerr << "debug: " ... #endif.
    • Крос-платформний код: #ifdef _WIN32 / #elif defined(__linux__) / #else.
    • ::terminal-preview — та сама програма з DEBUG і без.
  7. Область видимості директив
    • #define діє від точки визначення до кінця файлу.
    • #define у main.cpp не впливає на function.cpp — кожен файл компілюється окремо.
    • #define всередині функції: препроцесор ігнорує блоки {} — результат той самий.
    • #undef — явне скасування визначення.
  8. Практика та резюме
    • Рівень 1: Написати програму з #define MAX_STUDENTS 30; замінити на constexpr; пояснити різницю.
    • Рівень 2: Реалізувати систему логування через умовну компіляцію: LOG(msg) виводить повідомлення якщо визначено DEBUG, і нічого не робить у release.
    • Рівень 3: Написати крос-платформний заголовок platform.h, що визначає using FileHandle = ... по-різному для Windows та POSIX через #ifdef _WIN32.

Стаття 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 зручність.
  • Сформулювати правило: кожен заголовковий файл повинен мати захист.

Структура статті

  1. Вступ і Hook: Програма не компілюється, хоча кожен файл виглядає бездоганно. math.h підключено і в main.cpp, і в geometry.h — і обидва підключені в main.cpp. Два визначення однієї функції. Де помилка? Показати розгорнутий код після препроцесора — і все стає очевидним.
  2. Діагностика проблеми
    • Покрокове розгортання #include у прикладі math.hgeometry.hmain.cpp.
    • Що бачить компілятор: два визначення getSquareSides() в одному файлі.
    • Чому помилка виникає саме тут, а не в кожному файлі окремо.
    • ::caution — ця помилка проявляється лише при певній структурі залежностей, тому її легко пропустити.
  3. Header guards: механіка
    • Синтаксис: #ifndef MATH_H / #define MATH_H / ... / #endif.
    • Принцип: перше підключення — визначаємо символ і обробляємо вміст. Друге — символ вже є, #ifndef не пропускає.
    • Покрокова демонстрація: той самий приклад, але з header guards — розгортання і результат.
    • Конвенція іменування: FILENAME_H (наприклад, MATH_H, GEOMETRY_H).
    • Вимога унікальності: UTILS_H у двох різних папках — конфлікт. Краще MYPROJECT_UTILS_H.
  4. #pragma once
    • Синтаксис: єдиний рядок #pragma once на початку файлу.
    • Як це працює: компілятор запам'ятовує шлях до файлу, при повторному підключенні — пропускає.
    • Переваги: коротший, немає ризику помилки в імені макросу, читабельніший.
    • Обмеження: нестандартна директива (але підтримується GCC, Clang, MSVC — усіма сучасними компіляторами).
  5. Порівняння та рекомендація
    КритерійHeader guards#pragma once
    Стандарт C++✅ офіційний⚠️ розширення
    Підтримка сучасних компіляторів
    Ризик помилки в імені⚠️ є❌ відсутній
    Обсяг коду3 рядки1 рядок
    Поведінка з символічними лінками✅ коректно⚠️ залежить від реалізації
    Рекомендовано для нових проектів✅ (для сумісності)✅ (для зручності)
  6. Заголовки зі Стандартної бібліотеки
    • <iostream> теж має header guard (показати фрагмент _IOSTREAM_).
    • Захист — не лише для власних файлів, а індустріальна норма.
  7. Практика та резюме
    • Рівень 1: Відтворити проблему подвійного підключення; виправити через header guard; виправити через #pragma once.
    • Рівень 2: Аудит проекту з 4 файлів — додати захист до кожного заголовка, який його не має.
    • Рівень 3: Розробити заголовок config.h для великого проекту з унікальним іменем захисника (MYAPP_CONFIG_H) та набором constexpr-констант.

Стаття 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-оголошення як безпечну альтернативу.
  • Ввести анонімні та вкладені простори імен.

Структура статті

  1. Вступ і Hook: Два розробники написали функцію log() — один для виводу повідомлення, інший для математичного логарифму. Проект об'єднали. Компілятор не знає, яку log() викликати. Це конфлікт імен — і простори імен є його вирішенням.
  2. Анатомія конфлікту імен
    • Два .cpp з функцією doSomething() однакової сигнатури — помилка лінкера.
    • Два заголовки з однаковою функцією, підключені в один .cpp — помилка компілятора.
    • Чому ймовірність конфлікту зростає з розміром проекту та кількістю бібліотек.
    • ::terminal-preview з текстом помилки та поясненням.
  3. Простір імен: оголошення та використання
    • Синтаксис namespace MyLib { ... }.
    • Функції, змінні, структури всередині namespace.
    • Доступ через ::: MyLib::add(3, 4).
    • Той самий ідентифікатор у двох namespace — не конфлікт.
  4. Розміщення 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) { ... } — альтернативний синтаксис поза блоком.
  5. using namespace — зручність vs небезпека
    • using namespace std; — що воно робить: вносить усі імена зі std у поточний простір.
    • Чому це антипатерн у заголовках: забруднює простір імен усіх, хто підключить заголовок.
    • Чому навіть у .cpp це ризиковано: приховані конфлікти з майбутніми версіями стандарту.
    • ::caution — конкретний приклад: std::distance vs власна функція distance.
  6. using-оголошення як безпечна альтернатива
    • using std::cout; — вносить лише одне конкретне ім'я.
    • Локальний using всередині функції — безпечний патерн.
    • Порівняння: using namespace std; (всі) vs using std::cout; using std::endl; (вибіркові).
  7. Анонімні простори імен
    • namespace { ... } — еквівалент static для файлового рівня.
    • Функції та змінні в анонімному namespace видимі лише в поточному .cpp.
    • Коли використовувати: приватні допоміжні функції, що не повинні «витікати» назовні.
  8. Вкладені простори імен (C++17)
    • namespace Outer::Inner { ... } — скорочений синтаксис C++17.
    • Приклад: namespace MyApp::Network::Http { ... }.
    • Доступ: MyApp::Network::Http::Request.
  9. std та причини його існування
    • Коротка ретроспектива: чому весь STL перемістили в std при стандартизації.
    • cout, cin, string, vector — всі живуть у std.
    • Правило курсу: завжди писати std::cout, ніколи using namespace std;.
  10. Практика та резюме
    • Рівень 1: Оголосити namespace Geometry з функціями circleArea() і rectArea(); викликати через ::.
    • Рівень 2: Відтворити конфлікт імен між двома бібліотеками (власна log() і математична); усунути через namespace.
    • Рівень 3: Розробити міні-фреймворк AppCore з вкладеними namespace AppCore::Logger, AppCore::Config, AppCore::Math; кожен у своїй парі .h/.cpp; зібрати в main.cpp.

Стиль коду в матеріалах курсу

Усі приклади коду в статтях курсу дотримуються Java-подібного стилю, описаного в статті 02.code-style.md. Нижче — зведення обов'язкових правил для авторів матеріалів.

Іменування

Що іменуємоСтильПриклад
Змінні та параметриcamelCasestudentAge, itemPrice
ФункціїcamelCasecalculateTotal(), printResult()
Константи (const, constexpr)SCREAMING_SNAKE_CASEMAX_SIZE, TAX_RATE
Класи, структури, enumPascalCaseBankAccount, Direction
Файли .cpp / .hPascalCaseMathUtils.cpp, Triangle.h
Макроси препроцесораSCREAMING_SNAKE_CASEMATH_H, DEBUG_MODE

Форматування

  • Відступи — 4 пробіли на кожен рівень вкладеності. Не таби.
  • Фігурні дужки — стиль Allman: відкриваюча { на новому рядку, вирівняна з оголошенням.
  • Пробіли навколо операторів — навколо =, +, -, *, /, ==, && тощо.
  • Пробіл після ключових слівif (, while (, for (, switch (.
  • Без пробілу між іменем функції та дужкою — add(a, b), не add (a, b).
  • Пробіл після коми в списках аргументів — func(a, b, c).
  • Один рядок — одна інструкція.
  • Порожні рядки між логічними секціями (ввід / обчислення / вивід).

Змінні

Змінні оголошуються на початку блоку (Java-підхід), перед першим використанням — не розкидаються по тілу функції.

Коментарі

Коментарі пояснюють «навіщо», а не «що». Коментарі-заголовки для логічних секцій (// --- Обчислення ---). Очевидні коментарі (// збільшуємо i на 1) не використовуються.

Приклад еталонного оформлення

Example.cpp
#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;
}
Copyright © 2026