Справочное руководство по C++ - Бьярн Страустрап
Шрифт:
Интервал:
Закладка:
};
typedef enclose::inner ei;
int ei::x = 1;
void enclose::inner::f(int i) {/*… */}
Подобно функции-члену дружественная функция, определенная в данном классе, находится в области видимости этого класса. Она подчиняется тем же правилам связывания имен, что и функции-члены (они указаны выше и в §R.10.4), и не имеет так же как они особых прав доступа к членам объемлющего класса и к локальным переменным функций этого класса (§R.11).
R.9.8 Описания локальных классов
Класс можно описать в определении функции, такой класс называется локальным. Имя локального класса считается локальным в объемлющей области видимости, а областью видимости локального класса является объемлющая область видимости. В описаниях локального класса из объемлющей области видимости можно использовать только имена типов, статических переменных, внешних переменных и функций, а также элементы перечисления. Приведем пример:
int x;
void f()
{
static int s;
int x;
extern int g();
struct local {
int h() { return x; } // ошибка: `x' автоматическая
int j() { return s; } // нормально
int k() { return ::x; } // нормально
int l() { return g(); } // нормально
}
}
Объемлющая функция не имеет особых прав доступа к членам локального класса, она подчиняется обычным правилам доступа (§R.11). Функцию-член локального класса следует определять в определении этого класса. Локальный класс не может иметь статических членов, представляющих данные.
R.9.9 Имена локальных типов
Имена типов подчиняются точно таким же правилам областей видимости, как и другие имена. В частности, имена типов, определенные в описании класса, нельзя использовать вне этого класса без уточнения, например:
class X {
public:
typedef int I;
class Y {/*… */}
I a;
};
I b; // ошибка
Y c; // ошибка
X::Y d; // ошибка
Следующее положение ограничивает зависимость от контекста правил описания членов класса, а так же правила переноса тела функций, являющихся подстановками. После использования в описании класса имя константы, имя-класса или имя-typedef не может переопределяться в описании этого класса. Имя, не являющееся именем-класса или именем-typedef не может быть определено в описании класса как имя-класса или имя-typedef, если оно уже использовалось иначе в описании этого класса. Рассмотрим пример:
typedef int c;
enum { i = 1 };
class X {
char v[i];
int f() { return sizeof(c); }
char c; // ошибка: имя typedef
// переопределяется после использования
enum { i = 2 }; // ошибка: `i' переопределяется после
// использования в задании типа `char[i]'
};
typedef char* T;
struct Y {
T a;
typedef long T; // ошибка: имя T уже использовано
T b;
};
R.10 Производные классы
В описании класса можно указать список базовых классов с помощью следующих конструкций:
спец-базовых:
: список-базовых
список-базовых:
спецификация-базовых
список-базовых , спецификация-базовых
спецификация-базовых:
полное-имя-класса
virtual спецификация-доступа opt полное-имя-класса
спецификация-доступа virtual opt полное-имя-класса
спецификация-доступа:
private
protected
public
Конструкция имя-класса в спецификации-базовых должна обозначать ранее описанный класс (§R.9), который называется базовым по отношению к определяемому классу. Говорят, что класс является производным от своих базовых классов. Назначение конструкции спецификация-доступа объясняется в §R.11. К членам базового класса, если только они не переопределены в производном классе, можно обращаться так, как будто они являются членами производного класса. Говорят, что производный класс наследует члены базового класса. С помощью операции разрешения области видимости :: (§R.5.1) к члену базового класса можно обращаться явно. Такое обращение возможно и в том случае, когда имя члена базового класса переопределено в производном классе. Производный класс сам может выступать как базовый при контроле доступа, см. §R.11.2. Указатель на производный класс может неявно преобразовываться в указатель на однозначно определенный и доступный базовый класс (§R.4.6). Ссылка на производный класс может неявно преобразовываться в ссылку на однозначно определенный и доступный базовый класс (§R.4.7).
Рассмотрим пример:
class base {
public:
int a, b;
};
class derived: public base {
public:
int b, c;
};
void f()
{
derived d;
d.a = 1;
d.base::b = 2;
d.b = 3;
d.c = 4;
base* bp = &d; // стандартное преобразование derived* в base*
}
Здесь присваиваются значения четырем членам d, а bp настраивается на d.
Класс называется прямым базовым, если он находится в списке-базовых, и косвенным базовым, если сам не являясь прямым базовым, он служит базовым для одного из классов списка-базовых.
Отметим, что в обозначении имя-класса :: имя конструкция, имя может быть именем члена косвенного базового класса. Такое обозначение просто указывает класс, в котором следует начинать поиск этого имени.
Приведем пример:
class A { public: void f(); }
class B: public A {};
class C: public B { public: void f(); }
void C::f()
{
f(); // вызов f() из C
A::f(); // вызов f() из A
B::f(); // вызов f() из A
}
Здесь дважды вызывается A::f(), поскольку это единственная функция f() в классе B.
Инициализация объектов, представляющих базовые классы, задается в конструкторах, см. §R.12.6.2.
R.10.1 Множественные базовые классы
Класс может быть производным по отношению к любому числу базовых классов. Приведем пример:
class A {/*… */};
class B {/*… */};
class C {/*… */};
class D: public A, public B, public C {/*… */};
Использование более, чем одного прямого базового класса называется множественным наследованием.
Порядок наследования не важен, если не учитывать вопросов, связанных со стандартной инициализацией с помощью конструктора (§R.12.1), уничтожением (§R.12.4) и размещением в памяти ($$r.5.4, §R.9.2, §R.11.1). Порядок выделения памяти для базовых классов определяется реализацией.
Нельзя указывать класс в качестве прямого базового по отношению к производному классу более одного раза, но косвенным базовым классом он может быть неоднократно.
class B {/*… */};
class D: public B, public B {/*… */}; // недопустимо
class L {/*… */};
class A: public L {/*… */};
class B: public L {/*… */};
class C: public A, public B {/*… */}; // нормально
Здесь объект класса C будет иметь два вложенных объекта класса L.
К спецификации базового класса можно добавить служебное слово virtual. Отдельный объект виртуального базового класса V разделяется между всеми базовыми классами, которые указали V при задании своих базовых классов. Приведем пример:
class V {/*… */};
class A: virtual public V {/*… */};
class B: virtual public V {/*… */};
class C: public A, public B {/*… */};
Здесь объект класса C будет иметь только один вложенный объект класса V.
Класс может содержать виртуальные и невиртуальные базовые классы одного типа, например:
class B {/*… */};
class X: virtual public B {/*… */};
class Y: virtual public B {/*… */};
class Z: public B {/*… */};
class AA: public X, public Y, public Z {/*… */};
Здесь объект класса AA будет иметь два вложенных объекта класса B: из класса Z и виртуальный, разделяемый между классами X и Y.
R.10.1.1 Неоднозначности
Доступ к базовому классу должен быть задан однозначно. Доступ к члену базового класса считается неоднозначным, если выражение, используемое для доступа, задает более одной функции, объекта, типа или элемента перечисления. Проверка на однозначность происходит до проверки возможности доступа (§R.11). Приведем пример:
class A {
public:
int a;
int (*b)();
int f();
int f(int);
int g();
};
class B {
int a;
int b();
public:
int f();
int g();
int h();