Рассмотрим логически эквивалентный, но более реальный пример использования локальной области видимости.
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 {
// ...
};
// ...
};
Это допустимо только в сложных классах; помните, что в идеале класс должен быть маленьким и простым.