Что такое переменная
Переменная - это именованный участок памяти, который хранит значение. В C++ у каждой переменной есть фиксированный тип - выбранный при объявлении -, и этот тип никогда не меняется. Именно поэтому C++ является языком со статической типизацией: компилятор знает тип каждой переменной ещё до запуска программы и отказывается компилировать код, который хранит значение неправильного вида.
Объявление состоит из трёх частей: типа, имени и (почти всегда) начального значения.
Читайте int age = 30; как «создай int по имени age и положи в него 30». Точка с запятой завершает инструкцию, как и показано на странице о комментариях для каждой инструкции. Обратите внимание, что isActive вывелось как 1: bool по умолчанию отображается как 1/0 - на этом строится страница о типах данных.
Инициализация vs. присваивание
Эти две вещи выглядят похоже, но это разные операции, и эта разница - одна из важнейших идей на этой странице.
Инициализация даёт переменной её первое значение как часть объявления. Присваивание меняет значение уже существующей переменной.
Можно объявить без инициализации, а присвоить позже, но будьте осторожны:
Это работает, потому что мы присвоили score до того, как прочитали. Опасный вариант - прочитать его сначала, об этом дальше.
Ловушка неинициализированной переменной
Это классическая ловушка C++, которой нет во многих других языках. Чтение локальной переменной, которая была объявлена, но никогда не получала значения, - это неопределённое поведение: переменная содержит те байты, что случайно оказались в этой памяти.
int score; // не инициализирована
std::cout << score; // неопределённое поведение - печатает мусор, или «работает», или падает
Компилятор спокойно соберёт это. Может вывести 0, может вывести 32766, и может вести себя по-разному при каждом запуске или на каждой машине - из-за чего такие ошибки мучительно отлавливать. Две меры защиты:
- Всегда инициализируйте при объявлении.
int score = 0;ничего не стоит и полностью устраняет проблему. - Включите предупреждения. Компиляция с
-Wall -Wextraзаставляет компилятор отмечать множество неинициализированных чтений до того, как они вас укусят.
Для каждой локальной переменной предпочитайте первый вариант: дайте ей разумное начальное значение в тот же момент, когда создаёте.
Стили инициализации: =, () и {}
C++ предлагает несколько способов записать начальное значение. В основном они делают одно и то же, но у инициализации фигурными скобками есть одна дополнительная мера безопасности, о которой стоит знать.
Причина прибегать к {} в том, что она отвергает сужающие преобразования (narrowing) - присваивания, которые молча теряли бы данные. С = дробное значение тихо усекается; с {} компилятор вас останавливает:
int x = 3.9; // компилируется - x молча становится 3 (.9 отбрасывается)
int y{3.9}; // ошибка компиляции - сужение из double в int не допускается
Поймать это на этапе компиляции - именно то, что вам нужно. Распространённая современная привычка - использовать {} по умолчанию ради этой дополнительной проверки и прибегать к = только тогда, когда инициализация копированием читается естественнее.
Правила и соглашения об именах
C++ навязывает несколько жёстких правил, а поверх них все добавляют соглашения. Правила: имя может содержать буквы, цифры и _; оно не может начинаться с цифры; оно не может быть зарезервированным ключевым словом (например, int или return); и оно чувствительно к регистру (age и Age - две разные переменные). Избегайте имён, которые начинаются с подчёркивания, за которым следует заглавная буква, или содержат два подчёркивания подряд - они зарезервированы для реализации.
Соглашения, которым следует большинство кода на C++:
- Переменные используют
snake_caseилиcamelCase- выберите одно и придерживайтесь его:item_countилиitemCount. - Имена должны описывать значение:
count, а неc;user_email, а неx.
Понятные имена - это не украшение, это то, как ваше будущее «я» будет читать код. total_price = item_count * price_per_item объясняет себя сам так, как t = c * p не сможет никогда.
Область видимости переменных
Переменная существует только внутри блока - { ... } -, где она объявлена, и уничтожается на закрывающей фигурной скобке. Это её область видимости (scope). Переменная, объявленная внутри цикла или блока if, невидима снаружи:
И i, и square принадлежат циклу и исчезают, когда он завершается; total объявлена во внешнем блоке, поэтому она сохраняется. Внутренний блок также может перекрыть (shadow) имя из внешнего блока: новая переменная с тем же именем временно скрывает внешнюю, пока внутренний блок не закроется, - это частый источник путаницы, поэтому избегайте повторного использования имён во вложенных областях видимости.
Практический вывод: объявляйте каждую переменную в наименьшем блоке, которому она нужна, и инициализируйте её там же. Узкая область видимости означает меньше имён, борющихся за ваше внимание, и меньше шансов прочитать значение далеко от того места, где оно было задано.
Далее: Типы данных
Каждая переменная на этой странице начиналась с типа - int, double, string, bool. Следующая страница подробно разбирает типы данных C++: фундаментальные типы и их размеры, знаковые и беззнаковые целые, float vs. double, char и как выбрать правильный тип для задачи.
Часто задаваемые вопросы
Как объявить переменную в C++?
Напишите тип, затем имя, а затем при желании задайте значение: int age = 30;. Тип (int) зафиксирован на всю жизнь переменной; имя (age) - это то, как вы к ней обращаетесь. Можно объявить и без значения - int age; -, но для локальной переменной это оставляет в ней мусор до тех пор, пока вы не присвоите значение, поэтому всегда инициализируйте её прямо в месте объявления.
В чём разница между инициализацией и присваиванием в C++?
Инициализация даёт переменной её первое значение как часть объявления: int x = 5; или int x{5};. Присваивание меняет значение уже существующей переменной: x = 7;. Различие важно, потому что чтение локальной переменной, которая была объявлена, но никогда не инициализирована, - это неопределённое поведение.
Что произойдёт, если использовать неинициализированную переменную в C++?
Чтение значения неинициализированной локальной переменной - это неопределённое поведение (undefined behavior): переменная содержит те байты, что уже оказались в этой памяти, поэтому программа может вывести случайное число, сработать по счастливой случайности или упасть. Компилятор вас не остановит (хотя многие предупреждают при -Wall), поэтому решение - всегда давать локальным переменным значение при объявлении.