8.1. Область видимости

We use cookies. Read the Privacy and Cookie Policy

8.1. Область видимости

Каждое имя в С++ программе должно относиться к уникальной сущности (объекту, функции, типу или шаблону). Это не значит, что оно встречается только один раз во всей программе: его можно повторно использовать для обозначения другой сущности, если только есть некоторый контекст, помогающий различить разные значения одного и того же имени. Контекстом, служащим для такого различения, служит область видимости. В С++ поддерживается три их типа: локальная область видимости, область видимости пространства имен и область видимости класса.

Локальная область – это часть исходного текста программы, содержащаяся в определении функции (или в блоке). Любая функция имеет собственную такую часть, и каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область.

Область видимости пространства имен – часть исходного текста программы, не содержащаяся внутри объявления или определения функции или определения класса. Самая внешняя часть называется глобальной областью видимости или глобальной областью видимости пространства имен.

Объекты, функции, типы и шаблоны могут быть определены в глобальной области видимости. Программисту разрешено задать пользовательские пространства имен, заключенные внутри глобальной области с помощью определения пространства имен. Каждое такое пространство является отдельной областью видимости. Пользовательское пространство, как и глобальное, может содержать объявления и определения объектов, функций, типов и шаблонов, а также вложенные пользовательские пространства имен. (Они рассматриваются в разделах 8.5 и 8.6.)

Каждое определение класса представляет собой отдельную область видимости класса. (О таких областях мы расскажем в главе 13.)

Имя может обозначать различные сущности в зависимости от области видимости. В следующем фрагменте программы имя s1 относится к четырем разным сущностям:

#include iostream

#include string

// сравниваем s1 и s2 лексикографически

int lexicoCompare( const string &sl, const string &s2 ) { ... }

// сравниваем длины s1 и s2

int sizeCompare( const string &sl, const string &s2 ) { ... }

typedef int ( PFI)( const string &, const string & );

// сортируем массив строк

void sort( string *s1, string *s2, PFI compare =lexicoCompare )

{ ... }

string sl[10] = { "a", "light", "drizzle", "was", "falling",

"when", "they", "left", "the", "school" };

int main()

{

// вызов sort() со значением по умолчанию параметра compare

// s1 - глобальный массив

sort( s1, s1 + sizeof(s1)/sizeof(s1[0]) - 1 );

// выводим результат сортировки

for ( int i = 0; i

Поскольку определения функций lexicoCompare(), sizeCompare() и sort() представляют

собой различные области видимости и все они отличны от глобальной, в каждой

из этих областей можно завести переменную с именем s1.

Имя, введенное с помощью объявления, можно использовать от точки объявления

до конца области видимости (включая вложенные области). Так, имя s1 параметра

функции lexicoCompare() разрешается употреблять до конца ее области видимости,

то есть до конца ее определения.

Имя глобального массива s1 видимо с точки его объявления до конца исходного

файла, включая вложенные области, такие, как определение функции main().

В общем случае имя должно обозначать одну сущность внутри одной области видимости.

Если в предыдущем примере после объявления массива s1 добавить следующую строку,

компилятор выдаст сообщение об ошибке:

void s1(); // ошибка: повторное объявление s1

Перегруженные функции являются исключением из правила: можно завести несколько одноименных функций в одной области видимости, если они отличаются списком параметров. (Перегруженные функции рассматриваются в главе 9.)

В С++ имя должно быть объявлено до момента его первого использования в выражении. В противном случае компилятор выдаст сообщение об ошибке. Процесс сопоставления имени, используемого в выражении, с его объявлением называется разрешением. С помощью этого процесса имя получает конкретный смысл. Разрешение имени зависит от способа его употребления и от его области видимости. Мы рассмотрим этот процесс в различных контекстах. (В следующем подразделе описывается разрешение имен в локальной области видимости; в разделе 10.9 – разрешение в шаблонах функций; в конце главы 13 – в области видимости классов, а в разделе 16.12 – в шаблонах классов.)

Области видимости и разрешение имен – понятия времени компиляции. Они применимы к отдельным частям текста программы. Компилятор интерпретирует текст программы согласно правилам областей видимости и правилам разрешения имен.

8.1.1. Локальная область видимости

Локальная область видимости – это часть исходного текста программы, содержащаяся в определении функции (или блоке внутри тела функции). Все функции имеют свои локальные области видимости. Каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область. Такие области могут быть вложенными. Например, следующее определение функции содержит два их уровня (функция выполняет двоичный поиск в отсортированном векторе целых чисел):

const int notFound = -1; // глобальная область видимости

int binSearch( const vectorint vec, int val )

{ // локальная область видимости: уровень #1

int low = 0;

int high = vec.size() - 1;

while ( low = high )

{ // локальная область видимости: уровень #2

int mid = ( low + high ) / 2;

if ( val vec[ mid ] )

high = mid - 1;

else low = mid + 1;

}

return notFound; // локальная область видимости: уровень #1

}

Первая локальная область видимости – тело функции binSearch(). В ней объявлены параметры функции vec и val, а также переменные low и high. Цикл while внутри функции задает вложенную локальную область, в которой определена одна переменная mid. Параметры vec и val и переменные low и high видны во вложенной области. Глобальная область видимости включает в себя обе локальных. В ней определена одна целая константа notFound.

Имена параметров функции vec и val принадлежат к первой локальной области видимости тела функции, и в ней использовать те же имена для других сущностей нельзя. Например:

int binSearch( const vectorint vec, int val )

{ // локальная область видимости: уровень #1

int val; // ошибка: неверное переопределение val

// ...

Имена параметров употребляются как внутри тела функции binSearch(), так и внутри вложенной области видимости цикла while. Параметры vec и val недоступны вне тела функции binSearch().

Разрешение имени в локальной области видимости происходит следующим образом: просматривается та область, где оно встретилось. Если объявление найдено, имя разрешено. Если нет, просматривается область видимости, включающая текущую. Этот процесс продолжается до тех пор, пока объявление не будет найдено либо не будет достигнута глобальная область видимости. Если и там имени нет, оно будет считаться ошибочным.

Из-за порядка просмотра областей видимости в процессе разрешения имен объявление из внешней области может быть скрыто объявлением того же имени во вложенной области. Если бы в предыдущем примере переменная low была объявлена в глобальной области видимости перед определением функции binSearch(), то использование low в локальной области видимости цикла while все равно относилось бы к локальному объявлению, скрывающему глобальное:

int low;

int binSearch( const vectorint vec, int val )

{

// локальное объявление low

// скрывает глобальное объявление

int low = 0;

// ...

// low - локальная переменная

while ( low = high )

{//...

}

// ...

}

Для некоторых инструкций языка C++ разрешено объявлять переменные внутри управляющей части. Например, в цикле for переменную можно определить внутри инструкции инициализации:

for ( int index = 0; index vecSize; ++index )

{

// переменная index видна только здесь

if ( vec[ index ] == someValue )

break;

}

// ошибка: переменная index не видна

if ( index != vecSize ) // элемент найден

Подобные переменные видны только в локальной области самого цикла for и вложенных в него (это верно для стандарта С++, в предыдущих версиях языка поведение было иным). Компилятор рассматривает это объявление так же, как если бы оно было записано в виде:

// представление компилятора

{ // невидимый блок

int index = 0;

for ( ; index vecSize; ++index )

{

// ...

}

}

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

int index = 0;

for ( ; index vecSize; ++index )

{

// ...

}

// правильно: переменная index видна

if ( index != vecSize ) // элемент найден

Поскольку переменная, объявленная в инструкции инициализации цикла for, является локальной для цикла, то же самое имя допустимо использовать аналогичным образом и в других циклах, расположенных в данной локальной области видимости:

void fooBar( int *ia, int sz )

{

for (int i=0; isz; ++i) ... // правильно

for (int i=0; isz; ++i) ... // правильно, другое i

for (int i=0; isz; ++i) ... // правильно, другое i

}

Аналогично переменная может быть объявлена внутри условия инструкций if и switch, а также внутри условия циклов while и for. Например:

if ( int *pi = getValue() )

{

// pi != 0 -- *pi можно использовать здесь

int result = calc(*pi);

// ...

}

else

{

// здесь pi тоже видна

// pi == 0

cout "ошибка: getValue() завершилась неудачно" endl;

}

Переменные, определенные в условии инструкции if, как переменная pi, видны только внутри if и соответствующей части else, а также во вложенных областях. Значением условия является значение этой переменной, которое она получает в результате инициализации. Если pi равна 0 (нулевой указатель), условие ложно и выполняется ветвь else. Если pi инициализируется любым другим значением, условие истинно и выполняется ветвь if. (Инструкции if, switch, for и while рассматривались в главе 5.)

Упражнение 8.1

Найдите различные области видимости в следующем примере. Какие объявления ошибочны и почему?

int ix = 1024;

int ix() ;

void func( int ix, int iy ) {

int ix = 255;

if (int ix=0) {

int ix = 79;

{

int ix = 89;

}

}

else {

int ix = 99;

}

}

Упражнение 8.2

К каким объявлениям относятся различные использования переменных ix и iy в следующем примере:

int ix = 1024;

void func( int ix, int iy ) {

ix = 100;

for( int iy = 0; iy 400; iy += 100 ) {

iy += 100;

ix = 300;

}

iy = 400;

}