6.3 – Локальные переменные
В уроке «2.4 – Локальная область видимости в C++» мы рассмотрели локальные переменные, которые представляют собой переменные, которые определены внутри функции (включая параметры функции).
Оказывается, в C++ на самом деле нет единственного атрибута, который определял бы переменную как локальную. Вместо этого у локальных переменных есть несколько различных свойств, которые отличают их поведение от других (нелокальных) переменных. Мы рассмотрим эти свойства в этом и следующих уроках.
В уроке «2.4 – Локальная область видимости в C++» мы также познакомились с концепцией области видимости. Область видимости идентификатора определяет, где в исходном коде можно получить к нему доступ. Когда к идентификатору можно получить доступ, мы говорим, что он находится в области видимости. Когда к идентификатору невозможно получить доступ, мы говорим, что он находится вне области видимости. Область видимости – это свойство времени компиляции, и попытка использовать идентификатор, когда он находится вне области видимости, приведет к ошибке компиляции.
Локальные переменные имеют область видимости блока
Локальные переменные имеют область видимости блока, что означает, что они находятся в области видимости от точки определения до конца блока, в котором они определены.
Связанный контент
Если вам нужно напомнить о блоках, просмотрите урок «6.1 – Составные инструкции (блоки)».
int main()
{
int i { 5 }; // i входит в область видимости здесь
double d { 4.0 }; // d входит в область видимости здесь
return 0;
} // здесь i и d выходят из области видимости
Хотя параметры функции не определены внутри тела функции, для типовых функций они могут считаться частью области видимости блока тела функции.
int max(int x, int y) // x и y входят в область видимости здесь
{
// присваиваем большее из x и y переменной max
int max{ (x > y) ? x : y }; // max входит в область видимости здесь
return max;
} // x, y и max выходят из области видимости здесь
Исключение составляет обработка исключений на уровне функций (которую мы рассмотрим в уроке «20.7 – Блоки try функций»).
Все имена переменных в области видимости должны быть уникальными
Имена переменных в пределах заданной области должны быть уникальными, иначе любая ссылка на имя будет неоднозначной. Рассмотрим следующую программу:
void someFunction(int x)
{
int x{}; // сбой компиляции из-за конфликта имен с параметром функции
}
int main()
{
return 0;
}
Показанная выше программа не компилируется, потому что переменная x
, определенная внутри тела функции, и параметр функции x
имеют одно и то же имя, и оба находятся в области видимости одного блока.
Локальные переменные имеют автоматическую продолжительность хранения
Продолжительность хранения переменной (обычно называемая просто продолжительностью) определяет, какие правила определяют, когда и как переменная будет создаваться и уничтожаться. В большинстве случаев продолжительность хранения переменной напрямую определяет ее время жизни.
Например, локальные переменные имеют автоматическую продолжительность хранения, что означает, что они создаются в точке определения и уничтожаются в конце блока, в котором они определены. Например:
int main()
{
int i { 5 }; // i создается и инициализируется здесь
double d { 4.0 }; // d создается и инициализируется здесь
return 0;
} // i и d здесь уничтожаются
По этой причине локальные переменные иногда называют автоматическими переменными.
Локальные переменные во вложенных блоках
Локальные переменные можно определять внутри вложенных блоков. Это работает аналогично локальным переменным в блоках тела функции:
int main() // внешний блок
{
int x { 5 }; // x входит в область видимости и создается здесь
{ // вложенный блок
int y { 7 }; // y входит в область видимости и создается здесь
} // y выходит из области видимости и уничтожается здесь
// y не может использоваться здесь,
// потому что в этом блоке она находится вне области видимости
return 0;
} // x выходит из области видимости и уничтожается здесь
В приведенном выше примере переменная y
определена внутри вложенного блока. Ее область видимости ограничена от точки определения до конца вложенного блока, и ее время жизни такое же. Поскольку область видимости переменной y
ограничена внутренним блоком, в котором она определена, она недоступна нигде во внешнем блоке.
Обратите внимание, что вложенные блоки считаются частью области видимости внешнего блока, в котором они определены. Следовательно, переменные, определенные во внешнем блоке, можно увидеть внутри вложенного блока:
#include <iostream>
int main()
{ // внешний блок
int x { 5 }; // x входит в область видимости и создается здесь
{ // вложенный блок
int y { 7 }; // y входит в область видимости и создается здесь
// x и y здесь находятся в области видимости
std::cout << x << " + " << y << " = " << x + y << '\n';
} // y выходит из области видимости и уничтожается здесь
// y не может использоваться здесь, потому что
// в этом блоке она находится вне области видимости
return 0;
} // x выходит из области видимости и уничтожается здесь
Локальные переменные не имеют связывания
У идентификаторов есть еще одно свойство – связывание. Связывание идентификатора определяет, относятся ли другие объявления с этим именем к тому же объекту или нет.
Локальные переменные не имеют связывания, что означает, что каждое объявление относится к уникальному объекту. Например:
int main()
{
int x { 2 }; // локальная переменная, без связывания
{
int x { 3 }; // этот идентификатор x относится к другому объекту, а не к предыдущему x
}
return 0;
}
Область видимости и связывание могут показаться чем-то похожим. Однако область видимости определяет, где можно увидеть и использовать одно объявление. Связывание определяет, относятся ли несколько объявлений к одному и тому же объекту или нет.
Связанный контент
Что происходит, когда переменные с одинаковыми именами появляются во вложенных блоках, мы обсудим в уроке «6.5 – Затенение переменных (скрытие имен)».
Связывание не очень интересно в контексте локальных переменных, но мы поговорим о нем подробнее в следующих нескольких уроках.
Переменные следует определять в наиболее ограниченной области видимости
Если переменная используется только внутри вложенного блока, она должна быть определена внутри этого вложенного блока:
#include <iostream>
int main()
{
// не определяйте y здесь
{
// y используется только внутри этого блока, поэтому определите ее здесь
int y { 5 };
std::cout << y << '\n';
}
// иначе y можно было бы использовать здесь, где это не нужно
return 0;
}
Ограничивая область видимости переменной, вы уменьшаете сложность программы, потому что уменьшается количество активных переменных. Кроме того, так легче увидеть, где используются (или не используются) переменные. Переменная, определенная внутри блока, может использоваться только внутри этого блока (или вложенных блоков). Это может упростить понимание программы.
Если переменная необходима во внешнем блоке, ее нужно объявить во внешнем блоке:
#include <iostream>
int main()
{
int y { 5 }; // мы объявляем y здесь, потому что она нам понадобится во внешнем блоке позже
{
int x{};
std::cin >> x;
// если мы объявили здесь y, непосредственно перед первым фактическим использованием...
if (x == 4)
y = 4;
} // ... здесь она будет уничтожена
std::cout << y; // а нам нужно, чтобы y существовала здесь
return 0;
}
В приведенном выше примере показан один из редких случаев, когда вам может потребоваться объявить переменную задолго до ее первого использования.
Начинающие разработчики иногда задаются вопросом, стоит ли создавать вложенный блок только для того, чтобы намеренно ограничить область видимости переменной (и заставить ее выйти за пределы области видимости, чтобы пораньше уничтожить). Это упрощает эту переменную, но в результате итоговая функция становится длиннее и сложнее. Обычно компромисс таков – не стоит. Если создание вложенного блока кажется полезным для намеренного ограничения области видимости фрагмента кода, этот код может быть лучше вместо этого поместить в отдельную функцию.
Лучшая практика
Определяйте переменные в наиболее ограниченной существующей области видимости. Избегайте создания новых блоков, единственная цель которых – ограничить область видимости переменных.
Небольшой тест
Вопрос 1
Напишите программу, которая просит пользователя ввести два целых числа: одно – меньшее для переменной smaller
, другое – большее для переменной larger
. Если пользователь вводит меньшее значение для второго целого числа, используйте блок и временную переменную, чтобы поменять местами меньшее и большее значения. Затем выведите значения переменных smaller
и larger
. Добавьте в свой код комментарии, указывающие, где уничтожается каждая переменная. Примечание. Когда вы печатаете значения, переменная smaller
должна содержать меньшее входное значение, а переменная larger
– большее, независимо от того, в каком порядке они были введены.
Вывод программы должен соответствовать следующему:
Enter an integer: 4
Enter a larger integer: 2
Swapping the values
The smaller value is 2
The larger value is 4
Ответ
#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int smaller{};
std::cin >> smaller;
std::cout << "Enter a larger integer: ";
int larger{};
std::cin >> larger;
// если пользователь ввел данные неправильно
if (smaller > larger)
{
// меняем местами значения smaller и larger
std::cout << "Swapping the values\n";
int temp{ larger };
larger = smaller;
smaller = temp;
} // temp уничтожается здесь
std::cout << "The smaller value is: " << smaller << '\n';
std::cout << "The larger value is: " << larger << '\n';
return 0;
} // smaller и larger уничтожаются здесь
Вопрос 2
В чем разница между областью видимости, продолжительностью и временем жизни переменной? Какие область видимости и продолжительность у локальных переменных по умолчанию (и что они означают)?
Ответ
Область видимости переменной определяет, где переменная доступна. Продолжительность определяет правила, которые регулируют создание и уничтожение переменной. Время жизни переменной – это фактическое время между ее созданием и уничтожением.
Локальные переменные имеют область видимости блока, что означает, что к ним можно получить доступ внутри блока, в котором они определены.
Локальные переменные имеют автоматическую продолжительность, что означает, что они создаются в точке определения и уничтожаются в конце блока, в котором они определены.