
- •Міністерство надзвичайних ситуацій України Львівський державний університет безпеки життєдіяльності Юрій грицюк, Тарас рак
- •Навчальний посібник
- •Потреба використання об'єктно-орієнтованого програмування
- •Поняття про об’єктно-орієнтований підхід до розроблення складних програм
- •Основні компоненти об’єктно-орієнтованої мови програмування
- •Поняття про універсальну мову моделювання
- •Базові поняття класу
- •Код програми 2.1. Демонстрація механізму оголошення класу та його застосування
- •Поняття про конструктори і деструктори
- •Особливості реалізації механізму доступу до членів класу
- •Класи і структури - споріднені типи
- •Об'єднання та класи - споріднені типи
- •Поняття про вбудовані функції
- •Inline int myClass::Put() return c;
- •Особливості організації масивів об'єктів
- •Особливості використання покажчиків на об'єкти
- •Поняття про функції-"друзі" класу
- •Код програми 3.1. Демонстрація механізму використання "дружньої" функції для доступу до закритих членів класу
- •Код програми 3.2. Демонстрація механізму використання "дружньої" функції для перевірки статусу кожного об'єкта
- •Void Run(); //Таймер відліку часу
- •Void timerClass::Run()
- •Int mainO
- •Особливості механізму динамічної ініціалізації конструктора
- •Int s; public:
- •Void Run(); //Таймер відліку часу
- •Void timerClass::Run()
- •Int mainO
- •Особливості механізму присвоєння об'єктів
- •Int а, ь; public:
- •Int mainO
- •Особливості механізму передачі об'єктів функціям
- •Void Fun(myClassobj)
- •Int mainO
- •Конструктори, деструктори і передача об'єктів
- •Void Get(myClass obj)
- •Int mainO
- •Потенційні проблеми, які виникають при передачі об'єктів
- •Int *р; public:
- •Void Get(myClass &obj) // Передача об'єкта за посиланням
- •Int mainO
- •Особливості механізму повернення об'єктів функціями
- •Void Set(char*s) {strcpy(str, s);}
- •Void Show() {cout «"Рядок:" « str« endl;}
- •Int mainO
- •Int mainO
- •Механізми створення та використання конструктора копії
- •Використання конструктора копії для ініціалізації одного об'єкта іншим
- •Int mainO
- •Механізм використання конструктора копії для передачі об'єкта функції
- •Int mainO
- •Механізм використання конструктора копії при поверненні функцією об'єкта
- •Int mainO
- •3.7.4. Конструктори копії та їх альтернативи
- •Поняття про ключове слово this
- •Void Fun() {...};
- •Int mainO
- •Механізми перевизначення операторів з використанням функцій-членів класу
- •Int х, у, z; //Тривимірні координати
- •Int mainO
- •Intх,у,z; //Тривимірні координати
- •Void Show(char*s);
- •Int mainO
- •Int х, у, z; //Тривимірні координати
- •Int mainO
- •Особливості реалізації механізму перевизначення операторів
- •Механізми иеревизначення операторів з використанням функцій-не членів класу
- •Використання функцій-"друзів" класу для перевизначення бінарних операторів
- •Void Show(char*s);
- •Int mainO
- •Int mainO
- •Використання функцій-"друзів" класу для перевизначення унарних операторів
- •Int mainO
- •Особливості реалізації оператора присвоєння
- •Int mainO
- •Int mainO
- •Механізми перевизначення оператора індексації елементів масиву "[]"
- •Int mainO
- •Int aMas[size]; public:
- •Int mainO
- •Int aMas[size]; public:
- •Int mainO
- •Механізми перевизначення оператора виклику функцій "()"
- •Int mainO
- •Механізми перевизначення рядкових операторів
- •Конкатенація та присвоєння класу рядків з рядками класу
- •Void Show(char*s) {cout« s « string « endl;}
- •Конкатенація та присвоєння класу рядків з рядками, що закінчуються нульовим символом
- •Void Show(char*s) {cout« s « string « endl;}
- •Void Show(char*s) {cout« s « string « endl;}
- •Int mainO
- •Поняття про успадкування в класах
- •Int kolesa; // Кількість коліс int pasagyr; // Кількість пасажирів public:
- •Int mistkist; // Вантажомісткість у м куб. Public:
- •Int kolesa; // Кількість коліс int pasagyr; // Кількість пасажирів public:
- •Int mainO
- •Використання специфікатора доступу protected для надання членам класу статусу захищеності
- •Int mainO
- •Int mainO
- •Int d; // Захищений public:
- •Int mainO
- •Protected-членом класу derived, що робить його недоступним за його межами. */
- •Void showX() {cout« х « endl;}
- •Void showY() {cout« у « endl;}
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Успадкування віртуальних функцій
- •Void Show() {cout« Суг("Другий похідний клас.") « endl;}
- •Virtual void Show() {cout« Суг("Базовий клас.") « endl;}
- •Void Show() {cout« Суг("Перший похідний клас. ")« endl;}
- •Int mainO
- •Virtual void ShowO {cout« Суг("Базовий клас.") « endl;}
- •Void Show() {cout« Суг("Перший похідний клас. ")« endl;}
- •Int mainO
- •Void Show()
- •Void Show()
- •Int mainO
- •Void Show()
- •Void Show()
- •Int mainO
- •Void swapAb(aType &а, аТуре &b)
- •Int mainO
- •Приклад створення узагальненого класу для організації безпечного масиву
- •Int mainO
- •Використання в узагальнених класах аргументів, що не є узагальненими типами
- •Використання в шаблонних класах аргументів за замовчуванням
- •Int mainO
- •Механізм реалізації безпосередньо заданої спеціалізації класів
- •Int mainO
- •Основні особливості оброблення виняткових ситуацій
- •Системні засоби оброблення винятків
- •Xtest(1);
- •Xtest(2);
- •Перехоплення винятків класового типу
- •Використання декількох catch-наетанов
- •Варіанти оброблення винятків
- •Перехоплення всіх винятків
- •Накладання обмежень на тип винятків, які генеруються функціями
- •Int mainO
- •Xhandler(o); // Спробуйте також передати функції XhandlerO аргументи 1 і 2.
- •Void Xhandler(int test) throw 0
- •Повторне генерування винятку
- •Int mainO
- •Int mainO
- •Механізми перевизначення операторів new і delete
- •Void *р;
- •Void *p;
- •Int mainO
- •Класи потоків
- •Особливості механізмів перевизначення операторів введення-виведення даних
- •Створення перевюначених операторів виведення даних
- •Int mainO
- •Використання функцій-"друзів" класу для перевюначення операторів виведення даних
- •Int х, у, z; //Тривимірні координати (тепер це private-члени) public:
- •Int mainO
- •Створення перевюначених операторів введення даних
- •Istream &operator»(istream &stream, kooClass &obj)
- •Cout«"Введіть координати X, у і z:
- •Stream » obj.X » obj.Y » obj.Z;
- •Istream &operator»(istream &stream, objectType &obj)
- •// Код операторної функції введення даних
- •Class kooClass {// Оголошення класового типу int х, у, z; // Тривимірні координати
- •Istream &operator»(istream &stream, kooClass &obj)
- •Int mainO
- •Void unsetf(fmtflags flags),
- •Void showflags(ios::fmtflags f); // Відображення поточного стану опцій
- •Int mainO
- •Ios::fmtflags f; // Оголошення параметру для поточного стану опцій
- •Int mainO
- •Створення власних маніиуляторних функцій
- •Організація файлового введення-виведення даних
- •Відкриття та закриття файлу
- •Зчитування та запис текстових файлів
- •Ifstream in(argv[1], ios::in | ios::binary); if(!in){
- •In.CloseO; getchO; return 0;
- •Int mainO
- •Зчитування та записування у файл блоків даних
- •Int mainO
- •Ifstream inftest", ios::in | ios::binary); if(!in){
- •Використання функції eof() для виявлення кінця файлу
- •If(!in.Eof()) cout« ch;
- •In.CloseO; getchO; return 0;
- •Int main(int arge, char *argv[])
- •Ifstream f1(argv[1], ios::in | ios::binary); if(!f1) {
- •Ifstream f2(argv[2], ios::in | ios::binary);
- •Int main(int arge, char *argv[])
- •Ifstream in(argv[1], ios::in | ios::binary); if(!in){
- •In.Seekg(atoi(argv[2]), ios::beg); while(in.Get(ch)) cout« ch; getchO; return 0;
- •Int х, у, z; // Тривимірні координати; вони тепер закриті public:
- •Int mainO
- •Virtual void Fun() {}; // Робимо клас Base поліморфним
- •Int mainO
- •Virtual void FunO {cout«"у класі Base"« endl;}
- •Int mainO
- •Virtual void FunO {}
- •Void derivedOnlyO {cout«"Це об'єкт класу Derived"« endl;}
- •Int mainO
- •Void Fun(const int *р)
- •Int mainO
- •Namespace ns { int d;
- •Застосування настанови using
- •Int Comp(const void *а, const void *b);
- •Int mainO
- •Int Comp(const void *a, const void *b)
- •Int mainO
- •Int Fun10 const; // const-функція-член
- •Int PutO const; return c; // Все гаразд
- •Int mainO
- •0Bj.Set(1900); cout « Obj.PutO;
- •Void setJ(int х) const // Наступна функція не відкомпілюється.
- •Int а; public:
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int myClass::*dp; // Покажчик на int-члена класу void (myClass::*fp)(int X); // Покажчик на функцію-члена
- •Int mainO
- •Механізми роботи з векторами
- •Int mainO
- •1 234 5 678 9 10 11 12 13141516 17 1819 Вміст подвоєно:
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Символи н
- •Символів представляють голосні звуки.
- •Int mainO
- •Int mainO
- •Int mainO
- •Void getaLine(string& inStr); // Отримання рядка тексту char getaCharO; //Отримання символу
- •Int aptNumber; // Номер кімнати мешканця
- •Void DisplayO; // Виведення переліку мешканців
- •Int aptNo; float rent[12]; public:
- •Void setRent(int, float); // Запис плати за місяць
- •Void insertRent(int, int, float); void DisplayO;
- •Int month, day; string category, payee; float amount; expense() {}
- •Int mainO
- •Void rentRecord::insertRent(int aptNo, int month, float amount)
- •SetPtrsRr.Insert(ptrRow); // Занести рядок вектор
- •If( setPtrsRr.Empty())
- •Else // Інакше
- •Int month, day; string category, payee; float amount;
- •«" 'Є' для запису витрат:";
- •Навчальний посібник
Int mainO
{
derivedC ObjC;
ObjC.c = 10; //Тепер неоднозначності немає.
ObjC.d = 20;
ObjC.f = ЗО;
//Тепер неоднозначності немає.
ObjC.sum = ObjC.c + ObjC.d + ObjC.f;
//Тепер неоднозначності немає.
cout« ObjC.c «"";
cout« ObjC.d «"" « ObjC.f«"";
cout« ObjC.sum;
getchO; return 0;
}
Як бачите, ключове слово virtual передує решті частини специфікації успадкованого класу. Тепер обидва класи derivedA і derivedB успадковують клас baseClass як віртуальний, і тому при будь-якому багаторазовому їх успадкуванні у похідний клас буде включено тільки одну його копію. Отже, у класі derivedC є тільки одна копія класу baseClass, а настанова ObjC.c = 10 тепер абсолютно допускається і не містить ніякої неоднозначності.
Нео! хгдноапам'ятати! Навіть якщо обидва класи derivedA і derivedB задають клас baseClass як virtual-клас, він, як і раніше, присутній в об'єкті будь-якого типу.
Наприклад, така послідовність настанов цілком допускається:
// Оголошення похідного класу типу derivedA.
derivedA myClass;
myClass.c = 88;
Різниця між звичайним базовим і віртуальним класами стає очевидною тільки тоді, коли цей базовий клас успадковується більше одного разу. Якщо базовий клас оголошується віртуальним, то тільки один його примірник буде включено в об'єкт класу, що успадковується. Інакше у цьому об'єкті будуть міститися декілька його копій.
Розділ в. ПОНЯТТЯ ПРО ВІРТУАЛЬНІ ФУНКЦІЇ ТА ПОЛІМОРФІЗМ
Однією з трьох основних особливостей об'єктно-орієнтованого програмування є поліморфізм. Стосовно мови програмування С++, під терміном поліморфізм розуміють механізм реалізації функції, у якому різні результати можна отримати за допомогою одного її імені. З цієї причини поліморфізм іноді характеризується фразою "один інтерфейс, багато методів". Це означає, що до всіх функцій-членів класу можна отримати доступ одним і тим самим способом, незважаючи на можливу відмінність у конкретних діях, пов'язаних з кожною окремою операцією.
У мові програмування С++ поліморфізм підтримується як у процесі виконання програми, так у період її компілювання. Перевизначення операторів і функцій - це приклади поліморфізму, що належить до моменту компілювання. Але, попри потужність механізму перевизначення операторів і функцій, він не у змозі вирішити всі завдання, які виникають в реальних додатках, розроблених з використанням об'єктно-орієнтованого програмування. Тому у мові С++ також реалізовано поліморфізм періоду виконання, який базується на використанні похідних класів і віртуальних функцій, що і становить основні теми цього розділу.
Почнемо розгляд матеріалу з короткого опису покажчиків на похідні типи, оскільки саме вони забезпечують підтримку динамічного поліморфізму.
Покажчики на похідні типи - підтримка динамічного поліморфізму
Основою для динамічного поліморфізму слугує покажчик на базовий клас. Покажчики на базові та похідні класи пов'язані такими відносинами, які не властиві покажчикам інших типів. Як було зазначено вище у цьому навчальному посібнику, покажчик одного типу, як правило, не може вказувати на об'єкт іншого типу. Проте покажчики на об'єкти базових класів і об'єкти похідних класів - винятки з цього правила.
У мові програмування С++ покажчик на базовий клас також можна використовувати для посилання на об'єкт будь-якого класу, виведеного з базового. Наприклад, припустимо, що у нас є базовий клас ЬазеСІазБ і похідний клас сіегіуСІазБ, який виведено з класу ЬазеСІазБ. У мові програмування С++ будь-який покажчик, оголошений як покажчик на базовий клас ЬазеСІазБ, може бути також покажчиком на похідний клас сіегІуСІазБ. Отже, після цих оголошень ЬазеСІазБ *р; // Створення покажчика на об'єкт базового типу ЬазеСІазБ ОЬІВ; // Створення об'єкта базового типу сіегк/СІазБ ОЬІО; // Створення об'єкта похідного типу
обидві такі настанови є абсолютно законними:
р = &ОЬІВ; // Покажчик р вказує на об'єкт типу ЬазеСІазБ р = &ОЬІО; // Присвоєння покажчику адреси об'єкта похідного класу сіегіуСІазБ
І* Покажчик р вказує на об'єкт типу derivClass, який є об'єктом, що був виведений з класу baseClass */
У наведеному прикладі покажчик р можна використовувати для доступу до всіх елементів об'єкта ObjD, що є виведеним з об'єкта ObjB. Проте до елементів, які становлять специфічну "надбудову" (над базою, тобто над базовим класом baseClass) об'єкта ObjD, доступ за допомогою покажчика р отримати не можна.
Як конкретний приклад розглянемо навчальну програму, яка визначає базовий клас baseClass і похідний клас derivClass. У цьому коді програми проста ієрархія класів використовується для зберігання імен авторів і назв їх книг.
Код програми 6.1. Демонстрація механізму використання покажчиків на базовий клас для доступу до об'єктів похідних класів
#include <vcl>
#include <conio>
#include <iostream> // Для потокового введення-виведення #include <cstring> // Для роботи з рядковими типами даних
using namespace std; // Використання стандартного простору імен
#include <windows> char bufCyr[256]; char *Cyr(const char *text)
{
CharToOem(text, bufCyr); return bufCyr;
}
class baseClass { // Оголошення класового типу
char author[80]; public:
void putAuthor(char *s) {strcpy(author, s);}
void showAuthorO {cout« Суг("Аутор:") « author« endl;}
};
class derivClass: public baseClass { // Оголошення класового типу char title[80]; public:
void putTitle(char*n) {strcpy(title, n);}
void showTitleO {cout « Суг("Назва:") «title « endl;}
};
int mainO
{
baseClass *bp; // Створення покажчика на об'єкт базового типу baseClass ObjB; // Створення об'єкта базового типу derivClass *dp; // Створення покажчика на об'єкт похідного типу derivClass ObjD; // Створення об'єкта похідного типу
// Доступ до класу baseClass через покажчик, bp = &ObjB; // Присвоєння покажчику адреси об'єкта базового класу
bp->putAuthor(Cyr("EMmb Золя"));
// Доступ до класу derivClass через "базовий" покажчик, bp = &ObjD; // Присвоєння покажчику адреси об'єкта похідного класу
bp->putAuthor(Cyr("Bm^M Шекспір"));
ObjB.showAuthorO; // Покажемо, що кожен автор належить до відповідного об'єкта.
ObjD.showAuthorO;
cout« endl;
/* Оскільки функції putTitie() і showTitleO не є частиною базового класу, то вони недоступні через "базовий" покажчик Ьр, і тому до них потрібно звертатися або безпосередньо, або, як показано тут, через покажчик на похідний тип. */
dp = &ObjD; // Присвоєння покажчику адреси об'єкта похідного класу
dp->putTitle(Cyr("Бypя"));
dp->showAuthor(); // Тут можна використовувати або покажчик Ьр, або покажчик dp. dp->showTitle();
getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані такі результати:
Еміль Золя Вільям Шекспір
Вільям Шекспір Назва: Буря
У наведеному прикладі покажчик Ьр визначається як покажчик на базовий клас baseClass. Але він може також посилатися на об'єкт похідного класу derivClass, причому його можна використовувати для доступу тільки до тих елементів похідного класу, які успадковані від базового. Проте необхідно пам'ятати, що через "базовий" покажчик неможливо отримати доступ до тих членів, які належать похідному класу. Ось чому до функції showTitle() звернення реалізується за допомогою покажчика dp, який є покажчиком на похідний клас.
Якщо виникає потреба за допомогою покажчика на базовий клас отримати доступ до елементів, що визначені певним похідним класом, то необхідно привести цей покажчик до типу покажчика на похідний тип. Наприклад, у процесі виконання цього рядка коду програми дійсно буде викликано функцію showTitle() об'єкта ObjD:
((derivClass *)bp)->showTitleO;
Зовнішній набір круглих дужок використовують для зв'язку операції приведення типу з покажчиком р, а не з типом, що повертається функцією showTitle(). Незважаючи на те, що у використанні такої операції формально немає нічого некоректного, такого запису, за змогою, необхідно уникати, оскільки усі ці прийоми просто вносять у код програми плутанину1.
Крім цього, необхідно розуміти: хоча "базовий" покажчик можна використовувати для доступу до об'єктів будь-якого похідного типу, зворотне ж твердження є неправильним. Іншими словами, використовуючи покажчик на похідний клас, не можна отримати доступ до об'єкта базового типу.
Покажчик можна інкрементувати та декрементувати щодо свого базового типу. Отже, якщо покажчик на базовий клас використовують для доступу до об'єкта похідного типу, то механізм інкрементування або декрементування не примусить його посилатися на наступний об'єкт похідного класу. Натомість він вказуватиме на наступний об'єкт базового класу. Таким чином, інкрементування або декрементування покажчика на базовий клас необхідно розцінювати як некоректну операцію, якщо цей покажчик використовують для посилання на об'єкт похідного класу.
Той факт, що покажчик на базовий тип можна використовувати для посилання на будь-який об'єкт, який є виведеним з базового типу, надзвичайно важливий і принциповий для мови C++. Як буде показано нижче у цьому посібнику, ця гнучкість є ключовим моментом для механізму реалізації динамічного поліморфізму у мові програмування C++.
Посилання на похідні типи. Подібно до покажчиків, посилання на базовий клас також можна використовувати для доступу до об'єкта похідного типу. Ця можливість особливо часто застосовується при передачі аргументів функціям. Параметр, який має тип посилання на базовий клас, може приймати об'єкти базового класу, а також об'єкти будь-якого іншого типу, виведеного з нього.
Механізми реалізації віртуальних функцій
Динамічний поліморфізм можливий завдяки поєднанню двох засобів програмування: механізму успадкування і застосування віртуальних функцій. Про механізм успадкування було сказано у попередньому розділі. Тут ми познайомимося із застосуванням віртуальних функцій.
Поняття про віртуальні функції
Віртуальна функція - це така функція, яка оголошується в базовому класі з використанням ключового слова virtual і перевизначається в одному або декількох похідних класах. Таким чином, кожен похідний клас може мати власну версію віртуальної функції.
Під час застосування віртуальних функцій часто трапляються ситуації, коли віртуальна функція викликається через покажчик (або посилання) на базовий клас. У цьому випадку мова програмування C++ визначає, яку саме версію віртуальної функції необхідно викликати за типом об'єкта, який адресується цим покажчиком. Причому необхідно мати на увазі, що це рішення приймається у процесі виконання програми. Отже, при вказанні на різні об'єкти викликатимуться і різні версії віртуальної функції. Іншими словами, саме за типом об'єкта (а не за типом самого покажчика), який адресується, визначається, яку версію віртуальної функції буде виконано. Таким чином, якщо базовий клас містить віртуальну функцію і якщо з цього базового класу виведено два (або більше) інші класи, то при адресації різних типів об'єктів через покажчик на базовий клас виконуватимуться
і різні версії віртуальної функції. Аналогічний механізм працює і при використанні посилання на базовий клас.
Функція оголошується віртуальною в базовому класі за допомогою ключового слова virtual. При перевизначенні віртуальної функції у похідному класі ключове слово virtual повторювати не потрібно (хоча це не буде помилкою).
Цей термін також застосовується до класу, який успадковує базовий клас, що містить віртуальну функцію. Розглянемо наведену нижче навчальну програму, у якій продемонстровано механізм застосування віртуальних функцій.
Код програми 6.2. Демонстрація механізму застосування віртуальних функцій #include <vcl>
#include <conio>
#include <iostream> // Для потокового введення-виведення using namespace std; // Використання стандартного простору імен
class baseClass { // Оголошення базового класу
public:
// Оголошення віртуальної функції virtual void Show() {cout« Суг("Базовий клас.") « endl;}
};
class firstD : public baseClass { public:
// Перевизначення функції Show() для класу firstD. void Show() {cout« Суг("Перший похідний клас. ")« endl;}
};
class secondD : public baseClass { public:
// Перевизначення функції Show() для класу secondD. void Show() {cout« Суг("Другий похідний клас.") « endl;}
};