C/C++ | Вопросы собесов
4.3K subscribers
30 photos
1.05K links
Download Telegram
🤔 Какая есть оптимизация в строках С++?

Стандартный класс std::string в C++ имеет несколько механизмов оптимизации для повышения производительности и уменьшения расхода памяти. Рассмотрим основные из них.

🟠Small String Optimization (SSO) – Оптимизация малых строк
Если строка маленькая (обычно до 15-23 байт, зависит от реализации), std::string не выделяет динамическую память, а хранит символы прямо внутри объекта std::string.
Обычно std::string содержит:
- Указатель на динамическую память
- Размер строки
- Вместимость (capacity)
Но если строка короткая, память не выделяется в куче (heap), а данные хранятся прямо внутри объекта std::string.
#include <iostream>
#include <string>

int main() {
std::string small = "Hello"; // Использует SSO
std::string large = "This is a very long string that will not fit SSO"; // Динамическая память

std::cout << "Small string: " << small << "\n";
std::cout << "Large string: " << large << "\n";
}


🟠Copy-on-Write (COW) – Оптимизация копирования (устарела)
Раньше (до C++11) std::string использовал технику Copy-on-Write (COW), где несколько строк могли разделять одну и ту же память, пока одна из них не изменялась.
Почему отказались?
Не потокобезопасно (если один поток меняет строку, другой может получить неожиданные данные). В C++11 было добавлено std::move, которое делает копирование дешевле. SSO делает COW менее нужной.
std::string a = "Hello";
std::string b = a; // Копия не создается, оба указывают на одну память
b[0] = 'X'; // Теперь создаётся отдельная копия


🟠Move Semantics – Семантика перемещения (C++11)
Вместо дорогостоящего копирования std::string теперь можно перемещать с помощью std::move(), передавая владение ресурсом без копирования.
#include <iostream>
#include <string>

int main() {
std::string a = "Hello, world!";
std::string b = std::move(a); // Перемещение, a становится пустой
std::cout << "b: " << b << "\n"; // "Hello, world!"
}


🟠Reserve() – Избегание частых выделений памяти
Если заранее известно, сколько символов потребуется, можно использовать .reserve(size), чтобы избежать многократных перевыделений памяти.
std::string str;
str.reserve(100); // Сразу выделяет память на 100 символов
for (int i = 0; i < 100; i++) {
str += 'a'; // Не перевыделяет память 100 раз
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Для чего нужно ключевое слово explicit?

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

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

Чтобы взвести (установить) бит в определённое значение (1) в числе, можно использовать побитовую операцию ИЛИ (|). Эта операция позволяет установить конкретный бит в 1, не изменяя остальные биты числа.

🚩Как это работает

Операция ИЛИ (|) сравнивает каждый бит двух чисел. Если хотя бы один из битов в соответствующей позиции равен 1, результат в этой позиции будет 1. Иначе, результат будет 0.

🚩Шаги

1⃣Создаём маску, которая имеет единицу в позиции n и нули в остальных позициях. Это можно сделать с помощью выражения 1 << n.
2⃣Применяем операцию ИЛИ между числом x и маской.

#include <iostream>

int main() {
int x = 0b00001010; // Число, с которым работаем (10 в десятичной системе)
int n = 3; // Позиция бита, которую хотим установить (начиная с 0)

// Создаём маску с единицей в позиции n
int mask = 1 << n;

// Устанавливаем бит в позиции n
x = x | mask;

// Вывод результата
std::cout << "Результат: " << std::bitset<8>(x) << std::endl; // Двоичный вывод
std::cout << "Результат: " << x << std::endl; // Десятичный вывод

return 0;
}


1 << n создает маску, сдвигая 1 влево на n позиций. Например, если n = 3, результат будет 0b00001000. Операция x | mask устанавливает бит в x на позиции n в 1. Если бит в x на позиции n уже был 1, он останется 1, если он был 0, то станет 1.

🚩Пример вывода

Если x было 0b00001010 и n = 3, результат будет:
Маска: 0b00001000
x | mask: 0b00001010 | 0b00001000 = 0b00001010
В результате бит в позиции 3 установлен в 1, и итоговое значение числа в двоичном формате 0b00001010 (десятичное 10).

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

1. При коллизии несколько ключей имеют одинаковый хеш-код.
2. Контейнер использует методы разрешения коллизий:
o Связанные списки (chaining): все элементы с одним хешем добавляются в связанный список внутри одного bucket'а.
o Открытая адресация: поиск свободной ячейки для хранения данных.
3. После нахождения bucket'а выполняется проверка на равенство ключей с помощью метода equals.


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

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

🚩Основные применения

🟠Аппаратные регистры
Переменные, связанные с аппаратными устройствами, такими как порты ввода-вывода, часто меняются вне контроля программы. Использование volatile предотвращает оптимизации, которые могли бы кэшировать значение регистров.
volatile int* port = reinterpret_cast<int*>(0x400);
*port = 42; // Запись в аппаратный регистр


🟠Переменные, изменяемые прерываниями
В системах реального времени и встроенных системах значения переменных могут изменяться в обработчиках прерываний. volatile гарантирует, что каждое обращение к такой переменной будет действительным.
volatile bool interruptFlag = false;

void interruptHandler() {
interruptFlag = true; // Устанавливается в обработчике прерывания
}

void mainFunction() {
while (!interruptFlag) {
// Ожидание установки флага прерывания
}
// Обработка прерывания
}


🟠Многопоточные приложения
Переменные, которые могут изменяться из других потоков, также могут быть помечены как volatile. Однако следует отметить, что volatile не обеспечивает защиту от гонок данных и не заменяет средства синхронизации, такие как мьютексы или атомарные операции.
volatile bool stopThread = false;

void workerThread() {
while (!stopThread) {
// Выполнение работы
}
}

int main() {
std::thread t(workerThread);
// ...
stopThread = true; // Остановка рабочего потока
t.join();
return 0;
}


🚩Особенности и ограничения

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

🟠Не потокобезопасность
volatile не обеспечивает потокобезопасность. Для обеспечения корректной работы в многопоточной среде следует использовать мьютексы, атомарные операции или другие механизмы синхронизации.

🟠Совместное использование с `const`
Переменные могут быть как const volatile, если они не должны изменяться программой, но могут изменяться внешними факторами.

🚩Пример: Аппаратные регистры

#include <iostream>

volatile int* hardwareRegister = reinterpret_cast<int*>(0x400);

void writeToRegister(int value) {
*hardwareRegister = value; // Запись в регистр
}

int readFromRegister() {
return *hardwareRegister; // Чтение из регистра
}

int main() {
writeToRegister(100);
std::cout << "Register value: " << readFromRegister() << std::endl;
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Forwarded from Идущий к IT
🔥 Записал видос "Как за 3 минуты настроить Автоотклики на вакансии HeadHunter" больше не придется заниматься этой унылой рутиной

📺 Видео: https://youtu.be/G_FOwEGPwlw
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как можно проверить, что нет double exception?

Используйте функцию std::uncaught_exceptions() (C++17) или std::uncaught_exception() (до C++17) для проверки активного исключения.

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

Чтобы класс мог использоваться в качестве ключа в ассоциативных контейнерах (например, в std::map или std::unordered_map), он должен удовлетворять определённым требованиям.

🚩Ассоциативные контейнеры на основе дерева

🟠Оператор сравнения
Класс должен иметь определённый оператор сравнения. По умолчанию, std::map и std::set используют оператор operator< для сравнения ключей. Это необходимо, чтобы контейнер мог упорядочивать ключи. В этом примере класс MyKey имеет перегруженный оператор operator<, что позволяет использовать его в качестве ключа в std::map.
#include <iostream>
#include <map>

class MyKey {
public:
int value;

MyKey(int v) : value(v) {}

bool operator<(const MyKey& other) const {
return value < other.value;
}
};

int main() {
std::map<MyKey, std::string> myMap;
myMap[MyKey(1)] = "one";
myMap[MyKey(2)] = "two";

for (const auto& pair : myMap) {
std::cout << pair.first.value << ": " << pair.second << std::endl;
}

return 0;
}


🚩Неупорядоченные ассоциативные контейнеры

🟠Функция хэширования
Класс должен быть хэшируемым. Это означает, что для него должна быть определена функция хэширования. В стандартной библиотеке C++ можно определить специализированный шаблон std::hash для вашего класса.

🟠Операторы сравнения на равенство
Класс должен иметь определённый оператор operator==. В этом примере для класса MyKey определён оператор operator== и специализированный шаблон std::hash, что позволяет использовать его в качестве ключа в std::unordered_map.
#include <iostream>
#include <unordered_map>
#include <functional>

class MyKey {
public:
int value;

MyKey(int v) : value(v) {}

bool operator==(const MyKey& other) const {
return value == other.value;
}
};

namespace std {
template <>
struct hash<MyKey> {
std::size_t operator()(const MyKey& k) const {
return std::hash<int>()(k.value);
}
};
}

int main() {
std::unordered_map<MyKey, std::string> myMap;
myMap[MyKey(1)] = "one";
myMap[MyKey(2)] = "two";

for (const auto& pair : myMap) {
std::cout << pair.first.value << ": " << pair.second << std::endl;
}

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Принцип Open/Closed (открытости/закрытости)?

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

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

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

🚩std::vector

Это динамический массив, который обеспечивает быстрый доступ к элементам по индексу. Вектор хранит свои элементы в непрерывном блоке памяти, что делает возможным обращение к элементам за константное время O(1). Однако, такое хранение делает вставку и удаление элементов в начало или середину вектора менее эффективными (в среднем O(n), где n — количество элементов), так как может потребоваться перемещение большого количества элементов для освобождения места или заполнения пробела.
#include <vector>
#include <iostream>

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // Добавление элемента в конец вектора
std::cout << "Элемент по индексу 3: " << vec[3] << std::endl; // Быстрый доступ к элементам

return 0;
}


🚩std::list

В отличие от std::vector, представляет собой двусвязный список. Каждый элемент содержит данные и указатели на предыдущий и следующий элементы в списке. Это обеспечивает быструю вставку и удаление элементов в любом месте списка, так как требуется только изменение нескольких указателей (O(1)), независимо от размера списка. Однако, доступ к элементам в std::list осуществляется за линейное время O(n), так как для доступа к элементу нужно пройти через указатели от начала или конца списка.
#include <list>
#include <iostream>

int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
lst.push_front(0); // Быстрая вставка в начало списка
lst.push_back(6); // Быстрая вставка в конец списка

for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;

return 0;
}


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

Стандартная библиотека предоставляет набор контейнеров, таких как vector, list, deque, set, map и другие, которые позволяют удобно хранить и управлять данными. Они реализуют разные структуры данных, такие как массивы, списки, деревья и хэш-таблицы, предоставляя эффективные операции для различных сценариев.

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

PIMPL расшифровывается как Pointer to IMPLementation (Указатель на реализацию).
Это паттерн проектирования, который используется в C++ для разделения интерфейса и реализации с целью скрытия деталей реализации и уменьшения зависимости от заголовочных файлов.

🚩Как работает PIMPL?

Вместо того чтобы хранить данные прямо в классе, мы используем указатель на структуру реализации (Impl).
Без PIMPL (проблема: утечка зависимостей)
// File: MyClass.h
#include <string> // Подключаем заголовок, влияет на все файлы

class MyClass {
private:
std::string data; // Прямое хранение данных
public:
MyClass();
void print();
};


С PIMPL (скрываем детали реализации)
// File: MyClass.h
#include <memory>

class MyClass {
private:
struct Impl; // Объявляем, но не определяем
std::unique_ptr<Impl> pImpl; // Умный указатель на реализацию
public:
MyClass(); // Конструктор
~MyClass(); // Деструктор
void print();
};

// File: MyClass.cpp
#include "MyClass.h"
#include <iostream>
#include <string>

// Определяем реализацию
struct MyClass::Impl {
std::string data = "Hello, PIMPL!";
void print() { std::cout << data << std::endl; }
};

// Реализация методов
MyClass::MyClass() : pImpl(std::make_unique<Impl>()) {}
MyClass::~MyClass() = default;
void MyClass::print() { pImpl->print(); }


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

Коллизия в хеш-таблицах возникает, когда два разных ключа имеют одинаковое хэш-значение.
Методы разрешения коллизий:
1. Цепочки (chaining): элементы с одинаковым хэш-значением хранятся в связанном списке или другой структуре.
2. Открытая адресация (open addressing): ищется следующая доступная ячейка для хранения элемента.
Коллизии снижают производительность, поэтому важно выбирать хорошие хэш-функции.


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

Range-based for loop – это упрощённый цикл for, который позволяет перебирать элементы контейнера (std::vector, std::array, std::map, std::set и т. д.) без индексов и итераторов.
for (auto element : container) {
// Действие с element
}


🚩Как это работает внутри?

Простой пример с std::vector
#include <iostream>
#include <vector>

int main() {
std::vector<int> v = {1, 2, 3, 4, 5};

for (int x : v) { // Перебираем все элементы вектора
std::cout << x << " ";
}
}


Вывод
1 2 3 4 5


Как работает этот цикл?
Компилятор превращает его в обычный for с итератором:
for (auto it = v.begin(); it != v.end(); ++it) {
int x = *it; // Копируем элемент
std::cout << x << " ";
}


🚩Передача по ссылке (`&`) и по значению (`=`)

Передача по значению (=) – создаёт копию элемента
for (int x : v) { // x - копия элемента
x = 100; // НЕ изменит вектор!
}


Передача по ссылке (&) – изменяет оригинал
for (int& x : v) { // x - ссылка на элемент
x *= 2; // Изменит оригинальный вектор!
}


🚩Работает со всеми контейнерами STL

С std::map
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) { // structured binding (C++17)
std::cout << key << " -> " << value << "\n";
}


С std::set
std::set<int> s = {1, 2, 3, 4};
for (int x : s) { std::cout << x << " "; }


🚩Работает с `std::initializer_list`

for (int x : {10, 20, 30}) {
std::cout << x << " ";
}


Вывод
10 20 30


🚩Как работает range-based for с обычными массивами?

int arr[] = {1, 2, 3};
for (int x : arr) { std::cout << x << " "; }

Работает так же, как и с `std::vector`!

Работает с пользовательскими классами (если есть begin() и end())
class MyContainer {
int data[3] = {10, 20, 30};
public:
int* begin() { return data; }
int* end() { return data + 3; }
};

int main() {
MyContainer c;
for (int x : c) { std::cout << x << " "; }
}


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

При работе с кодом в хидерах возможны проблемы, связанные с повторным включением файлов (multiple inclusion), что может вызвать ошибки компиляции. Это решается использованием включающих защит (#pragma once или #ifndef). Также код в хидере увеличивает время компиляции, так как включается в несколько исходных файлов.

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

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

🟠Внимательная проверка кода (Code Review)
Перед тем как запускать код, полезно просмотреть его вручную или с коллегами. Это помогает:
Найти логические ошибки
Улучшить читаемость кода
Проверить соответствие код-стайлу
// Опытный разработчик может заметить, что деление на 0 возможно
int divide(int a, int b) {
return a / b; // Потенциальная ошибка, если b == 0
}


Лучше добавить проверку
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Деление на ноль!");
}
return a / b;
}


🟠Юнит-тестирование (Unit Testing)
Тесты проверяют каждую функцию отдельно, чтобы убедиться, что она работает правильно. В C++ используют Google Test (gtest), Catch2 или Boost.Test.
#include <gtest/gtest.h>
#include "math_utils.h"

TEST(DivideFunctionTest, HandlesZeroDenominator) {
EXPECT_THROW(divide(10, 0), std::runtime_error);
}

TEST(DivideFunctionTest, NormalCase) {
EXPECT_EQ(divide(10, 2), 5);
}


🟠Отладка (Debugging)
Если код работает не так, как ожидалось, используют отладчики (GDB, LLDB, Visual Studio Debugger). Они позволяют пошагово исполнять код и смотреть, где происходит ошибка.
g++ -g main.cpp -o program  # Компилируем с отладочной информацией
gdb ./program # Запускаем отладчик


🟠Статический анализ кода
Используются специальные инструменты, которые автоматически ищут ошибки в коде:
Clang-Tidy
Cppcheck
SonarQube
cppcheck --enable=all my_code.cpp


🟠Динамический анализ (Sanitizers, Valgrind)
Эти инструменты помогают находить утечки памяти, переполнения буфера и другие ошибки.
g++ -fsanitize=address -g main.cpp -o program
./program # Если есть проблемы, они будут найдены


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

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

Оператор new в C++ выделяет память из кучи (heap) для хранения объекта и возвращает указатель на эту область памяти. Помимо выделения памяти, new также вызывает конструктор объекта, если он определен. В случае, если не хватает памяти для выделения, new выбрасывает исключение std::bad_alloc. Для освобождения памяти, выделенной через new, необходимо использовать оператор delete.

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

МVC (Model-View-Controller) – это классический паттерн разделения логики приложения, но у него есть недостатки, особенно в сложных проектах.

🚩Запутанные связи между компонентами

Взаимодействие между Model, View и Controller может стать сложным. Когда модель изменяется, нужно обновлять все представления (View), которые её используют. Если контроллер слишком тесно связан с представлением, код становится сложно поддерживать.
В сложных GUI-приложениях (например, Qt, MFC) модель может изменяться из разных мест, и контроллеру трудно управлять обновлениями.

🚩Слабая масштабируемость в больших проектах

MVC не даёт чёткого разделения ответственности, если приложение очень сложное.
Один контроллер может управлять сразу несколькими представлениями, что создаёт запутанный код. Часто приходится создавать "промежуточные" контроллеры для разруливания логики → код становится сложнее.
В сложных веб-приложениях (React, Angular) MVC превращается в "спагетти", потому что представления (View) и контроллеры (Controller) начинают выполнять логические задачи, которые должны быть в модели.
Использовать слоистую архитектуру (Layered Architecture) или Service Layer.

🚩"Толстый" контроллер (Fat Controller)

Если бизнес-логика попадает в контроллер, он становится раздутым и сложно поддерживаемым. Контроллер начинает обрабатывать данные, проверять их, а не просто передавать управление.
class UserController {
public:
void login(std::string username, std::string password) {
if (username.empty() || password.empty()) {
std::cout << "Ошибка: пустые данные\n";
return;
}

if (username == "admin" && password == "1234") {
std::cout << "Вход выполнен!\n";
} else {
std::cout << "Неверный логин/пароль\n";
}
}
};


Здесь контроллер сам проверяет данные и логику аутентификации, что нарушает принцип разделения ответственности (SRP).
Вынести бизнес-логику в Model или Service.
Использовать слоистую архитектуру (Service Layer, Repository Pattern).
class AuthService {
public:
bool authenticate(const std::string& username, const std::string& password) {
return (username == "admin" && password == "1234");
}
};

class UserController {
AuthService authService;
public:
void login(const std::string& username, const std::string& password) {
if (authService.authenticate(username, password)) {
std::cout << "Вход выполнен!\n";
} else {
std::cout << "Неверный логин/пароль\n";
}
}
};


🚩Трудности тестирования (особенно View и Controller)

Model тестируется легко, потому что она "чистая" (без UI-кода).
Controller и View сложно тестировать, потому что они зависят от пользовательского ввода и интерфейса.
Unit-тестирование UI-кода практически невозможно, требуется мокирование.

🚩Не всегда подходит для многопоточных приложений

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

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

Если у базового класса есть хотя бы одна виртуальная функция, то у него создается одна таблица виртуальных функций (vtable). У производного класса также создается своя vtable, если он переопределяет виртуальные методы или добавляет новые.
- Если производный класс не добавляет новых виртуальных функций, он использует vtable родительского класса.
- Если переопределяет методы, создается отдельная vtable для производного класса.
Таким образом, в общем случае будет две таблицы vtable – по одной для каждого класса.


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

В unordered_set, который основан на хеш-таблице, самый худший случай поиска возникает, когда структура данных деградирует до такой степени, что время поиска становится линейным, то есть \(O(n)\), где \(n\) — количество элементов в контейнере. Это происходит по нескольким причинам:

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

🟠Высокая степень загрузки
Загрузка хеш-таблицы (load factor) — это отношение количества элементов в хеш-таблице к количеству ведер. Когда load factor становится слишком высоким, вероятность коллизий увеличивается, что также приводит к увеличению длины цепочек в каждом ведре. Хотя unordered_set автоматически увеличивает количество ведер при увеличении количества элементов, в случаях с экстремально высоким load factor поиск может деградировать до линейного.

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

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

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


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