Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛР2_ООП.doc
Скачиваний:
2
Добавлен:
14.11.2019
Размер:
191.49 Кб
Скачать

МІНІСТЕРСТВО ОСВІТИ І НАУКИ, МОЛОДІ ТА СПОРТУ УКРАЇНИ

НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ ЛЬВІВСЬКА ПОЛІТЕХНІКА

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

МЕТОДИЧНІ ВКАЗІВКИ

до виконання лабораторної роботи

на тему:

“Успадкування”

для студентів базового напряму 6.050101 “Комп’ютерні науки”

Затверджено

на засіданні кафедри загальної екології

та екоінформаційних систем

Протокол № 12 від 19.04.2012 р.

Львів – 2012

Об’єктно-орієнтоване програмування: Методичні вказівки до виконання лабораторної роботи на тему: “Успадкування” для студентів базового напряму 6.050101 “Комп’ютерні науки” / Укл.: І. Я. Казимира, О. Н. Кузь. – Львів: Видавництво Національного університету “Львівська політехніка”, 2012. – 23с.

Укладачі: Казимира І. Я., к.т.н., доц.

Кузь О. Н., асист.

Відповідальний за випуск: Заяць В. М., д.т.н., проф.

Рецензенти: Яцимірський М. М., д.т.н., проф.,

Фармага І. В., к.т.н., доц.

Вимоги до виконання лабораторної роботи

Кожен студент отримує індивідуальне завдання на лабораторну роботу (варіанти завдань відповідають номеру студента у журналі академічної групи).

Звіт про виконання індивідуального завдання лабораторної роботи обов’язково повинен містити такі пункти:

  1. Титульна сторінка встановленого зразка

  2. Формулювання завдання відповідного варіанту

  3. Опис розробки (опис класів відповідно до завдання, пояснення розроблених конструкторів, деструкторів, інших методів, відношень між класами, способів перевантаження операцій, тощо).

  4. Текст програми (з обов’язковими коментарями, що пояснюють хід виконання завдання).

  5. Протокол виконання програми

  6. Висновки

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

ЗРАЗОК ТИТУЛЬНОЇ СТОРІНКИ

МІНІСТЕРСТВО ОСВІТИ І НАУКИ, МОЛОДІ ТА СПОРТУ УКРАЇНИ

НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”

Інститут екології, природоохоронної діяльності та туризму ім. В. Чорновола

Кафедра загальної екології та

екоінформаційних систем

Лабораторна робота

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

на тему:

Успадкування

Варіант 1

Виконав:

студент групи КН-28

Петренко А.А.

Перевірив:

ас. Кузь О.Н.

Львів – 2012

Успадкування

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

Синтаксис оголошення похідного класу має наступний вигляд:

// Базовий клас

class Base1

{

//…опис класу Base1

};

// Базовий клас

class Base2

{

//…опис класу Base2

};

// Базовий клас

class BaseN

{

//…опис класу BaseN

};

// Похідний клас

class Derived:<специфікатор_доступу> Base1,

<специфікатор_доступу> Base2,

…,

<специфікатор_доступу> BaseN

{

//…опис специфікатора

};

Тут <специфікатор_доступу> це public, protected або private; він не є обов’язковим і по замовчуванню приймає значення private для класів і public для структур. Специфікатор доступу при успадкуванні визначає рівень доступу до елементів базового класу, який отримують елементи похідного класу. В наведеній нижче табл. 1 описані можливі варіанти доступу, який успадковується.

Таблиця 1

Доступ, який успадковується

Специфікатор доступу, який успадковується

Доступ в базовому класі

Доступ в похідному класі

public

public

protected

private

public

protected

недоступні

protected

public

protected

private

protected

protected

недоступні

private

public

protected

private

private

private

недоступні

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

class Base

{

int x, y;

public:

int GetX(){return x;}

int GetY(){return y;}

};

class Derived: private Base

{

public:

Base:: GetX;

};

main()

{

int x;

Derived ob;

x=ob. GetX();

return 0;

}

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

Нагадаємо, що при будь-якій спробі успадкування в похідному класі доступні тільки відкриті (public) і захищені (protected) члени базового класу (хоча успадковуються всі члени базового класу). Тобто, закриті члени базового класу залишаються закритими, незалежно від того, як цей клас успадковується.

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

  • конструктори;

  • конструктори копіювання;

  • деструктори;

  • оператори присвоювання, які визначені програмістом;

  • друзі класу.

Якщо у похідного класу є тільки один базовий клас, то говорять про просте (або одиничне) успадкування. Наступний приклад демонструє просте успадкування.

#include <iostream.h>

class Coord

{

int x, y;

public:

Coord(int_x, int_y){x=_x; y=_y;}

Coord(){x=0; y=0;}

int GetX(){return x;}

int GetY(){return y;}

void SetX(int_x){x=_x;}

void SetY(int_y){y=_y;}

};

class OutCoord: public Coord

{

public:

OutCoord(int_x, int_y):Coord(_x, _y){}

void ShowX(){cout<<GetX()<<’ ’;}

void ShowY(){cout<<GetY()<<’ ’;}

};

main()

{

OutCoord* ptr;

ptr=new OutCoord(10,20);//створення об’єкту

ptr-> ShowX();

ptr-> ShowY();

cout<<”\n”;

delete ptr;// Видалення об’єкту

return 0;

}

Тут клас OutCoord є похідним від базового класу Coord. До членів класу Coord він додає дві відкриті функції та конструктор. Нагадаємо, що конструктори не успадковуються. Тому похідний клас або повинен оголосити свій конструктор, або надати можливість компілятору згенерувати конструктор по замовчуванню. Розглянемо тепер, як будується конструктор похідного класу, який задається програмістом. Оскільки похідний клас повинен успадкувати всі члени батьківського, при побудові об’єкту свого класу він повинен забезпечити ініціалізацію успадкованих даних-членів, причому вона повинна бути виконана до ініціалізації даних-членів похідного класу, оскільки останні можуть використовувати значення перших. У зв’язку з цим для побудови конструктора похідного класу використовується наступна конструкція:

<констр_похід_класу>(<список параметрів>):

<констр_базового_класу>(<список_арг>)

{<тіло_конструктора>}

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

В наведену вище прикладі ця конструкція використана для створення конструктора класу OutClass. У даному випадку конструктор має вигляд:

OutCoord(int_x, int_y): Coord(_x, _y){}

Тіло конструктора залишене порожнім, оскільки клас OutCoord не має власних даних-членів. Всі параметри конструктора похідного класу просто передані конструктору базового класу для виконання ініціалізації. Аргументи конструктору похідного класу передаються в операторі new, який неявно викликає цей конструктор. Далі програма викликає функції-члени ShowX() і ShowY() об’єкту, який є представником похідного класу, які виводять на екран в одному рядку задані значення координат. І, нарешті, оператор delete викликає видалення об’єкту, для чого він неявно викликає деструктор похідного класу.

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

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

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

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

#include <iostream.h>

class Base

{

public:

Base()

{

cout<<”Ми в конструкторі базового класу”<<”\n”;

}

~ Base()

{

cout<<”Ми в деструкторі базового класу”<<”\n”;

}

};

class Derived: public Base

{

public:

Derived()

{

cout<<”Ми в конструкторі похідного класу”<<”\n”;

}

~ Derived()

{

cout<<”Ми в деструкторі похідного класу”<<”\n”;

}

};

main()

{

Derived ob;

return 0;

}

Ця програма виводить на екран наступне:

Ми в конструкторі базового класу

Ми в деструкторі базового класу

Ми в конструкторі похідного класу

Ми в деструкторі похідного класу

Дуже важливо розуміти роботу конструкторів та деструкторів при успадкуванні.

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

Модифікуємо розглянутий нами приклад так, щоб похідний клас перевизначав функції-члени свого базового класу GetX() і GetY():

#include <iostream.h>

class Coord

{

protected:

int x, y;

public:

Coord(int_x, int_y){x=_x; y=_y;}

Coord(){x=0; y=0;}

int GetX(){return x;}

int GetY(){return y;}

void SetX(int_x){x=_x;}

void SetY(int_y){y=_y;}

};

class OutCoord: public Coord

{

public:

OutCoord(int_x, int_y): Coord(_x, _y){}

int GetX(){return ++x;}

int GetY(){return ++y;}

void ShowX(){cout<< GetX()<<’ ’;}

void ShowY(){cout<< GetY()<<’ ’;}

};

main()

{

OutCoord* ptr;

ptr=new OutCoord(10,20); //створення об’єкту

ptr-> ShowX();

ptr-> ShowY();

cout<<”\n”;

delete ptr; //видалення об’єкту

return 0;

}

В цьому випадку функції-члени ShowX() і ShowY() об’єкту ptr використовують для отримання значень даних-членів перевизначені варіанти функцій базового класу GetX() і GetY(). В результаті програма виведе на екран інкрементовані значення заданих координат.

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

<ім’я_класу>::< ім’я _члена>

Це дає можливість компілятору «бачити» за межами поточної області видимості. Особливо часто це доводиться робити при перевизначенні функції-членів. Функція-член, яка перевизначається може викликати відповідну функцію-член базового класу, а потім виконувати деяку додаткову роботу (або навпаки). Наприклад, в попередньому прикладі можна було перевизначити функцію GetX() наступним чином:

int

OutCoord::GetX()

{

int z;

// виклик перевизначеної функції

z=Coord::GetX();

return ++z;

}

Точно так само можна було використати операцію розширення області видимості для кваліфікації перевантаженої функції в ShowX(), яка викликається:

void ShowX(){cout<< Coord::GetX()<<’ ’;}

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