Перейти к основному содержимому

Практические задания по C++

Разработчику
Как пользоваться списком

Задания для самостоятельной работы после теории intro. Каждый пункт — мини-проект с критерием готовности. Подсказки "с чего начать" помогают, если застряли

полное решение собирайте сами. Самопроверка: C++ — чек-лист.

Критерий "сделано"

Код собирается без предупреждений (-Wall или /W4), проходит описанные проверки, вы можете устно объяснить владение памятью и границы модулей.

Не требуют внешних учебников — только компилятор, CMake и при необходимости Qt SDK / Vulkan SDK.


Уровень 1 — язык и память

1.1. Каталог файлов

Цель: обход каталога, фильтр по расширению .cpp.

Что изучить: std::filesystem (C++17), directory_iterator, path::extension().

С чего начать:

#include <filesystem>
namespace fs = std::filesystem;

for (const auto& entry : fs::recursive_directory_iterator(".")) {
if (!entry.is_regular_file()) continue;
// сравнить entry.path().extension() с ".cpp"
}

Разбор:

  • #include <filesystem> подключает стандартный модуль для работы с файлами и директориями на уровне C++17.
  • namespace fs = std::filesystem; создаёт короткий псевдоним, чтобы не писать длинное имя пространства имён в каждой строке.
  • fs::recursive_directory_iterator(".") запускает рекурсивный обход от текущей папки, включая вложенные каталоги.
  • entry.is_regular_file() отсекает каталоги и специальные узлы, чтобы фильтрация выполнялась только по обычным файлам.
  • entry.path().extension() возвращает расширение файла; сравнение с ".cpp" формирует итоговый фильтр исходников C++.

Требования:

  • вывод относительного пути и размера файла;
  • при ошибке доступа — сообщение, программа продолжает обход.

Проверка: каталог с вложенными папками; пустой каталог — вежливое сообщение, без падения.


1.2. RAII-лог

Цель: класс LogScope, пишущий в файл при создании строку enter <имя> и при уничтожении leave <имя>.

Подсказка по каркасу:

class LogScope {
std::string name_;
std::ofstream& log_;
public:
LogScope(std::string name, std::ofstream& log);
~LogScope(); // записать leave
LogScope(const LogScope&) = delete;
LogScope& operator=(const LogScope&) = delete;
};

Разбор:

  • Класс LogScope реализует паттерн RAII: вход в область фиксируется в конструкторе, выход — в деструкторе.
  • Поле std::string name_ хранит человекочитаемое имя текущего scope, чтобы лог был связан с конкретной частью кода.
  • Поле std::ofstream& log_ хранит ссылку на уже открытый поток, что исключает лишние копии и повторные открытия файла.
  • Запись ~LogScope(); означает, что при выходе из области видимости автоматически будет вызван деструктор и записан leave.
  • = delete у копирующих операций защищает от двойного "владения" одним и тем же логическим scope и от некорректного дублирования записей.

Требования:

  • запрет копирования;
  • в main — вложенные блоки { LogScope a(...); { LogScope b(...); throw ... } } — для уже созданных scope строки leave всё равно появляются.

Материал: Идиомы современного C++, Управление памятью в C++, Обработка исключений в C++.


1.3. Rule of Five в мини-строке

Цель: класс SmallString с буфером в куче — конструктор, копирование, перемещение, деструктор, operator=.

Минимальный API: size(), data() или c_str(), push_back(char).

Проверка: AddressSanitizer / UB-санитайзер — нет утечек при цепочке присваиваний и std::vector<SmallString> с ростом.


Уровень 2 — STL и C++20

2.1. Частотный словарь

Цель: прочитать текстовый файл, посчитать слова, вывести топ-10.

Шаги:

  1. std::ifstream + построчное чтение.
  2. Разбить строку на слова (по пробелам или std::istringstream).
  3. Нормализация: tolower, убрать пунктуацию у краёв слова.
  4. std::unordered_map<std::string, int> для счётчиков.
  5. Положить пары в vector, отсортировать по частоте.

Материал: Диапазоны и представления в C++20 для ranges-версии сортировки.


2.2. Конвейер ranges

Цель: из vector<int> — сумма квадратов чётных чисел одной цепочкой filtertransform → свёртка.

Пример направления (C++20):

auto sum = nums
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; });
// далее std::ranges::fold_left или ручной цикл для суммы

Разбор:

  • Это конвейер ranges: данные проходят через несколько стадий без создания лишних промежуточных контейнеров.
  • std::views::filter(...) пропускает только чётные числа по условию n % 2 == 0.
  • Лямбда в filter принимает int n и возвращает bool, то есть решение "оставить элемент или отбросить".
  • std::views::transform(...) преобразует прошедшие фильтр элементы в их квадраты n * n.
  • Итоговая переменная sum в этом фрагменте содержит "представление диапазона"; для получения числа нужна отдельная свёртка (fold_left или цикл).

Проверка: результат совпадает с обычным циклом на тех же данных.


2.3. optional и ошибки

Цель: parse_int(std::string_view) -> std::optional<int> без исключений.

Логика: пройти символы; при первом не-цифре (кроме опционального - в начале) вернуть std::nullopt.

CLI: аргументы argv[1..], для неудачных — строка invalid.


2.4. Реализация вручную (по желанию)

Цель — написать одну структуру или алгоритм своими руками, прогнать тесты и сравнить с файлом из TheAlgorithms/C-Plus-Plus.

Варианты (достаточно одного):

ЗадачаЧто реализоватьФайл для сравнения
Стекpush, pop, top на массивеstack_using_array.cpp
Сортировка слияниемvector<int> → отсортированный массивmerge_sort.cpp
BSTinsert, contains в двоичном дереве поискаbinary_search_tree.cpp

Проверка

  • случайные тесты дают тот же результат, что и эталонный код;
  • в README проекта указаны ссылка на файл репозитория и одно отличие подхода (например, std::unique_ptr вместо new/delete — см. RAII, управление памятью).

Материалы


Уровень 3 — сборка и тесты

3.1. CMake-библиотека + exe

Цель: статическая mathlib (факториал, gcd) + demo.

Структура:

project/
├── CMakeLists.txt
├── include/mathlib.hpp
├── src/mathlib.cpp
└── src/main.cpp

CMake (скелет):

add_library(mathlib STATIC src/mathlib.cpp)
target_include_directories(mathlib PUBLIC include)
add_executable(demo src/main.cpp)
target_link_libraries(demo PRIVATE mathlib)

Разбор:

  • add_library(mathlib STATIC ...) создаёт статическую библиотеку, которая соберётся в отдельный артефакт и затем будет линковаться.
  • target_include_directories(... PUBLIC include) публикует каталог заголовков для самой библиотеки и для всех зависимых целей.
  • add_executable(demo ...) объявляет исполняемую программу, которая использует функции из mathlib.
  • target_link_libraries(demo PRIVATE mathlib) связывает demo с библиотекой; ключевое слово PRIVATE ограничивает эту зависимость только текущей целью.
  • Такой шаблон разделяет код на переиспользуемый модуль и приложение, что упрощает тестирование и расширение проекта.

Материал: CMake — первая программа, Конфигурация и сборка в C++.


3.2. GTest

Цель: три теста на mathlib (gcd норма, gcd(0,x), факториал малых n), target calc_tests, ctest.

Материал: Google Test и Catch2 в C++ — скопируйте блок FetchContent + gtest_discover_tests.


Уровень 4 — UI (Qt)

4.1. Widgets

Цель: окно со списком задач — поле ввода, кнопка "добавить", QListWidget.

Подсказки:

  • QLineEdit + QPushButton + QListWidget в QVBoxLayout;
  • в слоте кнопки: list->addItem(lineEdit->text()); lineEdit->clear();.

Проверка: данные живут в сессии (сохранение на диск — по желанию).

Материал: Qt — первая программа.


4.2. Qt Quick

Цель: счётчик по Qt Quick — первая программа на QML + кнопка "сброс", reset() в C++ с Q_INVOKABLE.

Проверка: Label с text: "..." + counter.value обновляется без setText из C++.


Уровень 5 — графика

5.1. Треугольник Vulkan

Цель: GLFW, Vulkan SDK, validation layers, один render pass, цветной треугольник.

Не требуется: текстуры, depth, индексные буферы.

Материал: Vulkan и низкоуровневая графика на C++, Разработка игр с использованием C++.


5.2. Индексы и uniform

Цель: квад из двух треугольников, ортографическая матрица в uniform buffer, вращение по таймеру.


Уровень 6 — интеграция

6.1. Мини-сервис

Цель: консоль — конфиг (JSON/INI), HTTP GET на localhost (cpp-httplib header-only), лог в файл с RAII.

Связь: Сетевое взаимодействие в C++, Простые приложения на C++.


Как проверять себя

ВопросЗачем
Кто владеет каждым new / unique_ptr?нет утечек
Что будет при исключении в середине функции?RAII
Какой стандарт C++ в CMake?ranges / optional
Сколько раз копируются данные в цепочке?производительность

После уровней 1–3 — C++ — чек-лист. После 4–5 — C++ — углублённые темы или /encyclopedia/9-spinoff/9-04-razrabotka-igr/intro.


Формат выполнения задания

Чтобы материал не выглядел как "просто список пунктов", для каждого задания фиксируйте одинаковый шаблон:

  1. Что сделал — 3-5 строк по сути.
  2. Код — ссылка на папку/файл задания.
  3. Как проверил — команды и результат.
  4. Что сломалось и как починил — минимум один найденный баг.
  5. Что улучшить дальше — 1-2 технических шага.

Этот формат превращает упражнения в портфолио инженерного прогресса.


Критерии качества решения

КритерийЧто считается хорошим результатом
Читаемостькороткие функции, предсказуемые имена, без "магических" чисел
Безопасность памятинет сырого владения через new/delete без необходимости
Сборкапроект собирается на чистой машине по инструкции в README
Проверяемостьесть автотесты или воспроизводимая ручная проверка
Связностьвидно связь задания с теорией из соседних статей

Рекомендуемая траектория по времени

  • Неделя 1: уровни 1.1-1.3.
  • Неделя 2: уровни 2.1-2.3.
  • Неделя 3: уровни 3.1-3.2.
  • Неделя 4: один путь на выбор: Qt или Vulkan.

Темп можно менять, но порядок полезно сохранить: язык → STL → сборка → UI/графика.


Кейс из практики — "делал задания, но прогресс не виден"

Ситуация: выполнено много упражнений, но на собеседовании трудно показать результат.

Рабочий формат:

  • отдельная папка на каждое задание (task-1-1, task-1-2, ...);
  • короткий README.md внутри — цель, команды запуска, что проверено;
  • один коммит на завершённый этап.

Мини-шаблон README:

# 2.1 Частотный словарь
- Цель: посчитать топ-10 слов
- Сборка: cmake -S . -B build && cmake --build build
- Запуск: ./build/freq input.txt
- Проверка: совпадает с эталонным выводом для sample.txt

Разбор:

  • Это минимальный шаблон README, который фиксирует не только "что написано", но и "как проверить результат".
  • Заголовок # 2.1 Частотный словарь связывает артефакт с конкретным заданием и помогает быстро найти нужный кейс в портфолио.
  • Строка Сборка документирует воспроизводимую команду сборки; любой проверяющий может повторить шаг без догадок.
  • Строка Запуск фиксирует входной формат и исполняемый файл, что снижает риск неверной интерпретации результата.
  • Строка Проверка задаёт критерий готовности и переводит задачу из "написал код" в "подтвердил корректность".

Результат: задания превращаются в прозрачную линейку навыков, а не в разрозненные файлы.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.