Область видимости класса *
Тело класса определяет область видимости. Объявления членов класса внутри тела вводят их имена в область видимости класса.
Для обращения к ним применяются операторы доступа (точка и стрелка) и оператор разрешения области видимости (::). Когда употребляется оператор доступа, то предшествующее ему имя обозначает объект или указатель на объект типа класса, а следующее за ним имя должно находиться в области видимости этого класса. Аналогично при использовании оператора разрешения области видимости поиск имени, следующего за ним, идет в области видимости класса, имя которого стоит перед оператором. (В главах 17 и 18 мы увидим, что производный класс может обращаться к членам своих базовых.)
Однако применение операторов доступа или оператора разрешения области видимости нужно не всегда. Некоторые части программы сами по себе находятся в области видимости класса, и в них к членам класса можно обращаться напрямую. Одной из таких частей является само определение класса. Имя его члена можно использовать в теле после объявления:
class String {
public:
typedef int index_type;
// тип параметра - это на самом деле String::index_type
char& operator[]( index_type )
};
Порядок объявления членов класса в его теле важен: нельзя ссылаться на члены, которые будут объявлены позже. Например, если объявление оператора operator[]() находится раньше объявления typedef index_type, то приведенное ниже объявление operator[]() оказывается ошибочным, поскольку в нем используется еще неизвестное имя index_type:
class String {
public:
// ошибка: имя index_type не объявлено
char &operator[]( index_type );
typedef int index_type;
};
Однако из этого правила есть два исключения. Первое касается имен, использованных в определениях встроенных функций-членов, второе – имен, применяемых как аргументы по умолчанию. Рассмотрим обе ситуации.
Разрешение имен в определениях встроенных функций-членов происходит в два этапа. Сначала объявление функции (т.е. тип возвращаемого значения и список параметров) обрабатывается в том месте, где оно встретилось в определении класса. Затем тело функции обрабатывается во всей области видимости, сразу после того, как были просмотрены объявления всех членов. Посмотрим на наш пример, в котором оператор operator[]() определен как встроенный внутри тела класса:
class String {
public:
typedef int index_type;
char &operator[]( index_type elem )
{ return _string[ elem ]; }
private:
char *_string;
};
На первом этапе просматриваются имена, использованные в объявлении operator[](), чтобы найти имя типа параметра index_type. Поскольку первый шаг выполняется тогда, когда в теле класса встретилось определение функции-члена, то имя index_type должно быть объявлено до определения operator[]().
Обратите внимание, что член _string объявлен в теле класса после определения operator[](). Это правильно, и _string не является в теле operator[]() необъявленным именем. Имена в телах функций-членов просматриваются на втором шаге разрешения имен в определениях встроенных функций-членов. Этот этап выполняется во всей области видимости класса, как если бы тела функций-членов обрабатывались последними, прямо перед закрытием тела класса, когда все его члены уже объявлены.
Аргументы по умолчанию также разрешаются на втором шаге. Например, в объявлении функции-члена clear() используется имя статического члена bkground, который определен позже:
class Screen {
public:
// bkground относится к статическому члену,
// объявленному позже в определении класса
Screen& clear( char = bkground );
private:
static const char bkground = '#';
};
Хотя такие аргументы в объявлениях функций-членов разрешаются во всей области видимости класса, программа будет считаться ошибочной, если он ссылается на нестатический член. Нестатический член должен быть привязан к объекту своего класса или к указателю на такой объект, иначе использовать его нельзя. Употребление подобных членов в качестве аргументов по умолчанию нарушает это ограничение. Если переписать предыдущий пример так:
class Screen {
public:
// ...
// ошибка: bkground - нестатический член
Screen& clear( char = bkground );
private:
const char bkground = '#';
};
то имя аргумента по умолчанию разрешается нестатическим членом bkground, а это считается ошибкой.
Определения членов класса, появляющиеся вне его тела, – это еще один пример части программы, которая находится в области видимости класса. В ней имена членов распознаются несмотря на то, что оператор доступа или оператор разрешения области видимости при обращении к ним не применяется. Как же разрешаются имена в определениях членов?
Как правило, если такое определение появляется вне тела, то часть программы, следующая за именем определяемого члена, считается находящейся в области видимости класса вплоть до конца определения члена. Вынесем определение оператора operator[]() из класса String:
class String {
public:
typedef int index_type;
char& operator[]( index_type );
private:
char *_string;
};
// в operator[]() есть обращения к index_type и _string
inline char& operator[]( index_type elem )
{
return _string[ elem ];
}
Обратите внимание, что в списке параметров встречается typedef index_type без квалифицирующего имени класса String::.Текст, следующий за именем члена String::operator[] и до конца определения функции, находится в области видимости класса. Объявленные в этой области типы рассматриваются при разрешении имен типов, использованных в списке параметров функции-члена.
Определения статических данных-членов также появляются вне определения класса. В них часть программы, следующая за именем статического члена вплоть до конца определения, считается находящейся в области видимости класса. Например, инициализатор статического члена может непосредственно, без соответствующих операторов, ссылаться на члены класса:
class Account:
// ...
private:
static double _interestRate;
static double initInterestRate();
};
// ссылается на Account::initInterest()
double Account::_interestRate = initInterest();
Инициализатор _interestRate вызывает статическую функцию-член Account::initInterest() несмотря на то, что ее имя не квалифицировано именем класса.
Не только инициализатор, но и все, что следует за именем статического члена _interestRate до завершающей точки с запятой, находится в области видимости класса Account. Поэтому в определении статического члена name может быть обращение к члену класса nameSize:
class Account:
// ...
private:
static const int nameSize = 16;
static const char name[nameSize];
// nameSize не квалифицировано именем класса Account
const char Account::name[nameSize] = "Savins Account";
Хотя член nameSize не квалифицирован именем класса Account, определение name не является ошибкой, так как оно находится в области видимости своего класса и может ссылаться на его члены после того, как компилятор прочитал Account::name.
В определении члена, которое появляется вне тела, часть программы перед определяемым именем не находится в области видимости класса. При обращении к члену в этой части следует пользоваться оператором разрешения области видимости. Например, если типом статического члена является typedef Money, определенный в классе Account, то имя Money должно быть квалифицировано, когда статический член данных определяется вне тела класса:
class Account {
typedef double Money;
//...
private:
static Money _interestRate;
static Money initInterest();
};
// Money должно быть квалифицировано именем класса Account::
Account::Money Account::_interestRate = initInterest();
С каждым классом ассоциируется отдельная область видимости, причем у разных классов эти области различны. К членам одного класса нельзя напрямую обращаться в определениях членов другого класса, если только один из них не является для второго базовым. (Наследование и базовые классы рассматриваются в главах 17 и 18.)