C/C++ | Вопросы собесов – Telegram
C/C++ | Вопросы собесов
4.3K subscribers
30 photos
1.07K links
Download Telegram
🤔 Что известно о ключевом слове override?

Ключевое слово override используется для явного указания, что метод переопределяет виртуальный метод базового класса. Оно предотвращает ошибки, связанные с неправильным именованием или сигнатурой методов.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🤔1
🤔 Для чего может применяться в С++ виртуальное наследование?

Виртуальное наследование применяется для решения проблемы "ромбовидного наследования" (или "diamond problem"), которая возникает в иерархиях классов с множественным наследованием. Это проблема возникает, когда класс наследуется от двух классов, которые в свою очередь наследуются от одного общего базового класса. В результате создаются две копии базового класса в наследнике, что приводит к неоднозначностям и увеличению использования памяти.

#include <iostream>

class A {
public:
void show() {
std::cout << "Class A" << std::endl;
}
};

class B : public A {};
class C : public A {};
class D : public B, public C {};

int main() {
D obj;
// obj.show(); // Ошибка: неоднозначность, show() есть и в B, и в C
return 0;
}


🚩Решение проблемы с использованием виртуального наследования

Виртуальное наследование решает эту проблему, гарантируя, что в иерархии будет только одна копия базового класса. Это достигается с помощью ключевого слова virtual при наследовании.
#include <iostream>

class A {
public:
void show() {
std::cout << "Class A" << std::endl;
}
};

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

int main() {
D obj;
obj.show(); // Корректно: вызов метода show() из A
return 0;
}


🚩Как работает виртуальное наследование

🟠Одна копия базового класса
При виртуальном наследовании в наследуемом классе создается только одна копия базового класса, независимо от того, сколько раз он виртуально наследуется.

🟠Оптимизация памяти
Виртуальное наследование предотвращает создание множества копий базового класса, что экономит память.

🟠Явное разрешение неоднозначности
Виртуальное наследование устраняет неоднозначности, связанные с вызовом методов и доступом к членам базового класса.

🟠Пример с конструкторами
При виртуальном наследовании конструкторы базовых классов вызываются инициализирующим классом.
#include <iostream>

class A {
public:
A() {
std::cout << "Constructor of A" << std::endl;
}
};

class B : virtual public A {
public:
B() {
std::cout << "Constructor of B" << std::endl;
}
};

class C : virtual public A {
public:
C() {
std::cout << "Constructor of C" << std::endl;
}
};

class D : public B, public C {
public:
D() {
std::cout << "Constructor of D" << std::endl;
}
};

int main() {
D obj; // Вывод: Constructor of A, Constructor of B, Constructor of C, Constructor of D
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Что будет, если для беззнаковой переменной, равной 0, сделать декремент?

Значение переменной перейдёт в максимальное значение типа (например, UINT_MAX для unsigned int).
Это связано с переполнением, так как беззнаковые типы используют арифметику по модулю.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Какие знаешь сортировки?

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

🟠Сортировка пузырьком (Bubble Sort)
Простейший алгоритм, который многократно проходит по массиву, сравнивая соседние элементы и меняя их местами, если они стоят в неправильном порядке.
Сложность: O(n²) в худшем и среднем случаях, O(n) в лучшем случае (если массив уже отсортирован).
Когда использовать: Почти никогда, так как слишком медленный.
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}


🟠Сортировка выбором (Selection Sort)
На каждом шаге ищется минимальный элемент и ставится в начало неотсортированной части массива.
Сложность: O(n²) всегда.
Когда использовать: Если важна простота реализации, но нужна немного лучшая производительность, чем у пузырьковой сортировки.
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
std::swap(arr[i], arr[minIdx]);
}
}


🟠Сортировка вставками (Insertion Sort)
Берём один элемент и вставляем его в правильное место среди уже отсортированных элементов.
Сложность: O(n²) в худшем случае, O(n) в лучшем (если массив почти отсортирован).
Когда использовать: Для небольших массивов или почти отсортированных данных.
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}


🟠Сортировка слиянием (Merge Sort)
Разделяем массив на две части, рекурсивно сортируем их и затем сливаем.
Сложность: O(n log n) всегда.
Когда использовать: Когда нужна стабильность и предсказуемая скорость работы.
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[l + i];
for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i];

int i = 0, j = 0, k = l;
while (i < n1 && j < n2) arr[k++] = (L[i] < R[j]) ? L[i++] : R[j++];
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}

void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как работает хеш таблица?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Чем отличаются STL контейнеры vector и array?

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

🟠Различия в управлении памятью
std::vector использует динамическую память, выделяемую в куче (heap). Его размер может изменяться во время выполнения.
std::array использует статическую память, выделяемую в стеке (stack) или в статической области памяти, и его размер фиксирован на этапе компиляции.
#include <vector>
#include <array>
#include <iostream>

int main() {
std::vector<int> vec = {1, 2, 3}; // Размер может изменяться динамически
vec.push_back(4); // Добавляем новый элемент

std::array<int, 3> arr = {1, 2, 3}; // Размер фиксирован, нельзя добавить новый элемент

std::cout << "Vector size: " << vec.size() << std::endl; // Выведет 4
std::cout << "Array size: " << arr.size() << std::endl; // Выведет 3

return 0;
}


🟠Гибкость и изменение размера
std::vector позволяет изменять размер в процессе работы, автоматически выделяя новую память при необходимости.
std::array имеет фиксированный размер, который нельзя изменить после создания.
std::vector<int> v = {1, 2, 3};
v.push_back(4); // Увеличиваем размер

std::array<int, 3> a = {1, 2, 3};
// a.push_back(4); // Ошибка! У std::array нет метода push_back


🟠Производительность
std::array работает быстрее, так как все данные хранятся в непрерывном участке памяти и нет затрат на динамическое выделение.
std::vector может требовать дополнительное время при изменении размера, так как может потребоваться новое выделение памяти и копирование элементов.
#include <vector>
#include <array>
#include <chrono>
#include <iostream>

int main() {
constexpr int N = 1'000'000;

std::vector<int> vec(N, 1); // Динамический массив
std::array<int, N> arr{}; // Статический массив

auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) vec[i] += 1;
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Vector time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< " us" << std::endl;

start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) arr[i] += 1;
end = std::chrono::high_resolution_clock::now();
std::cout << "Array time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< " us" << std::endl;

return 0;
}


🟠Совместимость с C-API
std::array хранит данные как обычный C-массив, поэтому можно легко передавать его в функции, ожидающие int*.
std::vector использует динамическую память, но можно получить указатель на внутренний буфер с помощью data().
void processArray(int* arr, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
}

int main() {
std::array<int, 3> arr = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};

processArray(arr.data(), arr.size()); // std::array можно передавать в C-функции
processArray(vec.data(), vec.size()); // std::vector тоже можно передавать

return 0;
}


🟠Итераторы и стандартные алгоритмы
Оба контейнера поддерживают итераторы и совместимы со стандартными алгоритмами из #include <algorithm>
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>

int main() {
std::vector<int> vec = {3, 1, 4, 1, 5};
std::array<int, 5> arr = {3, 1, 4, 1, 5};

std::sort(vec.begin(), vec.end());
std::sort(arr.begin(), arr.end());

for (int n : vec) std::cout << n << " "; // 1 1 3 4 5
std::cout << std::endl;
for (int n : arr) std::cout << n << " "; // 1 1 3 4 5

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Когда нужно объявлять деструктор виртуальным?

Деструктор должен быть виртуальным, если класс предназначен для использования в качестве базового, и предполагается полиморфное удаление через указатель (Base* ptr = new Derived; delete ptr;). Без виртуального деструктора деструкторы производных классов не будут вызваны, что приведет к утечке памяти.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
🤔 Зачем используются делиторы в умных указателях?

Делиторы – это специальные функции, которые определяют, как должен уничтожаться объект при освобождении памяти умным указателем.

Обычно std::unique_ptr и std::shared_ptr по умолчанию вызывают delete, но иногда это поведение нужно изменить.

🚩Когда нужны делиторы?

🟠Для работы с нестандартными ресурсами (не `new`)
Например, если объект создан через malloc(), fopen(), CreateFile(), то delete не подходит!
🟠Если нужно логировать или выполнять доп. действия при удалении
Можно добавить std::cout, логику очистки, сброс ресурсов.
🟠Если объект хранится в массиве (`new[]`)
delete не удалит массив корректно, нужно delete[].
🟠Если ресурс должен освобождаться особым способом
Например, в std::shared_ptr можно передать free(), fclose() или CloseHandle().

🚩Как передавать делитор в `std::unique_ptr`?

Пример: освобождение памяти от malloc() через std::free()
#include <iostream>
#include <memory>

int main() {
std::unique_ptr<int, void(*)(void*)> ptr(malloc(sizeof(int)), free);
*reinterpret_cast<int*>(ptr.get()) = 42;

std::cout << "Значение: " << *reinterpret_cast<int*>(ptr.get()) << "\n";
}


Здесь free используется вместо delete, потому что память выделена malloc().
Пример: закрытие файла через std::fclose()
#include <iostream>
#include <memory>
#include <cstdio>

int main() {
std::unique_ptr<FILE, decltype(&std::fclose)> file(fopen("test.txt", "w"), &std::fclose);

if (file) {
std::fprintf(file.get(), "Hello, world!\n");
}
} // `fclose(file)` вызовется автоматически!


🚩Как передавать делитор в `std::shared_ptr`?

В std::shared_ptr можно передавать делитор прямо в конструкторе
#include <iostream>
#include <memory>

void customDeleter(int* ptr) {
std::cout << "Удаляю объект: " << *ptr << "\n";
delete ptr;
}

int main() {
std::shared_ptr<int> ptr(new int(100), customDeleter);
} // В конце `customDeleter(ptr)` вызовется автоматически!


🚩Делитор для массивов (`delete[]`)

По умолчанию std::unique_ptr<int[]> сам вызывает delete[], но если std::unique_ptr<int> используется неправильно, то delete удалит только первый элемент массива!
Ошибка: delete вместо delete[]
std::unique_ptr<int> arr(new int[10]); // ОШИБКА! `delete` вызовет утечку памяти!


Правильный вариант
std::unique_ptr<int[], std::default_delete<int[]>> arr(new int[10]);


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Как проверять равенство двух float?

Равенство двух float проверяется с учётом допустимой разницы (эпсилон), чтобы избежать ошибок из-за неточности представления:
∣a−b∣<ϵ, где ϵ — небольшое значение, например 10−610^{-6}.
Такой подход помогает корректно сравнивать близкие числа, которые могут отличаться в пределах допустимой погрешности.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Расскажи как запретить от него наследоваться

В C++ можно запретить наследование от класса несколькими способами.

🟠Использование `final` (C++11 и новее)
Ключевое слово final запрещает наследование от класса.
class Base final {
public:
void show() { std::cout << "Base class\n"; }
};

// Ошибка! Наследование запрещено
class Derived : public Base {
};


🟠Закрытый или удалённый конструктор копирования и операторы присваивания
Можно сделать так, чтобы класс нельзя было создать или скопировать в унаследованных классах.
class Base {
private:
Base() = default; // Приватный конструктор
};


Можно сделать конструктор protected, если хотим создать объекты внутри класса, но не разрешать наследование снаружи.
class Base {
protected:
~Base() = default; // Деструктор защищённый
};


🟠Закрытый (private) деструктор
Если сделать деструктор private, то класс нельзя будет корректно удалить через указатель.
class Base {
private:
~Base() = default; // Закрытый деструктор
};


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое абстрактный класс?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM