
- •Міністерство надзвичайних ситуацій України Львівський державний університет безпеки життєдіяльності Юрій грицюк, Тарас рак
- •Навчальний посібник
- •Потреба використання об'єктно-орієнтованого програмування
- •Поняття про об’єктно-орієнтований підхід до розроблення складних програм
- •Основні компоненти об’єктно-орієнтованої мови програмування
- •Поняття про універсальну мову моделювання
- •Базові поняття класу
- •Код програми 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;
- •«" 'Є' для запису витрат:";
- •Навчальний посібник
Void Show()
{
cout« Суг("Трикутник з висотою ")« x; cout« Cyr(" і основою") « у; cout« Cyr(" мас площу ") « x * 0.5 * у ; cout« Суг(" кв. од. ")« endl;
}
};
class rectangle: public figure { public:
Void Show()
{
cout« Суг("Прямокутник розмірами") « x «" x" « y; cout« Cyr(" мас площу ") «х* у; cout« Cyr(" кв. од. ")« endl;
}
};
class circle: public figure {
// Відсутність визначення функції ShowO // Викличе повідомлення про помилку.
};
Int mainO
//
Створення покажчика на об'єкт базового
типу // Створення об'єкта похідного
типу // Створення об'єкта похідного
типу // Помилка: створення цього об'єкта
є неможливим!
//
Присвоєння покажчику адреси об'єкта
похідного класу
//
Присвоєння покажчику адреси об'єкта
похідного класу
_}
Якщо клас містить хоч би одну суто віртуальну функцію, то він називається а! страктним.
Абстрактний клас характеризує одна важлива особливість: у такого класу не може бути об'єктів. Абстрактний клас можна використовувати тільки як базовий,
з якого виводитимуться інші похідні класи. Причина того, що абстрактний клас не можна використовувати для побудови об'єктів, полягає, безумовно, у тому, що його одна або декілька функцій не мають визначення. Але навіть якщо базовий клас є абстрактним, то його все одно можна використовувати для оголошення покажчиків і посилань, які необхідні для підтримки динамічного поліморфізму.
Порівняння механізму раннього зв'язування з пізнім
Під час обговорення об'єктно-орієнтованих мов програмування зазвичай використовують два терміни: раннє зв’язування (early binding) і пізнє зв'язування (late binding). У мові програмування C++ ці терміни пов'язують з подіями, які відбуваються при компілюванні та у період виконання програми відповідно.
При ранньому зв'язуванні виклик функцгі готується при компілюванні програми, а при пізньому ~ у процесі виконання програми.
Раннє зв'язування означає, що вся інформація, необхідна для виклику функції, відома при компілюванні програми. Прикладами раннього зв'язування можуть слугувати виклики стандартних функцій і виклики перевизначених функцій (звичайних і операторних). З принципових переваг раннього зв'язування можна назвати ефективність: воно працює швидше від пізнього і часто вимагає менших витрат пам'яті. Його основний недолік - відсутність гнучкості.
Пізнє зв'язування означає, що точне рішення про виклик функції буде ухвалено у процесі виконання програми. Пізнє зв'язування у мові програмування C++
досягається за рахунок застосування віртуальних функцій і похідних типів. Перевага пізнього зв'язування полягає у тому, що воно забезпечує великий ступінь гнучкості. Його можна застосовувати для підтримки загального інтерфейсу і давати змогу при цьому різним об'єктам, які використовують цей інтерфейс, визначати їх власні реалізації. Понад це, пізнє зв'язування може допомогти програмісту у створенні бібліотек класів, що характеризуються багатократним використанням і можливістю розширюватися. Але до його недоліків можна віднести, хоч і незначне, але все таки пониження швидкості виконання програм.
Відповідь на запитання: чому віддати перевагу - ранньому або пізньому зв'язуванню, залежить від призначення Вашої програми1. Пізнє зв'язування (його ще називають динамічним) - це один з найпотужніших засобів мови програмування С++. Проте за цю потужність доводиться розплачуватися втратами у швидкості виконання програм. Тому пізнє зв'язування найкраще використовувати тільки у разі, коли воно істотно покращує структуру і керованість програмою. Як і всі практичні засоби, пізнє зв'язування, зазвичай, варто використовувати, але не зловживати ним. Викликані ним втрати у продуктивності є дуже незначні, тому, коли ситуація вимагає пізнього зв'язування, сміливо беріть його на озброєння.
Поняття про поліморфізм і пуризм
Впродовж всього навчального посібника (і зокрема, у цьому розділі) ми відзначаємо відмінності між динамічним і статичним поліморфізмом. Статичний поліморфізм (поліморфізм часу компілювання) реалізується у перевизначенні функцій і операторів. Динамічний поліморфізм (поліморфізм тривалості виконання програми) досягається за рахунок віртуальних функцій. Найзагальніше визначення поліморфізму поміщене у фразі "один інтерфейс, багато методів", і всі згадані вище "знаряддя" поліморфізму відповідають цьому визначенню. Проте під час використання самого терміну поліморфізм все ж таки існують деякі розбіжності.
Деякі пуристи (у цьому випадку - борці за чистоту термінології) об'єктно- орієнтованого програмування наполягають на тому, щоб цей термін використовувався тільки для подій, які відбуваються у процесі виконання програм. Вони стверджують, що поліморфізм підтримується тільки віртуальними функціями. Частково ця точка зору Грунтується на тому факті, що найпершими поліморфічними мовами програмування були інтерпретатори (для них характерним є те, що всі події належать тривалості виконання програми). Поява трансльованих поліморфічних мов програмування розширила концепцію реалізації поліморфізму. Однак все ще не утихають заяви про те, що термін поліморфізм повинен застосовуватися виключно до подій періоду виконання програми. Більшість С++-програмістів не погоджуються з цією точкою зору і вважають, що цей термін можна застосовувати до обох видів засобів. Тому Ви не повинні дивуватися, якщо хтось раптом стане сперечатися з Вами на предмет використання цього терміна!
1 Насправді в більшості крупних програм використовуються обидва види зв'язування. Ю.І. Грицнж, Т.Є. Рак
Розділ 7. РОБОТА З ШАБЛОННИМИ ФУНКЦІЯМИ ТА КЛАСАМИ
Шаблон - це один з складних і потужних засобів мови програмування C++. Він не увійшов до початкової специфікації мови C++, і тільки в кінці 90-х років став невід'ємною частиною програмування нею. Шаблони дають змогу виконати одне з найважчих завдань у програмуванні - створювати програмний код, який можна використовувати для оброблення різних типів даних.
Використовуючи шаблони, можна створювати узагальнені функції та узагальнені класи. В узагальненій функції (або класі) оброблюваний нею (ним) тип даних задається як параметр. Таким чином, одну і ту саму функцію або клас можна використовувати для роботи з різними типами даних, не вказуючи безпосередньо конкретні її (його) версії для оброблення кожного з типів. Детальний аналіз механізму реалізації узагальнених функцій і класів якраз і проведено у цьому розділі.
Поняття про узагальнені функції
Узагальнена функція визначає загальний набір операцій, які згодом використовуватимуться для оброблення даних різних типів. Тип даних, який обробляється функцією, передається їй як параметр. Використовуючи узагальнену функцію для оброблення широкого діапазону даних, можна застосувати єдину загальну процедуру. На сьогодні відомо багато алгоритмів, які мають однакову логіку оброблення різних типів даних. Наприклад, один і той самий алгоритм сортування Quicksort застосовується і для впорядкування елементів масиву цілих чисел, і до масиву чисел з плинною крапкою. Відмінність тут полягає тільки в типі сортованих даних. Створюючи узагальнену функцію, можна запрограмувати роботу алгоритму незалежно від типу оброблюваних даних. Після цього компілятор автоматично згенерує коректний програмний код для типу даних, який насправді встановлюється у процесі виконання цієї функції. Загалом, створюючи узагальнену функцію, створюється функція, яка автоматично перевизначає себе саму.
Узагальнена функція — це функція, яка перевизначає сама себе.
Узагальнена функція створюється за допомогою ключового слова template. Звичайне значення слова "template" точно відображає мету його застосування у мові програмування C++. Це ключове слово використовують для створення шаблону (або оболонки), який описує дії, виконувані функцією. Компіляторові ж залишається "доповнити відсутні деталі" відповідно до заданого значення параметра. Загальний формат визначення шаблонної функції має такий вигляд:
template <class tType> тип ім'я_функи/і(перелік_параметрів)
{
// тіло функції
}
У цьому записі елемент tType є "заповнювачем" для типу даних, які обробляються функцією. Це ім'я використовується в тілі самої функції. Але воно означає всього тільки заповнювач, замість якого компілятор автоматично підставить реальний тип даних при створенні конкретної версії функції. І хоча для задавання узагальненого типу в template-оголошенні за традицією застосовується ключове слово class, однак можна також використовувати ключове слово typename.
Механізм реалізації шаблонної функції з одним узагальненим типом
У наведеному нижче прикладі створюється шаблонна функція з одним узагальненим типом, яка міняє місцями значення двох змінних, що використовується під час її виклику. Оскільки загальний процес обміну значеннями змінних не залежить від їх типу, він є типовим претендентом для створення узагальненої функції.
Код програми 7.1. Демонстрація механізму застосування шаблонної функції з одним узагальненим типом
#include <vcl>
#include <iostream> // Для потокового введення-виведення
#include <conio> // Для консольного режиму роботи
using namespace std; // Використання стандартного простору імен
// Визначення шаблонної функції.
template <class аТуре> void swapAB(aType &а, аТуре &b)
{
аТуре tmp; // Створення тимчасової змінної tmp = а; а = Ь; b = tmp;
}
int mainO
{
int і = 10, j = 20; double x = 10.1, у = 23.3; char a = 'x', b = 'z';
cout«"Початкові значення і, j:" « і « "" «j« endl; cout«"Початкові значення x, у:"« x «""« у « endl; cout«"Початкові значення a, b:" « a «"" « b « endl;
swapAB(i, j); // Перестановка цілих чисел swapAB(x, у); // Перестановка чисел з плинною крапкою swapAB(a, b); // Перестановка символів
cout«"Після перестановки і, j:" « і «"" «j « endl; cout«"Після перестановки х, у:" « х «"" « у « endl; cout«"Після перестановки а, Ь:" « а «"" « b « endl; getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані такі результати:
Початкові значення і, j: 10 20
Початкові значення х, у: 10.1 23.3
Початкові значення a, b: х z
Після перестановки і, j: 20 10
Після перестановки х, у: 23.310.1
Після перестановки a, b: z х
Отже, розглянемо уважно код програми. Рядок template <class аТуре> void swapAB(aType &а, аТуре &b)
повідомляє компілятор, по-перше, що створюється шаблон, і, по-друге, що тут починається узагальнене визначення шаблонної функції. Позначення аТуре є узагальненим типом, який використовується як "заповнювач". За template-заголовком знаходиться оголошення функції swapABO, у якому символ аТуре означає тип даних для значень, які мінятимуться місцями. У функції main() продемонстровано виклик функції swapABO з використанням трьох різних типів даних: int, double і char. Оскільки функція swapAB() є узагальненою, то компілятор автоматично створює три версії функції swapABO: одну для обміну цілих чисел, другу для обміну чисел з плинною крапкою і третю для обміну символів.
Тут необхідно уточнити деякі важливі терміни, пов'язані з шаблонами. По- перше, узагальнена функція (тобто функція, оголошення якої передує template-Hac- танові) також називається шаблонною функцією. Обидва терміни використовуються у цьому навчальному посібнику як взаємозамінні. Коли компілятор створює конкретну версію цієї функції, то вважають, що створюється її спеціалізація (або конкретизація). Спеціалізація також називається породженою функцією (generated function). Дію породження функції визначають як її реалізацію (instantiating). Іншими словами, породжувана функція є конкретним примірником шаблонної функції.
Оскільки мова програмування C++ не розпізнає символ кінця рядка як ознаку кінця настанови, то template-частина визначення узагальненої функції може не знаходитися в одному рядку з іменем цієї функції. У наведеному нижче прикладі показано ще один (достатньо поширений) спосіб форматування функції swapABO: template <class аТуре> void swapAB(aType &a, аТуре &b)
{
аТуре tmp; // Створення тимчасової змінної
tmp = а; а = b; b = tmp;
}
При використанні цього формату важливо розуміти, що між template-настано- вою і початком визначення узагальненої функції ніякі інші настанови знаходитися не можуть. Наприклад, наведений нижче код програми не відкомпілюєгься:
// Цей програмний код не відкомпілюсться template <class аТуре> int і;//Тут помилка!