С++ для интервью
37 subscribers
10 photos
Готовься к собеседованиям по C++ эффективно: реальные вопросы, типичные HR уловки, примеры кода и объяснения.
Каждый пост — один шаг ближе к офферу 💼
Download Telegram
⚙️Простой алгоритм для реализации на собеседовании (junior)

🔎 Бинарный поиск
Сложность: O(log n)

❗️Вопрос на собеседовании:

Что будет, если массив не отсортирован?


Ответ: поведение непредсказуемо — алгоритм применим только к отсортированным массивам.

#cpp_algo #cpp_question
💬 Простой вопрос про ООП (Объектно-Ориентированное Программирование) (junior)

Зачем нужен виртуальный деструктор?


Пример виртуального деструктора:
struct Base
{
virtual ~Base() = default;
};


✅ Ответ:
Если в базовом классе нет виртуального деструктора, при удалении через Base* вызывается только базовый деструктор, а деструктор наследника — нет → утечка памяти!

#cpp_question
🧠 Частый вопрос на знание ключевых слов языка

Что делает ключевое слово explicit? (junior)

struct Client {
explicit Client(int x) {}
};

Client a = 5; // ❌ ошибка
Client b(5); // ✅ ок


📘 Ответ: explicit запрещает неявные преобразования.

Используй его, чтобы избежать случайного вызова конструктора при присваивании.

#cpp_question #cpp_tips
💬 auto — зло или добро?

💡 Тема: Осознанное использование auto
🎯 Цель: показать, как auto влияет на читаемость, безопасность и оптимизацию

Когда-то auto использовали для вывода типа переменной, теперь же — почти повсюду.
Но стоит ли всегда полагаться на него?

⚙️ Пример 1 — где auto реально помогает

std::unordered_map<std::string, std::vector<int>> data;
// ...
for (auto& [key, values] : data) {
std::cout << key << " → " << values.size() << "\n";
}

Здесь auto делает код чище и читабельнее.
Без него пришлось бы писать много нечитабельного кода:

for (std::pair<const std::string, std::vector<int>>& entry : data)


⚠️ Пример 2 — где auto вредит

std::vector<int> v = {1, 2, 3};
auto sum = std::accumulate(v.begin(), v.end(), 0.0);


На первый взгляд всё нормально, но sum — это double, а не int.
Если заменить 0.0 на 0, тип поменяется — и можно незаметно получить другую арифметику.

❗️ Иногда auto скрывает ошибки типов, особенно с const, &, && и decltype.

✅ Используй auto, когда:

1) Тип очевиден из правой части;
2) Хочешь избежать дублирования и повысить читаемость.

❌ Не используй auto, когда:

1) Важно сохранить const / &;
2) Выражение возвращает временный объект с неочевидным типом;
3) Тип влияет на поведение.

#cpp_tips
🧠 Мини опрос. Что выведет данный код?
👇 Проходите опрос ниже

#cpp_challenge
Что выведет данный код?
Anonymous Quiz
0%
11
50%
10
50%
Ошибка компиляции
✔️ decltype(auto) — ответ на вопрос:
Как сохранить точный тип выражения, используя ключевое слово auto ?


✨ decltype(auto) — это auto, который уважает тип

int x = 10;

auto f1() {
return x;
} // возвращает int (копию)

decltype(auto) f2() {
return x;
} // возвращает int&


Разница: decltype(auto) сохраняет точный тип выражения, включая &, const и &&.

💡 Итог:
Используй decltype(auto), если не хочешь терять ссылки и cv квалификаторы
⚙️ Современные ключевые слова C++: поведение, ABI (Application Binary Interface) и контроль компиляции

На собеседованиях спрашивают о назначении и применении различных модификаторов, квалификаторов и ключевых слов. Необходимо быстро ориентироваться и понимать, для чего они используются в коде. Разберем примеры таких слов.

🔹 explicit

Что делает:
Запрещает неявные вызовы конструктора или оператора преобразования.

Зачем нужно:
Чтобы не происходило неожиданных преобразований при передаче аргументов.

struct S {
explicit S(int x) {} // нельзя вызвать S s = 10;
};

S a(10); // ✅
S b = 10; // ❌ error: explicit constructor


🧩 Влияет: только на синтаксис, не меняет ABI.


🔹 default

Что делает:
Поручает компилятору сгенерировать дефолтную реализацию конструктора, оператора или деструктора.

struct Foo {
Foo() = default; // явно разрешаем
~Foo() = default; // деструктор по умолчанию
};


💡 Используется, если нужны “нормальные” поведенческие функции после переопределения других.
📦 Не меняет ABI, делает класс тривиальным.

🔹 delete

Что делает:
Запрещает генерацию функции компилятором.

struct Bar {
Bar(int) = delete; // нельзя создать из int
Bar() = default;
};


🧱 Полезно для контроля вызовов.
⚙️ Меняет ABI — удалённая функция исключается из таблицы символов.

🔹 constexpr

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

constexpr int square(int x) {
return x * x;
}

constexpr int n = square(5); // вычисляется на этапе компиляции


💡 С constexpr компилятор проверяет чистоту функции и детерминированность.
⚙️ Влияет на оптимизацию и инициализацию глобальных объектов.

🔹 consteval

Что делает:
Обязывает выполнить функцию только на этапе компиляции.

consteval int id() {
return 42;
}

constexpr int v = id(); // только compile-time


⚠️ Нельзя вызвать во время выполнения.
📦 Часто используется в метапрограммировании и проверках.

🔹 constinit

Что делает:
Гарантирует compile-time инициализацию, но функция может быть не constexpr.

constinit int x = 42;


💡 Позволяет избежать “order of initialization” багов между глобальными объектами.

🔹 noexcept

Что делает:
Объявляет, что функция не выбрасывает исключений.

void f() noexcept; // не может выбросить throw
void g() noexcept(false); // может


⚙️ Меняет ABI:
void f() и void f() noexcept — разные сигнатуры на уровне линковки.

🔹 override

Что делает:
Проверяет, что функция реально переопределяет виртуальную из базового класса.

struct Base { 
virtual void foo();
};

struct Derived : Base {
void foo() override; // ✅
void bar() override; // ❌ error: не виртуальная в базовом
};


⚙️ Не влияет на ABI, но предотвращает ошибки переопределения.

🔹 final

Что делает:
Запрещает дальнейшее переопределение или наследование.

struct A final {}; // нельзя наследовать
struct B {
virtual void f() final;
}; // нельзя переопределять


💡 Помогает компилятору оптимизировать виртуальные вызовы.

🔹 using

Что делает:

Способ унаследовать конструкторы или импортировать перегрузки.

using number_t = int; // синоним для int
number_t a = 42;

struct A {
void foo(int);
};
struct B : A {
using A::foo;
}; // импорт перегрузки


💡 Используется в шаблонах и при работе с зависимыми именами.
🧩 Не влияет на ABI. Используйте аккуратно, не рекомендуется к повсеместному использованию

🔹 concept и requires (C++20)

Что делают:
Определяют ограничения для шаблонов, чтобы типы соответствовали ожидаемым свойствам.

#include<concept>

template <typename M>
concept Integral = std::integral<M>; // M должен быть целочисленным типом

template<typename T>
concept Addable = requires(T a, T b) { a + b; };

template<Addable T>
T sum(T a, T b) { return a + b; }


🧠 Делают код предсказуемым при ошибках компиляции.

✅ Мы разобрали основные ключевые слова, модификаторы и квалификаторы C++. Сохраняйте и делитесь с друзьями. Если хотите еще разборов, пишите в комментариях, а мы подготовим новый пост👇
👇 Попробуйте решить серию интересных быстрых задач с собеседования уровень junior

#cpp_challenge

🧩 Задача 1 — "Кто изменит x?"

int x = 10;

auto f = [x]() mutable {
++x;
return x;
};

std::cout << f() << " " << x << "\n";
💭 Как думаете, что выведет программа?
Anonymous Quiz
0%
10 11
0%
11 11
50%
Ошибка компиляции
50%
11 10
⚙️ Задача 2 — "Кто здесь const?"

int n = 42;
const int& r = n;
auto x = r;
auto& y = r;

++n;
std::cout << x << " " << y << "\n";
🧠 Попробуйте понять, что выведет программа:
Anonymous Quiz
33%
43 43
33%
42 42
17%
42 43
17%
Ошибка компиляции
⚡️ Задача 3 — "Кто владелец строки?"

std::string getName() {
return "Bjarne";
}

auto&& name = getName();
std::cout << name << "\n";
⌨️ Рассмотрим несколько способов создания экземпляра класса. В компаниях используют разный код стайл. Поэтому важно знать все возможные способы.

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

struct Entity 
{
int x = 0, y = 0;
};

int main()
{
Entity entity_1;
Entity entity_2(2);
Entity entity_3(3, 3);
Entity entity_4 { .x = 4 };
Entity entity_5 { .x = 5, .y = 5 };
Entity entity_6 { .y = 6 };
// Entity entity_7 { .y = 7, .x = 7 }; // error
}

#cpp_tips
Please open Telegram to view this post
VIEW IN TELEGRAM
📦 Рассмотрим важный концепт языка C++. Move semantics.

💭Основная идея:
Копировать дорого. Перемещать — почти бесплатно


Современный C++ , начиная с C++11, позволяет не копировать объекты,
а просто передавать владение ресурсом. Это и есть move semantics.

std::string a = "Hello";
std::string b = a; // копирование
std::string c = std::move(a); // перемещение


📋 Копирование → создаёт новую строку, копирует все символы.
🚚 Перемещение → "забирает" указатель у a, а a становится пустой.

⚠️ std::move — это не move, это cast
🧠 std::move не перемещает ничего сам по себе!
Он лишь превращает объект в rvalue-ссылку,
давая компилятору понять:
Я больше не буду использовать этот объект — можешь забирать ресурсы.

❗️Важное правило: после std::move — не трогай объект

std::string s = "Hello";
auto t = std::move(s);
std::cout << s; // ❌


❓ Когда использовать std::move

✅ Возврат временного объекта из функции:

std::string make() {
std::string s = "Data";
return s; // RVO или move
}


✅ При передаче в контейнер:

vec.push_back(std::move(obj));


🚫 Не используем при передаче по const& — const запрещает move!

void f(const std::string& s) {
auto t = std::move(s); // ❌ всё равно копия
}


🎯 Что мы имеет в итоге:

1️⃣ std::move — это rvalue cast, не “перемещение”.

2️⃣ Move-конструктор переносит ресурсы, оставляя источник “пустым”.

3️⃣ После move объект можно уничтожать, но не использовать.

4️⃣ const запрещает move.

Пользуйтесь, друзья, и делитесь со своими товарищами программистами 🤝

#cpp_tips #cpp_question
Please open Telegram to view this post
VIEW IN TELEGRAM
Найдите все ошибки данной реализации структуры в рамках RAII
💡 Правильный ответ: 2 и 3

🤓 Пояснения: Если fopen вернул nullptr, fclose(nullptr) — UB.
Кроме того, копирование File приведёт к двойному закрытию — класс должен быть не копируемым, а перемещаемым.
Please open Telegram to view this post
VIEW IN TELEGRAM