ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Похідні Класи
         

     

    Інформатика, програмування

    Похідні Класи

    Щоб розділити завдання розуміння апарату мови та методів його застосування, знайомство з поняттям похідних класів робиться в три етапи. Спочатку за допомогою невеликих прикладів, які не треба сприймати як реалістичні, будуть описані самі засоби мови (запис і семантика). Після цього демонструються деякі неочевидні застосування похідних класів, і, нарешті, наводиться закінчена програма.

    Побудова похідного класу

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

    struct employee ( //Службовець

    char * name;// ім'я

    short age;// вік

    short department;// підрозділ

    int salary;//

    employee * next;

    // ...

    );

    Перелік аналогічних службовців буде зв'язуватися через поле next. Тепер давайте визначимо менеджера:

    struct manager ( //Менеджер

    employee emp;// запис про менеджера як про службовця

    employee * group;// підлеглі люди

    // ...

    );

    Менеджер також є службовцем; пов'язані з службовцю employee зберігаються дані в члені emp об'єкта manager. Для читає це людини це, можливо, очевидно, але немає нічого виділяє член emp для компілятора. Покажчик на менеджера (manager *) не є покажчиком на службовця (employee *), тому просто використовувати один там, де потрібно інший, не можна. Зокрема, не можна помістити менеджера в список службовців, не написав для цього спеціальну програму. Можна або застосувати до manager * явне перетворення типу, або помістити в список службовців адресу члена emp, але і те та інше мало елегантно і досить неясно. Коректний підхід полягає в тому, щоб встановити, що менеджер є службовцям з деякої додаткової інформацією:

    struct manager: employee (

    employee * group;

    // ...

    );

    manager є похідним від employee і, назад, employee є базовий клас для manager. Клас manager додатково до члена group має члени класу employee (name, age і т.д.).

    Маючи визначення employee і manager ми можемо тепер створити список службовців, деякі з яких є менеджерами.

    Наприклад:

    void f ()

    (

    manager m1, m2;

    employee e1, e2;

    employee * elist;

    elist = &m1;// помістити m1, e1, m2 і e2 в elist

    m1.next = &e1;

    e1.next = &m2;

    m2.next = &e2;

    e2.next = 0;

    )

    Оскільки менеджер є службовцям, manager * може використовуватися як employee *. Однак службовець необов'язково є менеджером, тому використовувати employee * як manager * не можна.

    Опції Члени

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

    Наприклад:

    class employee (

    char * name;

    // ...

    public:

    employee * next;

    void print ();

    // ...

    );

    class manager: public employee (

    // ...

    public:

    void print ();

    // ...

    );

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

    Розглянемо:

    void manager:: print ()

    (

    cout << "ім'я "<

    // ...

    )

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

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

    З іншого боку, адже можна використовувати механізм friend, щоб надати такий доступ, або об'єктів функцій, або всім функцій окремого класу.

    Наприклад:

    class employee (

    friend void manager:: print ();

    // ...

    );

    вирішило б проблему з manager:: print (), і

    class employee (

    friend class manager;

    // ...

    );

    зробило б доступним кожен член employee для всіх функцій класу manager. Зокрема, це зробить name доступним для manager:: print ().

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

    Наприклад:

    void manager:: print ()

    (

    employee:: print ();// друкує інформацію про слугує

    // ...// Друкує інформацію про менеджері

    )

    Зауважте, що треба використовувати::, тому що print () була перевизначено на manager. Таке повторне використання імен типово. Необережний міг би написати так:

    void manager:: print ()

    (

    print ();// друкує інформацію про слугує

    // ...// Друкує інформацію про менеджері

    )

    і виявити, що програма після виклику manager:: print () несподівано потрапляє в послідовність рекурсивних дзвінків.

    Видимість

    Клас employee став відкритим (public) базовим класом класу manager в результаті опису:

    class manager: public employee (

    // ...

    );

    Це означає, що відкритий член класу employee є також і відкритим членом класу manager.

    Наприклад:

    void clear (manager * p)

    (

    p-> next = 0;

    )

    буде компілюватися, так як next - відкритий член і employee і manager "а. Альтернатива - можна визначити закритий (private) клас, просто опустивши в описі класу слово public:

    class manager: employee (

    // ...

    );

    Це означає, що відкритий член класу employee є закритим членом класу manager. Тобто, функції члени класу manager можуть як і раніше використовувати відкриті члени класу employee, але для користувачів класу manager ці члени недоступні. Зокрема, при такому описі класу manager функція clear () компілюватися не буде. Друзі похідного класу мають до членів базового класу такий же доступ, як і функції члени.

    Бо, виявляється, опис відкритих базових класів зустрічається частіше опису закритих, шкода, що опис відкритого базового класу довше опису закритого. Це, крім того, служить джерелом заплутують помилок у початківців.

    Коли описується похідна struct, її базовий клас з умовчанням є public базовим класом. Тобто,

    struct D: B (...

    означає

    class D: public B (public: ...

    Звідси випливає, що якщо ви не вважали корисним то приховування даних, що дають class, public і friend, ви можете просто не використовувати ці ключові слова і дотримуватися struct. Такі засоби мови, як функції члени, конструктори і перевантаження операцій, що не залежать від механізму приховування даних.

    Можна також оголосити деякі, але не всі, відкриті $ члени базового класу відкритими членами похідного класу. Наприклад:

    class manager: employee (

    // ...

    public:

    // ...

    employee:: name;

    employee:: department;

    );

    Запис

    імя_класса:: імя_члена;

    не вводить новий член, а просто робить відкритий член базового класу відкритим для похідного класу. Тепер name і department можуть використовуватися для manager "а, а salary і age - ні. Природно, зробити зробити закритий член базового класу відкритим членом похідного класу неможливо. Неможливо за допомогою цього запису також зробити відкритими перевантажені імена.

    Підсумовуючи, можна сказати, що разом з наданням коштів додатково до наявних в базовому класі, похідний клас можна використовувати для того, щоб зробити засоби (імена) недоступними для користувача. Іншими словами, за допомогою похідного класу можна забезпечувати прозорий, напівпрозорий і непрозорий доступ до його базового класу.

    Покажчики

    Якщо похідний клас derived має відкритий базовий клас base, то покажчик на derived можна привласнювати змінної типу покажчик на base не використовуючи явне перетворення типу. Зворотне перетворення, покажчика на base в покажчик на derived, має бути явним.

    Наприклад:

    class base (/ * ... * /);

    class derived: public base (/ * ... * /);

    derived m;

    base * pb = &m;// неявне перетворення

    derived * pd = pb;// помилка: base * не є derived *

    pd = (derived *) pb;// явне перетворення

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

    Будь base закритим базовим класом класу derived, неявне перетворення derived * в base * не робилося б. Неявно перетворення не може в цьому випадку бути виконане, тому що до відкритого члкну класу base можна звертатися через вказівник на base, але не можна через вказівник на derived:

    class base (

    int m1;

    public:

    int m2;// m2 - відкритий член base

    );

    class derived: base (

    // m2 НЕ відкритий член derived

    );

    derived d;

    d.m2 = 2;// помилка: m2 із закритої частини класу

    base * pb = &d;// помилка: (закритий base)

    pb-> m2 = 2;// ok

    pb = (base *) &d;// ok: явне перетворення

    pb-> m2 = 2;// ok

    Крім усього іншого, цей приклад показує, що використовуючи явне приведення до типу можна зламати правила захисту. Ясно, робити це не рекомендується, і це приносить програмісту заслужену "нагороду". До нещастя, недисципліновані використання явного перетворення може створити пекельні умови для невинних жертв, які експлуатують програму, де це робиться. Але, на щастя, немає способу скористатися приведенням для отримання доступу до закритого імені m1. Закритий член класу може використовуватися тільки членами та друзями цього класу.

    Ієрархія Типів

    Похідний клас сам може бути базовим класом. Наприклад:

    class employee (... );

    class secretary: employee (... );

    class manager: employee (... );

    class temporary: employee (... );

    class consultant: temporary (... );

    class director: manager (... );

    class vice_president: manager (... );

    class president: vice_president (... );

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

    Наприклад:

    class temporary (... );

    class employee (... );

    class secretary: employee (... );

    // не C ++:

    class temporary_secretary: temporary: secretary ( ... );

    class consultant: temporary: employee (... );

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

    Наприклад:

    class temporary (... );

    class employee (... );

    class secretary: employee (... );

    // Альтернатива:

    class temporary_secretary: secretary

    (temporary temp; ... );

    class consultant: employee

    (temporary temp; ... );

    Це виглядає неелегантність і страждає саме від тих проблем, для подолання яких були винайдені похідні класи. Наприклад, оскільки consultant не є похідним від temporary, consultant "а не можна поміщати з список тимчасових службовців (temporary employee), не написав спеціальної програми. Однак у багатьох корисних програмах цей метод успішно використовується.

    Конструктори і Деструктори

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

    Наприклад:

    class base (

    // ...

    public:

    base (char * n, short t);

    ~ base ();

    );

    class derived: public base (

    base m;

    public:

    derived (char * n);

    ~ derived ();

    );

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

    Наприклад:

    derived:: derived (char * n): (n, 10), m ( "member", 123)

    (

    // ...

    )

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

    Поля Типу

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

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

    Помістити в базовий клас поле типу, яке зможуть переглядати функції; і

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

    Давайте спочатку досліджуємо просте рішення за допомогою поля типу, тобто рішення 2. Приклад зі службовцями і менеджерами можна було б перевизначити так:

    enum empl_type (M, E);

    struct employee (

    empl_type type;

    employee * next;

    char * name;

    short department;

    // ...

    );

    struct manager: employee (

    employee * group;

    short level;// рівень

    );

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

    void print_employee (employee * e)

    (

    switch (e-> type) (

    case E:

    cout < name << "t" < department << "n";

    // ...

    break;

    case M:

    cout < name << "t" < department << "n";

    // ...

    manager * p = (manager *) e;

    cout << "рівень" < level << "n";

    // ...

    break;

    )

    )

    і скористатися нею для того, щоб надрукувати список службовців:

    void f ()

    (

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

    )

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

    Наприклад:

    void print_employee (employee * e)

    (

    cout < name << "t" < department << "n";

    // ...

    if (e-> type == M) (

    manager * p = (manager *) e;

    cout << "рівень" < level << "n";

    // ...

    )

    )

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

    Віртуальні Опції

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

    Наприклад:

    struct employee (

    employee * next;

    char * name;

    short department;

    // ...

    virtual void print ();

    );

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

    Наприклад:

    void employee:: print ()

    (

    cout < name << "t" < department << "n";

    // ...

    )

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

    Наприклад:

    struct manager: employee (

    employee * group;

    short level;

    // ...

    void print ();

    );

    void manager:: print ()

    (

    employee:: print ();

    cout << "tуровень" <

    // ...

    )

    Функція print_employee () тепер не потрібна, оскільки її місце зайняли функції члени print (), і тепер зі списком службовців можна працювати так:

    void f (employee * ll)

    (

    for (; ll; ll = ll-> next) ll-> print ();

    )

    Кожен службовець буде друкуватися відповідно до його типом. Наприклад:

    main ()

    (

    employee e;

    e.name = "Дж.Браун";

    e.department = 1234;

    e.next = 0;

    manager m;

    m.name = "Дж.Сміт";

    e.department = 1234;

    m.level = 2;

    m.next = &e;

    f (& m);

    )

    видасть

    Дж.Сміт 1234

    рівень 2

    Дж.Браун 1234

    Зауважте, що це буде працювати навіть у тому випадку, якщо f () була написана і відкомпілювати ще до того, як похідний клас manager був задуманий! Очевидно, при реалізації цього в кожному об'єкті класу employee зберігається деяка інформація про тип. Займаного для цього простору (в поточної реалізації) як раз вистачає для зберігання покажчика. Цей простір займається тільки в об'єктах класів з віртуальними функціями, а не в усіх об'єктах класів і навіть не в усіх об'єктах похідних класів. Ви платите цю мито тільки за ті класи, для яких описали віртуальні функції.

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

    Список літератури

    Для підготовки даної роботи були використані матеріали з сайту http://www.realcoding.net

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status