🤔 Как проверять равенство двух float?
Равенство двух float проверяется с учётом допустимой разницы (эпсилон), чтобы избежать ошибок из-за неточности представления:
∣a−b∣<ϵ, где ϵ — небольшое значение, например 10−610^{-6}.
Такой подход помогает корректно сравнивать близкие числа, которые могут отличаться в пределах допустимой погрешности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Равенство двух float проверяется с учётом допустимой разницы (эпсилон), чтобы избежать ошибок из-за неточности представления:
∣a−b∣<ϵ, где ϵ — небольшое значение, например 10−610^{-6}.
Такой подход помогает корректно сравнивать близкие числа, которые могут отличаться в пределах допустимой погрешности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Какая сложность поиска в set и unordered_set?
Контейнеры set и unordered_set представляют собой различные структуры данных, каждая из которых имеет свои особенности по скорости выполнения основных операций, включая поиск. Вот как работают эти контейнеры и какова сложность их операций поиска:
🚩set
Реализуется как сбалансированное двоичное дерево поиска, обычно как красно-черное дерево. Он хранит элементы в отсортированном порядке, что позволяет выполнять двоичный поиск. Сложность поиска: Поиск в нем выполняется за логарифмическое время, \(O(\log n)\), где \(n\) — количество элементов в
🚩unordered_set
Реализуется с использованием хеш-таблицы. Это позволяет, при идеальных условиях, выполнять поиск за константное время. Сложность поиска: В среднем, поиск в нем занимает константное время \(O(1)\). Однако в худшем случае, например, при неудачной работе хеш-функции или при большом количестве коллизий, поиск может деградировать до \(O(n)\). В таких ситуациях все ключи могут оказаться в одной "корзине" или "ведре" (bucket), и для нахождения правильного элемента потребуется просмотреть все элементы в этом ведре.
Для
Для
Ставь 👍 и забирай 📚 Базу знаний
Контейнеры 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: программа может оказаться в некорректном состоянии при исключении.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
1. No-throw Guarantee: метод или операция не выбрасывает исключений. Пример: деструкторы.
2. Strong Guarantee: при выбросе исключения состояние объекта или программы остаётся неизменным.
3. Basic Guarantee: состояние объекта остаётся корректным, но изменения могут быть частичными.
4. No Guarantee: программа может оказаться в некорректном состоянии при исключении.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🔥2
🤔 Расскажите про ключевое слово _fastcall?
Это соглашение о вызове функций, используемое в некоторых компиляторах C и C++ (например, Microsoft Visual C++), которое указывает, что параметры функции должны передаваться через регистры, а не через стек. Это может привести к более быстрой работе программы, так как доступ к регистрами обычно быстрее, чем доступ к стеку.
🚩Основные характеристики
🟠Передача аргументов через регистры
🟠Обратная совместимость
🟠Оптимизация производительности
Передача аргументов через регистры уменьшает накладные расходы, связанные с использованием стека, что может улучшить производительность в функциях, которые вызываются часто или требуют высокой производительности.
🚩Примечания и ограничения
🟠Платформенная зависимость
🟠Совместимость с другими соглашениями о вызове
Если вы используете
🟠Использование в современных проектах
В современных проектах использование
Ставь 👍 и забирай 📚 Базу знаний
Это соглашение о вызове функций, используемое в некоторых компиляторах 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).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
В unordered_map используется хеш-таблица с цепочечной адресацией:
- Данные распределяются по бакетам (ячейкам), определяемым хеш-функцией.
- Если два разных ключа попадают в один бакет (коллизия), элементы связываются в список.
- При большом количестве элементов происходит рехеширование – увеличение количества бакетов и перераспределение данных.
Эта структура обеспечивает быстрые операции поиска, вставки и удаления в среднем за O(1).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
🤔 Сколько занимает места объект пустого класса?
Объект пустого класса не может иметь нулевой размер из-за требований стандарта. Поэтому компилятор добавляет "фиктивный байт" (
Простой пример
Вывод (на большинстве компиляторов):
Что если создать массив пустых объектов?
Вывод
🟠Унаследованный пустой класс (Empty Base Optimization - EBO)
Если пустой класс используется в наследовании, компилятор может убрать его размер (оптимизация Empty Base Optimization, EBO).
Вывод
Ставь 👍 и забирай 📚 Базу знаний
Объект пустого класса не может иметь нулевой размер из-за требований стандарта. Поэтому компилятор добавляет "фиктивный байт" (
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)
Перед тем как запускать код, полезно просмотреть его вручную или с коллегами. Это помогает:
Найти логические ошибки
Улучшить читаемость кода
Проверить соответствие код-стайлу
Лучше добавить проверку
🟠Юнит-тестирование (Unit Testing)
Тесты проверяют каждую функцию отдельно, чтобы убедиться, что она работает правильно. В C++ используют Google Test (gtest), Catch2 или Boost.Test.
🟠Отладка (Debugging)
Если код работает не так, как ожидалось, используют отладчики (GDB, LLDB, Visual Studio Debugger). Они позволяют пошагово исполнять код и смотреть, где происходит ошибка.
🟠Статический анализ кода
Используются специальные инструменты, которые автоматически ищут ошибки в коде:
Clang-Tidy
Cppcheck
SonarQube
🟠Динамический анализ (Sanitizers, Valgrind)
Эти инструменты помогают находить утечки памяти, переполнения буфера и другие ошибки.
🟠Интеграционное и системное тестирование
Когда код протестирован отдельно, его проверяют в реальной среде. Это включает тестирование работы программы с базами данных, сетевыми запросами и другими модулями.
Ставь 👍 и забирай 📚 Базу знаний
Чтобы убедиться, что в коде нет багов, используют различные методы и инструменты проверки. Рассмотрим основные подходы:
🟠Внимательная проверка кода (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?
Это паттерн проектирования, который используется в C++ для разделения интерфейса и реализации с целью скрытия деталей реализации и уменьшения зависимости от заголовочных файлов.
🚩Как работает PIMPL?
Вместо того чтобы хранить данные прямо в классе, мы используем указатель на структуру реализации (
Без PIMPL (проблема: утечка зависимостей)
С 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
🤔 В чем особенность выброса исключений в конструкторе и деструкторе?
Если исключение выбрасывается в конструкторе, объект считается несозданным, и деструктор для него не вызывается. Если исключение выбрасывается в деструкторе, оно может привести к завершению программы, если другое исключение уже активно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Если исключение выбрасывается в конструкторе, объект считается несозданным, и деструктор для него не вызывается. Если исключение выбрасывается в деструкторе, оно может привести к завершению программы, если другое исключение уже активно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний