Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 [ 6 ] 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

пропущено в производном классе, компилятор должен интерпретировать версию функции в производном классе так, словно она и там была объявлена виртуальной. Я люблю называть подобную логику работы компилятора DWIMNIS: «Do what I mean, not what I say» («Делай то, что я подразумеваю, а не то, что я говорю»). Как правило, в C++ эта логика начисто отсутствует, поэтому ее редкие проявления (как в данном случае) смотрятся неестественно. В следующем примере для обоих указателей будет вызвана функция Bar::Fn ():

class Foo { public:

virtual void Fn();

class Bar { public:

void Fn(); Все равно считается виртуальной

Bar* b = new Bar;

b->Fn(); Вызывает Bar::Fn()

Foo* f = b;

f->Fn(); Также вызывает Bar::Fn()

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

Если в производном классе создается функция с тем же именем, но с другой сигнатурой, она скрывает все сигнатуры базового класса для данной функции, но только в области действия производного класса. Понятно? Нет? Что ж, вы не одиноки.

class Foo { public:

virtual void Fn();

virtual void Fn(char*);

class Bar { public:

virtual void Fn(int); Можно, но не желательно

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

• При попытке вызвать Fn() через Ваr* доступной будет лишь одна сигнатура, void Fn(int). Обе версии базового класса скрыты и недоступны через Bar*.

• При преобразовании Bar* в Foo* становятся доступными обе сигнатуры, объявленные в Foo, но не сигнатура void Fn(int). Более того, это не переопределение, поскольку сигнатура Bar::Fn() отличается от версии базового класса. Другими словами, ключевое слово virtual никак не влияет на работу этого фрагмента.

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



Друзья

Любой класс может объявить что-нибудь своим другом (friend). Друзья компилируются обычным образом, за исключением того, что все защищенные и закрытые члены дружественного класса видны так, словно друг является функцией этого класса. Друзьями можно объявлять функции - как глобальные, так и члены классов. Классы тоже могут объявляться друзьями других классов; в этом случае во всех функциях класса-друга «видны» все члены того класса, другом которого он является.

class Foo; class BarBar { public:

int Fn(Foo*);

class Foo {

friend void GlobalFn(); Дружественные глобальные функции

friend class Bar; Дружественный класс

friend int BarBar::Fn(Foo*); Дружественная функция класса friend class DoesNotExist; См. Ниже private: int x;

struct ListNode {

ListNode* next; void* datum;

ListNode() : next(NULL), datum(NULL) {} } head; protected:

int y;

public:

void G();

void GlobalFn()

Foo* f = new Foo;

f->x = 17; Разрешается из-за дружеских отношений

class Bar { private:

Foo* f; public:

Bar() : f(new Foo) {}

void Wa1kList();

void Bar::Wa1kList()

Foo::ListNode* n = f->head.next; for (; n != NULL; n = n->next) cout << n->datum << endl;

int BarBar::Fn(Foo* f)

return f->x;



Друзей принято объявлять сначала, перед членами класса и перед ключевыми словами public, protected и private. Это объясняется тем, что на друзей не действуют обычные атрибуты видимости; нечто либо является другом, либо не является. Весь фрагмент программы после определения класса Foo вполне допустим. Друзья имеют доступ ко всем членам Foo, включая закрытые. В этом примере есть одна действительно интересная строка - та, в которой другом объявляется несуществующий класс DoesNotExist. Как ни странно, она не вызовет ни предупреждения, ни ошибки компилятора. Объявления друзей игнорируются на момент компиляции Foo. Они используются лишь тогда, когда будет компилироваться друг. Даже когда друга не существует, компилятор остается в счастливом неведении.

Типы и операторы

Темы, рассматриваемые в этом разделе, на первый взгляд не кажутся близкими, однако все они вращаются вокруг общей концепции - абстрактных типов данных.

Конструкторы

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

С конструкторами связаны очень сложные правила, но каждый программист C++ должен досконально знать их, иначе минимум три ночи в году ему придется проводить за отладкой.

Конструкторы без аргументов

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

class Foo { public:

Foo();

class Bar : public Foo { 1. Базовый класс public:

Bar();

class BarBar { private:

Foo f; 2. Переменная класса

Foo f; 3. Созданный экземпляр Foo

Foo* f1 = new Foo; 3. То же, что и предыдущая строка

Если в списке инициализации членов (см. следующий раздел) конструктора Bar не указан какой-нибудь другой конструктор Foo, то при каждом создании экземпляра Ваг будет вызываться конструктор Foo без аргументов. Аналогично, если f отсутствует в списке инициализации членов конструктора BarBar, будет использован конструктор Foo без аргументов. Наконец, при каждом создании экземпляра Foo без указания конструктора по умолчанию используется конструктор без аргументов.

Конструкторы с аргументами

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



0 1 2 3 4 5 [ 6 ] 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82