Включення об'єктів.
Є два варіанти включення об'єкта типу X в клас A:
- Оголосити в класі А член типу Х;
- Оголосити в класі А член типу X * або X.
Переважно включати власне об'єкт як в першому випадку. Це ефективніше і менше схильний до помилок, оскільки вони не пов'язані між містяться і містить об'єктами описується правилами конструювання та знищення.
Другий варіант з покажчиком можна застосовувати тоді, коли за час життя "містить" об'єкта потрібно змінити покажчик на "міститься" об'єкт.
Другий варіант можна використовувати, коли потрібно ставити "міститься" об'єкт в якості аргументу.
Маючи об'єкти, що включають інші об'єкти, ми створюємо ієрархію об'єктів. Вона є альтернативою і доповненням до ієрархії класів. А як бути в тому випадку, коли кількість включаються об'єктів заздалегідь невідомо і (або) може змінюватися за час життя "містить" об'єкта. Наприклад, якщо об'єкт School містить учнів, то їх кількість може змінюватися.
Існує два способи вирішення цієї проблеми. Перший полягає в тому, що організовується пов'язаний список включених об'єктів, а "що містить" об'єкт має член-покажчик на початок цього списку.
В цьому випадку при створенні об'єкта School створюється порожній список включених об'єктів. Для включення об'єкта викликається метод add (). якому в якості параметра передається покажчик на що включається об'єкт. Деструкція послідовно видаляє всі включені об'єкти. Об'єкт Person містить поле next. яке дозволяє зв'язати об'єкти в список.
Другий спосіб полягає у використанні спеціального об'єкта-контейнера.
Контейнерний клас призначений для зберігання об'єктів і представляє зручні прості і зручні способи доступу до них.
Поряд з контейнерами існують групи, тобто об'єкти, в які включені інші об'єкти. Об'єкти, що входять до групи, називаються елементами групи. Елементи групи, в свою чергу, також можуть бути групою.
- Вікно в інтерактивній програмі, яка володіє такими елементами, як поля введення і редагування даних, кнопки, списки вибору, діалогові вікна тощо
- Агрегат, що складається з більш дрібних вузлів.
- Город, що складається з рослин, системи поливу і плану вирощування.
- Якась організаційна структура (наприклад, ФАКУЛЬТЕТ, КАФЕДРА, СТУДЕНТСЬКА ГРУПА).
Поняття "група" від "контейнер" відрізняються. Контейнер використовується для зберігання інших даних. Приклад контейнерів: об'єкти контейнерних класів бібліотеки STL в C ++ (масиви, списки, черги).
На відміну від контейнера група є клас, який не тільки зберігає об'єкти інших класів, але і володіє власними властивостями, не випливають з властивостей його елементів.
Група дає другий вид ієрархії (перший вид - ієрархія класів, побудована на основі успадкування) - ієрархію об'єктів (ієрархію типу ціле / частина), побудовану на основі агрегації.
Реалізувати групу можна декількома способами:
- Клас "група" містить поля даних об'єктного типу. Таким чином, об'єкт "група" в якості даних містить або безпосередньо свої елементи, або покажчики на них Такий спосіб реалізації групи використовується в C ++ Builder.
- Група містить член-дане last типу TObject *. який вказує на початок пов'язаного списку об'єктів, включених в групу. В цьому випадку об'єкти повинні мати поле next типу TObject *. вказує на наступний елемент у списку.
- Створюється пов'язаний список структур типу TItem. Поле item вказує на об'єкт, включений в групу. Група містить поле last типу TItem *. яке вказує на початок пов'язаного списку структур типу TItem. Якщо необхідний доступ елементів групи до її полів і методів, об'єкт типу TObject повинен мати поле owner типу TGroup *. яке вказує на власника цього елемента.
Є два методи, які необхідні для функціонування групи:
Вставляє елемент в групу.
Крім цього група може містити наступні методи:
Показує, чи є хоча б один елемент в групі.
Видаляє елемент з групи, але зберігає його в пам'яті.
Видаляє елемент з групи і з пам'яті.
Ітератори дозволяють виконувати деякі дії для кожного елемента певного набору даних.
Такий цикл міг би бути виконаний для всього набору, наприклад, що-б надрукувати всі елементи набору, або міг би шукати певний елемент, який задовольняє певній умові, і в цьому випадку такий цикл може закінчитися, як тільки буде знайдений необхідний елемент.
Ми будемо розглядати ітератори як спеціальні методи класу-групи, що дозволяють виконувати деякі дії для всіх об'єктів, включених в групу. Прикладом ітератора є метод Show.
Нам би хотілося мати такий итератор, який дозволяв би виконувати над усіма елементами групи дії, задані не одним з методів об'єкта, а довільною функцією користувача. Такий итератор можна реалізувати, якщо цю функцію передавати йому через покажчик на функцію.
Визначимо тип покажчика на функцію наступним чином:
Функція має обов'язковий параметр типу TObject або TObject *. через який їй передається об'єкт, для якого необхідно виконати певні дії.
Метод-итератор оголошується наступним чином:
де action - єдиний обов'язковий параметр-покажчик на функцію, яка викликається для кожного елемента групи; додаткові параметри - передані функції, що викликається параметри.
Потім визначається покажчик на функцію і инициализируется переданої Ітератор функцією.
Тоді итератор буде викликатися, наприклад, для додаткового параметра типу int. так:
Тут gr - об'єкт-група.
Розглянемо відносини між спадкуванням та включенням.
Включення і успадкування.
Нехай клас D є похідний клас від класу B.
Слово public в заголовку класу D говорить про відкрите успадкування. Відкрите спадкування означає що похідний клас D є підтипом класу B. тобто об'єкт D є і об'єктом В. Таке спадкування є відношенням is-a або говорять, що D є різновид D. Іноді його називають також інтерфейсним спадкуванням. При відкритому спадкуванні змінна похідного класу може розглядається як змінна типу базового класу. Покажчик, тип якого - "покажчик на базовий клас", може вказувати на об'єкти, що мають тип похідного класу. Використовуючи спадкування ми будуємо ієрархію класів.
Розглянемо наступну ієрархію класів.
Визначимо покажчики на об'єкти цих класів.
Створимо об'єкти цих класів.
Нехай тепер клас D має член класу B.
У свою чергу клас B має член класу C.
Таке включення називають ставленням has-a Використовуючи включення ми будуємо ієрархію об'єктів.
На практиці виникає проблема вибору між спадкуванням та включенням. Розглянемо класи "Літак" і "Двигун". Новачкам часто приходить в голову зробити "Літак" похідним від "Двигун". Це не вірно, оскільки літак не є двигуном, він має двигун. Один із способів побачити це-задуматися, чи може літак мати кілька двигунів? Оскільки це можливо, нам слід використовувати включення, а не успадкування.
Розглянемо наступний приклад:
Чому в рядках # 1 і # 3 помилки?
У рядку # 1 немає перетворення D * в B *.
У рядку # 3 D не має члена g ().
На відміну від відкритого наслідування, не існує неявного перетворення з класу в один з його членів, і клас, що містить член іншого класу, які не заміщає віртуальних функцій того класу.
Якщо для класу D використовувати відкрите успадкування
не містить помилок.
Так як D є похідним класом від B. то виконується неявне перетворення з D в B. Наслідком є зростаюча залежність між B і D.
Існують випадки, коли вам подобається спадкування, але ви не можете дозволити таких перетворень.
Наприклад, ми хочемо повторно використовувати код базового класу, але не припускаємо розглядати об'єкти похідного класу як екземпляри базового. Все, що ми хочемо від наследованія- це повторне використання коду. Рішенням тут є закрите спадкоємство. Закрите спадкування не носить характеру відносини підтипів, або відносини is-a. Ми будемо називати його ставленням like-a (подібний) або успадкуванням реалізації, на противагу спадкоємства інтерфейсу. Закрите (також як і захищене) успадкування не створює ієрархії типів.
З точки зору проектування закрите спадкоємство рівносильно включенню, якщо не брати до уваги питання з заміщенням функцій. Важливе застосування такого підходу-відкрите успадкування з абстрактного класу, і одночасно закрите (або захищене) успадкування від конкретного класу для подання реалізації.
Приклад. Бінарне дерево пошуку
Вузли двійкового дерева містять узагальнений покажчик на дані data. Він буде відповідати типу покажчика в похідному класі. Поле count містить число повторюваних входжень даних. Для конкретного похідного класу ми повинні написати функцію comp для порівняння значень конкретного похідного типу. Функція insert () поміщає вузли в дерево.
Функція TP find (Node * r, TP d) шукає в поддереве з коренем r інформацію, представлену d.
Функція print () - стандартна рекурсія для обходу бінарного дерева
У кожному вузлі застосовується зовнішня функція. print ().
Тепер створимо похідний клас, який в якості членів даних зберігає покажчики на char.
У класі StringTree функція insert використовує неявне перетворення char * до void *.
Функція порівняння comp виглядає наступним чином
Для виведення значень, що зберігаються в вузлі, використовується зовнішня функція
Тут для явного приведення типу void * до char * ми використовуємо операцію приведення типу (імя_тіпа) вираз. Більш надійним є використання оператора static_cast
множинне спадкування
Клас може мати кілька безпосередніх базових класів
Таке спадкування називається множинним. При множині спадкування ніякої клас не може більше одного разу використовуватися в якості безпосереднього базового. Однак клас мyжет більше одного разу бути непрямим базовим класом.
Маємо наступну ієрархію класів (і об'єктів):
Таке дублювання класу відповідає включенню в похідний об'єкт кількох об'еaтов базового класу. У цьому прикладі існують два об'єкти класу Х. Для усунення можливих неоднозначностей потрібно звертатися до конкретного компоненту класу Х, використовуючи повну кваліфікацію
Щоб усунути дублювання об'єктів непрямого базового класу при множині спадкування, цей базовий клас оголошують віртуальним.
Тепер клас А включатиме лише один екземпляр Х, доступ до якого равrоправно мають кlасси Y і Z.
тут- об'єкт класу Base займає в пам'яті 15 байт:
- 4 байта - поле int;
- 2 байта - поле char;
- 10 байт - поле char [10];
- об'єкт класу ABase займає в пам'яті 79 байт:
- 8 байт - поле double;
- 15 байт - поля базового класу Base;
- 2 байта - для зв'язку в іерfрхіі віртуальних класів;
- об'єкт класу BBase займає в пам'яті 21 байт:
- 4 байта - поле float;
- 15 байт - поля базового класу Base; 2 байта - для зв'язку в ієрархії віртуальних класів;
- об'єкт класу Top займає h пам'яті 35 байт:
- 4 байта - поле long;
- 10 байт - дані і зв'язку ABase;
- 6 байт - даvние і зв'язку BBase;
- 15 байт - поля базового класу Base;
Якщо при спадкуванні Base в класах ABase і BBase базовий клас вдіяти не віртуальним, то результатb будуть такими:
- об'єкт класу Base займає в пам'яті 15 байт
- об'єкт класу ABase займає в пам'яті 26 байта (немає 2-х байтів для зв'язку);
- об'єкт класу BBase займає в пам'яті 59 байт (немає 2-х байтів для зв'язку);
- об'екv класу Top займає в пам'яті 46 байт (об'єкт Base входить двічі).
Локальні і вкладені класи
Клас може бути оголошений всередині блоку, наприклад, всередині визначення функції. Такий клас називається локальним. Локалізація класу передбачає недоступність його компонентів поза області визначення класу (поза блоком).
Локальний клас не може мати статичних даних, тому що компоненти локального класу не можуть бути визначені поза текстом класу.
Усередині локального класу дозволено використовувати з осяжний його області тільки імена типів, статичні (static) змінні, зовнішні (extern) змінні, зовнішні функції і елементи перерахувань. З того, що заборонено, важливо відзначити змінні автоматичної пам'яті. Rуществует ще одне важливе обмеження для локальниw класів - їх компонентні функції можуть бути тільки inline.
Усередині класу дозволяється визначати типи, отже, один клас може бути описаний всередині іншого. Такий клас називається вкладеним. Вкладений клас є локальним для класу, в рамках якого він описаний, і на нього рапространяется ті правила використання локального класу, про які гоpорілось вище. Слід особливо сказати, що вкладений клас не має ніякого wсобого права доступу до членів охоплює класу, тобто він може звертатися до них тільки через об'єкт типу цього класу (так само як і охоплює клас не має яких-небудь особливих прав доступу до вкладеного класу ).
Приклад. Клас "ПРЯМОКУТНИК".
Визначимо клас "прямокутник". Усередині цього класу визначимо клас як вкладений клас "відрізок". Прямокутник буде будується з відрізків.
Використовуючи цю методику можна визначити будь-яку геометричну фугуру, що складається з відрізків прямих.
Клас String зберігає рядок у вигляді масиву символів з завершальним нулем з стилі Сі і використовує механізм підрахунку посилань для мінімізації іперацій копіювання.
Клас String користується трьома допоміжними класами:- StringRepeater. який дозволяє розділяти дійсне уявлення між декількома об'єктами типу String з однаковими значеннями;
- Range - для генерації виключення в разі виходу за межі діапазону;
- Reference - для реалізації операції індексування, який розрізняє операції читання і запису.
Також як і інші члени, вкладений клас може бути оголошений в самому класі, а визначено пізніше.
Клас String забезпечує звичайний набір конструкторів, деструкторів і операторів присвоювання.