C/C++ | Вопросы собесов
前往频道在 Telegram
Сайт: https://easyoffer.ru/ Все каналы: t.me/+xGeAw6ckJ4liYzQy Контакт для рекламы: @sendme_ads
显示更多4 191
订阅者
-124 小时
-67 天
-730 天
帖子存档
Яндекс Музыка до 360 дней бесплатно
Яндекс Музыка для вас и 3-х ваших близких.
Кинопоиск и Яндекс Книги тоже в мультиподписке Плюс.
Попробуйте бесплатно❤️
Слушать
#реклама 18+
music.yandex.ru
О рекламодателе
🤔 Что будет если для беззнаковой переменной равной 0 сделать декремент?
Если вы сделаете декремент (
--) для беззнаковой переменной, значение которой равно 0, это приведет к переполнению, и переменная примет максимальное значение, которое может быть представлено этим типом.
Рассмотрим, что произойдет с переменной типа unsigned int, если её значение равно 0 и мы применим к ней декремент:
#include <iostream>
int main() {
unsigned int x = 0;
std::cout << "x before decrement: " << x << std::endl;
x--; // Декрементирование беззнаковой переменной 0
std::cout << "x after decrement: " << x << std::endl; // Переполнение
return 0;
}
Для беззнаковой переменной unsigned int: Диапазон значений: от 0 до 4294967295 (для 32-битной реализации).
Когда переменная x равна 0 и мы выполняем x--, происходит следующее:
🟠Значение 0 уменьшается на 1.
🟠Поскольку unsigned int не может представлять отрицательные значения, происходит переполнение.
🟠Переполнение для беззнакового типа приводит к "оборачиванию" значения через максимальное значение типа.
🟠Максимальное значение для unsigned int (при 32-битной реализации) равно 4294967295.
Таким образом, после выполнения x--, значение x становится 4294967295.
Результат выполнения приведенного выше кода будет следующим:
x before decrement: 0 x after decrement: 4294967295Для беззнаковых типов в C++ (например, unsigned char, unsigned short, unsigned int, unsigned long): Если значение переменной равно 0 и к ней применяется декремент, результатом будет максимальное значение этого типа. Ставь 👍 и забирай 📚 Базу знаний
Надежные VDS-сервера в NetAngels от 130₽/месяц
Подберем мощные VDS-сервер для любых задач. Техподдержка 24/7. Защита от DDoS-атак. Гибкая конфигурация.
Бесплатный перенос VDS с сохранением всех данных.
Попробуйте VDS-сервер от NetAngels уже сегодня!
Перейти на сайт
#реклама 16+
netangels.ru
О рекламодателе
🤔 Из чего состоит shared_ptr?
shared_ptr состоит из указателя на объект и счетчика ссылок, управляющего временем жизни объекта. Когда количество ссылок становится нулевым, shared_ptr автоматически удаляет объект. Он также поддерживает совместное использование ресурсов между несколькими владельцами, что делает его удобным для совместного владения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Что такое POD тип ?
POD (Plain Old Data) тип — это простой тип данных в C++, который совместим с C и обладает определенными свойствами, делающими его поведение предсказуемым и простым.
🚩Основные характеристики POD типов
🟠Тривиальность (Trivial)
Тривиальный конструктор (конструктор по умолчанию, копирования и перемещения). Тривиальный деструктор. Компилятор генерирует их автоматически, если они не определены явно.
🟠Стандартная макетируемость (Standard Layout)
Все нестатические члены данных имеют один и тот же доступ (public, private, или protected). Нет виртуальных функций и нет виртуальных базовых классов. Все базовые классы являются POD типами. Стандартные правила выравнивания и макетирования данных в памяти.
struct MyPOD {
int x;
float y;
};
Пример НЕ POD типа
struct NonPOD {
int x;
virtual void func() {} // Наличие виртуальной функции делает тип не POD
};
🚩Применение POD типов
🟠Совместимость с C
Поскольку POD типы совместимы с C, они могут быть безопасно использованы в смешанных C/C++ программах.
🟠Серилизация
Легко сохранять и загружать из файлов или передавать по сети, так как их память может быть прочитана или записана напрямую.
🟠Оптимизация
Компиляторы могут лучше оптимизировать код с POD типами из-за их простоты и предсказуемости.
🚩Проверка, является ли тип POD
В C++11 и новее можно использовать стандартную библиотеку для проверки, является ли тип POD
#include <type_traits>
struct MyPOD {
int x;
float y;
};
struct NonPOD {
int x;
virtual void func() {}
};
int main() {
std::cout << std::is_pod<MyPOD>::value << std::endl; // вывод: 1 (true)
std::cout << std::is_pod<NonPOD>::value << std::endl; // вывод: 0 (false)
return 0;
}
Ставь 👍 и забирай 📚 Базу знанийНовый ритм на побережье с Jumeirah Al Naseem
Новый взгляд на Jumeirah Al Naseem — современная элегантность побережья и гармония душевных моментов на берегу Дубая
Забронировать
#реклама
jumeirah.com
О рекламодателе
🤔 В unordered_set поиск — константа?
Поиск в unordered_set имеет среднюю сложность O(1), но в худшем случае при хеш-коллизиях может быть O(n).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Для чего при перегрузке оператора присваивания возвращать ссылку?
При перегрузке оператора
= в 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` не работает!
}
Ставь 👍 и забирай 📚 Базу знаний🤔 Как называется одинаковый результат после применения хэш-функции?
Это называется коллизия (hash collision) — ситуация, при которой два разных значения дают один и тот же хэш.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Что такое функции обратного вызова?
Это функции, которые передаются как аргументы другим функциям, чтобы быть вызванными в определенный момент. Это важный механизм в программировании, который позволяет реализовывать гибкие и модульные решения, особенно в контексте асинхронного программирования, обработчиков событий и многопоточности.
🚩Основные характеристики
🟠Передача как параметр
Функции обратного вызова передаются как параметры другим функциям.
🟠Вызов в определенный момент
Они вызываются внутри функции, которая их получила, в определенный момент времени или по определенному условию.
🟠Гибкость и расширяемость
Использование функций обратного вызова позволяет легко изменять поведение программы без необходимости изменения основной функции.
🟠Пример на С
В языке C функции обратного вызова обычно реализуются с использованием указателей на функции.
#include <stdio.h>
// Объявление функции обратного вызова
void callback_function(int value) {
printf("Callback called with value: %d\n", value);
}
// Функция, принимающая указатель на функцию в качестве параметра
void process_value(int value, void (*callback)(int)) {
// Некоторая обработка
value *= 2;
// Вызов функции обратного вызова
callback(value);
}
int main() {
int x = 5;
// Передача функции обратного вызова
process_value(x, callback_function);
return 0;
}
🟠Пример на C++
В C++ можно использовать указатели на функции, объекты-функции (functors) и лямбда-функции в качестве функций обратного вызова.
#include <iostream>
#include <functional>
// Функция обратного вызова
void callback_function(int value) {
std::cout << "Callback called with value: " << value << std::endl;
}
// Функция, принимающая std::function в качестве параметра
void process_value(int value, std::function<void(int)> callback) {
// Некоторая обработка
value *= 2;
// Вызов функции обратного вызова
callback(value);
}
int main() {
int x = 5;
// Использование указателя на функцию
process_value(x, callback_function);
// Использование лямбда-функции
process_value(x, [](int val) {
std::cout << "Lambda callback called with value: " << val << std::endl;
});
// Использование объекта-функции
struct Functor {
void operator()(int val) {
std::cout << "Functor callback called with value: " << val << std::endl;
}
};
Functor functor;
process_value(x, functor);
return 0;
}
🚩Применение в асинхронном программировании
Функции обратного вызова широко используются в асинхронном программировании, где задачи выполняются параллельно или по завершении некоторых событий. Это позволяет программе не блокироваться, ожидая завершения длительных операций.
Пример с асинхронным вызовом
#include <iostream>
#include <thread>
// Функция обратного вызова
void callback_function() {
std::cout << "Async operation completed" << std::endl;
}
// Асинхронная функция
void async_operation(std::function<void()> callback) {
std::thread([callback]() {
// Имитация длительной операции
std::this_thread::sleep_for(std::chrono::seconds(2));
// Вызов функции обратного вызова
callback();
}).detach();
}
int main() {
std::cout << "Starting async operation..." << std::endl;
async_operation(callback_function);
// Основной поток продолжает выполнять свою работу
std::cout << "Main thread continues..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
Ставь 👍 и забирай 📚 Базу знаний🤔 Как работает счетчик ссылок в shared_ptr?
В shared_ptr хранится счётчик ссылок, который увеличивается при создании новой копии и уменьшается при уничтожении указателя. Когда счётчик достигает нуля, объект автоматически освобождается из памяти.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Какие виды конструкторов могут быть у класса?
🟠Конструктор по умолчанию (Default Constructor)
Без параметров, создается автоматически компилятором, если не задан.
class MyClass {
public:
MyClass() {}
};
🟠Параметризованный конструктор (Parameterized Constructor)
Принимает параметры для инициализации объекта.
class MyClass {
private:
int x;
public:
MyClass(int value) : x(value) {}
};
🟠Конструктор копирования (Copy Constructor)
Создает копию существующего объекта.
class MyClass {
private:
int x;
public:
MyClass(const MyClass &other) : x(other.x) {}
};
🟠Конструктор перемещения (Move Constructor)
Перемещает ресурсы из временного объекта.
class MyClass {
private:
int* data;
public:
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
}
};
🟠Делегирующий конструктор (Delegating Constructor)
Вызывает другой конструктор того же класса.
class MyClass {
private:
int x, y;
public:
MyClass(int value) : MyClass(value, 0) {}
MyClass(int value1, int value2) : x(value1), y(value2) {}
};
🟠Явный конструктор (Explicit Constructor)
Помечен explicit для предотвращения неявных преобразований.
class MyClass {
public:
explicit MyClass(int value) {}
};
Ставь 👍 и забирай 📚 Базу знаний🤔 Что лежит в основе работы unordered_set, unordered_map и set, map?
1. Set и Map: основаны на сбалансированных бинарных деревьях, обычно красно-чёрных. Они поддерживают упорядоченность данных.
2. Unordered_set и Unordered_map: основаны на хеш-таблицах, обеспечивающих быструю вставку, удаление и поиск.
3. Основной принцип различия — структура данных: деревья для упорядоченности, хеш-таблицы для скорости.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Можно использовать в контексте нескольких потоков Shared_ptr и потокобезопасный он или нет?
Использование shared_ptr в многопоточном контексте требует определённой осторожности, поскольку он сам по себе имеет ограниченную потокобезопасность. Рассмотрим подробнее, что это значит и как можно безопасно использовать его в многопоточной среде.
🚩Потокобезопасность
🟠Безопасное копирование и удаление
Стандарт C++ гарантирует, что операции копирования и удаления
shared_ptr являются потокобезопасными в том смысле, что вы можете безопасно копировать и удалять разные экземпляры shared_ptr из разных потоков, даже если они указывают на один и тот же объект.
🟠Счётчик ссылок
Модификации счётчика ссылок в нем (т.е. увеличение и уменьшение) являются атомарными операциями, что означает, что при изменении счётчика из разных потоков состояние счётчика останется консистентным и корректным.
🟠Ограничения потокобезопасности
Не рекомендуется изменять сам объект, на который указывает shared_ptr, из нескольких потоков без дополнительной синхронизации, так как shared_ptr не предоставляет потокобезопасность для доступа к объекту. Если несколько потоков изменяют один и тот же shared_ptr (не копии), например, присваивают ему новые значения или обнуляют его, это должно синхронизироваться, так как стандартная библиотека не гарантирует безопасность таких операций без внешней синхронизации.
🚩Рекомендации для использования в многопоточной среде
🟠Избегайте разделять один и тот же `shared_ptr` между потоками
Лучше дать каждому потоку собственную копию shared_ptr, что позволяет избежать конфликтов на запись.
🟠Синхронизируйте доступ к общему shared_ptr
Если несколько потоков должны иметь доступ к одному и тому же shared_ptr, используйте мьютексы или другие механизмы синхронизации для защиты его при записи.
🟠Рассмотрите использование std::atomic<std::shared_ptr<T>> для C++20 и выше
Эта оболочка предоставляет потокобезопасные операции на shared_ptr без необходимости явной синхронизации при изменении указателя.
Ставь 👍 и забирай 📚 Базу знаний🤔 Сложность операций с vector и list
Для vector добавление элемента в конец имеет сложность O(1) в среднем, но при перераспределении памяти — O(n). Удаление и вставка в произвольное место занимают O(n), так как элементы необходимо сдвигать. Для list добавление и удаление имеют сложность O(1), но доступ по индексу — O(n), так как требуется последовательный обход элементов.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Как работает счетчик у shared_ptr?
shared_ptr использует счетчик ссылок для управления памятью, что позволяет нескольким умным указателям делить владение одним объектом. Это механизм, который подсчитывает количество экземпляров
shared_ptr, которые указывают на тот же управляемый объект. Когда новый shared_ptr создается и указывает на объект, счетчик ссылок увеличивается. Когда shared_ptr уничтожается или перестает указывать на объект, счетчик уменьшается. Объект, на который указывали, освобождается (то есть его деструктор вызывается), когда счетчик ссылок достигает нуля. Это гарантирует, что память освобождается корректно и в нужный момент.
🚩Как он работает
🟠Инициализация и копирование
Когда создается shared_ptr и он начинает управлять объектом, создается "блок управления" (control block), содержащий счетчик ссылок.
Каждый раз, когда создается новый shared_ptr, который указывает на тот же объект (через копирование или присваивание), счетчик ссылок увеличивается.
🟠Уничтожение и перемещение
Когда shared_ptr уничтожается или его значение присваивается другому shared_ptr, счетчик ссылок уменьшается.
Если shared_ptr перемещается (используя move-семантику), счетчик ссылок не изменяется, так как владение объектом передается новому shared_ptr, а старый перестает владеть объектом.
🟠Освобождение ресурсов
Как только счетчик ссылок достигает нуля (то есть последний shared_ptr, указывающий на объект, был уничтожен или переназначен), объект удаляется. Соответственно, если для объекта был задан пользовательский делетер, он вызывается вместо стандартного delete.
🚩Счетчик слабых ссылок
shared_ptr также поддерживает weak_ptr, который представляет собой "слабую" ссылку на объект. weak_ptr позволяет наблюдать за объектом, не увеличивая счетчик сильных ссылок:
Счетчик слабых ссылок отслеживает, сколько weak_ptr указывает на объект. Он не предотвращает удаление объекта, когда счетчик сильных ссылок достигает нуля.
Когда счетчик сильных ссылок достигает нуля и объект удаляется, счетчик слабых ссылок все еще отслеживает, сколько weak_ptr наблюдают за объектом. Объект блока управления остается, пока не исчезнут все weak_ptr.
Ставь 👍 и забирай 📚 Базу знаний🤔 Какие есть операторы приведения типов?
1. static_cast: Для явного и безопасного приведения типов, известных на этапе компиляции.
2. dynamic_cast: Для приведения указателей или ссылок в иерархии классов с проверкой типов во время выполнения.
3. const_cast: Для снятия или добавления const и volatile квалификаторов.
4. reinterpret_cast: Для низкоуровневого преобразования указателей или других типов без проверки безопасности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Какие знаешь сортировки?
Существует множество алгоритмов сортировки, каждый из которых имеет свои преимущества и недостатки в зависимости от условий использования. Рассмотрим основные из них.
🟠Сортировка пузырьком (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);
}
}
Ставь 👍 и забирай 📚 Базу знаний🔥 Скрытые вакансии с удаленной работой для C/C++ разработчика, которые нигде больше не публикуются. Напрямую от компаний, с контактами рекрутеров.
🔹 C/C++ Jobs | Вакансии
🔸 Все каналы с вакансиями
🤔 Как работает быстрая сортировка?
Быстрая сортировка (quick sort) — это алгоритм, который рекурсивно делит массив на подмассивы на основе опорного элемента (pivot). Элементы, меньшие опорного, перемещаются в левую часть массива, а элементы, большие — в правую. Процесс продолжается рекурсивно для каждого подмассива, пока все элементы не будут отсортированы. В среднем случае быстрая сортировка имеет сложность O(n log n), но в худшем — O(n^2), если выбирать неудачный опорный элемент.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
