Объявление создаёт переменную, инициализация задаёт ей первое значение. В C++ это разные вещи, и пропуск второго шага — не мелочь.
int a; // объявлена, значение неопределённое
int b = 0; // объявлена и инициализированаЧетыре слова, которые путают
int x;
x = 0;
x = 1;
int y = 2;int x;— объявление и определение сразу: объект уже существует, просто полезного начального значения у него нет.x = 0;иx = 1;— присваивания: запись значения в существующий объект. Первое присваивание не становится определением только потому, что оно первое.int y = 2;— инициализация: значение задаётся при создании объекта.
Знак = в инициализации не означает операцию присваивания. Записи похожи
внешне, но описывают разные действия — и это не придирка к словам: определение
выделяет объект, а присваивание работает с уже существующим.
Почему int a; опасно
У локальной переменной встроенного типа без инициализатора нет значения, которое разрешено прочитать. Чтение такого значения — Неопределённое поведение, а не «получим случайное число»: популярный ответ про мусор из памяти обещает больше, чем гарантирует язык.
Записать в такой объект можно, это законно:
int x;
x = 10;
std::cout << x << '\n'; // 10Но проще сразу написать int x = 10; — не стоит оставлять переменную без
значения, если подходящее уже известно.
Коварство в том, что программа обычно работает: в отладочной сборке там
оказывается ноль, тесты проходят, а на другой машине или с оптимизацией
поведение меняется. Поэтому clang-tidy и включает проверку
cppcoreguidelines-init-variables.
Исключение — глобальные и статические переменные: они инициализируются нулём до старта программы. Полагаться на это в локальном коде нельзя.
Формы записи
int a = 0; // копирующая инициализация — привычная и понятная
int b(0); // прямая
int c{0}; // списочная, с C++11
int d{}; // списочная без значения: ноль для встроенных типовЗначения получаются 0, 0, 0 и 0. Формы не во всех ситуациях взаимозаменяемы, их различия понадобятся позже; сейчас важно узнавать их в коде и не принимать круглые или фигурные скобки за другую операцию.
Не из лекции
Про запрет сужения у
{}на занятии не говорили — лонгрид перечисляет формы инициализации, но их различия оставляет на потом.
Практическая разница у списочной формы: она запрещает сужающие преобразования.
int x = 3.9; // компилируется, x == 3
int y{3.9}; // ошибка компиляции: сужение double → intТо есть {} ловит потерю данных на этапе компиляции. В учебном коде курса
чаще встречается = 0, и этого достаточно; главное — инициализировать хоть
как-нибудь.
Одна переменная на строку
Google Style Guide требует объявлять по одной переменной в инструкции —
и clang-tidy проверяет это readability-isolate-declaration:
int h, m, s; // так не надо: ни одна не инициализирована
int hours = 0; // так
int minutes = 0;
int seconds = 0;Помимо стиля тут есть и ловушка с указателями: в int* p, q; указателем
будет только p, а q — обычный int. Звёздочка привязана к имени, а не
к типу слева, и на занятии это встретилось вживую:
long long *plong, llx; // plong — указатель, llx — обычный long longОдна переменная на строку эту двусмысленность убирает.
Тонкости
- Инициализировать лучше сразу значением, а не «объявлю сверху, присвою ниже»: чем ближе объявление к использованию, тем меньше шансов забыть.
- Если переменная больше не меняется — сделайте её
const, см. const и constexpr. - Неинициализированное чтение ловится санитайзером при сборке с
-fsanitize=undefined,address, см. Компиляция и сборка.
Частые ошибки
int x;и сразуx += ...— накопление поверх мусора.- Объявление пачкой через запятую без инициализации.
- Уверенность, что «оно всё равно ноль»: в отладочной сборке — может быть, в проверяющей системе — не обязательно.
Углубление — L02
На L02 объявление и определение развели явно: объявление сообщает
компилятору, что имя существует, определение создаёт объект. Бывает
объявление без определения — extern int x;. Подробнее —
Объявление и определение.
Расхождение с L01
Вступление этой заметки (по L01): «Объявление создаёт переменную, инициализация задаёт ей первое значение». L02: «Объявление сообщает компилятору, что такое имя существует. Определение действительно создаёт объект».
Формулировка L01 верна для
int a;— это объявление и определение одновременно, о чём сказано и выше в разделе «Четыре слова». Но как общее правило она неточна:extern int x;— объявление, которое ничего не создаёт. Точнее формулировка L02.
Где встретится неинициализированная переменная ещё: переход goto через
объявление. Разбор в L02 - Области видимости и циклы.