z является локальной
}
int g(int x) // переменная g является глобальной;
// переменная x является локальной в функции g
{
int f = x+2; // переменная f является локальной
return 2*f;
}
Изобразим это графически.
Здесь переменная x, объявленная в функции f(), отличается от переменной x, объявленной в функции g(). Они не создают недоразумений, потому что принадлежат разным областям видимости: переменная x, объявленная в функции f(), не видна извне функции f(), а переменная x, объявленная в функции g(), не видна извне функции g(). Два противоречащих друг другу объявления в одной и той же области видимости создают коллизию (clash). Аналогично, переменная f объявлена и используется в функции g() и (очевидно) не является функцией f().
Рассмотрим логически эквивалентный, но более реальный пример использования локальной области видимости.
int max(int a, int b) // функция max является глобальной;
// а переменные a и b — локальными
{
return (a>=b) ? a : b;
}
int abs(int a) // переменная a, не имеющая отношения
// к функции max()
{
return (a<0) ? –a : a;
}
Функции max() и abs() принадлежат стандартной библиотеке, поэтому их не нужно писать самому. Конструкция ?: называется арифметической инструкцией if (arithmetic if), или условным выражением (conditional expression). Значение инструкции (a>=b)?a:b равно a, если a>=b, и b — в противном случае. Условное выражение позволяет не писать длинный код наподобие следующего:
int max(int a, int b) // функция max является глобальной;
// а переменные a и b — локальными
{
int m; // переменная m является локальной
if (a>=b)
m = a;
else
m = b;
return m;
}
Итак, за исключением глобальной области видимости все остальные области видимости обеспечивают локальность имен. В большинстве случаев локальность имени является полезным свойством, поэтому к нему надо стремиться изо всех сил. Когда мы объявляем свои переменные, функции и прочее в функциях, классах, пространствах имен и так далее, то не хотим, чтобы они совпадали с именами, объявленными кем-то другим. Помните: реальные программы содержат многие тысячи именованных сущностей. Для того чтобы сохранить контроль над такими программами, большинство имен должно быть локальными.
Рассмотрим более крупный технический пример, иллюстрирующий ситуацию, в которой имена выходят за пределы области видимости в конце инструкции и блоков (включая тела функций).
// здесь переменные r, i и v не видны
class My_vector {
vector<int> v; // переменная v принадлежит области
// видимости класса
public:
int largest()
{
int r = 0; // переменная r является локальной
// (минимальное неотрицательное целое число)
for (int i = 0; i<v.size(); ++i)
r = max(r,abs(v[i])); // переменная i принадлежит
// области видимости цикла
// здесь переменная i не видна
return r;
}
// здесь переменная r не видна
}
// здесь переменная v не видна
int x; // глобальная переменная — избегайте по возможности
int y;
int f()
{
int x; // локальная переменная, маскирующая глобальную
// переменную x
x = 7; // локальная переменная x
{
int x = y; // локальная переменная x инициализируется
// глобальной переменной y, маскируя локальную
// переменную x, объявленную выше
++x; // переменная x из предыдущей строки
}
++x; // переменная x из первой строки функции f()
return x;
}
Если можете, избегайте ненужных вложений и сокрытий. Помните девиз: “Будь проще!”
Чем больше область видимости имени, тем длиннее и информативнее должно быть ее имя: хуже имен x, y и z для глобальных переменных не придумаешь. Основная причина, по которой следует избегать глобальных переменных, заключается в том, что трудно понять, какие функции изменяют их значения. В больших программах практически невозможно понять, какие функции изменяют глобальную переменную. Представьте себе: вы пытаетесь отладить программу, и выясняется, что глобальная переменная принимает неожиданное значение. Какая инструкция присвоила ей это значение? Почему? В какой функции? Как это узнать?
Функция, присвоившая неправильное значение данной переменной, может находиться в исходном файле, который вы никогда не видели! В хорошей программе может быть лишь несколько (скажем, одна или две) глобальных переменных. Например, калькулятор, описанный в главах 6 и 7, содержит две глобальные переменные: поток лексем ts и таблицу символов names.
Обратите внимание на то, что большинство конструкций в языке С++ создают вложенные области видимости.
• Функции в классах: функции-члены (раздел 9.4.2).
class C {
public:
void f();
void g() // функция-член может быть определена в классе
{
// ...
}
// ...
void C::f() // определение функции-члена за пределами класса
{
// ...
}
Это наиболее типичный и полезный вариант.
• Классы в других классах: члены-классы (или вложенные классы).
class C {
public:
struct M {
// ...
};
// ...
};
Это допустимо только в сложных классах; помните, что в идеале класс должен быть маленьким и простым.
• Классы в функциях: локальные классы.
void f()
{
class L {
// ...
};
// ...
}
Избегайте таких конструкций; если вам нужен локальный класс, значит, ваша функция слишком велика.
• Функции в других функциях: локальные функции (или вложенные функции).
void f()
{
void g() // незаконно
{
// ...
}
// ...
}
В языке С++ это не допускается;