20 готовых программ на C++ с кодом: практика для новичков и не только (2026)

20 готовых программ на C++ с исходным кодом: от калькулятора до LRU-кэша и алгоритма Дейкстры. Копируйте, запускайте и прокачивайте навыки в 2026 году.

Лучший способ выучить C++ — писать код руками, а не только читать теорию. В этой подборке 20 готовых программ на C++ с кодом: каждая компилируется, запускается и учит конкретному навыку.

C++ остаётся основой игровых движков, высоконагруженных систем, embedded-разработки и HFT. Стандарты C++20 и C++23 сделали язык заметно удобнее, поэтому примеры написаны в современном стиле: std::string, std::vector, умные указатели, auto, range-for.

Как пользоваться статьёй:

  1. Идите по уровням: 1–5 для новичков, 6–10 базовый, 11–15 средний, 16–20 продвинутый.
  2. Сначала попробуйте написать программу сами, потом сверьтесь с кодом.
  3. Выполните задание «Прокачка» под каждым примером — именно там происходит рост.

Все примеры собираются командой g++ -std=c++20 -O2 main.cpp -o app (подробнее — в конце статьи).

Уровень 1: программы для новичков (1–5)

Первые пять примеров закрепляют ввод-вывод, условия, циклы и функции — базу, без которой не обойтись.

1. Консольный калькулятор

Чему учит: std::cin, switch, обработка деления на ноль, цикл до конца ввода.

#include <iostream>

int main() {
    double a, b;
    char op;
    std::cout << "Введите выражение (например, 7 * 3): ";
    while (std::cin >> a >> op >> b) {
        switch (op) {
            case '+': std::cout << "= " << a + b << '\n'; break;
            case '-': std::cout << "= " << a - b << '\n'; break;
            case '*': std::cout << "= " << a * b << '\n'; break;
            case '/':
                if (b == 0) std::cout << "Ошибка: деление на ноль\n";
                else        std::cout << "= " << a / b << '\n';
                break;
            default:  std::cout << "Неизвестная операция: " << op << '\n';
        }
        std::cout << "Следующее выражение (Ctrl+D для выхода): ";
    }
}

Прокачка: добавьте операции % и ^ (степень через std::pow) и историю последних 5 вычислений.

2. Игра «Угадай число»

Чему учит: современная генерация случайных чисел через <random> вместо устаревшего rand(), цикл while, счётчики.

#include <iostream>
#include <random>

int main() {
    std::mt19937 rng(std::random_device{}());
    std::uniform_int_distribution<int> dist(1, 100);
    const int secret = dist(rng);

    int guess = 0, attempts = 0;
    std::cout << "Я загадал число от 1 до 100. Угадайте!\n";
    while (guess != secret) {
        std::cout << "Ваш вариант: ";
        if (!(std::cin >> guess)) return 0;
        ++attempts;
        if (guess < secret)      std::cout << "Больше!\n";
        else if (guess > secret) std::cout << "Меньше!\n";
    }
    std::cout << "Угадали за " << attempts << " попыток!\n";
}

Прокачка: поменяйте роли — пусть программа угадывает ваше число бинарным поиском максимум за 7 ходов.

3. FizzBuzz

Чему учит: оператор %, работа со строками, тернарный оператор. Классическая задача с собеседований.

#include <iostream>
#include <string>

int main() {
    for (int i = 1; i <= 100; ++i) {
        std::string out;
        if (i % 3 == 0) out += "Fizz";
        if (i % 5 == 0) out += "Buzz";
        std::cout << (out.empty() ? std::to_string(i) : out) << '\n';
    }
}

Прокачка: вынесите правила в std::vector<std::pair<int, std::string>>, чтобы добавить «Jazz» для кратных 7 без нового if.

4. Конвертер температур

Чему учит: вынос логики в функции, форматирование вывода через <iomanip>.

#include <iomanip>
#include <iostream>

double toFahrenheit(double c) { return c * 9.0 / 5.0 + 32.0; }
double toCelsius(double f)    { return (f - 32.0) * 5.0 / 9.0; }
double toKelvin(double c)     { return c + 273.15; }

int main() {
    double value;
    char unit;
    std::cout << "Введите температуру и шкалу (например, 36.6 C или 100 F): ";
    std::cin >> value >> unit;
    std::cout << std::fixed << std::setprecision(2);

    if (unit == 'C' || unit == 'c') {
        std::cout << value << " °C = " << toFahrenheit(value) << " °F = "
                  << toKelvin(value) << " K\n";
    } else if (unit == 'F' || unit == 'f') {
        double c = toCelsius(value);
        std::cout << value << " °F = " << c << " °C = " << toKelvin(c) << " K\n";
    } else {
        std::cout << "Поддерживаются только C и F\n";
    }
}

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

5. Таблица умножения

Чему учит: вложенные циклы и выравнивание столбцов с помощью std::setw.

#include <iomanip>
#include <iostream>

int main() {
    int n;
    std::cout << "Размер таблицы: ";
    std::cin >> n;

    std::cout << "    ";
    for (int j = 1; j <= n; ++j) std::cout << std::setw(4) << j;
    std::cout << '\n';

    for (int i = 1; i <= n; ++i) {
        std::cout << std::setw(4) << i;
        for (int j = 1; j <= n; ++j) std::cout << std::setw(4) << i * j;
        std::cout << '\n';
    }
}

Прокачка: превратите программу в тренажёр: 10 случайных примеров, подсчёт правильных ответов и времени.

Уровень 2: базовые проекты (6–10)

Здесь появляются алгоритмы, работа с файлами, контейнеры STL и первый класс — шаг от «задачек» к настоящим программам.

6. Проверка на палиндром

Чему учит: std::getline, функции <cctype>, алгоритм std::equal и обратные итераторы.

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

bool isPalindrome(const std::string& s) {
    std::string clean;
    for (unsigned char ch : s)
        if (std::isalnum(ch)) clean += static_cast<char>(std::tolower(ch));
    return std::equal(clean.begin(), clean.begin() + clean.size() / 2, clean.rbegin());
}

int main() {
    std::string line;
    std::cout << "Введите строку: ";
    std::getline(std::cin, line);
    std::cout << (isPalindrome(line) ? "Это палиндром\n" : "Не палиндром\n");
}

Прокачка: добавьте поддержку кириллицы (подсказка: std::wstring или разбор UTF-8) и поиск самого длинного палиндрома в строке.

7. Простые числа: решето Эратосфена

Чему учит: std::vector, оценка сложности алгоритма, защита от переполнения через long long.

#include <iostream>
#include <vector>

// Решето Эратосфена: все простые числа до n за O(n log log n)
std::vector<int> sieve(int n) {
    std::vector<bool> isPrime(n + 1, true);
    std::vector<int> primes;
    for (int i = 2; i <= n; ++i) {
        if (!isPrime[i]) continue;
        primes.push_back(i);
        for (long long j = 1LL * i * i; j <= n; j += i) isPrime[j] = false;
    }
    return primes;
}

int main() {
    int n;
    std::cout << "Найти простые числа до: ";
    std::cin >> n;
    auto primes = sieve(n);
    for (int p : primes) std::cout << p << ' ';
    std::cout << "\nВсего: " << primes.size() << '\n';
}

Прокачка: сравните скорость решета с наивной проверкой делителей до √n на n = 10 000 000.

8. To-do список с сохранением в файл

Чему учит: std::ifstream / std::ofstream, меню в цикле, удаление из вектора, проверка ввода.

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

const std::string FILE_NAME = "todo.txt";

std::vector<std::string> load() {
    std::vector<std::string> tasks;
    std::ifstream in(FILE_NAME);
    for (std::string line; std::getline(in, line);)
        if (!line.empty()) tasks.push_back(line);
    return tasks;
}

void save(const std::vector<std::string>& tasks) {
    std::ofstream out(FILE_NAME);
    for (const auto& t : tasks) out << t << '\n';
}

int main() {
    auto tasks = load();
    while (true) {
        std::cout << "\n1) Список  2) Добавить  3) Удалить  0) Выход\n> ";
        int cmd;
        if (!(std::cin >> cmd) || cmd == 0) break;
        std::cin.ignore();

        if (cmd == 1) {
            for (size_t i = 0; i < tasks.size(); ++i)
                std::cout << i + 1 << ". " << tasks[i] << '\n';
            if (tasks.empty()) std::cout << "Задач нет\n";
        } else if (cmd == 2) {
            std::string task;
            std::cout << "Задача: ";
            std::getline(std::cin, task);
            tasks.push_back(task);
            save(tasks);
        } else if (cmd == 3) {
            size_t idx;
            std::cout << "Номер: ";
            std::cin >> idx;
            if (idx >= 1 && idx <= tasks.size()) {
                tasks.erase(tasks.begin() + (idx - 1));
                save(tasks);
            } else {
                std::cout << "Нет такой задачи\n";
            }
        }
    }
}

Прокачка: добавьте статус «выполнено», приоритет и дедлайн — храните задачи как struct и сохраняйте в CSV.

9. Крестики-нолики

Чему учит: std::array, структурирование игровой логики, проверка победы через таблицу линий.

#include <array>
#include <iostream>

using Board = std::array<char, 9>;

void print(const Board& b) {
    for (int i = 0; i < 9; i += 3)
        std::cout << ' ' << b[i] << " | " << b[i + 1] << " | " << b[i + 2]
                  << (i < 6 ? "\n---+---+---\n" : "\n");
}

char winner(const Board& b) {
    const int lines[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},
                             {1,4,7},{2,5,8},{0,4,8},{2,4,6}};
    for (auto& l : lines)
        if (b[l[0]] != ' ' && b[l[0]] == b[l[1]] && b[l[1]] == b[l[2]])
            return b[l[0]];
    return ' ';
}

int main() {
    Board b;
    b.fill(' ');
    char player = 'X';
    for (int turn = 0; turn < 9; ++turn) {
        print(b);
        int cell;
        std::cout << "Игрок " << player << ", клетка (1-9): ";
        if (!(std::cin >> cell)) return 0;
        if (cell < 1 || cell > 9 || b[cell - 1] != ' ') {
            std::cout << "Недопустимый ход\n";
            --turn;
            continue;
        }
        b[cell - 1] = player;
        if (winner(b) != ' ') {
            print(b);
            std::cout << "Победил " << player << "!\n";
            return 0;
        }
        player = (player == 'X') ? 'O' : 'X';
    }
    print(b);
    std::cout << "Ничья!\n";
}

Прокачка: напишите непобедимого бота на алгоритме минимакс.

10. Банковский счёт (ООП)

Чему учит: классы, инкапсуляция, конструктор со списком инициализации, const-методы, исключения.

#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

class BankAccount {
public:
    BankAccount(std::string owner, double balance = 0)
        : owner_(std::move(owner)), balance_(balance) {}

    void deposit(double amount) {
        if (amount <= 0) throw std::invalid_argument("Сумма должна быть > 0");
        balance_ += amount;
        history_.push_back("+" + std::to_string(amount));
    }

    void withdraw(double amount) {
        if (amount > balance_) throw std::runtime_error("Недостаточно средств");
        balance_ -= amount;
        history_.push_back("-" + std::to_string(amount));
    }

    double balance() const { return balance_; }
    const std::string& owner() const { return owner_; }
    const std::vector<std::string>& history() const { return history_; }

private:
    std::string owner_;
    double balance_;
    std::vector<std::string> history_;
};

int main() {
    BankAccount acc("Иван", 1000);
    try {
        acc.deposit(500);
        acc.withdraw(200);
        acc.withdraw(5000);   // выбросит исключение
    } catch (const std::exception& e) {
        std::cout << "Ошибка: " << e.what() << '\n';
    }
    std::cout << acc.owner() << ", баланс: " << acc.balance() << '\n';
    for (const auto& op : acc.history()) std::cout << "  " << op << '\n';
}

Прокачка: храните деньги в копейках (long long) вместо double — так делают в реальных финансовых системах. Добавьте наследника SavingsAccount с начислением процентов.

Уровень 3: средний (11–15)

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

11. Частотный анализ текста

Чему учит: std::map, сортировка с лямбда-компаратором, перенаправление ввода из файла.

#include <algorithm>
#include <cctype>
#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::map<std::string, int> freq;
    std::string word;
    // Чтение до конца ввода: ./app < text.txt
    while (std::cin >> word) {
        std::string clean;
        for (unsigned char ch : word)
            if (std::isalpha(ch)) clean += static_cast<char>(std::tolower(ch));
        if (!clean.empty()) ++freq[clean];
    }

    std::vector<std::pair<std::string, int>> items(freq.begin(), freq.end());
    std::sort(items.begin(), items.end(),
              [](const auto& a, const auto& b) { return a.second > b.second; });

    std::cout << "Топ-10 слов:\n";
    for (size_t i = 0; i < items.size() && i < 10; ++i)
        std::cout << items[i].first << " — " << items[i].second << '\n';
}

Прокачка: замените std::map на std::unordered_map, замерьте разницу на большой книге и добавьте список стоп-слов.

12. Шифр Цезаря

Чему учит: арифметика символов, остаток от отрицательных чисел, изменение строки по ссылке в range-for.

#include <iostream>
#include <string>

std::string caesar(const std::string& text, int shift) {
    shift = ((shift % 26) + 26) % 26;   // поддержка отрицательного сдвига
    std::string result = text;
    for (char& ch : result) {
        if (ch >= 'a' && ch <= 'z') ch = 'a' + (ch - 'a' + shift) % 26;
        else if (ch >= 'A' && ch <= 'Z') ch = 'A' + (ch - 'A' + shift) % 26;
    }
    return result;
}

int main() {
    std::string text;
    int shift;
    std::cout << "Текст (латиница): ";
    std::getline(std::cin, text);
    std::cout << "Сдвиг: ";
    std::cin >> shift;

    std::string encrypted = caesar(text, shift);
    std::cout << "Шифр:        " << encrypted << '\n';
    std::cout << "Расшифровка: " << caesar(encrypted, -shift) << '\n';
}

Прокачка: взломайте шифр без ключа частотным анализом (в английском чаще всего встречается «e») и реализуйте шифр Виженера.

13. Игра «Жизнь» Конвея

Чему учит: двумерные массивы, constexpr, клеточные автоматы, анимация в терминале через ANSI-коды и std::this_thread::sleep_for.

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

using Grid = std::vector<std::vector<int>>;
constexpr int W = 30, H = 15;

int neighbours(const Grid& g, int y, int x) {
    int count = 0;
    for (int dy = -1; dy <= 1; ++dy)
        for (int dx = -1; dx <= 1; ++dx) {
            if (dy == 0 && dx == 0) continue;
            int ny = (y + dy + H) % H, nx = (x + dx + W) % W;  // тор
            count += g[ny][nx];
        }
    return count;
}

Grid step(const Grid& g) {
    Grid next(H, std::vector<int>(W, 0));
    for (int y = 0; y < H; ++y)
        for (int x = 0; x < W; ++x) {
            int n = neighbours(g, y, x);
            next[y][x] = (n == 3 || (g[y][x] && n == 2)) ? 1 : 0;
        }
    return next;
}

int main() {
    Grid g(H, std::vector<int>(W, 0));
    // Планер
    g[1][2] = g[2][3] = g[3][1] = g[3][2] = g[3][3] = 1;

    for (int gen = 0; gen < 60; ++gen) {
        std::cout << "\x1b[H\x1b[2J";   // очистка экрана
        for (auto& row : g) {
            for (int c : row) std::cout << (c ? '#' : '.');
            std::cout << '\n';
        }
        g = step(g);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

Прокачка: загружайте стартовую фигуру из файла и перенесите отрисовку в окно через SFML или raylib.

14. Сортировки и бинарный поиск с замером времени

Чему учит: реализация классических алгоритмов, шаблонная функция-таймер, std::chrono. На 20 000 элементах пузырёк работает сотни миллисекунд, а quick sort и std::sort — около 1 мс: разница между O(n²) и O(n log n) видна наглядно.

#include <algorithm>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>

void bubbleSort(std::vector<int>& a) {
    for (size_t i = 0; i < a.size(); ++i)
        for (size_t j = 0; j + 1 < a.size() - i; ++j)
            if (a[j] > a[j + 1]) std::swap(a[j], a[j + 1]);
}

void quickSort(std::vector<int>& a, int lo, int hi) {
    if (lo >= hi) return;
    int pivot = a[(lo + hi) / 2], i = lo, j = hi;
    while (i <= j) {
        while (a[i] < pivot) ++i;
        while (a[j] > pivot) --j;
        if (i <= j) std::swap(a[i++], a[j--]);
    }
    quickSort(a, lo, j);
    quickSort(a, i, hi);
}

int binarySearch(const std::vector<int>& a, int target) {
    int lo = 0, hi = static_cast<int>(a.size()) - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == target) return mid;
        if (a[mid] < target) lo = mid + 1; else hi = mid - 1;
    }
    return -1;
}

template <typename F>
long long measure(F f) {
    auto start = std::chrono::steady_clock::now();
    f();
    auto end = std::chrono::steady_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}

int main() {
    std::mt19937 rng(42);
    std::vector<int> data(20000);
    for (int& x : data) x = rng() % 100000;

    auto a = data, b = data, c = data;
    std::cout << "Пузырёк:    " << measure([&] { bubbleSort(a); }) << " мс\n";
    std::cout << "Quick sort: " << measure([&] { quickSort(b, 0, (int)b.size() - 1); }) << " мс\n";
    std::cout << "std::sort:  " << measure([&] { std::sort(c.begin(), c.end()); }) << " мс\n";

    int target = c[12345];
    std::cout << "Бинарный поиск " << target << " -> индекс " << binarySearch(c, target) << '\n';
}

Прокачка: добавьте merge sort и heap sort, сравните время на уже отсортированном массиве.

15. Парсер CSV и отчёт по продажам

Чему учит: std::stringstream, аргументы командной строки, structured bindings (C++17), устойчивость к битым данным. Такие утилиты для обработки файлов часто пишут на реальной работе.

#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

// Формат sales.csv:
// date,category,amount
// 2026-01-05,Книги,1200.50
std::vector<std::string> split(const std::string& line, char sep = ',') {
    std::vector<std::string> parts;
    std::stringstream ss(line);
    for (std::string item; std::getline(ss, item, sep);) parts.push_back(item);
    return parts;
}

int main(int argc, char* argv[]) {
    std::ifstream in(argc > 1 ? argv[1] : "sales.csv");
    if (!in) { std::cerr << "Не удалось открыть файл\n"; return 1; }

    std::map<std::string, double> totals;
    std::string line;
    std::getline(in, line);   // пропускаем заголовок
    int lineNo = 1;
    while (std::getline(in, line)) {
        ++lineNo;
        auto cols = split(line);
        if (cols.size() != 3) { std::cerr << "Строка " << lineNo << " пропущена\n"; continue; }
        try {
            totals[cols[1]] += std::stod(cols[2]);
        } catch (...) {
            std::cerr << "Строка " << lineNo << ": неверная сумма\n";
        }
    }

    double grand = 0;
    std::cout << std::fixed << std::setprecision(2);
    for (const auto& [category, sum] : totals) {
        std::cout << std::left << std::setw(15) << category << sum << '\n';
        grand += sum;
    }
    std::cout << "Итого: " << grand << '\n';
}

Прокачка: добавьте группировку по месяцам, поддержку полей в кавычках с запятыми внутри и экспорт отчёта в JSON.

Уровень 4: продвинутый (16–20)

Шаблоны, move-семантика, многопоточность, графы и парсеры — темы, которые спрашивают на собеседованиях на Middle C++ разработчика.

16. Шаблонный стек и проверка скобок

Чему учит: шаблоны классов, variadic templates, perfect forwarding, std::move, практическое применение стека.

#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

template <typename T>
class Stack {
public:
    void push(const T& value) { data_.push_back(value); }
    void push(T&& value)      { data_.push_back(std::move(value)); }

    template <typename... Args>
    void emplace(Args&&... args) { data_.emplace_back(std::forward<Args>(args)...); }

    T pop() {
        if (empty()) throw std::out_of_range("Стек пуст");
        T top = std::move(data_.back());
        data_.pop_back();
        return top;
    }

    const T& top() const {
        if (empty()) throw std::out_of_range("Стек пуст");
        return data_.back();
    }

    bool empty() const { return data_.empty(); }
    size_t size() const { return data_.size(); }

private:
    std::vector<T> data_;
};

// Применение: проверка сбалансированности скобок
bool balanced(const std::string& s) {
    Stack<char> st;
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') st.push(c);
        else if (c == ')' || c == ']' || c == '}') {
            if (st.empty()) return false;
            char open = st.pop();
            if ((c == ')' && open != '(') || (c == ']' && open != '[') ||
                (c == '}' && open != '{')) return false;
        }
    }
    return st.empty();
}

int main() {
    Stack<std::string> words;
    words.emplace("мир");
    words.push("привет");
    while (!words.empty()) std::cout << words.pop() << ' ';
    std::cout << '\n';

    for (std::string s : {"{[()]}", "([)]", "(()"})
        std::cout << s << " -> " << (balanced(s) ? "OK" : "ошибка") << '\n';
}

Прокачка: перепишите стек на сыром массиве с ручным управлением памятью и реализуйте «правило пяти» (копирование, перемещение, деструктор).

17. Многопоточное суммирование

Чему учит: std::thread, деление работы на чанки, захват переменных в лямбде, join. Каждый поток пишет в свою ячейку, поэтому мьютекс не нужен.

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <thread>
#include <vector>

// Параллельная сумма большого массива
int main() {
    const size_t N = 100'000'000;
    std::vector<int> data(N, 1);

    unsigned threads = std::max(1u, std::thread::hardware_concurrency());
    std::vector<std::int64_t> partial(threads, 0);
    std::vector<std::thread> pool;

    auto start = std::chrono::steady_clock::now();
    size_t chunk = N / threads;
    for (unsigned t = 0; t < threads; ++t) {
        size_t from = t * chunk;
        size_t to = (t == threads - 1) ? N : from + chunk;
        pool.emplace_back([&, t, from, to] {
            partial[t] = std::accumulate(data.begin() + from, data.begin() + to, std::int64_t{0});
        });
    }
    for (auto& th : pool) th.join();
    std::int64_t total = std::accumulate(partial.begin(), partial.end(), std::int64_t{0});
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                  std::chrono::steady_clock::now() - start).count();

    std::cout << "Потоков: " << threads << "\nСумма: " << total
              << "\nВремя: " << ms << " мс\n";
}

Прокачка: перепишите на std::async / std::future, затем на std::reduce(std::execution::par, ...) и сравните с однопоточной версией. Собирайте с флагом -pthread.

18. LRU-кэш

Чему учит: комбинация std::list + std::unordered_map для операций за O(1), splice, std::optional, if с инициализатором. Одна из самых популярных задач на собеседованиях.

#include <iostream>
#include <list>
#include <optional>
#include <string>
#include <unordered_map>

template <typename K, typename V>
class LRUCache {
public:
    explicit LRUCache(size_t capacity) : capacity_(capacity) {}

    std::optional<V> get(const K& key) {
        auto it = index_.find(key);
        if (it == index_.end()) return std::nullopt;
        items_.splice(items_.begin(), items_, it->second);  // в начало, O(1)
        return it->second->second;
    }

    void put(const K& key, const V& value) {
        if (auto it = index_.find(key); it != index_.end()) {
            it->second->second = value;
            items_.splice(items_.begin(), items_, it->second);
            return;
        }
        if (items_.size() == capacity_) {
            index_.erase(items_.back().first);   // вытесняем самый старый
            items_.pop_back();
        }
        items_.emplace_front(key, value);
        index_[key] = items_.begin();
    }

private:
    size_t capacity_;
    std::list<std::pair<K, V>> items_;
    std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> index_;
};

int main() {
    LRUCache<int, std::string> cache(2);
    cache.put(1, "один");
    cache.put(2, "два");
    cache.get(1);              // 1 становится самым свежим
    cache.put(3, "три");       // вытесняет 2

    for (int k : {1, 2, 3}) {
        auto v = cache.get(k);
        std::cout << k << ": " << (v ? *v : "нет в кэше") << '\n';
    }
}

Прокачка: сделайте кэш потокобезопасным через std::mutex и добавьте время жизни записей (TTL).

19. Кратчайший путь: алгоритм Дейкстры

Чему учит: представление графа списком смежности, std::priority_queue с std::greater<>, восстановление пути. Алгоритм лежит в основе навигаторов и сетевой маршрутизации.

#include <algorithm>
#include <iostream>
#include <limits>
#include <queue>
#include <vector>

struct Edge { int to; int weight; };
using Graph = std::vector<std::vector<Edge>>;
const int INF = std::numeric_limits<int>::max();

std::vector<int> dijkstra(const Graph& g, int src, std::vector<int>& prev) {
    std::vector<int> dist(g.size(), INF);
    prev.assign(g.size(), -1);
    using Item = std::pair<int, int>;  // (расстояние, вершина)
    std::priority_queue<Item, std::vector<Item>, std::greater<>> pq;

    dist[src] = 0;
    pq.push({0, src});
    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();
        if (d > dist[u]) continue;
        for (const auto& [v, w] : g[u])
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                prev[v] = u;
                pq.push({dist[v], v});
            }
    }
    return dist;
}

int main() {
    Graph g(6);
    auto addEdge = [&](int a, int b, int w) { g[a].push_back({b, w}); g[b].push_back({a, w}); };
    addEdge(0, 1, 7); addEdge(0, 2, 9); addEdge(0, 5, 14);
    addEdge(1, 2, 10); addEdge(1, 3, 15); addEdge(2, 3, 11);
    addEdge(2, 5, 2);  addEdge(3, 4, 6);  addEdge(4, 5, 9);

    std::vector<int> prev;
    auto dist = dijkstra(g, 0, prev);
    for (int v = 0; v < (int)g.size(); ++v) {
        std::vector<int> path;
        for (int x = v; x != -1; x = prev[x]) path.push_back(x);
        std::reverse(path.begin(), path.end());
        std::cout << "0 -> " << v << ": " << dist[v] << "  путь: ";
        for (int x : path) std::cout << x << ' ';
        std::cout << '\n';
    }
}

Прокачка: читайте граф из файла и реализуйте A* для поиска пути в лабиринте на сетке.

20. Интерпретатор математических выражений

Чему учит: парсер рекурсивного спуска, приоритет операций, правая ассоциативность степени, обработка ошибок через исключения. Это первый шаг к собственному языку программирования. Пример: 2+3*4 даёт 14, 2^3^2 — 512.

#include <cctype>
#include <cmath>
#include <iostream>
#include <stdexcept>
#include <string>

// Рекурсивный спуск:
// expr   = term   { ('+' | '-') term }
// term   = factor { ('*' | '/') factor }
// factor = power  [ '^' factor ]
// power  = number | '(' expr ')' | '-' power
class Parser {
public:
    explicit Parser(std::string s) : s_(std::move(s)) {}

    double parse() {
        double v = expr();
        skip();
        if (pos_ != s_.size()) throw std::runtime_error("Лишний символ: " + std::string(1, s_[pos_]));
        return v;
    }

private:
    std::string s_;
    size_t pos_ = 0;

    void skip() { while (pos_ < s_.size() && std::isspace((unsigned char)s_[pos_])) ++pos_; }
    bool match(char c) { skip(); if (pos_ < s_.size() && s_[pos_] == c) { ++pos_; return true; } return false; }

    double expr() {
        double v = term();
        while (true) {
            if (match('+')) v += term();
            else if (match('-')) v -= term();
            else return v;
        }
    }
    double term() {
        double v = factor();
        while (true) {
            if (match('*')) v *= factor();
            else if (match('/')) {
                double d = factor();
                if (d == 0) throw std::runtime_error("Деление на ноль");
                v /= d;
            } else return v;
        }
    }
    double factor() {
        double base = power();
        if (match('^')) return std::pow(base, factor());  // правоассоциативно
        return base;
    }
    double power() {
        if (match('-')) return -power();
        if (match('(')) {
            double v = expr();
            if (!match(')')) throw std::runtime_error("Ожидалась ')'");
            return v;
        }
        skip();
        size_t start = pos_;
        while (pos_ < s_.size() && (std::isdigit((unsigned char)s_[pos_]) || s_[pos_] == '.')) ++pos_;
        if (start == pos_) throw std::runtime_error("Ожидалось число");
        return std::stod(s_.substr(start, pos_ - start));
    }
};

int main() {
    std::string line;
    std::cout << "calc> ";
    while (std::getline(std::cin, line)) {
        if (line == "exit") break;
        try {
            std::cout << Parser(line).parse() << '\n';
        } catch (const std::exception& e) {
            std::cout << "Ошибка: " << e.what() << '\n';
        }
        std::cout << "calc> ";
    }
}

Прокачка: добавьте переменные (x = 5), функции sin, sqrt и построение AST вместо немедленного вычисления.

Как скомпилировать и запустить примеры

Все 20 программ собираются стандартом C++20 без предупреждений при флагах -Wall -Wextra.

ПлатформаКоманда
Linux / macOS (GCC)g++ -std=c++20 -O2 -Wall main.cpp -o app && ./app
macOS (Clang)clang++ -std=c++20 -O2 main.cpp -o app && ./app
Windows (MSVC)cl /std:c++20 /EHsc main.cpp
Без установкионлайн-компиляторы Compiler Explorer (godbolt.org) или OnlineGDB

Для программы 17 добавьте флаг -pthread. Если в консоли Windows кириллица превращается в «кракозябры», выполните chcp 65001 перед запуском.

Часто задаваемые вопросы

С какой программы начать, если я новичок?

С калькулятора и «Угадай число»: они закрепляют ввод-вывод, условия и циклы за один вечер.

Какой стандарт C++ учить в 2026 году?

Ориентируйтесь на C++20 — его полностью поддерживают GCC, Clang и MSVC. Возможности C++23 (std::print, std::expected) изучайте вторым шагом.

Сколько времени займёт прохождение всех 20 программ?

При 1–2 часах в день — около месяца, включая задания «Прокачка».

Можно ли добавить эти проекты в портфолио?

Да, если доработаете их: выполните «Прокачку», добавьте CMake, юнит-тесты (GoogleTest или Catch2) и README, затем выложите на GitHub. Лучше всего смотрятся LRU-кэш, Дейкстра и интерпретатор.

Заключение

20 программ из этой статьи ведут от первого std::cin до многопоточности и парсеров — это готовый план практики на месяц. Не копируйте код вслепую: ломайте его, меняйте и расширяйте.

Сохраните статью в закладки и напишите в комментариях, какая программа далась сложнее всего.

Поделиться:

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *