Локальные и глобальные переменные

Для примерного понимания локализации переменных пожалуй начнем с аллегории. Что такое «Локальность»? Это ограниченность места дееспособности. Область видимости переменной — это тот участок кода (функция, цикл, пространство имени), в котором эта переменная объявлена (прописана). Вне этого участка — компилятор её не видит (она недоступна).

Так и получается — переменные описанные например внутри функций, не могут быть использованы за её пределами. В качестве примера можно рассмотреть такой код:

#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::Во всех наших примерах, до этого момента, мы подключали пространство имён std так using namespace std;. В этом простейшем примере, мы обращаемся только лишь к ключевым словам cout и endl из этого пространства, поэтому нет смысла подключать его полностью. Эффективней использовать записи std::cout и std::endl , обращаясь к пространству имён std сразу из функции main()

Давайте разберем пример поближе. Итак мы имеем переменную i, которая описана вне любых функций в программе. Ее область видимости и действия — вся программа без ограничения. А раз она «легитимна» во всей программе, во всех ее функциях и блоках операторов, заключенных в {}, то её называют Глобальной переменной.

Так же у нас в примере видна функция sum(), которая чего-то делает. В ней внутри описана вторая переменная — k. Она уже имеет конкретную «прописку» — функция. При этом данная переменная не может быть использована за пределами функции. Т.е. если в функции main() дописать еще и вывод этой k на экран, компилятор начнет «ругаться». Он ее просто напросто не видит вне функции sum(). Проверьте:

#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() — {…}. Дальше них ей хода нет, поэтому и использовать её нельзя вне этой функции. Компилируем:

Однако, нужно также учесть, что область видимости распространяется и на внутренние блоки. Для примера можно рассмотреть такой код:

#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, описанная в нём, теряет свои полномочия, или иначе говоря — «освобождается» от обязанностей. Это не шутка — именно так и поступает менеджер памяти. Когда переменная описывается, менеджер резервирует под неё память, а когда её область видимости заканчивается — память эта освобождается. Менеджер памяти у себя помечает, что этот участок памяти более не принадлежит кому-либо, и его можно отдавать под запись других данных.

Для закрепления еще один легкий примерчик:

#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, и определена выше вложенного блока.

Вообще запомните — в реальной жизни никогда не надо давать одинаковые имена переменным (за исключением тех, что классически используют для счетчиков циклов. Например ijk. Если у вас 10 циклов в коде и они не вложенные, то для каждого из них можно объявлять и определять счетчик с именем i. Это нормально). В других случаях, всегда давайте переменным уникальные имена, если не хотите, чтобы вас потом «вспоминали» программисты, которые будут разбираться в вашем коде.

Хотелось бы ещё немного дополнить. А именно примером с пространствами имен. Я не зря писала префикс std:: перед оператором вывода cout. и endl. Это тоже одна из формаций области видимости — пространство имен. Если не вдаваться в особые подробности, пространство имен, это некая группа операторов / функций / переменных / типов, объединенная в такую себе «фракцию» с именем std.

std это пространство имен из заголовочного файла (хедера по нашему) iostreamcoutcin, endl и др. — это все «династия» std. Если не подключить его в коде и не написать std:: перед этими операторами, С++ скажет «давай до свиданья». Потому что он не в курсе, что это за cout такой без «фамилии». Кстати в С++ и придуман оператор using namespace, который позволяет не писать имя пространства в качестве префикса к каждому входящему в него оператору . Это для удобства сделано, но нужно с этим оператором быть аккуратнее, если есть одноименные функции используемые в программе из разных пространств имен.

Пространство имен хорошо там, где нужно сгруппировать что-то в область видимости по имени. Например в программе могут быть описаны две разные функции sum(). Одна возвращает целое, вторая вещественное. Если по заданию их нельзя описывать как перегруженные, то можно вкрутить их в пространство имен:

#include <iostream>

 

int i = 2;

 

namespace integer

{

int sum()

{

int k = 2;

return i + k;

}

}

 

namespace floating

{

float sum()

{

float k = 2.5;

return k + i;

}

}

 

int main()

{

std::cout << integer::sum() << std::endl;

std::cout << floating::sum() << std::endl;

}

Пространство имен тут определяет область видимости для разных функций с одинаковым именем. За пределами пространства имен этих функций не существует.

Вообще пространство имен может стать спорным моментом относительно теории области видимости. По факту это просто группировка по имени, но действует она так же, как и блок операторов {…}. Хочешь обратиться к операторам из этого пространства — будь добр обратись сначала к имени пространства: integer :: sum() Тут двоеточие :: — это оператор расширения области видимости.

Пожалуй последнее, что можно сказать — область видимости весьма показательна в ООП (объектно ориентированное программирование). Когда в классе программист описывает private свойство (переменую или функцию-метод), он задает ей область видимости. Например в коде типа:

class C

{

private: int k;

 

public: int kc;

int sum()

{

return k + kc;

}

};

Свойства k и kc имеют разные области видимости. Первая доступна только внутри методов класса. К ней нельзя обратиться в основной программе, она приватная (private ). Закрытая для всех, кроме области видимости самого класса.

Вторая публичная (public). Её область видимости выходит за пределы класса. Из программы, где будет вызываться объект этого класса она доступна.

kc объявлена как публичная переменная, её область видимости в отличии от переменой k , шире. Поэтому компилятор не против её использования вне пределов класса.

Какой можно сделать из всего этого вывод? Гики с пеной у рта кричат на всех порталах — «долой глобальные переменные!». Гикам нравится ограничивать область видимость, локализуя и упаковывая всё в классы или пространства имен. Гики есть гики. Программист не должен злоупотреблять областью видимости, иначе получится, оказия, когда чтоб обратиться к переменной нужно писать её путь. Т.е. что-то типаmyname::group1::PeremenkiDlaPrepoda::i Ну жесть просто.

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