Билеты 34-43
34. Чем инициализируются переменная типа bool по умолчанию?
Локальные переменные (автоматическая область видимости) не инициализируются по умолчанию, то есть их значение неопределено, если их не инициализировать явно.
Глобальные переменные, статические переменные и члены классов, имеющие статическую длительность хранения, инициализируются нулём, что для bool
означает значение false
.
35. Какие варианты приведения типов по иерархии наследования существуют и чем отличаются?
Upcasting (приведение к базовому типу)
Преобразование указателя или ссылки на производный класс к указателю/ссылке на базовый класс. Это безопасное преобразование, часто выполняется неявно, так как производный всегда является базовым.
Downcasting (приведение к производному типу)
Преобразование указателя или ссылки базового класса к указателю/ссылке на производный класс. Это преобразование может быть небезопасным, если объект на самом деле не является экземпляром производного класса.
static_cast
Может использоваться для downcasting, но не выполняет проверку во время выполнения. Его следует применять, если вы уверены в типе объекта.
dynamic_cast
Выполняет проверку типа во время выполнения и возвращает nullptr (для указателей) или генерирует исключение (для ссылок) при неудачном приведении. Его используют для безопасного downcasting, когда базовый класс имеет хотя бы одну виртуальную функцию.
Другие виды приведения
const_cast
Удаляет или добавляет квалификатор const (или volatile).
reinterpret_cast
Выполняет низкоуровневое преобразование между указателями, не связанными с наследованием (небезопасное, платформозависимое).
36. В чем разница между enum и enum class, зачем нужны последние?
В C++ существуют два типа перечислений: классические enum (обычные или "неограниченные" перечисления) и enum class (ограниченные перечисления, введенные в C++11). Вот их ключевые различия и преимущества enum class:
Основные различия
Область видимости (scoping):
enum
: Имена перечислителей находятся в той же области видимости, что и само перечисление (может приводить к конфликтам имен)
enum Color { RED, GREEN, BLUE };
enum TrafficLight { RED, YELLOW, GREEN }; // Ошибка: повторное определение
enum class
: Имена перечислителей находятся внутри области видимости перечисления
enum class Color { RED, GREEN, BLUE };
enum class TrafficLight { RED, YELLOW, GREEN }; // OK
Неявное преобразование типов:
enum
: Неявно преобразуется в целочисленные типы
enum class
: Нет неявного преобразования в целочисленные типы
Тип перечисления:
enum
: Базовый тип не фиксирован (компилятор выбирает)
enum class SmallEnum : uint8_t { VALUE1, VALUE2 }; // 8 бит
enum class
: Можно явно указать базовый тип
Преимущества enum class: Предотвращение конфликтов имен
Типобезопасность
Явное указание размера
enum class NetworkPacketType : uint16_t {
DATA = 0x0102,
ACK = 0x0203
};
enum class
: Всегда, когда возможен C++11 и выше (рекомендуемый выбор)
Вывод:
enum class
обеспечивают:
- Лучшую инкапсуляцию (избегают "загрязнения" пространства имен)
- Повышенную типобезопасность
- Возможность явного контроля размера
- Более чистый и поддерживаемый код
37. Есть ли разница между структурой struct и классом class?
Различие между структурами и классами
- struct – по умолчанию, public права доступа к членам, public наследование
- class – по умолчанию, private права доступа к членам, private наследование
Стилистические различия
Хотя функционально они почти идентичны, сложились соглашения об использовании:
class
используют для:
- Сложных объектов с инкапсуляцией
- Когда нужны private-члены и методы
- Для реализации ООП (наследование, полиморфизм)
struct
используют для:
- Простых контейнеров данных (POD — Plain Old Data)
- Когда все члены должны быть публичными
- Для совместимости с C
38. Какие есть спецификаторы прав доступа в C++ и в чем отличие между ними?
В C++ существуют три спецификатора доступа, которые определяют видимость членов класса (полей и методов) и базовых классов:
public
private
protected
public
-Доступ: Из любого места программы -Для членов класса:
class MyClass {
public:
int publicVar; // Доступно отовсюду
void publicMethod() {}
};
-Для наследования:
class Derived : public Base {}; // Публичное наследование
protected
(ограниченный доступ)
Доступ:
- Из методов самого класса
- Из методов производных классов
- Недоступно извне иерархии наследования
private
(максимально закрытый)
Доступ: Только из методов самого класса
Пример
class BankAccount {
private: // Скрытая реализация
double balance;
protected: // Для наследников
void logTransaction() {}
public: // Интерфейс
void deposit(double amount) {
balance += amount;
logTransaction();
}
};
class SavingsAccount : public BankAccount {
public:
void addInterest() {
// balance -= 10; // Ошибка: private
logTransaction(); // OK: protected
}
};
int main() {
SavingsAccount acc;
acc.deposit(1000); // OK: public
// acc.logTransaction(); // Ошибка: protected
}
Когда что использовать:
public
: Интерфейс класса (что могут использовать все)protected
: Для расширения класса в наследникахprivate
: Внутренняя реализация (инкапсуляция)
Эти спецификаторы — основа инкапсуляции в C++, позволяющая скрывать детали реализации и предоставлять четкие интерфейсы.
39. Что такое конструктор и когда он вызывается?
Конструктор
Конструктор — это специальная не статическая функция-элемент класса, которая используется для инициализации объектов своего классового типа.
- Имя функции совпадает с именем класса.
- При создании объекта класса всегда вызывается один конструктор.
- Конструктор нельзя вызвать явно.
- У класса может быть произвольное число конструкторов (в том числе ноль).
Основные особенности:
- Имя совпадает с именем класса
- Не имеет возвращаемого типа (даже void)
- Может быть перегружен (несколько версий с разными параметрами)
Когда вызывается конструктор:
При создании объекта:
MyClass obj; // Вызывается конструктор по умолчанию
MyClass obj2(42); // Вызывается параметризованный конструктор
При динамическом выделении памяти:
MyClass* ptr = new MyClass(); // Вызывается конструктор
При создании временных объектов:
MyClass func() {
return MyClass(); // Вызывается конструктор
}
В составе других объектов:
class Container {
MyClass member; // Конструктор MyClass вызовется при создании Container
};
При копировании (если не используется move-семантика):
MyClass obj1;
MyClass obj2 = obj1; // Вызывается копирующий конструктор
Типы конструкторов:
По умолчанию (без параметров):
class MyClass {
public:
MyClass() { /* инициализация */ }
};
Параметризованный:
class MyClass {
public:
MyClass(int value) { /* использование value */ }
};
Копирующий:
class MyClass {
public:
MyClass(const MyClass& other) { /* копирование из other */ }
};
Перемещающий (C++11):
class MyClass {
public:
MyClass(MyClass&& other) { /* перемещение ресурсов из other */ }
};
Важные нюансы:
- Автоматическая генерация: Если не объявить ни одного конструктора, компилятор создаст конструктор по умолчанию
- Если объявить любой конструктор, конструктор по умолчанию не генерируется
40. Что такое деструктор и когда он вызывается?
Деструктор — это специальная функция-элемент, которая вызывается, когда заканчивается время жизни объекта. Цель деструктора освободить ресурсы, которые объект мог получить за время своего существования (или сделать какую-то финализацию).
- Имя функции
~ClassName
. - У деструктора не может быть аргументов.
- По умолчанию noexcept.
- При любом удалении объекта класса вызывается его деструктор.
- У класса может быть только один деструктор.
- Деструктор можно вызвать явно.
Основные особенности:
- Имя: ~ИмяКласса() (тильда + имя класса)
- Не имеет параметров и возвращаемого типа
- Не может быть перегружен (только один деструктор на класс)
- Виртуальный, если класс предназначен для наследования
Когда вызывается деструктор:
При выходе объекта из области видимости:
{
MyClass obj; // Конструктор
} // Деструктор (при выходе из блока)
При явном удалении динамических объектов:
MyClass* ptr = new MyClass();
delete ptr; // Вызывается деструктор
Для элементов массива:
MyClass* arr = new MyClass[5];
delete[] arr; // Деструкторы всех 5 элементов
Для членов класса:
class Container {
MyClass member;
public:
~Container() { /* Деструктор member вызовется автоматически после этого */ }
};
При исключениях (для локальных объектов в стеке)
41. Можно ли явно вызвать конструктор?
Конструктор нельзя вызвать явно. (см. 39 билет)
42. Можно ли явно вызвать деструктор?
Деструктор можно вызвать явно. (см. 40 билет)
43. В чем разница между оператором присваивания и конструктором копирования?
Это не шаблонный конструктор, принимающий первым аргументом lvalue ссылку на объект того же типа, что и класс конструктора, который может быть вызван с одним аргументом. Конструктор копирования вызывается всегда, когда объект класса инициализируется из lvalue выражения того же типа (класса).
Ключевые различия:
Характеристика | Конструктор копирования | Оператор присваивания |
---|---|---|
Когда вызывается | При создании объекта | Для существующего объекта |
Тип операции | Инициализация | Присваивание |
Возвращаемое значение | Нет | MyClass& (ссылка на объект) |
Проверка самокопирования | Не требуется | Обязательна |
Ответственность | Создание новой копии | Освобождение старых ресурсов + копирование |
class String {
char* data;
size_t size;
public:
// Конструктор копирования
String(const String& other) :
size(other.size),
data(new char[other.size + 1])
{
std::copy(other.data, other.data + size + 1, data);
}
// Оператор присваивания
String& operator=(const String& other) {
if (this != &other) { // Защита от самоприсваивания
delete[] data; // Освобождаем старые данные
size = other.size;
data = new char[size + 1];
std::copy(other.data, other.data + size + 1, data);
}
return *this;
}
~String() { delete[] data; }
};