Объектно-ориентированное программирование на C++: полный конспект ответов на 61 вопрос — классы, шаблоны, STL и потоки

Enviado por Anónimo y clasificado en Matemáticas

Escrito el en ruso con un tamaño de 48,33 KB

Объектно-ориентированное программирование на C++: ответы на вопросы

1. Понятие объектно-ориентированного программирования. Что определяет класс? Чем отличается класс от объекта?

ООП — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса.

Класс определяет новый тип данных. Он задает структуру (данные-члены) и поведение (функции-члены) будущих объектов.

Отличие: класс — это чертеж/шаблон (находится в памяти кода), а объект — конкретный экземпляр этого чертежа, созданный в оперативной памяти (имеет свое состояние, данные и уникальный адрес).

2. Конструктор копирования. Особенности. Назначение.

Назначение: создание нового объекта как точной копии уже существующего объекта того же класса.

Сигнатура: ClassName(const ClassName& other);

Особенности: если не объявлен пользователем, компилятор генерирует версию по умолчанию (побитовое копирование / shallow copy). Он критически важен, если класс управляет динамической памятью или системными ресурсами, чтобы избежать разделения указателей и двойного удаления (double free).

3. Конструктор по умолчанию. Особенности. Назначение.

Назначение: инициализация объекта при его создании без явного указания аргументов.

Сигнатура: ClassName(); (или конструктор, у которого все параметры имеют значения по умолчанию).

Особенности: компилятор автоматически генерирует его только в том случае, если в классе вообще не объявлено никаких других пользовательских конструкторов. Если определен любой другой конструктор, автоматическая генерация отключается.

4. Пользовательские конструкторы. Пример.

Это конструкторы с параметрами, созданные программистом для гарантированной инициализации объекта конкретными валидными значениями в момент его создания.

class Point {
public:
    int x, y;
    Point(int xVal, int yVal) : x(xVal), y(yVal) {} // Пользовательский конструктор
};
Point p(10, 20);

5. Деструктор. Виды.

Деструктор — специальная функция-член, вызываемая автоматически при уничтожении объекта (выход из области видимости, вызов delete) для освобождения занятых им ресурсов. Сигнатура: ~ClassName(); Не принимает параметров и не возвращает значений.

Виды:

  1. По умолчанию (автогенерируемый компилятором).
  2. Пользовательский (написанный вручную для очистки памяти).
  3. Виртуальный (virtual ~ClassName();) — обязателен в базовых классах полиморфных иерархий для корректного уничтожения объектов производных классов через указатель на базовый класс.
  4. Чисто виртуальный (virtual ~ClassName() = 0;) — делает класс абстрактным, но требует обязательного наличия тела деструктора (реализации).

6. Полиморфизм. Виды. Назначение.

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

Виды:

  1. Динамический (время выполнения / runtime): реализуется через наследование, виртуальные функции и указатели/ссылки на базовый класс.
  2. Статический (время компиляции / compile-time): реализуется через перегрузку функций, перегрузку операторов и шаблоны (параметрический полиморфизм).

7. Указатель this. Назначение. Пример использования.

this — скрытый константный указатель, доступный внутри всех нестатических функций-членов. Он содержит адрес объекта, для которого была вызвана данная функция.

Назначение: разрешение конфликтов имен параметров и полей класса, возврат ссылки на текущий объект для организации цепочки вызовов (return *this;).

class Node {
    int val;
public:
    void setVal(int val) {
        this->val = val; // Разделение имен поля и параметра
    }
};

8. Оператор присваивания копированием. Пример.

Назначение: копирование значений из одного существующего объекта в другой, уже существующий объект того же класса. Важно реализовывать защиту от самоприсваивания и корректно очищать старые ресурсы.

class Box {
    int w;
public:
    Box& operator=(const Box& other) {
        if (this == &other) return *this; // Защита от самоприсваивания
        w = other.w;
        return *this;
    }
};

9. Конструктор перемещения и оператор присваивания перемещением.

Назначение: перенос владения тяжелыми ресурсами (динамическая память, дескрипторы файлов, сокеты) из временного объекта (rvalue-ссылки) в создаваемый или существующий объект без дорогостоящего глубокого копирования.

Сигнатуры:

  • Конструктор перемещения: ClassName(ClassName&& other) noexcept;
  • Оператор присваивания перемещением: ClassName& operator=(ClassName&& other) noexcept;

Особенность: исходный объект-донор «опустошается» — переводится в валидное, но неопределенное состояние (обычно его указатели обнуляются).

10. Основные характеристики объектно-ориентированного программирования. Объясните принцип инкапсуляции.

Основные характеристики ООП: инкапсуляция, наследование, полиморфизм (и часто абстракция).

Принцип инкапсуляции заключается в объединении данных (свойств) и методов (поведений), работающих с этими данными, внутри единого защищенного компонента — класса, а также в жестком ограничении прямого доступа к внутренним деталям реализации извне. Доступ предоставляется только через строго контролируемый публичный интерфейс.

11. Абстракция данных.

Абстракция данных — это выделение ключевых, значимых характеристик объекта, важных для решения текущей задачи, и полное игнорирование несущественных деталей реализации. В C++ абстракция реализуется с помощью объявления классов: пользователь работает с логическими концепциями (методами public), не задумываясь о внутреннем представлении структур данных в памяти.

12. Инкапсуляция. Назначение. Пример.

Назначение: защита целостности внутреннего состояния объекта от некорректного или преднамеренного изменения извне, изоляция изменений кода (модификация private-части не ломает внешний код).

class BankAccount {
private:
    double balance = 0.0; // Скрытые данные
public:
    void deposit(double amt) {
        if (amt > 0) balance += amt; // Контроль валидности
    }
    double getBalance() const { return balance; }
};

13. Классы. Указатели и ссылки на объект. Пример.

Указатель на объект хранит его адрес в памяти, может быть переназначен на другой объект или равен nullptr. Доступ к членам идет через оператор ->.

Ссылка является неизменяемым псевдонимом (alias) уже существующего объекта, не может быть пустой (nullptr) и не может быть перенаправлена. Доступ идет через оператор .

class Animal {};
Animal cat;
Animal* ptr = &cat;  // Указатель: хранит адрес
Animal& ref = cat;   // Ссылка: псевдоним объекта

14. Какие типы можно признать хорошими? Аспекты необходимости классов.

Хорошие типы — это типы (встроенные или пользовательские), которые ведут себя интуитивно понятно, безопасны в использовании, эффективны и полностью поддерживают семантику значений (легко и предсказуемо копируются, перемещаются и уничтожаются, как стандартный int).

Аспекты необходимости классов:

  1. Группировка логически связанных данных.
  2. Автоматическое управление жизненным циклом ресурсов (концепция RAII).
  3. Сокрытие сложности и обеспечение инвариантов данных.

15. Виды функций класса, генерируемых компилятором по умолчанию. Копирование блока памяти. Пример.

Компилятор может автоматически сгенерировать 6 функций (rule of 6): конструктор по умолчанию, деструктор, конструктор копирования, оператор присваивания копированием, конструктор перемещения и оператор присваивания перемещением.

Копирование блока памяти: функции копирования по умолчанию выполняют поверхностное копирование (shallow copy) — для массивов и примитивных типов данные просто побитово дублируются (аналог memcpy), что опасно для динамических указателей.

struct Data {
    int arr[3];
};
Data d1 = {{1, 2, 3}};
Data d2 = d1; // Побитовое копирование всего блока памяти массива arr

16. Перегрузка операторов и функций.

Перегрузка функций — создание в одной области видимости нескольких функций с одинаковым именем, но отличающимися сигнатурами (количеством, типом или порядком аргументов).

Перегрузка операторов — разновидность полиморфизма, позволяющая определить поведение стандартных знаков операций (+, -, *, ==, << и т.д.) для пользовательских классов, придавая им интуитивный вид.

void print(int i);
void print(double d); // Перегрузка функций

Complex operator+(const Complex& a, const Complex& b); // Перегрузка оператора

17. Виртуальные функции. Чисто абстрактный класс. Пример.

Виртуальная функция (virtual) — функция-член базового класса, которая может быть переопределена в производных классах. Вызов такой функции через указатель/ссылку на базовый класс разрешается динамически во время выполнения (через таблицу vtable).

Чисто абстрактный класс — класс, который содержит хотя бы одну чистую виртуальную функцию (= 0) и служит исключительно интерфейсом. Создавать объекты такого класса запрещено.

class Interface {
public:
    virtual void run() = 0; // Чистая виртуальная функция
    virtual ~Interface() = default;
};

18. Виды приведения типов. Пример. Приведение типа объекта базового класса к производному. Пример.

В C++ используются 4 правила явного приведения типов: static_cast (базовые преобразования), dynamic_cast (безопасное полиморфное приведение вдоль иерархии), const_cast (снятие/добавление const), reinterpret_cast (низкоуровневая переинтерпретация байт).

Приведение базового к производному (downcasting): безопасно выполняется только через dynamic_cast. Требует наличия хотя бы одной виртуальной функции в базовом классе. Если приведение указателя невозможно, возвращает nullptr.

class Base { public: virtual void foo() {} };
class Derived : public Base { public: void bar() {} };

Base* b = new Derived();
Derived* d = dynamic_cast(b); // Успешное приведение вниз
if (d) d->bar();

19. Виды наследования. Пример.

По типу доступа: public (открытое — отношение «is-a»), protected (защищенное), private (закрытое — отношение реализации).

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

class Base {};
class Derived : public Base {}; // Открытое одиночное наследование

20. Дружественные функции и классы. Назначение. Пример.

Назначение: предоставление внешней функции или стороннему классу полного прямого доступа к приватным (private) и защищенным (protected) членам текущего класса без раскрытия их всему остальному коду.

class Window {
    int id = 101;
    friend class Manager; // Объявление дружественного класса
};
class Manager {
public:
    void showId(Window& w) { std::cout << w.id; } // Имеет доступ к private
};

21. Шаблоны. Специализация шаблонных классов.

Шаблоны (templates) — инструмент обобщенного программирования, позволяющий писать код (классы или функции) независимо от конкретных типов данных. Типы становятся параметрами.

Специализация шаблона — создание альтернативной, специфической реализации шаблона класса для определенного типа данных (например, для оптимизации по скорости или памяти, как это сделано в стандартном std::vector).

22. Шаблонные классы и функции. Пример.

Позволяют параметризовать логику вычислений или структуру хранения данных.

template
T myMax(T a, T b) { return (a > b) ? a : b; } // Шаблонная функция

template
class Wrapper { T val; }; // Шаблонный класс

23. Контейнер std::vector. Примеры.

std::vector — динамический последовательный массив. Хранит элементы в непрерывном блоке памяти. Гарантирует быстрый доступ по индексу за O(1), вставку и удаление в конце за амортизированное O(1). При переполнении емкости (capacity) автоматически перевыделяет память.

#include 
std::vector v = {1, 2, 3};
v.push_back(4); // Добавление в конец
int element = v[0]; // Доступ по индексу O(1)

24. Контейнер std::list. Примеры.

std::list — последовательный контейнер, реализованный как двусвязный список. Элементы разбросаны в памяти и связаны указателями. Обеспечивает вставку и удаление в любом месте за константное время O(1) (если итератор на позицию уже известен), но имеет медленный последовательный доступ за O(N).

#include 
std::list l = {1, 2, 3};
l.push_front(0); // Быстрая вставка в начало за O(1)
l.push_back(4);

25. Строки std::basic_string. Примеры. Конвертация строки в число и обратно.

std::basic_string — базовый шаблон класса для работы со строками. Его стандартная специализация для типа char — это привычный std::string. Конвертация выполняется встроенными функциями std::stoi, std::stod и std::to_string.

#include 
std::string s = "42";
int num = std::stoi(s); // Из строки в число
std::string s2 = std::to_string(num + 10); // Из числа в строку ("52")

26. Строки std::basic_string. Нахождение элемента в строке. Расчет подстроки. Широкосимвольные строки. Примеры.

Для поиска используется метод .find(), который при неудаче возвращает константу std::string::npos. Для получения части строки используется метод .substr(pos, count). Широкие строки (для работы с Unicode/UTF-16) представлены типом std::wstring на базе wchar_t.

std::string str = "HelloUniverse";
size_t pos = str.find("Universe"); // Нахождение элемента
std::string sub = str.substr(0, 5);  // Результат: "Hello"

std::wstring wstr = L"Широкая строка"; // Работает с wchar_t

27. Создание полиморфной иерархии. Пример.

Создается абстрактный базовый класс с виртуальным деструктором и виртуальными функциями, которые затем переопределяются (override) в наследниках.

class Shape {
public:
    virtual void draw() const = 0; // Интерфейс
    virtual ~Shape() = default;
};
class Circle : public Shape {
public:
    void draw() const override {} // Реализация
};

28. Три основных механизма разработки библиотеки классов.

  1. Наследование (Inheritance) — построение вертикальных иерархий для повторного использования кода и полиморфизма.
  2. Композиция и агрегирование (Composition/Aggregation) — включение объектов одних классов в состав других (горизонтальное проектирование «has-a»).
  3. Параметрический полиморфизм / Шаблоны (Generics) — создание обобщенных независимых алгоритмов и контейнеров.

29. Чисто виртуальные функции. Пример. Инициализация блока памяти. Пример.

Чисто виртуальная функция — это объявление интерфейса функции без реализации в базовом классе (= 0).

Инициализация блока памяти — заполнение или сброс сырого участка памяти определенным значением (байтом), часто выполняется через низкоуровневую функцию std::memset из заголовка .

// 1. Чисто виртуальная функция
class Engine { virtual void start() = 0; };

// 2. Инициализация блока памяти
int buffer[5];
std::memset(buffer, 0, sizeof(buffer)); // Заполнение нулями

30. Хранение объектов полиморфной иерархии. Пример.

Объекты полиморфной иерархии категорически нельзя хранить по значению в стандартных контейнерах, иначе возникнет «срезка» (slicing) объекта до базовой части. Их необходимо хранить строго через указатели (лучше всего умные: std::unique_ptr / std::shared_ptr) или ссылки.

#include 
#include 

std::vector> storage;
storage.push_back(std::make_unique()); // Хранение безопасно

31. Деструктор и конструктор. Порядок их вызовов при создании и удалении объекта производного класса полиморфной иерархии. Пример.

При создании (вызов конструктора): объекты строятся снизу вверх. Сначала вызывается конструктор базового класса, затем конструкторы вложенных полей, и в конце — конструктор производного класса.

При удалении (вызов деструктора): строго обратный порядок. Сначала отрабатывает тело деструктора производного класса, затем деструкторы полей, и лишь затем — деструктор базового класса.

class Base { public: Base() { /* 1 */ } ~Base() { /* 4 */ } };
class Derived : public Base { public: Derived() { /* 2 */ } ~Derived() { /* 3 */ } };

32. Полиморфная иерархия. Пример.

Классический пример полиморфной структуры для управления сущностями.

class Employee { public: virtual void work() = 0; virtual ~Employee() = default; };
class Developer : public Employee { public: void work() override {} };
class Designer : public Employee { public: void work() override {} };

33. «Время жизни» объекта. Память методов класса.

Время жизни определяется категорией памяти: статическое (вся программа), автоматическое (локальные переменные на стеке, уничтожаются при выходе из блока {}), динамическое (в куче, создается через new, живет до явного delete).

Память методов: методы класса компилируются в один общий сегмент кода и существуют в единственном экземпляре для всех объектов. Объекты в куче или стеке хранят только свои переменные-члены и указатель на таблицу виртуальных функций (vptr).

34. Удаленные функции. Назначение. Пример.

Назначение: явный запрет компилятору вызывать или автоматически генерировать определенные функции-члены (например, жесткий запрет копирования объекта для уникальных ресурсов).

class UniqueResource {
public:
    UniqueResource(const UniqueResource&) = delete; // Запрет конструктора копирования
    UniqueResource& operator=(const UniqueResource&) = delete; // Запрет присваивания
};

35. Чистая функция. Пример.

Чистая функция (pure function) — функция, результат которой полностью детерминирован её входными аргументами и которая не имеет побочных эффектов (не меняет глобальные переменные, не выполняет файловый ввод-вывод, не модифицирует окружение).

int calculateSquare(int x) {
    return x * x; // Чистая функция
}

36. dynamic_cast. Пример.

Оператор динамического приведения типов, выполняющий безопасную проверку типов во время выполнения программы (RTTI) для указателей и ссылок на полиморфные объекты.

Base* ptr = new Derived();
if (Derived* d = dynamic_cast(ptr)) {
    // Приведение успешно, объект реально является Derived
}

37. typeid. Пример.

Оператор typeid возвращает ссылку на объект std::type_info, содержащий метаданные о типе переменной или объекта. Позволяет сравнивать типы во время выполнения.

#include 
Base* b = new Derived();
if (typeid(*b) == typeid(Derived)) {
    // Объект под указателем имеет точный тип Derived
}

38. Адаптеры контейнеров.

Адаптеры контейнеров — классы-оболочки STL, которые берут существующий последовательный контейнер (std::vector, std::deque или std::list) и изменяют/ограничивают его интерфейс для предоставления специфической структуры данных.

Примеры: std::stack (LIFO, по умолчанию поверх deque), std::queue (FIFO), std::priority_queue (очередь с приоритетом).

39. Предикаты. Пример.

Предикат — это функция, лямбда-выражение или объект-функтор, который принимает один или несколько аргументов и возвращает логическое значение (bool). Широко используются в алгоритмах STL для фильтрации и поиска.

auto isPositive = [](int x) { return x > 0; }; // Предикат

40. Функторы. Пример.

Функтор (объект-функция) — класс, в котором перегружен оператор вызова функции operator(). Это позволяет использовать объект класса синтаксически так же, как обычную функцию, но при этом хранить внутри состояние.

struct Adder {
    int delta;
    int operator()(int value) const { return value + delta; }
};
Adder addTen{10};
int result = addTen(5); // Результат: 15

41. Потоки данных. Форматирование. Примеры.

В C++ ввод-вывод построен на потоках (). Форматирование чисел, точности и систем счисления выполняется специальными манипуляторами из заголовка .

#include 
#include 

std::cout << std::hex << 255; // Выведет: ff
std::cout << std::setw(6) << std::setfill('0') << 77; // Выведет: 000077

42. Потоки данных. Файловые потоки. Примеры.

Для работы с файлами применяются специализированные классы потоков из : std::ifstream (чтение), std::ofstream (запись) и std::fstream (двунаправленный обмен).

#include 
std::ofstream outFile("log.txt");
outFile << "Экзаменационный лог";
outFile.close();

43. Алгоритм std::equal_range. Пример использования с контейнером std::vector.

Алгоритм std::equal_range за время O(log N) находит диапазон всех элементов, эквивалентных заданному значению в отсортированном контейнере. Возвращает пару итераторов (std::pair), где first — это lower_bound, а second — upper_bound.

#include 
#include 

std::vector v = {1, 2, 2, 2, 3};
auto range = std::equal_range(v.begin(), v.end(), 2);

44. Алгоритм std::set_difference. Пример использования с контейнером std::vector.

Находит разность двух отсортированных диапазонов — копирует элементы, которые присутствуют в первом контейнере, но отсутствуют во втором.

#include 
#include 

std::vector v1 = {1, 2, 3, 4}, v2 = {2, 4}, res;
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(res));
// res содержит: {1, 3}

45. Алгоритм std::rotate. Пример использования с контейнером std::vector.

Выполняет циклический сдвиг элементов диапазона так, чтобы элемент, на который указывает итератор middle, переместился в самое начало диапазона.

#include 
#include 

std::vector v = {1, 2, 3, 4, 5};
std::rotate(v.begin(), v.begin() + 2, v.end());
// v содержит: {3, 4, 5, 1, 2}

46. Алгоритм std::mismatch. Пример использования с контейнером std::list.

Попарно сравнивает элементы двух диапазонов и возвращает пару итераторов, указывающих на первое обнаруженное несовпадение между ними.

#include 
#include 

std::list l1 = {1, 2, 3}, l2 = {1, 9, 3};
auto p = std::mismatch(l1.begin(), l1.end(), l2.begin());
// p.first указывает на 2, p.second указывает на 9

47. Алгоритм std::transform. Пример использования с контейнером std::list.

Применяет заданную унарную или бинарную операцию (функцию/лямбду) ко всем элементам диапазона и последовательно записывает результаты в целевой контейнер.

#include 
#include 

std::list l = {1, 2, 3}, res;
std::transform(l.begin(), l.end(), std::back_inserter(res), [](int x) { return x * 3; });
// res содержит: {3, 6, 9}

48. Способы передачи данных в функцию.

  1. По значению (void f(int x)) — создается локальная копия данных. Изменения внутри функции безопасны для оригинала.
  2. По указателю (void f(int* x)) — передается адрес памяти. Функция может изменять оригинал. Поддерживает передачу nullptr.
  3. По ссылке (void f(int& x)) — передается явный псевдоним оригинала. Изменения видны сразу извне, синтаксис чистый.
  4. По константной ссылке (void f(const BigObj& x)) — передача без тяжелого копирования, но с гарантией неизменяемости.

49. Итераторы. Пример.

Итератор — объект, инкапсулирующий указатель и представляющий абстракцию для последовательной навигации по элементам любого контейнера STL без раскрытия его внутреннего устройства.

#include 
std::vector v = {10, 20};
for (std::vector::iterator it = v.begin(); it != v.end(); ++it) {
    int value = *it; // Разыменование
}

50. Сортировка. Предикаты. Пример.

Использование стандартного алгоритма std::sort с передачей пользовательского предиката (условия сравнения элементов) для изменения критерия упорядочивания.

#include 
#include 

std::vector v = {5, 2, 9};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Сортировка по убыванию

51. Классы и шаблоны: отличия и сходство. Пример.

Сходство: оба инструмента служат чертежами и описывают внутреннюю структуру и поведение будущих программных единиц.

Отличия: класс — это готовый конкретный тип данных, компилируемый сразу. Шаблон — это мета-инструкция (генератор кода). Сам шаблон скомпилировать нельзя, компилятор создает конкретный класс по шаблону только в момент его инстанцирования конкретным типом.

class IntBox { int val; }; // Обычный класс

template
class Box { T val; }; // Шаблон класса

52. «Глубокое» копирование. Пример.

Глубокое копирование (deep copy) — стратегия копирования объекта, при которой все связанные динамические ресурсы (память в куче) не просто копируются по адресу указателя, а заново выделяются оператором new, после чего данные физически дублируются.

class Deep {
    int* data;
public:
    Deep(const Deep& other) {
        data = new int(*other.data); // Выделение нового блока памяти
    }
    ~Deep() { delete data; }
};

53. Адаптеры итераторов. Примеры.

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

Примеры:

  1. std::back_insert_iterator (вызывает метод push_back при присваивании).
  2. std::reverse_iterator (инвертирует движение: ++ двигает итератор назад).
  3. std::istream_iterator (превращает поток чтения данных в последовательный итератор).

54. Категории итераторов. Примеры для каждой категории.

  1. Input (входные): чтение в один проход, только вперед (std::istream_iterator).
  2. Output (выходные): запись в один проход, только вперед (std::ostream_iterator).
  3. Forward (однонаправленные): многократный проход, чтение и запись, только вперед (std::forward_list::iterator).
  4. Bidirectional (двунаправленные): движение вперед и назад (++ и --) (std::list::iterator, std::set::iterator).
  5. Random Access (произвольного доступа): прыжки на любое смещение за O(1), сравнения (<, >) (std::vector::iterator).

55. Деструктор базы данных объектов полиморфной иерархии. Пример.

Менеджер ресурсов (база данных объектов), хранящий полиморфные указатели, обязан корректно очищать память. Для этого у базового класса должен быть объявлен virtual деструктор, иначе очистится только базовая часть объекта.

class DB {
    std::vector records;
public:
    ~DB() {
        for (auto p : records) {
            delete p; // Безопасно только при наличии virtual ~Base()
        }
    }
};

56. Конструктор. Порядок вызовов конструкторов классов полиморфной зависимости.

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

57. Конструктор копирования и оператор присваивания копированием. Реализация.

Каноническая корректная ручная реализация для управления динамическим ресурсом.

class SmartItem {
    int* val;
public:
    SmartItem(const SmartItem& other) { val = new int(*other.val); }
    SmartItem& operator=(const SmartItem& other) {
        if (this == &other) return *this; // Защита
        delete val;                        // Удаление старого
        val = new int(*other.val);         // Выделение нового
        return *this;
    }
    ~SmartItem() { delete val; }
};

58. Контейнер std::list. Операции вставки и удаления из него. Примеры.

Вставка и удаление в std::list происходят физической перестройкой указателей соседних узлов, поэтому выполняются за константное время O(1) и не инвалидируют итераторы на другие элементы.

#include 
std::list l = {1, 3};
auto it = ++l.begin();
l.insert(it, 2); // l станет: {1, 2, 3}
l.erase(--it);   // Удалит элемент 2. l станет: {1, 3}

59. Полиморфная иерархия: практическая значимость. Пример.

Практическая значимость заключается в соблюдении принципа «открыт для расширения, закрыт для изменения» (Open-Closed). Можно добавлять новые классы-наследники, не меняя и не перекомпилируя основной код, так как он работает с объектами через базовый интерфейс.

// Код работает с иерархией UI-компонентов независимо от их конкретных типов
void refreshScreen(const std::vector& widgets) {
    for (auto w : widgets) w->repaint(); // Полиморфный вызов
}

60. Операции над итераторами. Пример.

Операции зависят от категории: для всех доступны ++it, *it, it1 != it2. Для двунаправленных добавляется --it. Для итераторов произвольного доступа доступны арифметические операции: it + 5, it1 - it2, а также индексация it[3] и сравнения <, >.

std::vector v = {10, 20, 30, 40};
auto it = v.begin();
it += 3; // Прыжок сразу к элементу 40 за O(1)
int val = *it;

61. Потоковые классы. Виды. Пример.

Потоковые классы C++ делятся на три ключевые группы:

  1. Стандартный консольный ввод-вывод (: std::istream, std::ostream).
  2. Файловые потоки (: std::ifstream, std::ofstream).
  3. Строковые потоки, позволяющие форматировать данные в оперативной памяти (: std::istringstream, std::ostringstream).
#include 
#include 

std::ostringstream ss;
ss << "Result: " << 2026;
std::string output = ss.str(); // Сборка строки "Result: 2026"

Entradas relacionadas: