Тема, которую мы рассмотрим на этом уроке, касается не только функций, но и переменных. Поэтому для упрощения буду дальше говорить в основном о них. Область видимости, это часть программы в которой можно использовать указанную переменную. Бывает она всего двух видов: глобальная и локальная. Глобальная область это весь наш файл с исходным кодом. Локальная же область, это область, принадлежащая определенному блоку кода, например функции. Локальные области могут быть вложенными друг в друга. Вне своей области локальная переменная невидима, то есть ее нельзя использовать, тогда как глобальная переменная может использоваться в любой объявленной в ней локальной. Локальная область видимости ограничивается фигурными скобками. Давайте рассмотрим простой пример.
Code
int i;
void main() { i=20; }
void func() { i = 321; }
Это весь код, никаких инклюдов, ничего.
Переменная i, объявленная вне главной функции main(), является глобальной. Она видима и внутри main и внутри вообще любой другой функции, например func. Запомните, глобальная область предназначена только для объявлений или определений. Никакие другие действия в ней не работают. Если вы захотите выполнить, например, присваивание:
Code
int i;
i=123; // ошибка void main() { i=20; }
то компилятор укажет на ошибку. Он решит, что вы пытаетесь заново объявить переменную i, да еще и неправильно, забыв указать тип. Если нужно задать глобальной переменной начальное значение, то делается это сразу при объявлении вот так:
Code
int i=123; // все ОК void main() { i=20; }
Теперь слегка изменим пример:
Code
void func();
void main() { int s; s = 10; // все ОК }
void func() { s = 321; // ошибка }
Здесь внутри функции main объявлена переменная с, внутри самой функции с ней можно делать все что угодно. Но попытка использовать ее в функции func вызовет ошибку, поскольку func не принадлежит к области видимости функции main. Казалось бы, чисто теоретически, можно было бы сделать вот так.
Code
void func();
void main() { int s; s = 10; // все ОК void func() { s = 321; } }
Но, не судьба. Запомните, объявлять и писать реализацию функции можно только в глобальной области. Поэтому попытка проделать такое вызовет ошибку. Следующий пример:
Code
int s;
void main() { s = 10; // все ОК while() { s = 321; // тоже все ОК int w; w = 128; // все ОК } w = 512; // А вот здесь ошибка! }
Что и где доступно, видно из комментариев. Здесь у цикла есть собственная область видимости. Создать такую область можно, просто выделив что-то фигурными скобками.
Code
int s;
void main() { s = 10; // все ОК { s = 321; // тоже все ОК int w; w = 128; // все ОК } w = 512; // А вот здесь ошибка! }
Здесь тот же пример но без цикла, тем не менее области видимости сохранились. Ключевое значение имеют только фигурные скобки. Но поскольку по синтаксису наличие таких скобок в цикле обязательно, то можно смело говорить что циклы имеют собственную локальную область видимости.
Ранее, описывая требования для имен переменных, я говорил, что имя должно быть уникальным. Теперь я хочу это правило дополнить. Имя должно быть уникальным в пределах одной области видимости.
То есть:
Code
void func();
void main() { int s; }
void func() { int s; }
Не вызовет никаких проблем. Переменная s является для каждой из функций локальной, посему в другой области она невидима и такая запись не приводит к возникновению ошибки. Хорошо, теперь вот такой пример:
Code
int s; void func();
void main() { int s; s = 10; }
void func() { s = 50; }
Здесь переменная s объявлена также и в глобальной области. Казалось бы, она должна быть видимой и внутри других функций и подобная запись должна приводить к ошибке. Но нет! Ранее озвученное правило продолжает действовать. Просто если имя объявлено в глобальной области, то везде используется оно, но если в локальной области объявлена переменная с аналогичным именем, то используется локальная. Поэтому в приведенном выше примере
Присваивание s = 10; никак не затрагивает s глобальную. Тогда как в функции func присваивание производится как раз глобальной переменной. Так что можно даже сказать, что это две разные переменные. И следующая программа это продемонстрирует:
Code
#include <iostream> using namespace std;
int s; void func();
void main() { setlocale(0,""); int s=30; cout<<"Локальная s ="<<s; func(); cin.get(); }
void func() { s = 10; cout<<"\nГлобальная s ="<<s; }
Результат ее работы наглядно иллюстрирует, что изменение локальной переменной никак не влияет на ее глобального тезку. Отлично. Все это хорошо. А вот что тогда делать, если нам зачем-то нужно обратится к этой самой глобальной одноименной переменной. Для этого есть специальный оператор - ::. С его помощью можно запросто обратится к глобальной области. Как им пользоваться проиллюстрировано в примере ниже:
Code
#include <iostream> using namespace std;
int s=10;
void main() { setlocale(0,""); int s=30; cout << "Локальная s =" << s; cout << "\nГлобальная s =" << ::s; // Вот здесь вот оно, это двойное двоеточие cin.get(); }
Все, если вы внимательно читали и рассматривали примеры, то должны все понять. Теперь можно вернутся к функциям, и поговорить о том, зачем же нужно их объявление. Допустим где то в программе, глубоко в областях видимости, хотя это и не важно, у нас есть две функции, использующие для работы друг друга:
Code
void a() { b(); } ... void b() { a(); }
Если объявления нет, то как же первая функция узнает о существовании второй? Вот такой вариант не пройдет:
Компилятор не допустит существования двух функций с одинаковыми именами. Да и не эффективно это, ведь функция может содержать несколько сотен строк кода. Поэтому, дабы избежать подобных недоразумений, и используется предварительное объявление:
Code
void b();
void a() { b(); } ... void b() { a(); }
Проблемы области видимости.
А теперь поговорим о некоторых проблемах для локальных областей видимости. Есть у них один недостаток. Объявленная внутри локальной области переменная существует до тех пор, пока эта область активна. Рассмотрим следующий пример:
Code
int* func() { int* pi; int i=55; pi = &i; return pi; }
void main() { int* pС; pС = func();
}
Сейчас нам необходимо вспомнить об указателях. Функции способны работать с ними так же, как и с любым другим типом данных. В приведенном примере func() возвращает указатель на переменную i. Вот только сама переменная, по завершении работы функции, перестает существовать. В результате наш указатель указывает на все, что угодно, то есть по старому адресу, только не на существовавшую ранее переменную, а ее значение (55) окажется потерянным. Хуже всего то, что компилятор никак не сможет помочь вам избежать таких вот ситуаций. Тут уж все в руках программиста.
На этом с видимостью завязываем, и вновь возвращаемся непосредственно к функциям. Ведь узнать нам придется еще очень много.