Переменные — типы, инициализация, владение
Переменная — именованная область памяти с типом, значением и временем жизни. C++ даёт программисту явный контроль над всеми тремя: вы решаете, сколько байт занимает объект, как он инициализируется, можно ли его изменить, как долго он живёт и где видно его имя.
Это не педантичность ради педантичности. Выбор int вместо int32_t, забытая инициализация, неверная форма const T* vs T* const, или путаница между static (linkage) и static (storage duration) — источники реальных багов. На этих базовых вещах опираются ссылки, шаблоны и move-семантика — без них дальше не двинуться.
Карта темы
- Типы данных —
int/float/char/bool, статические массивы, UB переполнения. - Инициализация — формы (
=,(),{}), brace-init и narrowing, UB чтения. - const-квалификаторы —
constна типах, указателях, методах;mutable. - const vs constexpr — runtime read-only vs compile-time константа.
- Категории значений — lvalue/rvalue и привязка ссылок.
- auto и вывод типов —
auto, structured bindings, CTAD. - Битовые операции — операторы, bit fields, UB сдвигов.
- Ссылки и указатели — когда что использовать.
- Scope и linkage — block/namespace/class scope, external/internal linkage, ODR.
- Storage duration — automatic/static/thread/dynamic, magic statics, fiasco.
- Раскладка struct — padding,
alignas,offsetof, POD/standard-layout/trivially-copyable.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
int x; без инициализации, потом чтение | UB — компилятор вправе сгенерировать что угодно |
int x{3.14} ожидание усечения | Ошибка компиляции — brace-init запрещает narrowing |
int s = -1; if (s < unsigned(1)) | false — signed конвертируется в большое unsigned |
Сравнение double через == | Накопленная погрешность — нужен эпсилон |
INT_MAX + 1 или сдвиг на ширину типа | UB переполнения signed / сдвига |
const int* vs int* const путаница | Неверно: невозможно компилировать или меняется не то |
auto x = getRef() ожидание ссылки | Копия; auto& или decltype(auto) нужно |
| Глобал из TU1 зависит от глобала из TU2 | Static initialization order fiasco — UB |
static int x; в .h файле без inline | По одной копии на TU — не то, что ожидалось |
if (x & MASK == 0) без скобок | Парсится как x & (MASK==0) — ошибка приоритета |
| Поля struct в случайном порядке | Лишний padding, sizeof больше необходимого |
Значение для собеседований
Типы и инициализация — один из самых частых источников вопросов на C++ собеседованиях. Интервьюер проверяет не знание синтаксиса наизусть, а понимание механики — почему всё устроено именно так.
Что обычно проверяют:
- Brace-init vs copy-init: почему
int x{3.14}не компилируется, аint x = 3.14— да (narrowing). - Неинициализированные переменные — UB, не «просто мусор».
const T*vsT* const— две разные вещи, умение читать сложные объявления.- lvalue/rvalue — основа понимания move-семантики и perfect forwarding.
- Знаковое переполнение — UB, и как компилятор этим пользуется при оптимизациях.
intне всегда 32 бита — стандарт гарантирует только минимум 16; нуженint32_t.staticв трёх смыслах: linkage, storage duration, член класса.- Storage initialization order fiasco — почему глобалы из разных TU опасны.
Типичный неверный ответ: «int всегда занимает 4 байта». Стандарт гарантирует только sizeof(int) >= sizeof(short) >= sizeof(char) и минимум 16 бит для int. Хорошим C++-кодом считается тот, где автор явно выражает требования (int32_t, size_t), а не полагается на платформенные значения по умолчанию.