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 #2347 · Back to latest

Older Posts 20 shown
Post #2346 497
🤔 Как можно отсортировать list?

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


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

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

🚩Методы открытой адресации (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 #2343 546
🤔 Как работает хеш таблица?

Хеш-таблица использует хеш-функцию для вычисления индекса в массиве, по которому будет сохранено или найдено значение. Ключ преобразуется хеш-функцией в индекс массива, куда сохраняется значение. При поиске значения ключ снова хешируется для определения индекса. Если происходит коллизия (разные ключи дают один и тот же хеш), то используются методы разрешения коллизий, такие как цепочки (связные списки) или открытая адресация.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 1
Post #2341 550
🤔 Что будет если сделать delete для nullptr?

В C++ delete nullptr безопасен и не делает ничего. Стандарт гарантирует, что delete не вызывает ошибок при передаче nullptr.
int* p = nullptr;
delete p; // НИЧЕГО НЕ ПРОИЗОЙДЁТ (без ошибки)


🚩Почему `delete nullptr` не вызывает ошибку?

Стандарт C++ (C++98, C++11, C++17, C++20) говорит:
> Если переданный в delete указатель равен nullptr, то delete ничего не делает.
Это сделано, чтобы избежать избыточных проверок в коде:
if (ptr) { // Проверка не нужна
delete ptr;
}


🚩Как работает `delete` внутри?

Когда вызывается delete p, компилятор:
Проверяет, равен ли p nullptr. Если да → ничего не делает.
Вызывает деструктор объекта, если p не nullptr.
Освобождает память с помощью operator delete().

🚩Что с `delete[]`?

Тоже безопасно
int* arr = nullptr;
delete[] arr; // НИЧЕГО НЕ ПРОИЗОЙДЁТ


🚩Ошибки, которых `delete nullptr` помогает избежать

Безопасно
void destroy(int* p) {
delete p; // Даже если p == nullptr, ошибки не будет
}


Опасность: двойное удаление
Хотя delete nullptr безопасен, удаление уже освобождённого указателя — ошибка!
int* p = new int(10);
delete p; // Освободили память
delete p; // ❌ НЕСКОЛЬКО DELETE - неопределённое поведение (UB)!


Решение: после delete занулять указатель
int* p = new int(10);
delete p;
p = nullptr; // Теперь повторный delete безопасен
delete p; // ОК, ничего не делает


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2339 583
🤔 Коллизия в хеш-таблицах

Коллизия в хеш-таблицах возникает, когда два разных ключа имеют одинаковое хэш-значение.
Методы разрешения коллизий:
1. Цепочки (chaining): элементы с одинаковым хэш-значением хранятся в связанном списке или другой структуре.
2. Открытая адресация (open addressing): ищется следующая доступная ячейка для хранения элемента.
Коллизии снижают производительность, поэтому важно выбирать хорошие хэш-функции.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2338 594
🤔 Расшифруйте аббревиатуру 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
Post #2336 551
🤔 Сложность удаление из начала у vector?

Удаление элемента из начала vector имеет временную сложность O(n), так как после удаления первого элемента все последующие элементы необходимо сдвигать на одну позицию влево. Это приводит к линейному времени выполнения операции. Vector не подходит для частых операций удаления в начале структуры. Для этих целей лучше использовать контейнеры, такие как list.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2335 611
🤔 Какое преимущество у list перед vector?

List обеспечивает быстрые вставки и удаления за \(O(1)\) в произвольной позиции, благодаря двусвязной структуре. В отличие от vector, list не требует перераспределения памяти при добавлении элементов. Однако доступ к элементам в list медленнее, так как отсутствует прямой индексированный доступ.

🚩Основные преимущества `std::list`

🟠Эффективные вставка и удаление элементов в середине контейнера
std::list реализован как двусвязный список, что позволяет эффективно вставлять и удалять элементы в любом месте контейнера за постоянное время O(1), если у вас уже есть итератор на нужное место.
#include <list>
#include <iostream>

int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
auto it = myList.begin();
std::advance(it, 2); // Переместить итератор на третий элемент

myList.insert(it, 10); // Вставка за O(1)
myList.erase(it); // Удаление за O(1)

for (int n : myList) {
std::cout << n << " ";
}

return 0;
}


🟠Нет необходимости в перемещении элементов при вставке или удалении
В std::vector при вставке или удалении элементов в середине контейнера происходит сдвиг элементов, что может быть дорогостоящей операцией (сложность O(n)). В std::list этого не происходит, так как элементы не хранятся в смежных блоках памяти.

🟠Постоянное время для операций вставки и удаления в произвольной позиции
Операции вставки и удаления в произвольной позиции в std::list всегда выполняются за O(1) (при условии, что у вас есть итератор на эту позицию), в то время как в std::vector они могут занимать до O(n) из-за необходимости сдвига элементов.

🟠Итераторы не инвалидируются при вставке или удалении элементов
В std::vector итераторы могут быть инвалидированы (становятся недействительными) при вставке или удалении элементов. В std::list итераторы остаются валидными, что делает работу с итераторами более безопасной и предсказуемой.

🚩Примеры, когда `std::list` предпочтительнее

🟠Частые вставки и удаления в середине контейнера
Если ваша задача предполагает частые операции вставки и удаления в середине контейнера, std::list будет предпочтительнее из-за своей эффективности в этих операциях.

🟠Работа с большим количеством итераторов
Если вам нужно работать с множеством итераторов, которые должны оставаться валидными даже после вставки или удаления элементов, std::list будет лучше подходить для таких задач.

🚩Ограничения `std::list`

🟠Доступ по индексу
В отличие от std::vector, в std::list нет возможности быстрого доступа к элементам по индексу. Для доступа к элементу необходимо пройти весь список от начала до нужного элемента (сложность O(n)).

🟠Высокие накладные расходы на память
std::list требует больше памяти на хранение элементов, так как для каждого элемента хранится два дополнительных указателя (на следующий и предыдущий элементы).

Ставь 👍 и забирай 📚 Базу знаний
Post #2333 568
🤔 Double except привести примеры, когда может полететь повторное исключение?

Повторное исключение (double exception) в C++ может возникнуть, когда исключение генерируется внутри блока обработчика другого исключения. Например, если в конструкторе копирования объекта, который копируется для передачи в catch-блок, происходит исключение, это приведет к повторному исключению. Также повторное исключение может возникнуть, если в блоке catch происходит вызов функции, генерирующей исключение, в то время как первое исключение еще не обработано полностью.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2332 562
🤔 Расскажите про ключевое слово _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;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2331 557
🤔 Какие знаешь Smart Pointers?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2330 597
🤔 Что знаешь о умных указателях?

Умные указатели (smart pointers) в C++ – это классы-обёртки для обычных указателей, которые автоматически управляют временем жизни объектов в динамической памяти. Они помогают избежать утечек памяти и делают код более безопасным, минимизируя вероятность ошибок с указателями.

🚩Зачем нужны умные указатели?

🟠Управление ресурсами
Автоматически освобождают память, когда объект больше не нужен.
🟠Исключение утечек памяти
С их помощью можно избежать забывания вызова delete.
🟠Улучшение безопасности
Умные указатели предотвращают доступ к освобождённой памяти (dangling pointers).
🟠Снижение сложности
RAII (Resource Acquisition Is Initialization) позволяет делегировать управление ресурсами объектам.

🚩Типы умных указателей

🟠`std::unique_ptr`
Уникальный указатель (unique pointer) обладает единственным владельцем объекта. После передачи владения объектом другому std::unique_ptr, исходный указатель становится недействительным.
Обеспечивает строгую семантику владения.
Лёгкий, так как не использует счётчики ссылок.
Не копируемый, но перемещаемый.

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // Владеет объектом
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // Передача владения
// ptr1 теперь nullptr, ptr2 владеет объектом
return 0; // Объект автоматически удаляется
}


🟠`std::shared_ptr`
Разделяемый указатель (shared pointer) поддерживает разделённое владение объектом. Указатель освобождает память только тогда, когда все std::shared_ptr, ссылающиеся на объект, будут уничтожены.
Использует счётчик ссылок (reference count).
Обеспечивает совместное использование ресурса несколькими владельцами.

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // Разделение владения
// Указатель ptr1 и ptr2 указывают на один и тот же объект
return 0; // Объект удаляется после уничтожения последнего shared_ptr
}


🟠`std::weak_ptr`
Слабый указатель (weak pointer) не увеличивает счётчик ссылок. Используется для предотвращения циклических зависимостей между std::shared_ptr.
Ссылается на объект, которым управляет std::shared_ptr.
Не владеет объектом.
Может проверять существование объекта с помощью expired().

#include <iostream>
#include <memory>

class MyClass {
public:
MyClass() { std::cout << "Создан объект\n"; }
~MyClass() { std::cout << "Удалён объект\n"; }
};

int main() {
std::shared_ptr<MyClass> shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared; // Слабая ссылка
if (auto ptr = weak.lock()) { // Проверка на существование
std::cout << "Объект существует\n";
}
shared.reset(); // Освобождаем shared_ptr
if (weak.expired()) {
std::cout << "Объект удалён\n";
}
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2328 517
🤔 Как еще называются контейнеры set, map?

Контейнеры set и map называют ассоциативными контейнерами, так как их элементы организованы в виде пары ключ-значение (в случае map) или в виде уникальных ключей (set). Они поддерживают автоматическую сортировку и быстрый доступ к элементам.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2326 574
🤔 Что такое функции обратного вызова?

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

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

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

🟠Пример на С
В языке 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;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 2
Post #2324 549
🤔 Что будет, если для беззнаковой переменной, равной 0, сделать декремент?

Значение переменной перейдёт в максимальное значение типа (например, UINT_MAX для unsigned int).
Это связано с переполнением, так как беззнаковые типы используют арифметику по модулю.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 3
Post #2323 605
🤔 Каким свойством должен обладать объект, чтобы его можно было добавить в ассоциативные контейнеры в качестве ключа?

Чтобы объект можно было использовать в качестве ключа в ассоциативных контейнерах (std::set, std::map, std::unordered_set, std::unordered_map), он должен обладать определёнными свойствами, которые зависят от типа контейнера.

🚩Требования к объекту-ключу

🟠Для `std::map` и `std::set` (красно-чёрное дерево)
Класс или структура, используемая в качестве ключа, должна поддерживать операцию < (меньше).
#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::unordered_map` и `std::unordered_set` (хеш-таблица)
Объект-ключ должен поддерживать операции:
Оператор == (для проверки равенства)
Функция-хешер (по умолчанию 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';
}
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2321 568
🤔 Как называется одинаковый результат после применения хэш-функции?

Это называется коллизия (hash collision) — ситуация, при которой два разных значения дают один и тот же хэш.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2319 561
🤔 Строгая гарантия исключений?

Гарантии безопасности исключений (exception safety) в C++ бывают трёх уровней:
Базовая гарантия (Basic Guarantee) – программа не падает, но состояние может быть некорректным.
Строгая гарантия (Strong Guarantee) – либо операция завершается успешно, либо объект остаётся в исходном состоянии.
Гарантия отсутствия исключений (No-throw Guarantee) – операция никогда не бросает исключения.

🚩Что такое строгая гарантия исключений?

Строгая гарантия исключений означает, что если во время выполнения метода выбросится исключение, объект останется в том же состоянии, в каком был до вызова метода.
#include <iostream>
#include <vector>

class BadContainer {
std::vector<int> data;
public:
void add(int value) {
data.push_back(value); // push_back() может выбросить исключение
}

void print() {
for (int x : data) std::cout << x << " ";
std::cout << std::endl;
}
};

int main() {
BadContainer c;
c.add(1);
c.add(2);
c.add(3);
c.print(); // Вывод: 1 2 3

try {
c.add(42); // Может выбросить исключение (например, при нехватке памяти)
} catch (...) {
std::cout << "Ошибка!" << std::endl;
}

c.print(); // ?? Возможно, состояние испорчено!
}


🚩Как реализовать строгую гарантию?

Чтобы добиться строгой гарантии, используем Copy & Swap:
1. Создаём временный объект.
2. Выполняем изменения на временном объекте.
3. Если всё прошло успешно – меняем указатель (swap).
#include <iostream>
#include <vector>

class SafeContainer {
std::vector<int> data;
public:
void add(int value) {
std::vector<int> temp = data; // Копируем текущее состояние
temp.push_back(value); // Изменяем копию
std::swap(data, temp); // Если исключения нет, меняем данные
}

void print() {
for (int x : data) std::cout << x << " ";
std::cout << std::endl;
}
};

int main() {
SafeContainer c;
c.add(1);
c.add(2);
c.add(3);
c.print(); // Вывод: 1 2 3

try {
c.add(42); // Если тут исключение, объект не изменится
} catch (...) {
std::cout << "Ошибка!" << std::endl;
}

c.print(); // Вывод: 1 2 3 (не испорчен!)
}


🚩Где ещё применяется строгая гарантия?

Операции присваивания (operator=) с Copy & Swap
Функции, изменяющие состояние контейнеров (std::vector::resize, std::map::insert)
Операции перевыделения памяти
Функции стандартной библиотеки (std::sort)

Ставь 👍 и забирай 📚 Базу знаний
Post #2317 527
🤔 Принцип подстановки Барбары Лисков?

Принцип Лисков (LSP) утверждает, что объект базового класса должен быть заменим объектом производного класса без изменения поведения программы. Производный класс не должен нарушать инварианты и контракт базового класса.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Post #2315 605
🤔 Расскажи о различиях ссылки и указателя

Ссылки (references) и указатели (pointers) в C++ служат для работы с памятью и позволяют передавать и изменять данные по адресу. Однако у них есть важные различия, которые влияют на использование и безопасность кода.

🚩Подробное объяснение с примерами

🟠Ссылка – это псевдоним для переменной
Ссылка является просто альтернативным именем для существующей переменной. Она не может существовать без объекта, к которому привязана.
#include <iostream>

void modify(int& ref) {
ref = 20; // Меняем значение переданного объекта
}

int main() {
int a = 10;
int& refA = a; // refA теперь синоним a

std::cout << "До: " << a << std::endl; // 10
modify(refA);
std::cout << "После: " << a << std::endl; // 20

return 0;
}


🟠Указатель – это переменная, хранящая адрес
Указатели могут указывать на разные переменные, быть nullptr, поддерживают арифметику.
#include <iostream>

void modify(int* ptr) {
if (ptr) {
*ptr = 30; // Разыменование указателя для изменения значения
}
}

int main() {
int a = 10;
int* ptrA = &a; // ptrA хранит адрес a

std::cout << "До: " << a << std::endl; // 10
modify(ptrA);
std::cout << "После: " << a << std::endl; // 30

return 0;
}


Указатели могут менять адреса, ссылки – нет
int a = 10, b = 20;
int* ptr = &a; // ptr указывает на a
ptr = &b; // Теперь ptr указывает на b

int& ref = a; // ref – это псевдоним a
ref = b; // Это НЕ смена привязки! Теперь a == 20


🟠Арифметика указателей
Указатели позволяют выполнять арифметические операции, что полезно при работе с массивами.
int arr[] = {1, 2, 3};
int* ptr = arr; // ptr указывает на первый элемент

std::cout << *ptr << std::endl; // 1
std::cout << *(ptr + 1) << std::endl; // 2 (следующий элемент)
std::cout << *(ptr + 2) << std::endl; // 3


🚩Когда использовать ссылки, а когда указатели?

Используйте ссылки, если объект гарантированно существует и не должен изменять свою привязку
Используйте указатели, если нужна возможность "ни на что не указывать" (nullptr) или менять адрес
В большинстве случаев лучше использовать ссылки, так как они безопаснее

Ставь 👍 и забирай 📚 Базу знаний
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 →