C/C++ | Вопросы собесов
4.19K subscribers
36 photos
1.57K links
Сайт: https://easyoffer.ru/
Все каналы: t.me/+xGeAw6ckJ4liYzQy

Контакт для рекламы: @sendme_ads
Download Telegram
🤔 Как проверять равенство двух float?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Какая сложность поиска в 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;
}


Ставь 👍 и забирай 📚 Базу знаний
🤔 Какие есть уровни exception гарантий?

1. No-throw Guarantee: метод или операция не выбрасывает исключений. Пример: деструкторы.
2. Strong Guarantee: при выбросе исключения состояние объекта или программы остаётся неизменным.
3. Basic Guarantee: состояние объекта остаётся корректным, но изменения могут быть частичными.
4. No Guarantee: программа может оказаться в некорректном состоянии при исключении.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🔥2
🤔 Расскажите про ключевое слово _fastcall?

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

🚩Основные характеристики

🟠Передача аргументов через регистры
_fastcall передает первые два целочисленных аргумента (например, int, char, long) через регистры ECX и EDX на архитектуре x86. Остальные аргументы передаются через стек.

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

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

#include <iostream>

// Функция, использующая соглашение о вызове _fastcall
int __fastcall add(int a, int b) {
return a + b;
}

int main() {
int result = add(5, 3);
std::cout << "Result: " << result << std::endl; // Вывод: Result: 8
return 0;
}


🚩Примечания и ограничения

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

🟠Совместимость с другими соглашениями о вызове
Если вы используете _fastcall вместе с другими соглашениями о вызове (например, __cdecl, __stdcall), нужно быть осторожным, чтобы избежать ошибок при вызове функций с разными соглашениями о вызове.

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

#include <iostream>

// Проверка платформы
#ifdef _M_IX86 // Если используется компилятор для архитектуры x86
int __fastcall add(int a, int b) {
return a + b;
}
#else
int add(int a, int b) {
return a + b;
}
#endif

int main() {
int result = add(5, 3);
std::cout << "Result: " << result << std::endl; // Вывод: Result: 8
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
🤔 Как устроена хеш-таблица в unordered_map?

В unordered_map используется хеш-таблица с цепочечной адресацией:
- Данные распределяются по бакетам (ячейкам), определяемым хеш-функцией.
- Если два разных ключа попадают в один бакет (коллизия), элементы связываются в список.
- При большом количестве элементов происходит рехеширование – увеличение количества бакетов и перераспределение данных.
Эта структура обеспечивает быстрые операции поиска, вставки и удаления в среднем за O(1).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Сколько занимает места объект пустого класса?

Объект пустого класса не может иметь нулевой размер из-за требований стандарта. Поэтому компилятор добавляет "фиктивный байт" (dummy byte), чтобы каждый объект имел уникальный адрес.

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

class Empty {};

int main() {
std::cout << "Размер пустого класса: " << sizeof(Empty) << " байт\n";
return 0;
}


Вывод (на большинстве компиляторов):
Размер пустого класса: 1 байт


Что если создать массив пустых объектов?
#include <iostream>

class Empty {};

int main() {
Empty arr[10]; // Создаем массив из 10 объектов

std::cout << "Размер массива из 10 пустых объектов: " << sizeof(arr) << " байт\n";
return 0;
}


Вывод
Размер массива из 10 пустых объектов: 10 байт


🟠Унаследованный пустой класс (Empty Base Optimization - EBO)
Если пустой класс используется в наследовании, компилятор может убрать его размер (оптимизация Empty Base Optimization, EBO).
#include <iostream>

class Empty {};

class Derived : public Empty {
int value; // 4 байта (обычно)
};

int main() {
std::cout << "Размер пустого класса: " << sizeof(Empty) << " байт\n";
std::cout << "Размер наследника: " << sizeof(Derived) << " байт\n";
return 0;
}


Вывод
Размер пустого класса: 1 байт
Размер наследника: 4 байта (а не 5!)


Ставь 👍 и забирай 📚 Базу знаний
🤔 Какая польза от безымянного пространства имен?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
👍1🔥1
🤔 Как убедиться что нет багов в коде?

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

🟠Внимательная проверка кода (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 # Если есть проблемы, они будут найдены


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

Ставь 👍 и забирай 📚 Базу знаний
🤔 В set сложность вставки, удаления, поиска - логарифмическая?

Да, в стандартной реализации `std::set` в C++ вставка, удаление и поиск выполняются за O(log n), так как `set` реализован как сбалансированное бинарное дерево (обычно красно-чёрное дерево). Логарифмическая сложность обеспечивается благодаря тому, что элементы хранятся в отсортированном порядке, и операция деления дерева пополам происходит при каждом шаге. Это делает операции эффективными даже для больших наборов данных.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Расшифруйте аббревиатуру 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(); }


Ставь 👍 и забирай 📚 Базу знаний
👍1
🤔 В чем особенность выброса исключений в конструкторе и деструкторе?

Если исключение выбрасывается в конструкторе, объект считается несозданным, и деструктор для него не вызывается. Если исключение выбрасывается в деструкторе, оно может привести к завершению программы, если другое исключение уже активно.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний