Для примерного понимания локализации переменных пожалуй начнем с аллегории. Что такое “Локальность”? Это ограниченность места дееспособности. Область видимости переменной – это тот участок кода (функция, цикл, пространство имени), в котором эта переменная объявлена (прописана). Вне этого участка – компилятор её не видит (она недоступна).
Так и получается – переменные описанные например внутри функций, не могут быть использованы за её пределами. В качестве примера можно рассмотреть такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <iostream> int i = 2; // глобальная переменная (видна в любом участке кода программы) int sum() { int k = 2; // локальная переменная (видна только внутри функции sum()) return i + k; } int main() { std::cout << i << std::endl << sum() << std::endl; } |
Давайте разберем пример поближе. Итак мы имеем переменную i, которая описана вне любых функций в программе. Ее область видимости и действия – вся программа без ограничения. А раз она “легитимна” во всей программе, во всех ее функциях и блоках операторов, заключенных в {}, то её называют Глобальной переменной.
Так же у нас в примере видна функция sum(), которая чего-то делает. В ней внутри описана вторая переменная – k. Она уже имеет конкретную “прописку” – функция. При этом данная переменная не может быть использована за пределами функции. Т.е. если в функции main() дописать еще и вывод этой k на экран, компилятор начнет “ругаться”. Он ее просто напросто не видит вне функции sum(). Проверьте:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <iostream> int i = 2; // глобальная переменная (видна в любом участке кода) int sum() { int k = 2; // локальная переменная (видна только внутри функции sum()) return i + k; } int main() { std::cout << i << std::endl << sum() << std::endl; std::cout << k << std::endl; // попытка обратиться к переменной k } |
Переменная k называется локальной, и ее область видимости определена открывающей и закрывающей фигурными скобками функции sum() – {…}. Дальше них ей хода нет, поэтому и использовать её нельзя вне этой функции. Компилируем:
Однако, нужно также учесть, что область видимости распространяется и на внутренние блоки. Для примера можно рассмотреть такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <iostream> int i = 2; int sum() { int k = 2; for (int i = 0; i < 10; i++) // эта i - локальная, объявленная в теле цикла k += 1; return i + k; // эта i - глобальная, объявленная вне функций } int main() { std::cout << i << std::endl << sum() << std::endl; } |
Здесь проявлены следующие области видимости:
- Глобальная – i = 2 принадлежит ей;
- Локальная относительно функции – переменная k;
- Локальная относительно цикла for() – вторая i.
Несмотря на то, что у for есть своя область видимости, сам for принадлежит функции sum(). А значит он, и все что в нем находится, подчиняется локальной области видимости этой функции. Т.е. все переменные, определенные в функции так же действительны и в теле for, что и позволяет работать оператору k += 1 (он же k = k+1 или k++).
Интереснее дело обстоит с переменной i, которая описана внутри for. Несмотря на имя, идентичное с глобальной переменной описанной выше, это уже другая переменная.
Как только цикл свое отработал, переменная i, описанная в нём, теряет свои полномочия, или иначе говоря – “освобождается” от обязанностей. Это не шутка – именно так и поступает менеджер памяти. Когда переменная описывается, менеджер резервирует под неё память, а когда её область видимости заканчивается – память эта освобождается. Менеджер памяти у себя помечает, что этот участок памяти более не принадлежит кому-либо, и его можно отдавать под запись других данных.
Для закрепления еще один легкий примерчик:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <iostream> int main() { int a = 0; { int a = 2; std::cout << a << std::endl; } std::cout << a << std::endl; } |
Здесь область видимости описана блоками операторов, ограничивающимися {}, Т.е. первый cout выведет 2, потому что определен блок операторов, в котором сработает это cout. И в этом же блоке определена локальная переменная с именем а, из нее он и возьмет значение прежде всего.
А уже за пределами {} оператор cout выведет 0, поскольку для него не существует той а, что равнялась 2. Его область видимости совпадает с другой а, которая равна 0, и определена выше вложенного блока.
Вообще запомните – в реальной жизни никогда не надо давать одинаковые имена переменным (за исключением тех, что классически используют для счетчиков циклов. Например i, j, k. Если у вас 10 циклов в коде и они не вложенные, то для каждого из них можно объявлять и определять счетчик с именем i. Это нормально). В других случаях, всегда давайте переменным уникальные имена, если не хотите, чтобы вас потом “вспоминали” программисты, которые будут разбираться в вашем коде.
Предлагаем также просмотреть видео-урок по теме область видимости.