Переменные, Инициализация и Присваивание. Урок 10

Программируя на языке C++, мы создаем, обрабатываем и уничтожаем объекты. Объект — это часть памяти, которая может хранить значение. В качестве аналогии мы можем использовать почтовый ящик, куда мы помещаем информацию и откуда её извлекаем. Все компьютеры имеют оперативную память, которую используют программы. При создании объекта, часть оперативной памяти выделяется для этого объекта. Большинство объектов, с которыми мы будем работать в языке C++, являются переменными.

Переменные

Cтейтмент a = 8; выглядит довольно простым: мы присваиваем значение 8 переменной a. Но что такое aa — это переменная, объект с именем.

На этом уроке мы рассмотрим только целочисленные переменные. Целое число — это число, которое можно записать без дроби, например: -11, -2, 0, 5 или 34.

Для создания переменной используется стейтмент объявления (разницу между объявлением и определением переменной мы рассмотрим несколько позже). Вот пример объявления целочисленной переменной a (которая может содержать только целые числа):

int a;

При выполнении этой инструкции центральным процессором часть оперативной памяти выделяется под этот объект. Например, предположим, что переменной a присваивается ячейка памяти под номером 150. Когда программа видит переменную a в выражении или в стейтменте, то она понимает, что для того, чтобы получить значение этой переменной, нужно заглянуть в ячейку памяти под номером 150.

Одной из наиболее распространенных операций с переменными является операция присваивания, например:

a = 8;

Когда процессор выполняет эту инструкцию, он понимает её как «поместить значение 8 в ячейку памяти под номером 150».

Затем мы сможем вывести это значение на экран с помощью std::cout:

std::cout << a; // выводим значение переменной a (ячейка памяти под номером 150) на экран

l-values и r-values

В языке C++ все переменные являются l-values. l-value (в переводе «л-значение», произносится как «ел-валью») — это значение, которое имеет свой собственный адрес в памяти. Поскольку все переменные имеют адреса, то они все являются l-values (например, переменные abc — все они являются l-values). l от слова «left», так как только значения l-values могут находиться в левой стороне в операциях присваивания (в противном случае, мы получим ошибку). Например, стейтмент 9 = 10; вызовет ошибку компилятора, так как 9 не является l-value. Число 9 не имеет своего адреса в памяти и, таким образом, мы ничего не можем ему присвоить (9 = 9 и ничего здесь не изменить).

Противоположностью l-value является r-value (в переводе «р-значение», произносится как «ер-валью»). r-value — это значение, которое не имеет постоянного адреса в памяти. Примерами могут быть единичные числа (например, 7, которое имеет значение 7) или выражения (например, 3 + х, которое имеет значение х плюс 3).

Вот несколько примеров операций присваивания с использованием r-values:

int a;      // объявляем целочисленную переменную a
a = 5;      // 5 имеет значение 5, которое затем присваивается переменной а
a = 4 + 6;  // 4 + 6 имеет значение 10, которое затем присваивается переменной а
 
int b;      // объявляем целочисленную переменную b
b = a;      // a имеет значение 10 (исходя из предыдущих операций), которое затем присваивается переменной b
b = b;      // b имеет значение 10, которое затем присваивается переменной b (ничего не происходит)
b = b + 2;  // b + 2 имеет значение 12, которое затем присваивается переменной b

Давайте детально рассмотрим последнюю операцию присваивания:

b = b + 2;

Здесь переменная b используется в двух различных контекстах. Слева b используется как l-value (переменная с адресом в памяти), а справа b используется как r-value и имеет отдельное значение (в данном случае, 12). При выполнении этого стейтмента, компилятор видит следующее:

b = 10 + 2;

И здесь уже понятно, какое значение присваивается переменной b.

Сильно беспокоиться о l-values или r-values сейчас не нужно, так как мы еще вернемся к этой теме на следующих уроках. Всё, что вам нужно сейчас запомнить — это то, что в левой стороне операции присваивания всегда должно находиться l-value (которое имеет свой собственный адрес в памяти), а в правой стороне операции присваивания — r-value (которое имеет какое-то значение).

Инициализация vs. Присваивание

В языке C++ есть две похожие концепции, которые новички часто путают: присваивание и инициализация.

После объявления переменной, ей можно присвоить значение с помощью оператора присваивания (знак равенства =):

int a; // это объявление переменной
a = 8; // а это присваивание переменной a значения 8

В языке C++ вы можете объявить переменную и присвоить ей значение одновременно. Это называется инициализацией (или «определением»).

int a = 8; // инициализируем переменную a значением 8

Переменная может быть инициализирована только после операции объявления.

Хотя эти два понятия близки по своей сути и часто могут использоваться для достижения одних и тех же целей, все же в некоторых случаях следует использовать инициализацию, вместо присваивания, а в некоторых — присваивание вместо инициализации.

Правило: Если у вас изначально имеется значение для переменной, то используйте инициализацию, вместо присваивания.

Неинициализированные переменные

В отличие от других языков программирования, языки Cи и C++ не инициализируют переменные определенными значениями (например, нулем) по умолчанию. Поэтому, при создании переменной, ей присваивается ячейка в памяти, в которой уже может находиться какой-нибудь мусор! Переменная без значения (со стороны программиста или пользователя) называется неинициализированной переменной.

Использование неинициализированных переменных может привести к ошибкам, например:

#include <iostream>
 
int main()
{
    // Объявляем целочисленную переменную a
    int a;
 
    // Выводим значение переменной a на экран (a - это неинициализированная переменная)
    std::cout << a;
 
    return 0;
}

В этом случае компилятор присваивает переменной a ячейку в памяти, которая в данный момент свободна (не используется). Затем значение переменной a отправляется на вывод. Но что мы увидим на экране? Ничего, так как компилятор это не пропустит — выведется ошибка, что переменная a является неинициализированной. В более старых версиях Visual Studio компилятор вообще мог бы вывести какое-то некорректное значение (например, 7177728, т.е. мусор), которое было бы содержимым той ячейки памяти, которую он присвоил нашей переменной.

Использование неинициализированных переменных является одной из самых распространенных ошибок начинающих программистов, но, к счастью, большинство современных компиляторов выдадут ошибку во время компиляции, если обнаружат неинициализированную переменную.

Хорошей практикой считается всегда инициализировать свои переменные. Это будет гарантией того, что ваша переменная всегда имеет определенное значение и вы не получите ошибку от компилятора.

Правило: Убедитесь, что все ваши переменные в программе имеют значения (либо через инициализацию, либо через операцию присваивания).

Тест

Какой результат выполнения следующих стейтментов?

int a = 6;
a = a - 3;
std::cout << a << std::endl; // №1
 
int b = a;
std::cout << b << std::endl; // №2
 
// В этом случае a + b является r-value 
std::cout << a + b << std::endl; // №3
 
std::cout << a << std::endl; // №4
 
int c;
std::cout << c << std::endl; // №5

Ответы

Чтобы просмотреть ответ, кликните на него мышкой.

Ответ 1

Программа выведет 3: a – 3 = 3, что и присваивается переменной a.

Ответ 2

Программа выведет 3: переменной b присваивается значение переменной a (3).

Ответ 3

Программа выведет 6: a + b = 6. Здесь не используется операция присваивания.

Ответ 4

Программа выведет 3: значением переменной a до сих пор является 3.

Ответ 5

Результатом будет ошибка, так как c — это неинициализированная переменная.

Прогаем.ру
Добавить комментарий