Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

closeBase();

}

211

8 ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ ТА С++.

8.1 ЕЛЕМЕНТИ КОНЦЕПЦІЇ ООП

Об’єктно-орієнтована парадигма зумовлює специфічний підхід до розробки програмного забезпечення. Фундаментальна концепцiя об’єктно-орієнтованої парадигми полягає в передачi повiдомлень об'єктам. Для цього необхiдно, щоб процедури визначались в одному програмному об’єкті разом з даними, якими вони маніпулюють. Відмітимо, що в процедурному програмуванні спочатку визначаються структури даних, якi потiм передаються в процедури як параметри.

Iснує п'ять компонент об’єктно-орієнтованої парадигми (пара- дигма-концепція):

1 - об'єкт ;

2- повiдомлення ;

3- клас ;

4- наслiдування ;

5- метод .

Будь-яка об’єктно-орієнтована мова повинна мати властивостi абстракцiї, iнкапсуляцiї, наслiдування та полiморфiзму.

Об’єкт - це iнкапсульована абстракцiя , яка включає iнформацiю про стан та чітко визначену множину протоколу доступу (повiдомлення, яке обробляє об'єкт.)

Повідомлення - це спецiальний символ, iдентифiкатор чи ключове слово (може бути з параметрами чи без), яке представляє дiю , що виконується об'єктом.

Клас представляє собою певний тип об'єктiв i задається за допомогою опису

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

Метод існує для кожного повідомлення, визначеного для деякого класу. Він визначає реакцiю об'єкта на повiдомлення. Як правило, метод складається з ряду виразів і може використовувати протокол з іншого класу.

Таким чином, об’єкти належать до окремих класів. Об’єкти обробляють повідомлення у відповідності з методами, заданими в описі класу. Об’єкти мають змінні стану, визначені в описі класу. Змінні стану можуть мати однакові чи різні значення в різних об’єктів (екземплярів класу).

212

Розглянемо основні поняття об’єктно-орієнтованої парадигми по відношенні до С++.

Клас - це новий тип даних , який вводиться за допомогою ключового слова class.

Об’єкт - це змiнна типу ClassName, де ClassName - це ім’я ранiше визначеного класу.

Змінні стану - це змiннi, якi оголошенi в описi класу. Будемо називати їх полями даних .

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

Метод - це визначення(реалізація) функцiї-члена класу. Абстрагування - це формування представлення про якостi чи властивостi предмету шляхом мисленного видiлення деяких його часткових рис.

Iнкапсуляція - це об'єднання , закриття в "капсулi" певних конструкцій.

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

Потрiбно розрiзняти наслiдування та контейнернi вiдношення. Якщо клас 1 має всi властивостi класу 0, i крiм того , якiсь свої додатковi, то в такому випадку оправдана iєрархiчна структура ( наслiдування):

class 1 <- class 0

Про контейнерне вiдношення говорять, якщо об'єкт класу class 0 мiстить об'єкт класу class 1 , тобто одне з полiв класу class0 має тип class 1.

Поліморфізм - цє здатність повiдомлення викликати рiзнi дiї (реакції методів) на етапi виконання програми . Тобто конкретна форма реакції на повiдомлення визначається i зв'язується з об'єктом пiд час виконання програми (пiзнє зв'язування). В С++ поліморфізм проявляється у підтримці наступних механізмів:

1.Перевантаження функцiй (всерединi опису класу можна перевантажити функцiї).

2.Перевантаження операцiй.

213

3. Вiртуальнi функцiї.

8.2 ВІД СТРУКТУР ANSI С ДО КЛАСІВ С++.

Синтаксичною основою реалізації концепції об’єктноорієнтованого програмування стало введення в С++ нових властивостей структур та класів. Розглянемо, як можна прийти до розуміння необхідності цих властивостей, виходячи з точки зору ідеології об’єктно-орієнтованого програмування, поняття об‘єкта. Нехай описана, наприклад, така структура:

struct Time { int year; int month; int day; int hour;

int minute; }; Time v;

Очевидно, що змінна v може використовуватись для збереження інформації про певний час (рік, місяць, день, година, хвилина). Безсумнівно, що нам може бути необхідною функція, яка виводить цю інформацію в певній формі на якийсь стандартний пристрій виводу (монітор, наприклад):

void Display (Time*p)

{printf ("year=%d month=%d day=%d hour=%d \ minute=%d \n", p->year, p->month, p->day,\ p-> hour ,p-> minute);}

Проте, функція Display семантично дуже пов’язана з структурою Time. Це наводить на думку пов’язати функцію з структурою синтаксично. В С++ (на відміну від ANSI C) є така можливість: функцію можна записувати як поле структури. Тоді наш приклад можемо переписати так:

struct Time { int year; int month; int day; int hour; int minute;

void Display (void)

{printf ("year=%d month=%d day=%d hour=%d \ minute=%d \n", p->year, p->month, p->day,\ p->hour ,p->minute);} };

214

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

struct Time {

int year; int month; int day; int hour; int minute;

void Display (void);}; void Time::Display (void)

{printf ("year=%d month=%d day=%d hour=%d \ minute=%d \n", p->year,p->month, p->day,\

p->hour ,p->minute);}

Що ж ми отримали ? Зробивши функцію членом структури, ми не внесли нічого нового з точки зору можливостей . Адже будь-яка зовнішня функція має вільний доступ до полів даних структури (через операцію “.”). Все те, що можна робити з даними за допомогою функції-члена структури можна робити і за допомогою зовнішніх функцій . Проте, в семантичному плані це безсумнівне досягнення. Об’єднання даних та функцій , що працюють з цими даними в одній структурі - це приклад інкапсуляції.

Функція-член структури стає ніби властивістю структури. А що ж це за структура, що має якісь властивості? Де її можна використовувати? Очевидно, що такі структури могли б бути корисними для моделювання реальних природних об’єктів. В нашому випадку, наприклад, структура Time з функцією-членом Display може служити моделлю годинника. Тобто об’єкта, при звертанні до якого ми отримуємо інформацію про час та дату. Але очевидно, що природні об’єкти мають дані, які є скритими від певних зовнішніх об’єктів.

Наприклад, спостерігаючи за літаком, можна визначити його швидкість, висоту, тип, але не можна ззовні визначити, скільки у нього залишилося пального та боєприпасів. Тоді приходимо до думки, що деякі поля даних повинні бути закритими. Тобто недопустимим синтаксично повинне бути звертання до цих полів за допомогою, наприклад, операції “ . ”. Так, виходячи з поняття інкапсуляції, приходимо до концепції закритих полів даних. В С++ існують спеціальні службові слова - специфікатори доступу: public, protected та private. Дані, розміщені в розділах protected та private стають доступними лише для функцій-членів і недоступними для зовнішніх функцій. Доступ до них може здійснюватись лише за допомогою функцій, описа-

215

них у відкритому розділі.

Оголосимо поля даних в нашому прикладі закритими: struct Time {

private:

int year; int month; int day; int hour; int minute;

public:

void Display (void);} tt1; void Time::Display (void)

{printf ("year=%d month=%d day=%d hour=%d \ minute=%d \n",p->year,p->month,p->day,\

p->hour ,p->minute);}

Тоді при звертанні виду tt1.day компілятор видасть повідомлення про помилку.

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

ри Time.

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

Відмітимо, що структури та класи в С++ мають лише незначні відмінності у властивостях. Іх можна розглядати як синтаксично допустимий спосіб утворення типів даних користувача. В С++ ім’я структури чи класу - це тип даних. Якщо, наприклад, в ANSI C хочемо оголосити змінну типу struct Time, то можемо написати оголошення виду: struct Time a; Тут обов’язковим є ключове слово struct. В С++

216

Timer { private: int year;
int month; int day; int hour; int minute; public:
void Display (void);}; void Timer::Display (void)
{printf ("year=%d month=%d day=%d hour=%d \ te=%d \n", p--> year, p--> month, p--> day,\
p--> hour ,p--> minute);}

слово struct не обов’язкове. Тобто допускається еквівалентне оголошенння виду: Time а; Для можливості такого оголошення в ANSI C потрібно використовувати засіб typedef:

typedef struct Time {…};

Time a;

8.3 ОПИС ПРОТОКОЛУ КЛАСУ.

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

class

Схематично опис класу можемо задати наступним чином: class <Ім’я_Класу>

{ private:

<тип>

<ідентифікатор>;

. . . . . . . . . . . . . . . . . . .

<тип>

<ідентифікатор>;

<тип> <Ім’я_Функції 11>(<сигнатура>);

. . . . . . . . . . . . . . . . . . .

<тип> <Ім’я_Функції 1N1>(<сигнатура>); protected:

<тип> <ідентифікатор>;

. . . . . . . . . . . . . . . . . . .

<тип> <ідентифікатор>;

<типрезультату> <Ім’я_Функції21>(<сигнатура>);

. . . . . . . . . . . . . . . . . . .

<типрезультату> <Ім’я_Функції2N2>(<сигнатура>); public:

<тип> <ідентифікатор>;

. . . . . . . . . . . . . . . . . . .

217

<тип> <ідентифікатор>;

<типрезультату> <Ім’я_Функції 31>(<сигнатура>);

. . . . . . . . . . . . . . . . . . .

<типрезультату> <Ім’я_Функції 3N3>(<сигнатура>);

<Ім’я_Класу>(<сигнатура А>); <Ім’я_Класу> (<сигнатура Б>)

{<тіло функції> }

~<Ім’я_Класу>(void);};

<типрезультату> <Ім’я_Класу>::< Ім’я_Функції 11>(<сигнатура>) { <тіло функції>}

<типрезультату> <Ім’я_Класу>:: <Ім’я_Функції 12>(<сигнатура>) {<тіло функції>}

. . . . . . . . . . . . . . . . . . . . . . . . . . .

<типрезультату> <Ім’я_Класу>:: <Ім’я_Функції 3N3>(<сигнатура>) {<тіло функції>}

<Ім’я_Класу> ::<Ім’я_Класу>(<сигнатура А>) {<тіло функції>}

<Ім’я_Класу> (<сигнатура Б>) { <тіло функції> }

<Ім’я_Класу> :~ <Ім’я_Класу>(void)

{ <тіло функції> }

Всі програмні компоненти, що містяться між відкриваючою та закриваючою фігурними дужками опису класу “{“ - ”};” утворюють

формальний опис класу. Під протоколом класу будемо розуміти об-

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

Бачимо, що формальний опис класу ділиться на три роздiли , що задаються за допомогою ключових слів private, protected та public. Кожен з цих розділів може мiстити поля даних та функцiї-члени. Порядок слідування роздiлiв та їх кiлькiсть можуть бути довiльними. Кількість полів у кожному розділі та порядок їх розміщення також може бути довільним. Якщо ключове слово вiдсутнє , то поля даних i функцiї-члени в цiй частинi опису класу вважаються закритими (для структур -вiдкритими ).

Як бачимо з схеми опису класу, крім “звичайних “ функційчленів класи можуть мати функції, імена яких співпадають з іменем класу або утворюються по схемі: ~ <Ім’я_Класу>. Це так звані конструктори та деструктори.

Конструктор - це функцiя-член, iм'я якої спiвпадає з iменем класу. Конструктор може мати порожнiй список параметрiв. Допускаються параметри по замовчуванню, перевантаження. Ціль констру-

218

кторів - ініціалізація полів об’єкту (екземпляра класу). Присутність конструктора в протоколі класу не є обов’язковою.

Деструктор - це функція, ім’я якої ~ <Ім’я_Класу> . Деструктор повинен обов’язково мати порожнiй список параметрiв. В класі може бути не більше одного деструктора. Ціль деструктора - проведення корректних операцій при знищенні екземпляра класу (наприклад, звільнення пам’яті).

Конструктори i деструктори, так само як i iншi функцiї-члени можуть бути визначеними за межами формального опису класу. Якщо визначення функцiї включається в межі формального опису класу, то така функцiя розглядається компілятором як inline -функцiя. Діє правило: будь-яка змінна та функція є доступною в межах протоколу класу. Тобто в межах протоколу класу можна вільно звертатись до будь-якої змінної чи функції, описаної в цьому ж протоколі (так, ніби вони знаходяться в одному складеному операторі). Наприклад, в функції void Timer::Display (void) вільно використовуються поля month, hour та ін. без будь-яких додаткових описів. Що ж стосується доступу до полів ззовні протоколу (випадок, коли вже визначений певний об’єкт-екземпляр класу), то прямий доступ можливий лише до полів даних та функцій -членів відкритого розділу (аналогічно, як вже розглядалось стосовно структур). Доступ до полів закритого та захищеного розділів здійснюється лише через функції-члени, що містяться у відкритих розділах.

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

а) перший етап вiдладки програми , - локалiзацiя помилки -, виконується ще до запуску програми за рахунок її органiзацiї . Адже помилка, пов’язана з використанням закритих даних, може виникнути лише в функції ( описаній у відкритому розділі), що працює з цими даними.

б) для того, щоб працювати з об’єктами, користувач не обов'язково повинен знати структуру закритих даних. Достатньо лише знати функцiї, якi працюють з цими даними.

в) легко можна змiнювати закриту частину даних без змiни основної програми.

Специфіка захищеного розділу буде розглянута пізніше.

Для ілюстрації пункту б) розглянемо, наприклад , ввід-вивід в С++ (детально він буде розглянутий пізніше). В систему С++ включаються класи ostream та istream. Вивiд iнформацiї здiйснюється

219

шляхом передачi об'єкту cout класа ostream повiдомлення, яке виводиться. Наприклад, якщо хочемо надрукувати рядок " a string", в С++ можемо зробити це так:

cout << " a string\n";

Об'єкту cout можуть передаватись множиннi повiдомлення: int i;

cout<<"<<i<<"\n";

Допускається при цьому (в ранніх версіях) форматований вивiд за допомогою функцiї form , параметри якої аналогічні параметрам функ-

ції printf:

cout<< form( “ i=%d”,i);

Для вводу потрiбно здiйснити передачу об'єкту cin класу istream повiдомлення iз змiнною, яка приймає данi, що вводяться, в якостi параматра. Аналогічно здійснюється форматований ввiд:

cin>> form( “ i=%d”,&i);

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

Синтаксично, визначення об’єкта (екземпляра класу) у найпростішому випадку нічим не відрізняється від визначення елемента ти-

пу struct:

class ClassName

{

public: int p;};

ClassName object;

Можемо визначити вказівник на клас ClassName:

ClassName * objectPointer;

Очевидно, що і доступ до відкритих полів екземпляра класу буде синтаксично реалізований аналогічно, як і для структур, за допомогою операцій “.” та ”->”:

cout<<ClassName .p; objectPointer = new ClassName; cout<<objectPointer->p;

З точки зору концепції ООП доступ до полів об’єкта означає переда-

220

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]