на тему рефераты Информационно-образоательный портал
Рефераты, курсовые, дипломы, научные работы,
на тему рефераты
на тему рефераты
МЕНЮ|
на тему рефераты
поиск
Розробка власного класу STRING
i>pm->level = 2; // відмінно: pm указує на об'єкт mm

// типу manager, а в ньому при розміщенні

// виділена пам'ять для члена 'level'

}

Іншими словами, якщо робота з об'єктом похідного класу йде через вказівник, то його можна розглядати як об'єкт базового класу. Зворотне невірно. Відзначимо, що у звичайній реалізації С++ не передбачається динамічного контролю над тим, щоб після перетворення типу, подібного тому, що використовувалося в присвоюванні pe в pm, отриманий у результаті вказівник дійсно був налаштований на об'єкт необхідного типу.

1.14.2 Функції-члени

Прості структури даних начебто employee і manager самі по собі не занадто цікаві, а часто й не дуже корисні
. Тому додамо до них функції:

class employee {

char* name;

// ...

public:

employee* next; // перебуває в загальній частині, щоб

// можна було працювати зі списком

void print () const;

// ...

};

class manager: public employee {

// ...

public:

void print () const;

// ...

};

Треба відповісти на деякі питання. Яким чином функція-член похідного класу manager може використати члени базового класу employee? Які члени базового класу employee можуть використати функції-члени похідного класу manager? Які члени базового класу employee може використати функція, що не є членом об'єкта типу manager? Які відповіді на ці питання повинна давати реалізація мови, щоб вони максимально відповідали завданню програміста?

Розглянемо приклад:

void manager:: print () const

{

cout << " ім'я " << name << '\n';

}

Член похідного класу може використати ім'я із загальної частини свого базового класу нарівні з усіма іншими членами, тобто без вказівки імені об'єкта. Передбачається, що є об'єкт, на який настроєний this, тому коректним звертанням до name буде this->name. Однак, при трансляції функції manager:: print () буде зафіксована помилка: члену похідного класу не надане право доступу до приватних членів його базового класу, значить name недоступно в цій функції.

Можливо багатьом це здасться дивним, але давайте розглянемо альтернативне рішення: функція-член похідного класу має доступ до приватних членів свого базового класу. Тоді саме поняття частки (закритого) члена втрачає всякий зміст, оскільки для доступу до нього досить просто визначити похідний клас. Тепер уже буде недостатньо для з'ясування, хто використає приватні члени класу, переглянути всі функції-члени й друзів цього класу. Прийдеться переглянути всі вихідні файли програми, знайти похідні класи, потім досліджувати кожну функцію цих класів. Далі треба знову шукати похідні класи від уже знайдених і т.д. Це, принаймні, утомливо, а швидше за все нереально. Потрібно всюди, де це можливо, використати замість приватних членів захищені (protected).

Як правило, саме надійне рішення для похідного класу - використати тільки загальні члени свого базового класу:

void manager:: print () const

{

employee:: print (); // друк даних про службовців

// друк даних про керуючих

}

Відзначимо, що операція:: необхідна, оскільки функція print () перевизначена в класі manager. Таке повторне використання імен типово для С++. Необережний програміст написав би:

void manager:: print () const

{

print (); // печатка даних про службовців

// печатка даних про керуючих

}

У результаті він одержав би рекурсивну послідовність викликів manager:: print ().

1.14.3 Конструктори й деструктори

Для деяких похідних класів потрібні конструктори
. Якщо конструктор є в базовому класі, то саме він і повинен викликатися із вказівкою параметрів, якщо такі в нього є:

class employee {

// ...

public:

// ...

employee (char* n, int d);

};

class manager: public employee {

// ...

public:

// ...

manager (char* n, int i, int d);

};

Параметри для конструктора базового класу задаються у визначенні конструктора похідного класу. У цьому змісті базовий клас виступає як клас, що є членом похідного класу:

manager:: manager (char* n, int l, int d)

: employee (n,d), level (l), group (0)

{

}

Конструктор базового класу employee:: employee () може мати таке визначення:

employee:: employee (char* n, int d)

: name (n), department (d)

{

next = list;

list = this;

}

Тут list повинен бути описаний як статичний член employee.

Об'єкти класів створюються знизу вверх: спочатку базові, потім члени й, нарешті, самі похідні класи. Знищуються вони у зворотному порядку: спочатку самі похідні класи, потім члени, а потім базові. Члени й базові створюються в порядку опису їх у класі, а знищуються вони у зворотному порядку.

1.14.4 Ієрархія класів

Похідний клас сам у свою чергу може бути базовим класом
:

class employee {/*... */ };

class manager: public employee {/*... */ };

class director: public manager {/*... */ };

Така безліч зв'язаних між собою класів звичайно називають ієрархією класів. Звичайно вона представляється деревом, але бувають ієрархії з більш загальною структурою у вигляді графа:

class temporary {/*... */ };

class secretary: public employee {/*... */ };

class tsec

: public temporary, public secretary { /*... */ };

class consultant

: public temporary, public manager { /*... */ };

Бачимо, що класи в С++ можуть утворювати спрямований ациклічний граф.

1.14.5 Поля типу

Щоб похідні класи були не просто зручною формою короткого опису, у реалізації мови повинно бути вирішено питання
: якому з похідних класів ставиться об'єкт, на який дивиться вказівник base*? Існує три основних способи відповіді:

[1] Забезпечити, щоб вказівник міг посилатися на об'єкти тільки одного типу;

[2] Помістити в базовий клас поле типу, що зможе перевіряти функції;

[3] використати віртуальні функції.

Вказівники на базові класи, звичайно, використаються при проектуванні контейнерних класів (вектор, список і т.д.). Тоді у випадку [1] ми одержимо однорідні списки, тобто списки об'єктів одного типу.

Способи [2] і [3] дозволяють створювати різнорідні списки, тобто списки об'єктів декількох різних типів (насправді, списки вказівників на ці об'єкти).

Спосіб [3] - це спеціальний надійний у сенсі типу варіант спосіб [2]. Особливо цікаві й потужні варіанти дають комбінації способів [1] і [3].

Спочатку обговоримо простий спосіб з полем типу, тобто спосіб [2]. Приклад із класами manager/employee можна перевизначити так:

struct employee {

enum empl_type { M, E };

empl_type type;

employee* next;

char* name;

short department;

// ...

};

struct manager: employee {

employee* group;

short level;

// ...

};

Маючи ці визначення, можна написати функцію, що друкує дані про довільного службовця:

void print_employee (const employee* e)

{

switch (e->type) {

case E:

cout << e->name << '\t' << e->department << '\n';

// ...

break;

case M:

cout << e->name << '\t' << e->department << '\n';

// ...

manager* p = (manager*) e;

cout << "level" << p->level << '\n';

// ...

break;

}

}

Надрукувати список службовців можна так:

void f (const employee* elist)

{

for (; elist; elist=elist->next) print_employee (elist);

}

Це цілком гарне рішення, особливо для невеликих програм, написаних однією людиною, але воно має істотний недолік: транслятор не може перевірити, наскільки правильно програміст поводиться з типами. У більших програмах це приводить до помилок двох видів. Перша - коли програміст забуває перевірити поле типу. Друга - коли в перемикачі вказуються не всі можливі значення поля типу. Цих помилок досить легко уникнути в процесі написання програми, але зовсім нелегко уникнути їх при внесенні змін у нетривіальну програму, а особливо, якщо це велика програма, написана кимось іншим. Ще сутужніше уникнути таких помилок тому, що функції типу print () часто пишуться так, щоб можна було скористатися спільністю класів:

void print (const employee* e)

{

cout << e->name << '\t' << e->department << '\n';

// ...

if (e->type == M) {

manager* p = (manager*) e;

cout << "level" << p->level << '\n';

// ...

}

}

Оператори if, подібні наведеним у прикладі, складно знайти у великій функції, що працює з багатьма похідними класами. Але навіть коли вони знайдені, нелегко зрозуміти, що відбувається насправді. Крім того, при всякім додаванні нового виду службовців потрібні зміни у всіх важливих функціях програми, тобто функціях, що перевіряють поле типу. У результаті доводиться правити важливі частини програми, збільшуючи тим самим час на налагодження цих частин.

Іншими словами, використання поля типу чревате помилками й труднощами при супроводі програми. Труднощі різко зростають по мірі росту програми, адже використання поля типу суперечить принципам модульності й приховування даних. Кожна функція, що працює з полем типу, повинна знати подання й специфіку реалізації всякого класу, котрий є похідним для класу, що містить поле типу.

1.14.6 Віртуальні функції

За допомогою віртуальних функцій можна перебороти труднощі, що виникають при використанні поля типу
. У базовому класі описуються функції, які можуть перевизначатися в будь-якому похідному класі. Транслятор і завантажник забезпечать правильну відповідність між об'єктами й застосовуваними до них функціями:

class employee {

char* name;

short department;

// ...

employee* next;

static employee* list;

public:

employee (char* n, int d);

// ...

static void print_list ();

virtual void print () const;

};

Службове слово virtual (віртуальна) показує, що функція print () може мати різні версії в різних похідних класах, а вибір потрібної версії при виклику print () - це завдання транслятора. Тип функції вказується в базовому класі й не може бути перевизначений у похідному класі. Визначення віртуальної функції повинне даватися для того класу, у якому вона була вперше описана (якщо тільки вона не є чисто віртуальною функцією). Наприклад:

void employee:: print () const

{

cout << name << '\t' << department << '\n';

// ...

}

Ми бачимо, що віртуальну функцію можна використати, навіть якщо немає похідних класів від її класу. У похідному ж класі не обов'язково перевизначити віртуальну функцію, якщо вона там не потрібна. При побудові похідного класу треба визначати тільки ті функції, які в ньому дійсно потрібні:

class manager: public employee {

employee* group;

short level;

// ...

public:

manager (char* n, int d);

// ...

void print () const;

};

Місце функції print_employee () зайняли функції-члени print (), і вона стала не потрібна. Список службовців будує конструктор employee. Надрукувати його можна так:

void employee:: print_list ()

{

for (employee* p = list; p; p=p->next) p->print ();

}

Дані про кожного службовця будуть друкуватися відповідно до типу запису про нього. Тому програма

int main ()

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15



© 2003-2013
Рефераты бесплатно, курсовые, рефераты биология, большая бибилиотека рефератов, дипломы, научные работы, рефераты право, рефераты, рефераты скачать, рефераты литература, курсовые работы, реферат, доклады, рефераты медицина, рефераты на тему, сочинения, реферат бесплатно, рефераты авиация, рефераты психология, рефераты математика, рефераты кулинария, рефераты логистика, рефераты анатомия, рефераты маркетинг, рефераты релиния, рефераты социология, рефераты менеджемент.