50 важных вопросов для собеседования по C/C++

Проходите собеседования с ИИ помощником Hintsage

Эта статья содержит подборку из 50 важных вопросов для собеседования по C/C++ с подробными и понятными ответами. Каждый ответ не только даёт определение, но и показывает, зачем это нужно на практике и какие ошибки встречаются чаще всего.

Вопросы для начинающих

1. В чем разница между C и C++?

C — процедурный язык программирования. Основная единица организации кода — функции, а данные передаются между ними явно. В языке нет встроенной поддержки объектно-ориентированного программирования, классов, наследования и полиморфизма.

C++ начинался как «C с классами», но сейчас это полноценный мультипарадигменный язык: поддерживает процедурное, объектно-ориентированное и обобщённое (шаблонное) программирование.

  • ООП: в C++ есть классы, наследование, виртуальные функции и полиморфизм. В чистом C всего этого нет.
  • Пространства имен: в C++ есть namespace, в C его нет.
  • Перегрузка: в C++ можно перегружать функции и операторы, в C — нет.
  • Строки: в C используются массивы char с завершающим символом '\\0', в C++ есть класс std::string.
  • Исключения: в C++ есть try / catch / throw, в C — в основном коды ошибок.

На практике C чаще используют для низкоуровневых систем, драйверов и встроенных устройств, а C++ — для крупных приложений, где важны абстракции и архитектура (игры, десктоп, highload-сервисы).

// Простой пример на C
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main(void) {
    printf("%d\n", add(2, 3));
    return 0;
}

// Похожий пример на C++
#include <iostream>

int main() {
    std::cout << (2 + 3) << std::endl;
    return 0;
}

2. Что такое объектно-ориентированное программирование (ООП)?

ООП — это подход, в котором программа описывается через объекты. Объект объединяет данные (поля) и операции над ними (методы). Вместо набора несвязанных функций вы строите модель предметной области: Пользователь, Счёт, Заказ, Игрок и т.д.

  • Инкапсуляция: объект скрывает внутреннее состояние и предоставляет только публичные методы. Это уменьшает связность и помогает избежать несанкционированных изменений.
  • Наследование: создание нового класса на основе существующего. Общая логика помещается в базовый класс, а производные расширяют и уточняют поведение.
  • Полиморфизм: возможность работать с разными типами через единый интерфейс и получать различное поведение в зависимости от реального типа объекта.
  • Абстракция: выделение только важных деталей и скрытие всего лишнего (например, вы работаете с сущностью «Файл», не думая о конкретных системных вызовах).
class Player {
public:
    void move(int dx, int dy) {
        x += dx;
        y += dy;
    }

    void print() const {
        std::cout << "Player at (" << x << ", " << y << ")\n";
    }

private:
    int x = 0;
    int y = 0;
};

3. В чем разница между классом и структурой в C++?

В C++ class и struct почти одинаковы по возможностям: оба могут иметь методы, конструкторы, деструкторы, наследование и т.д. Главное отличие — режим доступа по умолчанию.

  • В class члены по умолчанию private.
  • В struct члены по умолчанию public.
class MyClass {
    int x; // private по умолчанию
public:
    void setX(int val) { x = val; }
};

struct MyStruct {
    int x; // public по умолчанию
};

На практике struct обычно используют для простых структур данных (POD-объекты, конфиги, DTO), а class — для полноценной инкапсуляции и сложной логики.


4. Что такое конструктор?

Конструктор — это специальный метод, который вызывается при создании объекта и отвечает за инициализацию его полей и ресурсов. Конструктор не имеет типа возвращаемого значения и называется так же, как класс.

class Person {
public:
    Person()                   // конструктор по умолчанию
        : name("Unknown"), age(0) {}

    Person(const std::string& n, int a)   // параметризованный конструктор
        : name(n), age(a) {}

private:
    std::string name;
    int age;
};

Важно использовать список инициализации (то, что после двоеточия), особенно для констант, ссылок и сложных объектов. Так вы избегаете лишних операций инициализации и присваивания.


5. Что такое деструктор?

Деструктор — это метод, который вызывается автоматически при уничтожении объекта. Его задача — корректно освободить ресурсы (память, файлы, сокеты и т.п.), которыми владеет объект.

class Buffer {
public:
    Buffer(std::size_t n) {
        data = new int[n];
        size = n;
    }

    ~Buffer() {                 // деструктор
        delete[] data;          // освобождение памяти
    }

private:
    int* data = nullptr;
    std::size_t size = 0;
};

Если класс владеет динамическими ресурсами, деструктор обязателен. В противном случае будут утечки памяти или других ресурсов.


6. Что такое перегрузка функций?

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

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int add(int a, int b, int c) {
    return a + b + c;
}

Нельзя перегружать функции только по возвращаемому типу. Перегрузка делает интерфейс понятнее: одно имя для одной логической операции над разными типами.


7. Что такое перегрузка операторов?

Перегрузка операторов позволяет определять своё поведение для стандартных операторов при работе с пользовательскими типами: например, складывать комплексные числа оператором плюс или выводить объект через оператор <<.

class Complex {
public:
    Complex(double r = 0, double i = 0)
        : real(r), imag(i) {}

    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

private:
    double real;
    double imag;
};

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


8. Что такое виртуальная функция?

Виртуальная функция — это метод базового класса, который может быть переопределён в производных классах и вызывается полиморфно, то есть в зависимости от реального типа объекта, а не типа указателя или ссылки.

class Base {
public:
    virtual void show() {
        std::cout << "Base\n";
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived\n";
    }
};

void print(Base& b) {
    b.show();   // полиморфный вызов
}
Derived d;
Base& ref = d;
print(ref);   // вызовется Derived::show()

Реализация полиморфизма обычно основана на таблице виртуальных функций (vtable), но программисту это знать не обязательно — важнее понимать, когда и зачем использовать виртуальность.


9. Что такое чисто виртуальная функция?

Чисто виртуальная функция — это виртуальный метод, для которого в базовом классе нет реализации. Она объявляется с помощью присваивания нуля и превращает класс в абстрактный.

class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() = 0;  // чисто виртуальная функция
};

Любой класс, содержащий хотя бы одну чисто виртуальную функцию, нельзя инстанцировать. Производные классы обязаны реализовать эту функцию, иначе они тоже становятся абстрактными.


10. Что такое абстрактный класс?

Абстрактный класс — это класс, от которого нельзя создать объект напрямую. Обычно он содержит хотя бы одну чисто виртуальную функцию и описывает общий интерфейс для группы классов.

class AbstractLogger {
public:
    virtual ~AbstractLogger() = default;
    virtual void log(const std::string& msg) = 0; // делает класс абстрактным

    void logInfo(const std::string& msg) {
        log("[INFO] " + msg);
    }
};

Конкретные логгеры (в файл, в консоль, по сети) наследуются от абстрактного логгера и реализуют метод log.


11. Что такое наследование?

Наследование — механизм, который позволяет одному классу (производному) использовать поля и методы другого класса (базового), расширяя или изменяя поведение. Это помогает избегать дублирования кода и явно выражать отношения «является».

class Animal {
public:
    void eat() {
        std::cout << "Eating...\n";
    }
};

class Dog : public Animal {
public:
    void bark() {
        std::cout << "Bark!\n";
    }
};
Dog d;
d.eat();  // унаследовано от Animal
d.bark(); // собственный метод Dog

12. Что такое множественное наследование?

Множественное наследование — это когда класс наследуется сразу от нескольких базовых классов. Это даёт больше гибкости, но может усложнить архитектуру.

class A {
public:
    void funcA() {}
};

class B {
public:
    void funcB() {}
};

class C : public A, public B {
    // имеет и funcA, и funcB
};

Проблема множественного наследования — возможные конфликты имён и ромбовидное наследование, которое обычно решается виртуальным наследованием.


13. Что такое виртуальное наследование?

Виртуальное наследование используется, чтобы общий базовый класс в сложной иерархии («ромб») присутствовал только один раз. Это предотвращает дублирование данных базового класса.

class A {
public:
    int value;
};

class B : virtual public A {};
class C : virtual public A {};

class D : public B, public C {
    // тут только одна A
};

Без virtual базовый класс A оказался бы в D дважды (через B и через C), что приводит к неоднозначностям и ошибкам.


14. В чем разница между указателем и ссылкой?

УказательСсылка
Хранит адрес и может быть перенаправлен.Является другим именем уже существующей переменной.
Может быть нулевым (nullptr).Должна быть инициализирована и не может быть «пустой».
Требует разыменования оператором *.Используется как обычная переменная.
int x = 10;
int* ptr = &x;  // указатель
int& ref = x;   // ссылка

*ptr = 20;      // изменяем x через указатель
ref = 30;       // изменяем x через ссылку

15. Что такое указатель?

Указатель — это переменная, которая хранит адрес другой переменной или области памяти. Указатели используются для динамического выделения памяти, передачи больших объектов без копирования и реализации сложных структур данных.

int x = 42;
int* p = &x;          // p хранит адрес x

std::cout << *p;      // выводит 42
*p = 100;             // изменяем x через указатель

С указателями нужно быть осторожным: неиспользуемые или неверно освобождаемые указатели приводят к утечкам, крашам и неопределённому поведению.


16. Что такое ссылка?

Ссылка — это альтернативное имя существующей переменной. После инициализации ссылка всегда указывает на один и тот же объект и не может быть переназначена.

int x = 5;
int& r = x;    // r — это другое имя x

r = 10;        // теперь x == 10

В C++ ссылки часто используют в параметрах функций и возвращаемых значениях, чтобы избежать лишнего копирования крупных объектов, особенно в виде const T&.


17. Что такое динамическое выделение памяти?

Динамическое выделение памяти — это выделение памяти во время выполнения программы (а не на этапе компиляции). В C++ для этого используются операторы new и delete.

int* p = new int(10);   // один int
delete p;

int* arr = new int[10]; // массив
// ... работаем с arr ...
delete[] arr;           // обязательно delete[]

Нельзя смешивать new/delete и malloc/free. В современном C++ желательно использовать контейнеры STL и умные указатели, а не работать с сырым new/delete напрямую.


18. В чем разница между new и malloc?

newmalloc
Оператор языка C++ (может быть перегружен).Функция из стандартной библиотеки C.
Вызывает конструктор и возвращает указатель нужного типа.Просто выделяет сырую память и возвращает void*.
Освобождается через delete / delete[].Освобождается через free.
// C-стиль
int* p1 = (int*)std::malloc(sizeof(int));
if (p1) {
    *p1 = 10;
    std::free(p1);
}

// C++-стиль
int* p2 = new int(10);
delete p2;

19. Что такое стек и куча?

Стек и куча — это разные области памяти процесса:

  • Стек хранит локальные переменные, параметры функций и адреса возврата. Память выделяется и освобождается автоматически при входе/выходе из функций.
  • Куча (heap) используется для динамического выделения памяти через new/malloc. Управление памятью здесь на вашей совести.
void foo() {
    int a = 10;             // в стеке
    int* p = new int(20);   // в куче
    delete p;               // освобождаем вручную
} // a автоматически уничтожается при выходе из foo

20. Что такое конструктор копирования?

Конструктор копирования — это конструктор, который создаёт новый объект как копию уже существующего объекта того же класса.

class MyClass {
public:
    MyClass(int v) {
        data = new int(v);
    }

    // конструктор копирования
    MyClass(const MyClass& other) {
        data = new int(*other.data); // глубокая копия
    }

    ~MyClass() {
        delete data;
    }

private:
    int* data = nullptr;
};

Если ваш класс владеет ресурсами (память, файл и т.п.), важно реализовать конструктор копирования так, чтобы избежать двойного освобождения или совместного владения одним и тем же сырым указателем.


21. Что такое оператор присваивания?

Оператор присваивания копирует состояние одного уже существующего объекта в другой существующий объект. Для классов, владеющих ресурсом, он должен правильно управлять освобождением старого ресурса и копированием нового.

class MyClass {
public:
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            delete data;                  // освобождаем старое
            data = new int(*other.data);  // копируем новое
        }
        return *this;
    }

    ~MyClass() { delete data; }

private:
    int* data = nullptr;
};

22. Что такое правило трех (Rule of Three)?

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


23. Что такое const в C++?

const делает объект (или его представление) неизменяемым. Можно объявлять константные переменные, параметры, ссылки, указатели и методы. Это повышает безопасность кода и позволяет компилятору лучше оптимизировать.

const int x = 10;

void print(const std::string& s);

class A {
public:
    int get() const {   // const-метод
        return value;
    }
private:
    int value = 0;
};

24. Что такое mutable?

mutable разрешает изменять поле даже в const-методах. Обычно используется для кешей и вспомогательных флагов, которые не считаются частью логического состояния объекта.

class CachedValue {
public:
    int get() const {
        if (!cached) {
            value = compute();
            cached = true;
        }
        return value;
    }

private:
    int compute() const { return 42; }

    mutable bool cached = false;
    mutable int value = 0;
};

25. Что такое friend?

friend даёт внешней функции или классу доступ к приватным и защищённым членам. Это удобно, например, при перегрузке операторов ввода/вывода, но ослабляет инкапсуляцию, поэтому требует аккуратного использования.

class MyClass {
    friend void debug(const MyClass& obj);
private:
    int value = 0;
};

void debug(const MyClass& obj) {
    std::cout << obj.value << "\n";
}

26. Что такое шаблоны (templates)?

Шаблоны — это механизм обобщённого программирования. Позволяют писать функции и классы, которые работают с разными типами, без дублирования кода.

template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int a = maximum(1, 2);
double d = maximum(1.5, 3.0);

27. В чем разница между typename и class в шаблонах?

В списке параметров шаблона typename и class эквивалентны. Разница проявляется в другом контексте: typename также используется для указания зависимых типов внутри шаблонов.


28. Что такое STL?

STL (Standard Template Library) — часть стандартной библиотеки C++, включающая контейнеры, итераторы и алгоритмы. Позволяет быстро собирать сложную функциональность из готовых примитивов.


29. Что такое vector?

std::vector — динамический массив с автоматическим управлением памятью и поддержкой быстрого доступа по индексу.

std::vector<int> v;
v.push_back(10);
v.push_back(20);
std::cout << v[0] << " " << v.size() << "\n";

30. В чем разница между vector и array?

vector имеет динамический размер, array (и обычный C-массив) — фиксированный. vector удобнее и безопаснее, но немного тяжелее по накладным расходам. Обычный массив максимально простой и быстрый, но не растёт и не знает своего размера.


31. Что такое map?

std::map — ассоциативный контейнер, хранящий пары ключ–значение в отсортированном виде. Внутри обычно реализован как сбалансированное дерево.

std::map<std::string, int> ages;
ages["Alice"] = 25;
ages["Bob"] = 30;

32. В чем разница между map и unordered_map?

map хранит элементы отсортированными (дерево, O(log n) операции). unordered_map — хеш-таблица, порядок не гарантирован, операции в среднем O(1), но в худшем O(n).


33. Что такое итератор?

Итератор — обобщённый указатель на элементы контейнера. Через них алгоритмы STL могут работать с разными контейнерами, не зная их внутренней структуры.

std::vector<int> v = {1, 2, 3};
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}

34. Что такое пространство имен (namespace)?

Пространство имён группирует сущности и предотвращает конфликты имён. Стандартная библиотека живёт в пространстве std.

namespace My {
    void func();
}

My::func();

35. Что такое обработка исключений?

Обработка исключений — механизм, позволяющий отделить основной код от обработки ошибок с помощью конструкций try/catch/throw.

try {
    if (x == 0) {
        throw std::runtime_error("division by zero");
    }
    int res = 10 / x;
} catch (const std::exception& e) {
    std::cerr << e.what() << "\n";
}

36. Что такое RTTI (Run-Time Type Information)?

RTTI позволяет во время выполнения определить реальный тип объекта. Используется через dynamic_cast и typeid и работает только с полиморфными типами (с виртуальными методами).

Base* p = new Derived;
if (auto d = dynamic_cast<Derived*>(p)) {
    // безопасно использовать Derived
}
delete p;

37. В чем разница между static_cast, dynamic_cast, const_cast и reinterpret_cast?

  • static_cast — обычные, относительно безопасные приведения совместимых типов.
  • dynamic_cast — безопасный downcast в иерархии наследования.
  • const_cast — добавление или снятие const/volatile.
  • reinterpret_cast — низкоуровневое переинтерпретирование битов, максимально опасное.

38. Что такое inline функция?

inline — подсказка компилятору, что функцию можно встроить в место вызова. В современном C++ чаще используется для функций в заголовках, чтобы избежать множественного определения при линковке.

inline int add(int a, int b) {
    return a + b;
}

39. Что такое статическая переменная?

Статическая переменная живёт всё время работы программы (или модуля) и сохраняет значение между вызовами функции.

void foo() {
    static int counter = 0;
    ++counter;
    std::cout << counter << "\n";
}

40. Что такое статический член класса?

Статический член класса принадлежит всему классу, а не отдельному объекту. Существует одна общая копия для всех экземпляров.

class MyClass {
public:
    static int count;
    MyClass() { ++count; }
};

int MyClass::count = 0;

41. Что такое статическая функция-член?

Статическая функция-член не имеет указателя this и может вызываться без объекта: через ClassName::func(). Она имеет доступ только к статическим членам напрямую.

class Util {
public:
    static void printHello() {
        std::cout << "Hello\n";
    }
};

Util::printHello();

42. Что такое указатель this?

this — неявный указатель на текущий объект внутри нестатических методов. Используется для доступа к полям и для возвращения ссылки на себя в цепочках вызовов.

class A {
public:
    A& setX(int x) {
        this->x = x;
        return *this;
    }
private:
    int x = 0;
};

43. Что такое умные указатели (smart pointers)?

Умные указатели (unique_ptr, shared_ptr, weak_ptr) — RAII-обёртки над сырыми указателями, которые автоматически освобождают ресурсы при выходе из области видимости.

auto p = std::make_unique<int>(10);
auto sp = std::make_shared<int>(20);

44. В чем разница между unique_ptr и shared_ptr?

unique_ptr реализует единоличное владение ресурсом (нельзя копировать, только перемещать). shared_ptr реализует разделяемое владение с подсчётом ссылок. unique_ptr дешевле по памяти и CPU, shared_ptr удобен, когда ресурс реально разделяется.


45. Что такое lambda-функция?

Лямбда — анонимная функция, которую можно определить прямо в месте использования. Может захватывать переменные из внешней области видимости.

auto sum = [](int a, int b) { return a + b; };
int r = sum(2, 3);

46. Что такое auto?

auto просит компилятор вывести тип переменной из выражения инициализации. Тип по-прежнему статический и известен на этапе компиляции.

auto x = 10;      // int
auto d = 3.14;   // double

47. Что такое nullptr?

nullptr — специальное значение нулевого указателя в C++11+. Имеет тип std::nullptr_t, не путается с целочисленным нулём и лучше, чем 0/NULL.

int* p = nullptr;
if (p == nullptr) {
    // указатель ни на что не указывает
}

48. Что такое перегрузка операторов ввода/вывода?

Перегружая операторы << и >>, можно задать, как объекты класса будут выводиться в потоки и считываться из них.

class Point {
public:
    int x = 0;
    int y = 0;
};

std::ostream& operator<<(std::ostream& os, const Point& p) {
    return os << "(" << p.x << ", " << p.y << ")";
}

49. Что такое виртуальный деструктор?

Виртуальный деструктор гарантирует, что при удалении объекта производного класса через указатель на базовый будет вызван деструктор производного. Это критично для корректного освобождения ресурсов.

class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    ~Derived() {
        // освобождение ресурсов Derived
    }
};

50. Что такое RAII (Resource Acquisition Is Initialization)?

RAII — идиома C++, при которой ресурс (память, файл, мьютекс) захватывается в конструкторе и освобождается в деструкторе. Время жизни ресурса жёстко связано со временем жизни объекта.

class File {
public:
    File(const char* name) {
        f = std::fopen(name, "r");
        if (!f) throw std::runtime_error("cannot open file");
    }

    ~File() {
        if (f) std::fclose(f);
    }

private:
    std::FILE* f = nullptr;
};

Умные указатели, lock_guard, потоки и многие другие компоненты стандартной библиотеки опираются на RAII, делая код безопасным даже при исключениях.

Заключение

Эти 50 вопросов покрывают основу современного C/C++: от базового синтаксиса до RAII и умных указателей. Если вы уверенно понимаете каждую тему и можете объяснить её на простых примерах, вы будете выглядеть значительно увереннее на техническом собеседовании.