// ... p>
) p>
Член похідного класу може використовувати відкрите ім'я
зі свого базового класу так само, як це можуть робити інші члени останнього,
тобто без вказівки об'єкта. Передбачається, що на об'єкт вказує this,
тому (коректної) посиланням на ім'я name є this-> name. Однак функція
manager:: print компілюватися не буде, член похідного класу не має
ніякого особливого права доступу до закритих членам його базового класу, тому
для неї name недоступне. p>
Це багатьом здасться дивним, але уявіть собі
інший варіант: що функція член могла б звертатися до закритих членам свого
базового класу. Можливість, що дозволяє програмісту отримувати доступ до
закритої частини класу просто за допомогою виведення з нього іншого класу, позбавила б
поняття закритого члена будь-якого сенсу. Більш того, не можна було б дізнатися про все
використання закритого імені подивившись на функції, описані як члени та
друзі цього класу. Довелося б перевіряти кожен вихідний файл у всій
програмі на наявність у ньому похідних класів, потім досліджувати кожну
функцію цих класів, потім шукати всі класи, похідні від цих класів, і т.д.
Це щонайменше утомливо і швидше за все нереально. p>
З іншого боку, адже можна використовувати механізм
friend, щоб надати такий доступ, або об'єктів функцій, або всім
функцій окремого класу. p>
Наприклад:
p>
class employee ( p>
friend void manager:: print (); p>
// ... p>
); p>
вирішило б проблему з manager:: print (), і p>
class employee ( p>
friend class manager; p>
// ... p>
); p>
зробило б доступним кожен член employee для всіх
функцій класу manager. Зокрема, це зробить name доступним для
manager:: print (). p>
Інше, іноді більш прозоре рішення для похідного
класу, - використовувати тільки відкриті члени його базового класу. p>
Наприклад: p>
void manager:: print () p>
( p>
employee:: print ();// друкує інформацію про слугує p>
// ...// Друкує інформацію про менеджері p>
) p>
Зауважте, що треба використовувати::, тому що print ()
була перевизначено на manager. Таке повторне використання імен типово.
Необережний міг би написати так: p>
void manager:: print () p>
( p>
print ();// друкує інформацію про слугує p>
// ...// Друкує інформацію про менеджері p>
) p>
і виявити, що програма після виклику manager:: print ()
несподівано потрапляє в послідовність рекурсивних дзвінків. p>
Видимість p>
Клас employee став відкритим (public) базовим класом
класу manager в результаті опису: p>
class manager: public employee ( p>
// ... p>
); p>
Це означає, що відкритий член класу employee є
також і відкритим членом класу manager. p>
Наприклад:
p>
void clear (manager * p) p>
( p>
p-> next = 0; p>
) p>
буде компілюватися, так як next - відкритий член і
employee і manager "а. Альтернатива - можна визначити закритий (private)
клас, просто опустивши в описі класу слово public: p>
class manager: employee ( p>
// ... p>
); p>
Це означає, що відкритий член класу employee є
закритим членом класу manager. Тобто, функції члени класу manager можуть як
і раніше використовувати відкриті члени класу employee, але для користувачів
класу manager ці члени недоступні. Зокрема, при такому описі класу
manager функція clear () компілюватися не буде. Друзі похідного класу мають
до членів базового класу такий же доступ, як і функції члени. p>
Бо, виявляється, опис відкритих базових
класів зустрічається частіше опису закритих, шкода, що опис відкритого
базового класу довше опису закритого. Це, крім того, служить джерелом
заплутують помилок у початківців. p>
Коли описується похідна struct, її базовий клас з
умовчанням є public базовим класом. Тобто, p>
struct D: B (... p>
означає
p>
class D: public B (public: ... p>
Звідси випливає, що якщо ви не вважали корисним то приховування
даних, що дають class, public і friend, ви можете просто не використовувати
ці ключові слова і дотримуватися struct. Такі засоби мови, як функції
члени, конструктори і перевантаження операцій, що не залежать від механізму приховування
даних. p>
Можна також оголосити деякі, але не всі, відкриті $
члени базового класу відкритими членами похідного класу. Наприклад: p>
class manager: employee ( p>
// ... p>
public: p>
// ... p>
employee:: name; p>
employee:: department; p>
); p>
Запис p>
імя_класса:: імя_члена; p>
не вводить новий член, а просто робить відкритий член
базового класу відкритим для похідного класу. Тепер name і department
можуть використовуватися для manager "а, а salary і age - ні. Природно,
зробити зробити закритий член базового класу відкритим членом похідного
класу неможливо. Неможливо за допомогою цього запису також зробити відкритими
перевантажені імена. p>
Підсумовуючи, можна сказати, що разом з наданням
коштів додатково до наявних в базовому класі, похідний клас можна використовувати
для того, щоб зробити засоби (імена) недоступними для користувача. Іншими
словами, за допомогою похідного класу можна забезпечувати прозорий,
напівпрозорий і непрозорий доступ до його базового класу. p>
Покажчики h2>
Якщо похідний клас derived має відкритий базовий
клас base, то покажчик на derived можна привласнювати змінної типу покажчик
на base не використовуючи явне перетворення типу. Зворотне перетворення,
покажчика на base в покажчик на derived, має бути явним. p>
Наприклад:
p>
class base (/ * ... * /); P>
class derived: public base (/ * ... * /); P>
derived m; p>
base * pb = &m;// неявне перетворення p>
derived * pd = pb;// помилка: base * не є derived * p>
pd = (derived *) pb;// явне перетворення p>
Інакше кажучи, об'єкт похідного класу при роботі з ним
через вказівник і можна розглядати як об'єкт його базового класу. Зворотне
невірно. p>
Будь base закритим базовим класом класу derived,
неявне перетворення derived * в base * не робилося б. Неявно перетворення
не може в цьому випадку бути виконане, тому що до відкритого члкну класу base
можна звертатися через вказівник на base, але не можна через вказівник на derived:
p>
class base ( p>
int m1; p>
public: p>
int m2;// m2 - відкритий член base p>
); p>
class derived: base ( p>
// m2 НЕ відкритий член derived p>
); p>
derived d; p>
d.m2 = 2;// помилка: m2 із закритої частини класу p>
base * pb = &d;// помилка: (закритий base) p>
pb-> m2 = 2;// ok p>
pb = (base *) &d;// ok: явне перетворення p>
pb-> m2 = 2;// ok p>
Крім усього іншого, цей приклад показує, що
використовуючи явне приведення до типу можна зламати правила захисту. Ясно, робити
це не рекомендується, і це приносить програмісту заслужену
"нагороду". До нещастя, недисципліновані використання явного
перетворення може створити пекельні умови для невинних жертв, які
експлуатують програму, де це робиться. Але, на щастя, немає способу
скористатися приведенням для отримання доступу до закритого імені m1.
Закритий член класу може використовуватися тільки членами та друзями цього
класу. p>
Ієрархія Типів h2>
Похідний клас сам може бути базовим класом.
Наприклад: p>
class employee (... ); p>
class secretary: employee (... ); p>
class manager: employee (... ); p>
class temporary: employee (... ); p>
class consultant: temporary (... ); p>
class director: manager (... ); p>
class vice_president: manager (... ); p>
class president: vice_president (... ); p>
Таке безліч споріднених класів прийнято називати
ієрархією класів. Оскільки можна виводити клас тільки з одного базового
класу, така ієрархія є деревом і не може бути графом більш загальної
структури. p>
Наприклад:
p>
class temporary (... ); p>
class employee (... ); p>
class secretary: employee (... ); p>
// не C ++: p>
class temporary_secretary: temporary: secretary (
... ); p>
class consultant: temporary: employee (... ); p>
І цей факт викликає жаль, тому що спрямований
ациклічні граф похідних класів був би дуже корисний. Такі структури
описати не можна, але можна змоделювати за допомогою членів відповідний типів. p>
Наприклад:
p>
class temporary (... ); p>
class employee (... ); p>
class secretary: employee (... ); p>
// Альтернатива: p>
class temporary_secretary: secretary p>
(temporary temp; ... ); p>
class consultant: employee p>
(temporary temp; ... ); p>
Це виглядає неелегантність і страждає саме від тих
проблем, для подолання яких були винайдені похідні класи. Наприклад,
оскільки consultant не є похідним від temporary, consultant "а
не можна поміщати з список тимчасових службовців (temporary employee), не написав
спеціальної програми. Однак у багатьох корисних програмах цей метод успішно
використовується. p>
Конструктори і Деструктори p>
Для деяких похідних класів потрібні конструктори.
Якщо у базового класу є конструктор, він повинен викликатися, і якщо для цього
конструктора потрібні параметри, їх треба надати. p>
Наприклад:
p>
class base ( p>
// ... p>
public: p>
base (char * n, short t); p>
~ base (); p>
); p>
class derived: public base ( p>
base m; p>
public: p>
derived (char * n); p>
~ derived (); p>
); p>
Параметри конструктора базового класу специфікується в
визначенні конструктора похідного класу. У цьому сенсі базовий клас
працює точно також, як неіменованний член похідного класу. p>
Наприклад:
p>
derived:: derived (char * n): (n, 10),
m ( "member", 123) p>
( p>
// ... p>
) p>
Об'єкти класу конструюються знизу вгору: спочатку
базовий, потім члени, а потім сам похідний клас. Знищуються вони в
зворотному порядку: спочатку сам похідний клас, потім члени а потім базовий. p>
Поля Типу p>
Щоб використовувати похідні класи не просто як
зручну скорочену запис в описах, треба вирішити таку проблему: Якщо
задано вказівник типу base *, якому похідному типу в дійсності
належить що указується об'єкт? Є три основних способи вирішення цієї
проблеми: p>
Забезпечити, щоб завжди вказувалися тільки об'єкти
одного типу; p>
Помістити в базовий клас поле типу, яке зможуть
переглядати функції; і p>
Використовувати віртуальні функції. Звичайно покажчики
на базові класи використовуються при розробці контейнерних (або що вміщають)
класів: безліч, вектор, список і т.п. У цьому випадку рішення 1 дає
однорідні списки, тобто списки об'єктів одного типу. Рішення 2 і 3 можна
використовувати для побудови неоднорідних списків, тобто списків об'єктів
(покажчиків на об'єкти) кількох різних типів. Рішення 3 - це спеціальний
варіант рішення 2, безпечний щодо типу. p>
Давайте спочатку досліджуємо просте рішення за допомогою поля
типу, тобто рішення 2. Приклад зі службовцями і менеджерами можна було б
перевизначити так: p>
enum empl_type (M, E); p>
struct employee ( p>
empl_type type; p>
employee * next; p>
char * name; p>
short department; p>
// ... p>
); p>
struct manager: employee ( p>
employee * group; p>
short level;// рівень p>
); p>
Маючи це, ми можемо тепер написати функцію, що
друкує інформацію про кожного службовця: p>
void print_employee (employee * e) p>
( p>
switch (e-> type) ( p>
case E: p>
cout < name << "t"
< department << "n"; p>
// ... p>
break; p>
case M: p>
cout < name << "t"
< department << "n"; p>
// ... p>
manager * p = (manager *) e; p>
cout << "рівень" < level << "n"; p>
// ... p>
break; p>
) p>
) p>
і скористатися нею для того, щоб надрукувати список
службовців: p>
void f () p>
( p>
for (; ll; ll = ll-> next) print_employee (ll); p>
) p>
Це прекрасно працює, особливо в невеличкій програмі,
написаної однією людиною, але має той корінний недолік, що
неконтрольованим компілятором чином залежить від того, як програміст працює
з типами. У великих програмах це зазвичай призводить до помилок двох видів. Перший
- Це невиконання перевірки поля типу, другий - коли не всі випадки case
поміщаються в перемикач switch як у попередньому прикладі. Обидва уникнути
досить легко, коли програму спочатку пишуть на папері $, але при
модифікації нетривіальною програми, особливо написаною іншою людиною,
дуже важко уникнути і того, і іншого. Часто від цих складнощів стає
важче вберегтися з-за того, що функції начебто print () часто бувають
організовані так, щоб користуватися спільність класів, з якими вони працюють.
p>
Наприклад:
p>
void print_employee (employee * e) p>
( p>
cout < name << "t"
< department << "n"; p>
// ... p>
if (e-> type == M) ( p>
manager * p = (manager *) e; p>
cout << "рівень" < level << "n"; p>
// ... p>
) p>
) p>
Відшукання всіх таких операторів if, прихованих всередині
великий функції, яка працює з великою кількістю похідних класів, може
виявитися складним завданням, і навіть коли всі вони знайдені, буває нелегко зрозуміти,
що ж у них робиться. p>
Віртуальні Опції h2>
Віртуальні функції долають складності рішення з
допомогою полів типу, дозволяючи програмісту описувати в базовому класі функції,
які можна перевизначати в будь-якому похідному класі. Компілятор та завантажувач
забезпечують правильне відповідність між об'єктами і які застосовуються до них
функціями. p>
Наприклад:
p>
struct employee ( p>
employee * next; p>
char * name; p>
short department; p>
// ... p>
virtual void print (); p>
); p>
Ключове слово virtual вказує, що можуть бути
різні варіанти функції print () для різних похідних класів, і що пошук
серед них відповідної для кожного дзвінка print () є завданням компілятора.
Тип функції описується в базовому класі і не може листуватися в
похідному класі. Віртуальна функція повинна бути визначена для класу, в
якому вона описана вперше. p>
Наприклад:
p>
void employee:: print () p>
( p>
cout < name << "t"
< department << "n"; p>
// ... p>
) p>
Віртуальна функція може, таким чином, використовуватися
навіть у тому випадку, коли немає похідних класів від її класу, і в похідному
класі, в якому не потрібен спеціальний варіант віртуальної функції, її здавати
не обов'язково. Просто при виведенні класу відповідна функція задається у тому
випадку, якщо вонапотрібна. p>
Наприклад:
p>
struct manager: employee ( p>
employee * group; p>
short level; p>
// ... p>
void print (); p>
); p>
void manager:: print () p>
( p>
employee:: print (); p>
cout << "tуровень" <
// ... p>
) p>
Функція print_employee () тепер не потрібна, оскільки її
місце зайняли функції члени print (), і тепер зі списком службовців можна працювати
так: p>
void f (employee * ll) p>
( p>
for (; ll; ll = ll-> next) ll-> print (); p>
) p>
Кожен службовець буде друкуватися відповідно до його
типом. Наприклад: p>
main () p>
( p>
employee e; p>
e.name = "Дж.Браун"; p>
e.department = 1234; p>
e.next = 0; p>
manager m; p>
m.name = "Дж.Сміт"; p>
e.department = 1234; p>
m.level = 2; p>
m.next = &e; p>
f (& m); p>
) p>
видасть p>
Дж.Сміт 1234 p>
рівень 2 p>
Дж.Браун 1234 p>
Зауважте, що це буде працювати навіть у тому випадку, якщо
f () була написана і відкомпілювати ще до того, як похідний клас manager
був задуманий! Очевидно, при реалізації цього в кожному об'єкті класу employee
зберігається деяка інформація про тип. Займаного для цього простору (в
поточної реалізації) як раз вистачає для зберігання покажчика. Цей простір
займається тільки в об'єктах класів з віртуальними функціями, а не в усіх
об'єктах класів і навіть не в усіх об'єктах похідних класів. Ви платите цю
мито тільки за ті класи, для яких описали віртуальні функції. p>
Виклик функції за допомогою операції дозволу області
видимості::, як це робиться в manager:: print (), гарантує, що механізм
віртуальних функцій застосовуватися не буде. Інакше manager:: print () піддавалося
б нескінченної рекурсії. Застосування уточненого імені має ще один ефект,
який може виявитися корисним: якщо описана як virtual функція описана
ще й як inline (у чому нічого незвичайного немає), то там, де у виклику
застосовується:: може застосовуватися inline-підстановка. Це дає програмісту
ефективний спосіб справлятися з тими важливими спеціальними випадками, коли одна
віртуальна функція викликає іншу для того самого об'єкта. Оскільки тип об'єкта
був визначений при виклику першої віртуальної функції, звичайно, його не треба знову
динамічно визначати іншому виклик для того самого об'єкта. p>
Список літератури h2>
Для підготовки даної роботи були використані матеріали
з сайту http://www.realcoding.net
p>