Объектно-ориентированное программирование на 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(); Не принимает параметров и не возвращает значений.
Виды:
- По умолчанию (автогенерируемый компилятором).
- Пользовательский (написанный вручную для очистки памяти).
- Виртуальный (
virtual ~ClassName();) — обязателен в базовых классах полиморфных иерархий для корректного уничтожения объектов производных классов через указатель на базовый класс. - Чисто виртуальный (
virtual ~ClassName() = 0;) — делает класс абстрактным, но требует обязательного наличия тела деструктора (реализации).
6. Полиморфизм. Виды. Назначение.
Назначение: возможность использовать один и тот же интерфейс для различных типов данных, позволяя коду быть гибким и расширяемым.
Виды:
- Динамический (время выполнения / runtime): реализуется через наследование, виртуальные функции и указатели/ссылки на базовый класс.
- Статический (время компиляции / 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).
Аспекты необходимости классов:
- Группировка логически связанных данных.
- Автоматическое управление жизненным циклом ресурсов (концепция RAII).
- Сокрытие сложности и обеспечение инвариантов данных.
15. Виды функций класса, генерируемых компилятором по умолчанию. Копирование блока памяти. Пример.
Компилятор может автоматически сгенерировать 6 функций (rule of 6): конструктор по умолчанию, деструктор, конструктор копирования, оператор присваивания копированием, конструктор перемещения и оператор присваивания перемещением.
Копирование блока памяти: функции копирования по умолчанию выполняют поверхностное копирование (shallow copy) — для массивов и примитивных типов данные просто побитово дублируются (аналог memcpy), что опасно для динамических указателей.
struct Data {
int arr[3];
};
Data d1 = {{1, 2, 3}};
Data d2 = d1; // Побитовое копирование всего блока памяти массива arr16. Перегрузка операторов и функций.
Перегрузка функций — создание в одной области видимости нескольких функций с одинаковым именем, но отличающимися сигнатурами (количеством, типом или порядком аргументов).
Перегрузка операторов — разновидность полиморфизма, позволяющая определить поведение стандартных знаков операций (+, -, *, ==, << и т.д.) для пользовательских классов, придавая им интуитивный вид.
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_t27. Создание полиморфной иерархии. Пример.
Создается абстрактный базовый класс с виртуальным деструктором и виртуальными функциями, которые затем переопределяются (override) в наследниках.
class Shape {
public:
virtual void draw() const = 0; // Интерфейс
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() const override {} // Реализация
};28. Три основных механизма разработки библиотеки классов.
- Наследование (Inheritance) — построение вертикальных иерархий для повторного использования кода и полиморфизма.
- Композиция и агрегирование (Composition/Aggregation) — включение объектов одних классов в состав других (горизонтальное проектирование «has-a»).
- Параметрический полиморфизм / Шаблоны (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); // Результат: 1541. Потоки данных. Форматирование. Примеры.
В C++ ввод-вывод построен на потоках (). Форматирование чисел, точности и систем счисления выполняется специальными манипуляторами из заголовка .
#include
#include
std::cout << std::hex << 255; // Выведет: ff
std::cout << std::setw(6) << std::setfill('0') << 77; // Выведет: 00007742. Потоки данных. Файловые потоки. Примеры.
Для работы с файлами применяются специализированные классы потоков из : 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 указывает на 947. Алгоритм 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. Способы передачи данных в функцию.
- По значению (
void f(int x)) — создается локальная копия данных. Изменения внутри функции безопасны для оригинала. - По указателю (
void f(int* x)) — передается адрес памяти. Функция может изменять оригинал. Поддерживает передачуnullptr. - По ссылке (
void f(int& x)) — передается явный псевдоним оригинала. Изменения видны сразу извне, синтаксис чистый. - По константной ссылке (
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. Адаптеры итераторов. Примеры.
Специальные классы стандартной библиотеки, которые принимают обычный итератор и модифицируют его базовое поведение.
Примеры:
std::back_insert_iterator(вызывает методpush_backпри присваивании).std::reverse_iterator(инвертирует движение:++двигает итератор назад).std::istream_iterator(превращает поток чтения данных в последовательный итератор).
54. Категории итераторов. Примеры для каждой категории.
- Input (входные): чтение в один проход, только вперед (
std::istream_iterator). - Output (выходные): запись в один проход, только вперед (
std::ostream_iterator). - Forward (однонаправленные): многократный проход, чтение и запись, только вперед (
std::forward_list::iterator). - Bidirectional (двунаправленные): движение вперед и назад (
++и--) (std::list::iterator,std::set::iterator). - 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++ делятся на три ключевые группы:
- Стандартный консольный ввод-вывод (:
std::istream,std::ostream). - Файловые потоки (:
std::ifstream,std::ofstream). - Строковые потоки, позволяющие форматировать данные в оперативной памяти (:
std::istringstream,std::ostringstream).
#include
#include
std::ostringstream ss;
ss << "Result: " << 2026;
std::string output = ss.str(); // Сборка строки "Result: 2026"