Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Являются контейнерами из стандартной библиотеки, но они значительно различаются по своей структуре и оптимальности использования в разных ситуациях.
Это динамический массив, который обеспечивает быстрый доступ к элементам по индексу. Вектор хранит свои элементы в непрерывном блоке памяти, что делает возможным обращение к элементам за константное время
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::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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
PIMPL расшифровывается как Pointer to IMPLementation (Указатель на реализацию). Это паттерн проектирования, который используется в C++ для разделения интерфейса и реализации с целью скрытия деталей реализации и уменьшения зависимости от заголовочных файлов.
Вместо того чтобы хранить данные прямо в классе, мы используем указатель на структуру реализации (
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 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; // Изменит оригинальный вектор!
}С
std::mapstd::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::setstd::set<int> s = {1, 2, 3, 4};
for (int x : s) { std::cout << x << " "; }for (int x : {10, 20, 30}) {
std::cout << x << " ";
}Вывод
10 20 30
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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Чтобы убедиться, что в коде нет багов, используют различные методы и инструменты проверки. Рассмотрим основные подходы:
Перед тем как запускать код, полезно просмотреть его вручную или с коллегами. Это помогает:
Найти логические ошибки
Улучшить читаемость кода
Проверить соответствие код-стайлу
// Опытный разработчик может заметить, что деление на 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;
}Тесты проверяют каждую функцию отдельно, чтобы убедиться, что она работает правильно. В 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);
}
Если код работает не так, как ожидалось, используют отладчики (GDB, LLDB, Visual Studio Debugger). Они позволяют пошагово исполнять код и смотреть, где происходит ошибка.
g++ -g main.cpp -o program # Компилируем с отладочной информацией
gdb ./program # Запускаем отладчик
Используются специальные инструменты, которые автоматически ищут ошибки в коде:
Clang-Tidy
Cppcheck
SonarQube
cppcheck --enable=all my_code.cpp
Эти инструменты помогают находить утечки памяти, переполнения буфера и другие ошибки.
g++ -fsanitize=address -g main.cpp -o program
./program # Если есть проблемы, они будут найдены
Когда код протестирован отдельно, его проверяют в реальной среде. Это включает тестирование работы программы с базами данных, сетевыми запросами и другими модулями.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
МVC (Model-View-Controller) – это классический паттерн разделения логики приложения, но у него есть недостатки, особенно в сложных проектах.
Взаимодействие между Model, View и Controller может стать сложным. Когда модель изменяется, нужно обновлять все представления (
View), которые её используют. Если контроллер слишком тесно связан с представлением, код становится сложно поддерживать. В сложных GUI-приложениях (например, Qt, MFC) модель может изменяться из разных мест, и контроллеру трудно управлять обновлениями.
MVC не даёт чёткого разделения ответственности, если приложение очень сложное.
Один контроллер может управлять сразу несколькими представлениями, что создаёт запутанный код. Часто приходится создавать "промежуточные" контроллеры для разруливания логики → код становится сложнее.
В сложных веб-приложениях (React, Angular) MVC превращается в "спагетти", потому что представления (View) и контроллеры (Controller) начинают выполнять логические задачи, которые должны быть в модели.
Использовать слоистую архитектуру (Layered Architecture) или Service Layer.
Если бизнес-логика попадает в контроллер, он становится раздутым и сложно поддерживаемым. Контроллер начинает обрабатывать данные, проверять их, а не просто передавать управление.
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";
}
}
};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, который основан на хеш-таблице, самый худший случай поиска возникает, когда структура данных деградирует до такой степени, что время поиска становится линейным, то есть \(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
Зарплата 207.000р у Middle-разработчика в Яндекс
«В день уходит несколько часов на созвоны, в остальное время закрываю задачки из спринта, редко перерабатываю. У компании топовый офис, но с коллективом как-то не заладилось. Радуюсь классному ДМС и стабильной зарплате» - middle разработчик из Яндекса.
Бигтех по-русски - канал с реальными зарплатами и историями IT-специалистов российского БигТеха. Там уже опубликованы рассказы программистов Альфа-банка, Сбера и Тинькофф🤯
Читайте: @bigtech_russia
«В день уходит несколько часов на созвоны, в остальное время закрываю задачки из спринта, редко перерабатываю. У компании топовый офис, но с коллективом как-то не заладилось. Радуюсь классному ДМС и стабильной зарплате» - middle разработчик из Яндекса.
Бигтех по-русски - канал с реальными зарплатами и историями IT-специалистов российского БигТеха. Там уже опубликованы рассказы программистов Альфа-банка, Сбера и Тинькофф
Читайте: @bigtech_russia
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2
Чтобы объект можно было использовать в качестве ключа в ассоциативных контейнерах (
std::set, std::map, std::unordered_set, std::unordered_map), он должен обладать определёнными свойствами, которые зависят от типа контейнера.Класс или структура, используемая в качестве ключа, должна поддерживать операцию
< (меньше). #include <iostream>
#include <map>
struct Person {
std::string name;
int age;
// Оператор сравнения, необходимый для std::map и std::set
bool operator<(const Person& other) const {
return age < other.age; // Ключи будут упорядочены по возрасту
}
};
int main() {
std::map<Person, std::string> people;
people[{ "Alice", 30 }] = "Doctor";
people[{ "Bob", 25 }] = "Engineer";
for (const auto& [key, value] : people) {
std::cout << key.name << " (" << key.age << "): " << value << '\n';
}
}
Объект-ключ должен поддерживать операции:
Оператор
== (для проверки равенства)Функция-хешер (по умолчанию
std::hash<T>)#include <iostream>
#include <unordered_map>
struct Person {
std::string name;
int age;
// Оператор равенства нужен для сравнения ключей
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// Специализация std::hash для структуры Person
namespace std {
template <>
struct hash<Person> {
std::size_t operator()(const Person& p) const {
return std::hash<std::string>()(p.name) ^ (std::hash<int>()(p.age) << 1);
}
};
}
int main() {
std::unordered_map<Person, std::string> people;
people[{ "Alice", 30 }] = "Doctor";
people[{ "Bob", 25 }] = "Engineer";
for (const auto& [key, value] : people) {
std::cout << key.name << " (" << key.age << "): " << value << '\n';
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
2. Unordered_set/Unordered_map: элементы не сортируются, используют хеш-таблицы для быстрого доступа.
3. Set и Map медленнее на вставке/поиске (O(log N)), но позволяют итерировать в отсортированном порядке.
4. Unordered_set и Unordered_map быстрее для поиска (O(1) в среднем), но не поддерживают упорядоченный доступ.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
В умном указателе shared_ptr используется два основных счётчика: счётчик сильных ссылок (strong reference count) и счётчик слабых ссылок (weak reference count). Эти счётчики управляют жизненным циклом объекта и связанных с ним ресурсов различными способами.
Увеличивается каждый раз, когда новый
shared_ptr создаётся как копия другого shared_ptr или когда объект присваивается shared_ptr. Этот счётчик уменьшается, когда shared_ptr уничтожается или когда его значение присваивается другому объекту. Когда счётчик достигает нуля, это означает, что больше нет shared_ptr, управляющих этим объектом, и объект удаляется. Это гарантирует, что ресурсы, связанные с объектом, будут освобождены только тогда, когда не останется ни одной "сильной" ссылки.Используется вместе с
weak_ptr, другим типом умных указателей, который может ссылаться на объект, управляемый shared_ptr, но не увеличивает счётчик сильных ссылок. Слабые ссылки не предотвращают удаление объекта, к которому они имеют доступ, так как не участвуют в владении объектом. Счётчик слабых ссылок увеличивается каждый раз, когда создаётся weak_ptr, указывающий на объект, и уменьшается, когда такой weak_ptr уничтожается. Когда счётчик сильных ссылок достигает нуля и объект удаляется, память, выделенная под сам объект, освобождается, но "control block" (блок управления), содержащий счётчики, сохраняется до тех пор, пока счётчик слабых ссылок также не обнулится.#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
std::cout << "sp1 use_count: " << sp1.use_count() << '\n'; // Вывод: 1
{
std::shared_ptr<int> sp2 = sp1; // Копирование shared_ptr
std::cout << "sp1 use_count after copy: " << sp1.use_count() << '\n'; // Вывод: 2
std::weak_ptr<int> wp1 = sp1; // Создание weak_ptr
std::cout << "wp1 use_count: " << wp1.use_count() << '\n'; // Вывод: 2
} // sp2 выходит из области видимости, use_count уменьшается до 1
std::cout << "sp1 use_count after sp2 destruction: " << sp1.use_count() << '\n'; // Вывод: 1
return 0;
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1