C/C++ | Вопросы собесов – Telegram
C/C++ | Вопросы собесов
4.3K subscribers
30 photos
1.07K links
Download Telegram
🤔 Как называется одинаковый результат после применения хэш функции?

Одинаковый результат после применения хэш-функции называется коллизией.

🚩Что такое коллизия?

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

🚩Почему коллизии возникают?

🟠Ограниченный размер хэш-значений
Хэш-функция генерирует значения фиксированной длины (например, 32 или 64 бита), поэтому существует конечное количество возможных хэшей. Однако количество входных данных, которые можно подать на вход, теоретически бесконечно.
🟠Особенности алгоритма хэширования
Некоторые хэш-функции менее устойчивы к коллизиям из-за их структуры.

🚩Как справляться с коллизиями?

Есть несколько способов обработки коллизий в хэш-таблицах:

🟠Метод цепочек (chaining)
Каждое значение хэша хранит список элементов, которые получили одинаковый хэш. При добавлении нового ключа он просто добавляется в связанный список.
#include <iostream>
#include <list>
#include <vector>
using namespace std;

class HashTable {
int size;
vector<list<int>> table;

public:
HashTable(int s) : size(s), table(s) {}

int hashFunction(int key) {
return key % size;
}

void insert(int key) {
int index = hashFunction(key);
table[index].push_back(key);
}

void display() {
for (int i = 0; i < size; i++) {
cout << i << ": ";
for (int key : table[i]) {
cout << key << " -> ";
}
cout << "NULL" << endl;
}
}
};

int main() {
HashTable ht(5);
ht.insert(10);
ht.insert(15);
ht.insert(20);
ht.insert(7);
ht.insert(2);
ht.display();
return 0;
}


🟠Открытая адресация (open addressing)
При коллизии новое значение записывается в следующую свободную ячейку в таблице. Используются разные стратегии, такие как линейное пробирование, квадратичное пробирование или двойное хэширование.

🟠Использование криптографических хэш-функций
Устойчивые хэш-функции (например, SHA-256) уменьшают вероятность коллизий, но они медленнее и чаще используются в безопасности.

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

Если функция, помеченная как `noexcept`, выбросит исключение, программа вызовет `std::terminate()`, что приведёт к немедленному завершению выполнения. Это связано с тем, что `noexcept` гарантирует, что функция не выбросит исключений, и нарушение этого обещания считается критической ошибкой. Использование `noexcept` позволяет оптимизировать код, так как компилятор может делать определённые оптимизации, полагаясь на то, что исключения не будут выбрасываться. Следует избегать выбрасывания исключений в таких функциях.

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

Контейнеры set и unordered_set представляют собой различные структуры данных, каждая из которых имеет свои особенности по скорости выполнения основных операций, включая поиск. Вот как работают эти контейнеры и какова сложность их операций поиска:

🚩set

Реализуется как сбалансированное двоичное дерево поиска, обычно как красно-черное дерево. Он хранит элементы в отсортированном порядке, что позволяет выполнять двоичный поиск. Сложность поиска: Поиск в нем выполняется за логарифмическое время, \(O(\log n)\), где \(n\) — количество элементов в set. Эта эффективность достигается за счёт использования структуры сбалансированного дерева, которое позволяет быстро делить данные на меньшие сегменты.

🚩unordered_set

Реализуется с использованием хеш-таблицы. Это позволяет, при идеальных условиях, выполнять поиск за константное время. Сложность поиска: В среднем, поиск в нем занимает константное время \(O(1)\). Однако в худшем случае, например, при неудачной работе хеш-функции или при большом количестве коллизий, поиск может деградировать до \(O(n)\). В таких ситуациях все ключи могут оказаться в одной "корзине" или "ведре" (bucket), и для нахождения правильного элемента потребуется просмотреть все элементы в этом ведре.

Для set
#include <iostream>
#include <set>

int main() {
std::set<int> mySet = {5, 3, 9, 1};
auto search = mySet.find(3);
if (search != mySet.end()) {
std::cout << "Found " << *search << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}


Для unordered_set
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> mySet = {5, 3, 9, 1};
auto search = mySet.find(3);
if (search != mySet.end()) {
std::cout << "Found " << *search << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Какая сложность поиска в set и unordered_set?

В set поиск имеет сложность O(log n), так как он реализован как сбалансированное бинарное дерево. В unordered_set сложность поиска в среднем O(1), но в худшем случае (при большом количестве коллизий) может достигать O(n).

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

🟠Управление памятью
Стек: Память в стеке управляется автоматически. Когда вызывается функция, память для её локальных переменных выделяется одним блоком при входе в функцию и освобождается при выходе из неё. Эта операция выполняется за постоянное время (O(1)).
Куча: Память в куче управляется вручную (программистом) или через автоматическое управление памятью (например, сборщик мусора). Выделение и освобождение памяти в куче требуют поиска подходящего блока памяти, что может занимать больше времени (O(log n) или даже O(n)).

🟠Локальность данных
Стек: Данные в стеке расположены компактно и последовательно. Это означает, что доступ к данным будет быстрее из-за лучшего использования кэш-памяти процессора.
Куча: Данные в куче могут быть фрагментированы, что приводит к меньшей эффективности кэширования и увеличению времени доступа.

🟠Предсказуемость
Стек: Память в стеке выделяется и освобождается в строго определённом порядке (LIFO - Last In, First Out). Это делает операции со стеком предсказуемыми и упрощает управление памятью.
Куча: Память в куче может выделяться и освобождаться в произвольном порядке, что приводит к фрагментации и усложняет управление памятью.

🟠Минимизация накладных расходов
Стек: Операции выделения и освобождения памяти на стеке имеют минимальные накладные расходы, так как это просто смещение указателя стека.
Куча: Операции выделения и освобождения памяти в куче требуют более сложных алгоритмов и могут включать в себя дополнительные накладные расходы, такие как управление списками свободных блоков и слияние фрагментов.
#include <iostream>

void stackFunction() {
int stackArray[1000]; // Массив на стеке
// Работа с массивом
}

void heapFunction() {
int* heapArray = new int[1000]; // Массив в куче
// Работа с массивом
delete[] heapArray; // Освобождение памяти
}

int main() {
stackFunction(); // Быстрая работа со стеком
heapFunction(); // Медленная работа с кучей
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Если std::move() не перемещает данные, то что их перемещает?

Это просто явное преобразование объекта в rvalue-ссылку, что позволяет использовать семантику перемещения.
Реальное перемещение выполняется методами, поддерживающими rvalue-ссылки, например, конструктором перемещения или оператором присваивания.


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

При перегрузке оператора = в C++ принято возвращать ссылку на текущий объект (*this). Это позволяет цепочечное присваивание (a = b = c), а также улучшает производительность и удобство использования.

🚩Правильная сигнатура оператора `=`

class MyClass {
public:
MyClass& operator=(const MyClass& other) {
if (this != &other) { // Защита от самоприсваивания
// Копируем данные
}
return *this; // Возвращаем ссылку на текущий объект
}
};


🚩Что даёт возвращение ссылки (`T&`)?

Поддержка цепочки присваивания (a = b = c)
a = b = c;  // Интерпретируется как a = (b = c);


Пример
#include <iostream>

class MyClass {
public:
int value;
MyClass(int v) : value(v) {}

MyClass& operator=(const MyClass& other) {
if (this != &other) { // Защита от самоприсваивания
value = other.value;
}
return *this;
}
};

int main() {
MyClass a(1), b(2), c(3);
a = b = c; // Работает, потому что `b = c` возвращает ссылку на `b`
std::cout << "a: " << a.value << ", b: " << b.value << ", c: " << c.value << std::endl;
}


Вывод
a: 3, b: 3, c: 3


🟠Убираем лишнее копирование (повышаем производительность)
Если оператор = вернёт не ссылку, а объект, то произойдёт лишнее копирование.

Плохо (возвращаем объект, а не ссылку)
MyClass operator=(const MyClass& other) { // ⚠️ Ошибка: возвращаем объект
value = other.value;
return *this; // Здесь создаётся временный объект (лишняя копия!)
}


Хорошо (возвращаем T&)
MyClass& operator=(const MyClass& other) { //  Возвращаем ссылку
value = other.value;
return *this;
}


🚩Поддержка `if (a = b)`

Если оператор = вернёт ссылку, мы можем использовать его в условии:
if ((a = b).value == 42) { // ОК
std::cout << "Присваивание выполнено, a == 42";
}


🚩Что если вернуть `void`?

Если оператор = вернёт void, цепочка a = b = c; не будет работать.
class MyClass {
public:
void operator=(const MyClass& other) { // ⚠️ Ошибка!
value = other.value;
}
};

int main() {
MyClass a, b, c;
a = b = c; // Ошибка: `b = c` возвращает void, а `a = void` не работает!
}


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

В list сложность вставки и удаления — O(1), так как он двусвязный. В vector вставка и удаление в конец — O(1), а в произвольное место — O(n) из-за необходимости сдвига элементов.

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

std::map реализован как самобалансирующееся красно-чёрное дерево (Red-Black Tree).
Вставка (insert)
Поиск (find)
Удаление (erase)
выполняются за O(log n) в худшем и среднем случае.
Однако std::map не имеет амортизированной O(1) сложности, как std::unordered_map.
Амортизированная сложность O(1) появляется в std::unordered_map, но не в std::map!

🚩Как работает балансировка?

Каждая операция (insert, erase) перестраивает дерево так, чтобы его высота оставалась O(log n). Это достигается с помощью поворотов (rotate left, rotate right) и перекраски узлов.
Вставка нового элемента иногда требует перестройки дерева, но в среднем это занимает O(log n).
        10
/ \
5 20


🚩Почему `std::unordered_map` имеет амортизированное O(1)?

В std::unordered_map используется хеш-таблица, где:
Операции insert, find, erase – O(1) в среднем (поиск по хешу).
Но если возникает коллизия (несколько ключей в одном бакете), сложность ухудшается до O(n).
Чтобы избежать O(n), std::unordered_map использует rehash (перехеширование).
При увеличении элементов контейнер расширяет бакеты.
Операции разделяются по разным вызовам → за счёт этого амортизированная сложность O(1).

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3
🤔 Что известно о ключевом слове 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