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

osn_progr_final

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

клас утворювався з одного базового. В С++ похiдний клас можна утворювати з кiлькох базових класiв. Множинне наслідування відрізніється лише тим, що похідний клас наслідує всі властивості базових.

Нехай А,B,С-деякi класи. Тодi похiдний вiд цих класiв оголошується так:

class D:public A,public B,public C { //. . .};

Аналогiчно як цифiкатора доступу цифiкатором(private).

Приклад:

i в простому наслiдуваннi, вiдсутнiсть спепо замовчуванню iнтепретується закритим спе-

class Coord {protected: int x,y;

public:

Coord(int _x=0,int _y=0); void SetLoc(int _x,int _y);}; Coord::Coord(int _x,int _y) {SetColor(_x,_y);};

void Coord::SetLoc(int _x,int _y) {x=_x;

y=_y;}

class Message {protected:

char msg[MAX_LEN]; public:

void SetMsg(char* _msg) {strcpy(msg,_msg);}

};

Class MessageXY:public Coord,public Message {public:

void show( );};

void Message XY::show( ) { goto(x,y); printf(msg); }

int main( ) {MessageXY greeting;

greeting.SetLoc(10,10);

greeting.SetMsg("Hellow"); greeting.Show( );

return 0;};

261

У прикладі бачимо, як з двох класів: Coord та Message утворюється третій клас–MessageXY. Він успадковує всі поля даних та функціїчлени базових класів. Це дозволяє в функції Message XY::show( ) використовувати поля x, y та msg.

У випадку конфлікта імен (наприклад, в двух базових класах оголошена функція-член з одним і тим самим іменем), необхідно використовувати операцію розширення області видимості для визначення необхідного нам члена класу.

Припустимо, що в двох базових класах оголошена функцiя з одним і тим самим iм'ям (func):

class A {protected: int data; public:

void func( void){} };

class B{ public:

void func(void ){}; class C:public A,public B

{public: int data;

}; int main(void)

{C c;

c.func( ) // помилка на етапi компiляцiї

с.data=10; return 0;}

Вихiд з даної ситуацiї один - розширення областi видимості, тобто виклик функцiї func повинно здiйснюватися таким чином(в main):

int main(void) {C c;

с. A::func( );

с. B::func( ); с.data=10; return 0;}

Очевидно, що можна описати ще одну функцію, в якій вказати, яку з конфліктуючих функцій та коли викликати.

В класі, виведеному з кількох базових, може виникнути необхідність у виклику конструкторів цих класів. Якщо класи А, В, С мають конструктори по замовчуванню, в похідному класі D їх можна викликати

262

наступним чином:

class D:public A,public B,public C{ public:

D( ):A( ),B( ),C( ){ }//...};

Або ж конструктор можна оголосити так: class D:public A,public B,public C{

public:

D( );//...};

і реалізувати його за межами фомального опису класу:

D::D():A(),B(),C(){ }

У базових класах можуть бути конструктори з параметрами. Вони викликаються аналогiчно, як i в звичайному наслiдуваннi пiсля двокрапки при визначеннi конструктора. Конструктори в похiдному класi викликаються в тому порядку, в якому оголошенi базовi класи. Навіть якщо в конструкторі похідного класу вказати послідовність виклику іншою (відмінною від порядку слідування базових класів), це не вплине на результат.

ЗАВДАННЯ

1.Перевiрити,чи вiдслiдкує компiлятор змiну порядку слiдування виклику конструкторiв базового класу.

2.Придумати приклад програми, коли результат залежить вiд порядку слiдування базових класiв.

3.Придумати приклад, коли виникає конфлікт імен при множинному наслідування.

4.Використовуючи клас Літак як базовий, утворити кілька класів літаків різних модифікацій (винищувач, бомбардувальник тощо), які відрізняються своїми характеристиками.

5.Використовуючи клас Океан , визначити клас Хижак та Жертва як похідні від нього. Визначити поле простір в класі Океан як масив вказівників на цей клас.

6.Описати класи Вірус та Плазмоклітина як похідні від класу Клітина. Плазмоклітина повинна вміти генерувати антитіла, вірус повинен вміти перетворювати звичайну сусідню клітину на вірусну, якщо сумарна потужність антитіл у ній менша від його потужності (або згідно з деяким іншим законом). Вірус повинен підвищувати температуру сусідніх звичайних клітин або плазмоклітин.

7.Описати клас База даних , в якому зберігається деяка інформація про працівників та визначаються похідні від нього для:

а) загального користування

263

б) нарахування заробітної плати в) СБУ

8.10 ВIРТУАЛЬНI ФУНКЦIЇ ТА КЛАСИ 8.10.1 Віртуальні функції.

У всiх прикладах, наведених раніше, повiдомлення, яке посилалось об'єкту, зв'язувалося з конкретним методом на етапi компiляцiї. Раннє зв'язування має переваги, пов'язанi з оптимальнicтю коду. Поряд 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дних . Cукупнiсть класiв, в яких визначається та перевизначається конкретна вiртуальна функцiя називається полiморфiчним кластером, асоцiйованим з даною вiртуальною функцi'ю.

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

Розглянемо наступний приклад: class Value {

protected: int value; public:

Value(int n)

{value=n;}

int Getvalue(void) {return value;}};

Визначимо похiдний клас:

class Mult:public Value

{

protected: int mult; public:

264

Mult(int n,int m): Value(n) {mult=m;}

int GetValue(void) {return value*mult;}

main( )

{ Value* base; base=Mult(10,2) cout<<base->GetValue;}

//тут буде надрукованo 10 за рахунок //механiзму раннього зв'зування

Вiртуальна функцiя–це звичайна функцiя-член класу, яка вiдрiзняється вiд iнших лише наявнiстю ключового слова virtual перед оголошенням функцiї:

virtual int GetValue(){ //...}

Якщо оголосити функцію GetValue( ) в обох класах як віртуальну, то в наведеному вище прикладі буде надруковано 20. Тобто оголошення функції GetValue( ) як віртуальної за допомогою спеціального механізму дозволяє через вказівник на базовий клас викликати функції тих похідних класів по ієрархії, адреси яких містить в даний момент цей вказівник.

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

virtual int GetValue( );

Тоді за межами формального опису:

int Value::GetValue( ){return value;}

Але чи потрiбне ключове слово virtual при оголошенні функції в усiх членах полiморфiчного кластеру? Виявляється, що достатнім є його наявність лише в першому члені поліморфічного кластера, перед віртуальною функцією самого високого рівня.

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

class Form {

public:virtual void Draw( ) { };};

265

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

Form * picture[100];

Тоді для побудови конкретної фігури будемо визначати класи, похідні від Form, які містять віртуальні функції Draw( ). При такому підході main() -функцію можемо написати зразу, не чекаючи опису конкретних класів з функціями , що малюють фігури:

main( ) {int i=0;

while(i<100&& picture[i]!=0) {picture[i]->Draw(); i+=1}}

Все, основна програма написана. А далі можемо писати собі похідні класи, скільки завгодно:

class Circle:public Form

{public:virtual void Draw( ){//тіло}}; class Line:public Form

{public:virtual void Draw( ){//тіло}};

Тоді виділяємо пам’ять,

picture[0]=new Line; picture[1]=new Line; picture[2]=new Circle;

і програма готова !

8.10.2 Чисті віртуальні функції. Абстрактні класи.

Чиста вiртуальна функцiя-член - це прототип функцiї, для якого не потрiбне визначення в протоколi класу.Cинтаксично чиста віртуальна функція задається так:

virtual<тип><iм'я>(сигнатура)=0;

Клас, який мiстить чисті вiртуальнi функцiї називається абстрактним.

Приклад:

class AbstractClass { public:

virtual void f1(void);

virtual void f2(void)=0;//...};

Специфiка абстрактного класу полягає в тому, що вiн не може мати екземплярiв. При оголошенні AbstractClass my; буде помилка на етапі компіляції. Для того, щоб використовувати абстрактні класи, необхідно визначити похідний від нього клас, в якому чиста віртуальна функція оголошується як звичайна віртуальна функція. Причому в цьому випадку вона повинна мати тіло. Розглянемо, наприклад, такий клас:

class MyClass:public AbstractClass{

266

public:

virtual void f2(void);//...} void MyClass::f2(void){\\тіло}

Звернемо увагу на те, що у класi MyClass f2() є вже звичайною вiртуальною функцiєю, отже повинне бути її визначення (в даному випадку-за межами формального опису класу). Тодi, очевидно, можемо описати екземпляр класу MyClass.

Чиста вiртуальна функцiя може вiльно використовуватись в протоколi абстрактного класу.

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

#include<stdio.h>

#include<iostream.h>

#include<stdlib.h>

#include<string.h> class Element;

typedef Element* PElem; typedef PElem* PPElem; class Set;

typedef Set* PSet; class Set{ private:

int max; //-кiлькiсть елементiв int index;

PPElem set; protected:

virtual int CompareElem(PElem P1,PElem P2)=0; public:

Set(int n) {max=n;index=0; set=new PElem[n];} virtual ~Set() {delete[]set;}

void AddElem(PElem p); int HasElem(PElem p); };

void Set::AddElem(PElem p) {if (set==NULL) {cout<<"\n Error"; exit(1);}

if (index>=max){

cout<<"\n Error,Set limit excluded"; exit(1);}

267

set [index]=p; ++index;}

int Set::HasElem(PElem p) {if (set==NULL) return 0; for(int i=0;i<index;i++)

if (CompareElem(p,set[i])==0) return 1;return 0;}

Запишемо визначений клас Set у файл Set.h:

#include"set.h" class Element{ private:

char* sp; public:

Element(const char*s) {sp=strdup(s);} virtual~Element ( ){delete sp;}

virtual const char* GetString(void) {return sp;}

};

class Myset:public Set{ protected:

virtual int CompareElem(PElem p1,PElem p2)

{

return strcmp(p1->GetString(),p2->GetString()); };

public:

Myset(int n):Set(n){} };

void Test(const char* s,PSet setp) { Element testElem(s); if(setp->HasElem(& testElem)) cout <<"yes";

else cout<<"No";} main( )

{Myset t(4);

t.AddElem(new Element("Sep")); t.AddElem(new Element("Jan")); t.AddElem(new Element("Feb")); t.AddElem(new Element("Mar")); Test("Jan",&t);

return 0;}

268

Переваги використання абстрактних класів:

-Модуль Set можна скомпiлювати ранiше i зберегти в бiблiотецi класу;

-Очевидно, що клас Set може використовувати iншi програми, причому повторно компiлювати файл Set не потрiбно;

-При проектуваннi абстрактних класiв бажано розмiщувати в них кiлька вiртуальних функцiй членiв на випадок, коли вони можуть бути корисними;

-Вiртуальнi деструктори.

8.10.3 Віртуальні деструктори.

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

Розглянемо приклад: class Base{

private: char* sp1; public:

Base(const char* s) {sp1=strdup(s);} virtual ~Base( ) {delete sp1;} };

class Derived::public Base{ private:

char* sp2; public:

Derived(const char* s1,const char* s2):Base(s1) {sp2=strdup(s2);}

virtual ~Derived( ) {delete sp2;}}; Base* pbase;

pbase=new Derived("string1","string2"); delete pbase; // s1-знищився,s2-залишився.

Якщо в даному прикладi деструктори не були б вiртуальними, то при виходi з областi видимостi об'єкта класу Derived, на який вказує вказiвник pbase, викличеться лище деструктор класу Base, тобто копiя рядка string2 залишиться в пам'ятi. Якщо ж деструктори оголошенi як вiртуальнi, то в данiй ситуацiї буде викликаний спочатку де-

269

структор похiдного класу, а потiм i деструктор базового.

8.10.4 Посилання як засіб для реалізації поліморфізму

Для реалiзацiї полiморфiзму в С++ крiм вказiвників можна використовувати і посилання. Розглянемо наступний приклад:

#include "form.h" main

{circle c1; Line l1,l2;

shape & pic0=c1 shape & pic1=l1; shape & pic2=l2; pic0.Draw ( ); pic1.Draw ( ); pic2.Draw ( );}

В цьому прикладі кожен виклик функції Draw() призводить до малювання відповідної фігури.

При передачi параметрiв вiртуальним функцiям може виникнути помилка. Виникає питання: коли здійснюється контроль помилок при передачі параметрів? Адже конкретна реалізація віртуальної функції зв’язується з об’єктом на етапі виконання програми. Що стосується С++, то контроль помилок при передачi параметрiв вiртуальним функцiям здiйснюється на етапi компiляцiї , а не виконання програми, тому що сигнатури вiртуальних функцiй повиннi жорстко спiвпадати.

8.10.5 Дещо про механізм віртуальних функцій

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

classname* object; object->message(15);

то компiлятором він перетворюється в такий: classname_message(object ,15);

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

1. ТВФ будується компiлятором автоматично для кожного класу, який має вiртуальнi функцiї i мiстить адреси вiртуальних функцiй,

270

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