TGViewer
Channel Public Channel
C/C++ | Вопросы собесов

C/C++ | Вопросы собесов

@easy_c_plus

Сайт: https://easyoffer.ru/
Все каналы: t.me/+xGeAw6ckJ4liYzQy

Контакт для рекламы: @sendme_ads
Subscribers
4.19K
Photos
36
Videos
0
Links
1.6K

Showing posts older than #2532 · Back to latest

Older Posts 20 shown
Post #2531 427
🤔 Почему по идее в конструкторе нужны исключения?

Необходимы для того, чтобы сигнализировать о неудачной инициализации объекта. Если инициализация объекта не может быть завершена успешно, конструктор должен сообщить об этом, чтобы предотвратить дальнейшее использование объекта в некорректном состоянии.

🟠Гарантия корректного состояния объекта
Конструктор должен гарантировать, что объект находится в корректном состоянии после его создания. Если возникает ошибка при инициализации, использование объекта в некорректном состоянии может привести к непредсказуемым результатам и трудноотлаживаемым ошибкам. Если подключение к базе данных не удалось, исключение сигнализирует о неудачной инициализации.
class DatabaseConnection {
public:
DatabaseConnection(const std::string& connectionString) {
if (!connect(connectionString)) {
throw std::runtime_error("Connection failed");
}
}
private:
bool connect(const std::string& connectionString) {
// Логика подключения
return false; // Имитация ошибки подключения
}
};


🟠Безопасность ресурсов
Конструкторы часто выделяют ресурсы, такие как память, файлы или сетевые соединения. Если инициализация не удалась, необходимо убедиться, что все выделенные ресурсы будут корректно освобождены. Исключения помогают автоматизировать этот процесс, используя RAII (Resource Acquisition Is Initialization). Если файл не удалось открыть, исключение будет выброшено, и деструктор корректно освободит ресурсы.
class FileHandler {
public:
FileHandler(const std::string& filename) {
file = fopen(filename.c_str(), "r");
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandler() {
if (file) {
fclose(file);
}
}
private:
FILE* file;
};


🟠Целостность программы
Исключения в конструкторах позволяют предотвратить использование некорректно инициализированных объектов. Это важно для поддержания целостности программы и избежания непредсказуемых ошибок. Если загрузка конфигурации не удалась, исключение предотвращает создание некорректного объекта ConfigLoader.
class ConfigLoader {
public:
ConfigLoader(const std::string& configPath) {
if (!loadConfig(configPath)) {
throw std::runtime_error("Failed to load config");
}
}
private:
bool loadConfig(const std::string& configPath) {
// Логика загрузки конфигурации
return false; // Имитация ошибки загрузки
}
};


🚩Обработка исключений в конструкторах

Исключения в конструкторах должны быть обработаны на более высоком уровне программы, чтобы гарантировать корректное завершение программы или повторение попытки инициализации. В этом примере ошибка инициализации обрабатывается в main, что позволяет программе корректно завершить работу или предпринять другие действия.
int main() {
try {
DatabaseConnection db("connection_string");
} catch (const std::exception& e) {
std::cerr << "Initialization failed: " << e.what() << std::endl;
// Дополнительные действия по обработке ошибки
}
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2529 408
🤔 Как можно отсортировать list?

Контейнер std::list предоставляет встроенный метод sort(), который использует алгоритм сортировки слиянием (merge sort) с временной сложностью O(n log n). Также можно использовать std::vector, скопировав в него данные, отсортировав с std::sort(), а затем записав обратно в list.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2528 456
🤔 Для чего используется ключевое слово 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;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2526 449
🤔 Как называется возможность вызова функции без явного указания namespace?

Такую возможность называют ADL (Argument-Dependent Lookup) или Koenig Lookup. Компилятор автоматически ищет функцию в пространстве имен аргумента, если оно не указано явно.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2524 482
🤔 Какие есть тонкости при вызове виртуальной функции из конструктора или деструктора?

Вызов виртуальных функций из конструктора или деструктора в C++ может привести к неожиданным результатам, поскольку полиморфизм в этих контекстах работает иначе. Рассмотрим эти тонкости подробнее.

🚩Поведение виртуальных функций в конструкторах и деструкторах

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

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

🚩Пример для иллюстрации

#include <iostream>

class Base {
public:
Base() {
std::cout << "Base constructor\n";
// Вызов виртуальной функции из конструктора
this->virtualFunction();
}

virtual ~Base() {
std::cout << "Base destructor\n";
// Вызов виртуальной функции из деструктора
this->virtualFunction();
}

virtual void virtualFunction() {
std::cout << "Base virtualFunction\n";
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor\n";
}

~Derived() override {
std::cout << "Derived destructor\n";
}

void virtualFunction() override {
std::cout << "Derived virtualFunction\n";
}
};

int main() {
Derived d;
return 0;
}


🤔 Вывод программы:
Base constructor
Base virtualFunction
Derived constructor
Derived destructor
Base destructor
Base virtualFunction


Ставь 👍 и забирай 📚 Базу знаний
Post #2522 461
🤔 Что известно об умных указателях?

Обёртки над обычными указателями, автоматически управляющие временем жизни объекта.
- std::unique_ptr — единственный владелец, нельзя копировать.
- std::shared_ptr — подсчёт ссылок, разделённое владение.
- std::weak_ptr — не влияет на счётчик shared_ptr, нужен для избежания циклов.
Плюсы:
- Безопасное управление памятью.
- Нет утечек при правильном использовании.
- Легче соблюдать RAII.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2521 481
🤔 Какие знаешь алгоритмы реализации коллизии?

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

🚩Методы открытой адресации (Open Addressing)

🟠Линейное пробирование (Linear Probing)
Просто идём вперёд (с фиксированным шагом 1), пока не найдём свободное место.
Хешируем key1, попадаем в index = 3 → занято.
Проверяем index = 4 → занято.
Проверяем index = 5 → свободно, вставляем!
int hash(int key, int size) {
return key % size;
}

int linearProbe(int key, int size, int table[]) {
int index = hash(key, size);
while (table[index] != -1) { // -1 означает пустую ячейку
index = (index + 1) % size; // Двигаемся вперёд
}
return index;
}


🟠Квадратичное пробирование (Quadratic Probing)
Идём по квадратичному шагу: +1², +2², +3², …
index = (hash(key) + i²) % size;


🟠Двойное хеширование (Double Hashing)
Если ячейка занята, используем вторую хеш-функцию для поиска нового места.
index = (hash1(key) + i * hash2(key)) % size;


🚩Методы цепочек (Chaining)

🟠Связный список (Separate Chaining)
Каждая ячейка – это список (обычно std::list), в который добавляются элементы с одинаковым хешем.
#include <iostream>
#include <list>
#include <vector>

class HashTable {
std::vector<std::list<int>> table;
int size;
public:
HashTable(int s) : size(s), table(s) {}

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

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

int main() {
HashTable ht(5);
ht.insert(10);
ht.insert(15);
ht.insert(20);
ht.insert(25);
ht.display();
}


🟠Хеширование с ко-хешированием (Coalesced Hashing)
Комбинация цепочек и открытой адресации:
В таблице хранятся указатели на следующий элемент с таким же хешем.
Не требует выделения памяти для списков.

Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2519 478
🤔 В чем разница vector и list?

Vector в C++ представляет собой динамический массив с последовательным размещением элементов в памяти, что обеспечивает быстрый доступ по индексу. List — это двусвязный список, где каждый элемент хранит указатели на соседние элементы, что обеспечивает быструю вставку и удаление. Вставка и удаление в vector могут быть медленными из-за необходимости сдвига элементов, тогда как в list такие операции происходят быстрее. Однако доступ по индексу в list требует обхода элементов, что делает его медленнее для случайного доступа.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 2
Post #2518 500
🤔 Какая есть оптимизация в строках С++?

Стандартный класс 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 раз
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2517 487
🤔 Сколько будет таблиц виртуальных функций у двух классов, участвующих в наследовании?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2515 493
🤔 Расскажи как запретить от него наследоваться

В 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; // Закрытый деструктор
};


Ставь 👍 и забирай 📚 Базу знаний
Post #2513 508
🤔 Что представляют из себя статические библиотеки?

Статические библиотеки — это файлы, содержащие скомпилированный код и связанные функции, которые компилятор включает в конечный исполняемый файл. Они имеют расширение .lib (Windows) или .a (Linux) и увеличивают размер программы, но не требуют наличия библиотеки на целевой системе.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 1
Post #2511 563
🤔 В каком порядке элементы списка инициализируются в конструкторе после двоеточия?

Порядок инициализации элементов списка инициализации конструктора (initializer list) определяется порядком объявления членов класса, а не порядком, указанным в списке инициализации. Это важно понимать, так как неправильный порядок может привести к неожиданным ошибкам, особенно при инициализации зависимых членов.

#include <iostream>

class MyClass {
private:
int a;
int b;
int c;

public:
MyClass(int x, int y, int z) : c(z), b(y), a(x) {
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}
};

int main() {
MyClass obj(1, 2, 3);
return 0;
}


🚩Список инициализации конструктора

В конструкторе MyClass список инициализации указан в порядке c(z), b(y), a(x). Однако это не влияет на порядок инициализации членов класса. Члены класса будут инициализированы в порядке их объявления: a, затем b, затем c.

🚩Порядок инициализации

1⃣a инициализируется первым (значение x).
2⃣b инициализируется вторым (значение y).
3⃣c инициализируется третьим (значение z).

🚩Вывод программы

Программа выведет
a: 1, b: 2, c: 3


🚩Почему это важно

Неправильный порядок в списке инициализации не приведет к ошибке компиляции, но может вызвать логические ошибки, особенно если один член зависит от другого. Рассмотрим пример с зависимыми членами:
class MyClass {
private:
int a;
int& ref;

public:
MyClass(int x) : ref(a), a(x) { // Неправильный порядок: ref инициализируется до a
std::cout << "a: " << a << ", ref: " << ref << std::endl;
}
};


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 2
Post #2509 486
🤔 Сколько места занимают bool, char, short, int, long long, float, double?

- bool: 1 байт.
- char: 1 байт.
- short: 2 байта.
- int: 4 байта.
- long long: 8 байт.
- float: 4 байта.
- double: 8 байт.
Эти значения могут варьироваться в зависимости от платформы и компилятора.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 💊 3
Post #2503 502
🤔 Что необходимо иметь классу, что бы использовать его в качестве ключа к ассоциативным контейнерам?

Чтобы класс мог использоваться в качестве ключа в ассоциативных контейнерах (например, в 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;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2501 506
🤔 Как работает new?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2499 550
🤔 Почему не сделать стек вызова очень большим?

Может показаться логичным способом избежать переполнения стека, но в реальности это решение приводит к проблемам

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

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

🟠Сложности с отладкой
Большие стеки могут затруднить обнаружение ошибок и утечек памяти. Например, если стек слишком велик, ошибка переполнения стека может не проявиться сразу, что усложняет диагностику проблемы.

🟠Влияние на производительность
Большие стеки могут повлиять на производительность системы из-за увеличения времени переключения контекста (context switching) и необходимости резервирования большого объема памяти при каждом запуске потока.

🚩Как оптимально настроить?

1⃣Анализ программы
Оцените потребности вашей программы в памяти. Измерьте, сколько памяти реально требуется для выполнения типичных задач.

2⃣Тестирование
Проведите тесты с различными размерами стека, чтобы определить оптимальный баланс между достаточным размером стека и эффективным использованием памяти.

3⃣Использование рекурсии с осторожностью
Если ваш код активно использует рекурсию, убедитесь, что глубина рекурсии контролируется и не приводит к переполнению стека.
#include <pthread.h>
#include <stdio.h>

void* myThreadFunction(void* arg) {
// Код потока
return NULL;
}

int main() {
pthread_t thread;
pthread_attr_t attr;
size_t stacksize;

// Инициализация атрибутов
pthread_attr_init(&attr);

// Установка размера стека
stacksize = 16 * 1024 * 1024; // 16 МБ
pthread_attr_setstacksize(&attr, stacksize);

// Создание потока
pthread_create(&thread, &attr, myThreadFunction, NULL);

// Ожидание завершения потока
pthread_join(thread, NULL);

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2497 467
🤔 Что такое back_inserter, зачем он нужен?

std::back_inserter — это адаптер итератора из библиотеки STL, который позволяет удобно добавлять элементы в конец контейнера при использовании алгоритмов стандартной библиотеки (например, std::copy, std::transform и т. д.).

Он создает итератор-вставку (inserter iterator), который при попытке записи нового элемента фактически вызывает метод push_back() у контейнера.

🚩Почему он нужен?

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

🚩Пример использования

Без back_inserter (приведет к ошибке!)
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination; // Пустой контейнер

// Ошибка! У destination нет места для элементов
std::copy(source.begin(), source.end(), destination.begin());

return 0;
}


Используем back_inserter (правильный вариант)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination; // Начинаем с пустого контейнера

// Используем back_inserter
std::copy(source.begin(), source.end(), std::back_inserter(destination));

// Вывод результата
for (int num : destination) {
std::cout << num << " ";
}

return 0;
}


Вывод
1 2 3 4 5


🚩Где используется `back_inserter`?

🟠**std::transform – Преобразование элементов**
Применяем функцию ко всем элементам и добавляем результат в новый контейнер
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

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

std::transform(nums.begin(), nums.end(), std::back_inserter(squared),
[](int x) { return x * x; });

for (int num : squared) {
std::cout << num << " ";
}

return 0;
}


Вывод:
1 4 9 16 25


std::unique_copy – Удаление дубликатов
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

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

std::unique_copy(nums.begin(), nums.end(), std::back_inserter(unique_nums));

for (int num : unique_nums) {
std::cout << num << " ";
}

return 0;
}


Вывод:
1 2 3 4 5


Ставь 👍 и забирай 📚 Базу знаний
Post #2495 513
🤔 Какая сложность работы с map и unordered_map?

В std::map и std::unordered_map используются разные структуры данных, поэтому их операции (insert, find, erase) имеют разную сложность.

🚩Почему `std::map` медленнее?

std::map – это самобалансирующееся красно-чёрное дерево, где все операции (insert, find, erase) выполняются за O(log n).
std::map<int, std::string> m;
m[10] = "ten"; // O(log n)
m[5] = "five"; // O(log n)
m[20] = "twenty"; // O(log n)
m.find(5); // O(log n)


Дерево std::map выглядит так
        10
/ \
5 20


🚩Почему `std::unordered_map` быстрее?

std::unordered_map – это хеш-таблица, где insert, find, erase работают за O(1) в среднем.
std::unordered_map<int, std::string> um;
um[10] = "ten"; // O(1)
um[5] = "five"; // O(1)
um[20] = "twenty"; // O(1)
um.find(5); // O(1)


Внутри std::unordered_map выглядит так (разбито по бакетам)
Bucket 0:  ---
Bucket 1: ---
Bucket 2: (10, "ten")
Bucket 3: (5, "five")
Bucket 4: (20, "twenty")


🚩Когда `std::unordered_map` становится медленным (`O(n)`)?

В худшем случае все элементы попадают в один бакет (из-за плохой хеш-функции), тогда поиск превращается в O(n).
std::unordered_map<int, std::string> um;
um[1] = "one";
um[2] = "two";
um[3] = "three";


Ставь 👍 и забирай 📚 Базу знаний
Post #2494 463
🤔 Что будет, если несколько раз вызвать lock?

1. Если используется обычный std::mutex, повторный вызов lock из того же потока вызовет deadlock.
2. Для избежания этой ситуации можно использовать std::recursive_mutex, который позволяет одному потоку многократно блокировать мьютекс


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →