1.4 – Присваивание и инициализация переменных
В предыдущем уроке «1.3 – Знакомство с переменными в C++» мы рассмотрели, как определить переменную, которую мы сможем использовать для хранения значений. В этом уроке мы узнаем, как помещать значения в переменные, и как использовать эти значения.
Напоминаем, что следующий короткий фрагмент сначала выделяет память для одной целочисленной переменной с именем x
, а затем выделяет память еще для двух целочисленных переменных с именами y
и z
:
int x; // определяем целочисленную переменную с именем x
int y, z; // определяем две целочисленные переменные с именами y и z
Присваивание значения переменной
После того, как переменная была определена, вы можете присвоить ей значение (в отдельной инструкции) с помощью оператора =
. Этот процесс для краткости называется копирующим присваиванием (или просто присваиванием).
int width; // определяем целочисленную переменную с именем width
width = 5; // копирующее присваивание значения 5 в переменную width
// переменная width теперь имеет значение 5
Копирующее присваивание названо так, потому что оно копирует значение с правой стороны оператора =
в переменную с левой стороны оператора. Оператор =
называется оператором присваивания.
Вот пример, где мы используем присваивание дважды:
#include <iostream>
int main()
{
int width;
width = 5; // копирующее присваивание значения 5 в переменную width
// переменная width теперь имеет значение 5
width = 7; // изменить значение, сохраненное в переменной width, на 7
// переменная width теперь имеет значение 7
return 0;
}
Когда мы присваиваем значение 7 переменной width
, значение 5, которое было там ранее, перезаписывается. Обычные переменные могут содержать только одно значение за раз.
Предупреждение
Одна из наиболее распространенных ошибок, которые делают новички, – это путать оператор присваивания (=
) с оператором равенства (==
). Присваивание (=
) используется для присвоения значения переменной. Равенство (==
) используется для проверки того, равны ли два операнда по значению.
Копирующая и прямая инициализации
Одним из недостатков присваивания является то, что для него требуются как минимум две инструкции: одна для определения переменной, а другая для присвоения значения.
Эти два шага можно совместить. При определении переменной вы также можете одновременно указать для нее начальное значение. Это называется инициализацией.
C++ поддерживает три основных способа инициализации переменных. Во-первых, мы можем выполнить копирующую инициализацию, используя знак равенства:
int width = 5; // копирующая инициализация значения 5 в переменную width
Подобно копирующему присваиванию, этот код копирует значение с правой стороны знака равно в переменную, создаваемую с левой стороны.
Во-вторых, мы можем выполнить прямую инициализацию с помощью скобок.
int width(5); // прямая инициализация значения 5 в переменную width
Для простых типов данных (например, целых чисел) копирующая и прямая инициализации, по сути, одинаковы. Различия между копирующей инициализацией и прямой инициализацией мы увидим в этой серии статей позже.
В-третьих, инициализация списком, которую мы рассмотрим в следующем разделе.
Инициализации списком
К сожалению, прямая инициализация с круглыми скобками не может использоваться для всех типов инициализации (таких как инициализация объекта списком данных). Чтобы обеспечить более согласованный механизм инициализации, существует инициализация списком (также иногда называемая унифицированной инициализацией или инициализацией с фигурными скобками), в которой используются фигурные скобки.
Инициализация списком бывает двух форм:
int width{5}; // прямая инициализация списком значения 5 в переменную width (предпочтительно)
int height = {6}; // копирующая инициализация списком значения 6 в переменную height
Эти две формы функционируют почти одинаково, но обычно предпочтительнее прямая форма.
Инициализация переменной с пустыми фигурными скобками указывает на инициализацию значения. В большинстве случаев инициализация значения инициализирует переменную нулевым значением (или пустым, если это более подходит для данного типа). В таких случаях, когда происходит обнуление, инициализация значения также называется нулевой инициализацией.
int width{}; // инициализация значения значением 0
Инициализация списком имеет дополнительное преимущество, так как запрещает «сужающие» преобразования. Это означает, что если вы попытаетесь использовать инициализацию списком для инициализации переменной значением, которое она не может безопасно удерживать, компилятор выдаст предупреждение или ошибку. Например:
int width{4.5}; // ошибка: не все значения типа double помещаются в int
В приведенном выше фрагменте мы пытаемся присвоить число (4.5), имеющее дробную часть (часть .5), целочисленной переменной (которая может содержать только числа без дробных частей). При копирующей и прямой инициализациях дробная часть будет отброшена, что приведет к инициализации переменной width
значением 4. Однако при инициализации списком это приведет к тому, что компилятор выдаст ошибку (что, как правило, хорошо потому, что потеря данных редко бывает желательной). Преобразования, которые могут быть выполнены без потенциальной потери данных, в этом случае разрешены.
Лучшая практика
По возможности отдавайте предпочтение прямой инициализации списком.
Вопрос: C++ обеспечивает копирующую, прямую инициализации и инициализацию списком, а также копирующее присваивание. Существует ли прямое присваивание или присваивание списком?
Нет, C++ не поддерживает синтаксис прямого присваивания или присваивания списком.
Вопрос: Когда следует инициализировать с помощью {0}
, а когда с помощью {}
?
Используйте явную инициализацию значением, если вы действительно используете это значение.
int x{0}; // явная инициализация значением 0
std::cout << x; // мы используем это нулевое значение
Используйте инициализацию значения, если значение временное и будет заменено.
int x{}; // инициализация значения
std::cin >> x; // мы немедленно заменяем это значение
Инициализируйте свои переменные
Инициализируйте свои переменные при создании. В конечном итоге вам могут встретиться случаи, когда вы захотите проигнорировать этот совет по определенной причине (например, критический для производительности фрагмент кода, который использует множество переменных), и это нормально, если выбор сделан сознательно.
Для более подробного обсуждения этой темы Бьёрн Страуструп (создатель C++) и Герб Саттер (эксперт по C ++) сами дают рекомендацию здесь.
Мы исследуем, что произойдет, если вы попытаетесь использовать переменную, для которой нет четко определенного значения, в уроке «1.6 – Неинициализированные переменные и неопределенное поведение».
Лучшая практика
Инициализируйте свои переменные при создании.
Инициализация нескольких переменных
В последнем разделе мы отметили, что можно определить несколько переменных одного типа в одной инструкции, разделив имена запятыми:
int a, b;
Мы также отметили, что лучше всего вообще избегать этого синтаксиса. Однако, поскольку вы можете столкнуться с чужим кодом, использующим этот стиль, всё же полезно поговорить об этом немного подробнее хотя бы, чтобы акцентировать внимание на некоторых причинам, по которым вам следует избегать его.
Вы можете инициализировать несколько переменных, определенных в одной строке:
int a = 5, b = 6; // копирующая инициализация
int c(7), d(8); // прямая инициализация
int e{9}, f{10}; // инициализация списком (предпочтительно)
К сожалению, здесь есть распространенная ошибка, которая может возникнуть, когда программист по ошибке пытается инициализировать обе переменные с помощью одной инструкции инициализации:
int a, b = 5; // неверно (a не инициализируется!)
int a = 5, b = 5; // верно
В верхней инструкции переменная a
будет оставлена неинициализированной, и компилятор может пожаловаться, а может и не пожаловаться. Если он не пожалуется, это отличный способ для вашей программы периодически давать сбои и выдавать случайные результаты. В ближайшее время мы подробнее поговорим о том, что произойдет, если вы будете использовать неинициализированные переменные.
Лучший способ запомнить, что это неправильно, – рассмотреть случай прямой инициализации или инициализации списком:
int a, b(5);
int c, d{5};
Этот код делает более ясным, что значение 5 используется только для инициализации переменной b
или d
, а не a
или c
.
Небольшой тест
Вопрос 1
В чем разница между инициализацией и присваиванием?
Ответ
Инициализация дает переменной начальное значение в момент ее создания. Присваивание дает переменной значение в какой-то момент после создания.
Вопрос 2
Какую форму инициализации следует использовать?
Ответ
Прямая инициализация списком.