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(); // собственный метод Dog12. Что такое множественное наследование?
Множественное наследование — это когда класс наследуется сразу от нескольких базовых классов. Это даёт больше гибкости, но может усложнить архитектуру.
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?
| new | malloc |
|---|---|
| Оператор языка 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 автоматически уничтожается при выходе из foo20. Что такое конструктор копирования?
Конструктор копирования — это конструктор, который создаёт новый объект как копию уже существующего объекта того же класса.
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; // double47. Что такое 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 и умных указателей. Если вы уверенно понимаете каждую тему и можете объяснить её на простых примерах, вы будете выглядеть значительно увереннее на техническом собеседовании.