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

osn_progr_final

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

доступних в цьому класi;

2. Кожний екземпляр класу мiстить скритий вказiвник на його ТВФ;

3. Компiлятор автоматично вставляє в початок конструктора кла-

су

фрагмент коду, який iнiцiалiзує вказiвник на ТВФ кожного класу;

4. Для будь-якої iєрархiї класiв адреса деякої вiртуальної функцiї має одне i теж змiщення в ТВФ кожного класу;

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

Приклад:

class Parent {int value; public:

virtual int method1(float r); virtual void method2(void); virtual float method3(char*s);}; class child1:public Parent

{public:

virtual void method2(void);} class child2:public child1

{public:

virtual float method3(char*s);}

Для цього прикладу ТВФ можна уявити так:

// клас Parent: virtual_table1-> parent::method1 parent::method2 parent::method3

//для Child1: virtual_table2->

parent::method1

child1::method2

parent::method3

//Беремо до уваги однаковість зміщення. //для Child2:

virtual_table3=> parent::method1 child1::method2 child2::method3

271

Тоді виклик виду: child2 *c;

c->method3(string");

компiлятор перетворює в наступний:

(*(c=>virtual_table3[2]))(c,"string");

8.10.6 Віртуальні базові класи

8.10.6.1 Ієрархії класів та наслідування

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

class A {protected: int data; public:

void func( void){//тіло}}; class B: public A

{//...} class C: public A

{//...}

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

main() {D d;

d.func();}

Зразу ж виникає питання з точки зору синтаксису: яка функція func() викликається - унаслідувана класом В чи класом С ? Формально тут виникає розглянутий вище конфлікт імен. Бачимо, що у випадку, коли один клас унаслiдується iншим класом " кiлька разiв " (через множинне наслiдування) можуть виникнути негативні моменти. Такі помилки виявляються на етапі компіляції ? У прикладi клас A унаслiдується в D 2 рази, будучи базовим для класiв В i С. Очевидно, що "неприємностi" можуть виникнути і у тому випадку, коли класи В та С мають конструктори, якi iнiцiалiзуть поле data. Тодi клас D також повинен мати конструктор, хоча б для того, щоб викликати конструктори базових класiв. Але при цьому виникне неоднозначнiсть при iнiцiалiзацiї поля data.

Визначимо ще один клас

272

class E: public A, public D {\\ . . .}

У цьому випадку також на етапі компіляції буде виявлена помилка:

A is inaccessible becouse also in D (А недоступний, оскільки він вже в

D).

Для виходу з таких ситуацій використовуються віртуальні базові класи.

8.10.6.2 Віртуальні базові класи

Синтаксично для оголошення базового класу віртуальним достатньо в базовому списку вказати ключове слово virtual перед атрибутом доступу:

class<ім’я похідного класу>: virtual <атрибут доступу> <ім’я базового класу> , . . .

Семантично така конструкція вказує компілятору, що у випадку багатократного унаслідування береться до уваги лише одна “копія” базового класу. При цьому ліквідується небезпека повторного унаслідування. Ключове слово virtual повинне бути присутнім у всіх випадках, де можливе повторне унаслідування базового класу. Якщо в нашому прикладі оголосити базовий клас А віртуальним, то ніяких негативних моментів, пов’язаних з повторним унаслідуванням не буде:

class A {protected: int data; public:

void func( void){//тіло}}; class B: virtual public A

{//...};

class C: virtual public A {//...};

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

class E

:virtual public A, public D

{//...}; main()

{D d; d.func();}

Бачимо, що в описi класiв В i С присутнє слово virtual. При цьому

273

ситуацiя змiниться таким чином, що якщо клас А унаслiдується в кількох класах, то вiн дублюватись не буде . Розглянемо ще один приклад:

class Company { private: char*name; public:

company(const char* s) {name=strdup(s);} void Display(void) {cout<<name<<'\n'}

};

class CocaCola:virtual public Company { public: CocaCola(void):Company("CocaCola") { }

};

class CocaColaUkr:virtual public Company, virtual public CocaCola{

public:

CocaColaUkr(void):Company("CocaColaUkr") {//...}

};

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

Допишемо до попереднього прикладу клас Pepsi: class Pepsi:public Company{ public:

Pepsi( ):Company("Pepsi"){ }}; class Corporation:public CocaCola, public Pepsi {//...};

Тут також буде помилка. Pepsi та CocaCola виводяться з company(хоч в CocaCola company є віртуальним). Щоб помилки не було, клас Company повинен бути оголошеним віртуальним в Pepsi.

Опишемо тепер клас Corporation таким чином: class Corporatin:public CocaCola, public CocaColaUkr{ }

У цьому випадку помилка відсутня.

8.10.6.3 Виклик конструкторів та віртуальні базові класи.

1.Вiртуальнi базовi класи(ВБК) iнiцiалiзуються (викликається void-конструктор) перед будь-якими не ВБК в тому порядку, в якому вони зустрiчаються в графi наслiдування.

2. Якщо ВБК має хоча б один конструктор, то вiн повинен мати voidконструктор. З похiдного класу не можна прямо викликати не

274

void-конструктор ВБК . Приклад:

class Domastic_animal {protected : int size; public:

Domastic_animal(void)

{cout <<"\n void constructor for Domastic_animal

\n";

size=0;}

Domastic_animal (int asize)

{

cout <<"\n multiple constructor for Domastic_animal \n";

size=asize;}

virtual void print(void) {cout<<"The size="<<size<<'\n';}

};

class Cat:public virtual Domastic_animal {public:

Cat(void)

{cout<<"\n void constructor for Cat\n";} Cat(int aSize)

{cout<<"\n multiple constructor for Cat\n"; size=aSize;}

void print(void)

{cout<<"The Cat size\n"; Domastic_animal::print( );

}

};

class Tomcat:public virtual Domastic_animal

{//аналогiчно Cat public:

Tomcat(void)

{cout<<"\n void constructor for Tomcat\n";} Tomcat(int aSize)

{cout<<"\n multiple constructor for

Tomcat\n";

size=aSize;} void print(void)

{cout<<"The Tomcat size\n"; Domastic_animal::print( );

}

275

};

class Kitten:public Cat,public Tomcat {public:

Kitten(int aSize)

{cout<< "\n multiple constructor for Kitten\n"; size=aSize;}

void print (void)

{//аналог функції в класі Cat}; cout<<"The Kitten size\n"; Domastic_animal::print( );

}

}; main( )

{Cat aCat(60); Kitten aKitten(25);

Domastic_animal& myCat=aCat; Domastic_animal & myKitten=aKitten; myCat.print( );

myKitten.print( );}

Надрукується:

1.void constructor for Domastic_animal

2.multiple constructor for Cat

3.void constructor for Domastic_animal

4.void constructor for Сat

5.void constructor for Tomcat

6.multiple constructor for Kitten.

The Cat size

The size=60

The Kitten size

The size=25

ЗАВДАННЯ

1.Протестувати клас form, дописавши відповідні функції draw().

2.Визначити ряд функцій в класі Літак як віртуальні (наприклад , функцію що відповідає за рух літаків та ін.)

3.Описати базовий клас Фрукт , що містить віртуальну функцію, яка видає деяке повідомлення. Описати ряд класів, похідних від нього, що містять віртуальні функції друку повідомлень про назву відповідного фрукта ( яблуко, груша , тощо). Створити однозв’язний список таких “фруктових” об’єктів та надрукувати їх.

4.Написати програму створення абстрактного синтаксичного дерева

276

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

Наприклад, вираз 5+12*4 може бути представлений так:

+

*

4

 

_

12

 

 

5

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

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

5.Протестувати приклад з класом Domastic_animal (розглянути випадки , коли Domastic_animal є віртуальним базовим класом та невіртуальним).

6.Протестувати приклад з класом Domastic_animal , видаливши з базового класу void-конструктор.

8.11 ДРУЗІ

Інкапсуляція даних - одна з основних переваг ООП. Проте в С++ існує можливість обійти правила інкапсуляції, тобто доступу до різних частин опису класу.

8.11.1Дружні класи.

Вкласі можна оголосити інший клас як дружній. Один клас (в якому оголошується друг) дає можливість іншому класу(другу) мати доступ до всіх закритих та захищених членів першого класу. Як відомо, відкриті члени завжди доступні. Тому немає необхідності оголошувати один клас другом іншого, щоб дати йому доступ до відкритих членів останнього. Механізм дружніх класів використовується у випадках, коли для двох класів, не пов’язаних “родинним” відношенням, необхідний доступ до закритих та захищених секцій класів. Нехай оголошений такий клас:

class Aclass{ private: double value; public:

Aclass() {v=3.14159;}};

Нехай оголошено ще один клас: class Bclass{ private:

277

Aclass anobject; public:

void ShowValue(void) {cout <<anobject.value;}};

Поле value об”єкта anobject типу Aclass є закритим в класі Bclass. Тому в наведеному вище прикладі в функції ShowValue буде помилка. Вийти з цієї ситуації можна, якщо оголосити клас Bclass другом класу Aclass. Для цього використовується ключове слово friend:

class Aclass{

friend class Bclass; private:

double value; public:

Aclass() {value=3.14159;}};

Оголошення дружнього класу може міститись будь-де в протоколі опису класу (не обов’язково на початку формального опису після “{”). При оголошенні дружніх класів потрібно дотримуватись деяких правил:

1.В класі повинні бути перераховані всі його друзі.

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

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

4.Похідні від дружніх класів не є дружніми.

5.Синтаксично похідний клас може бути другом іншого класу. Результат -аналогічний.

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

friend class Two; private:

int x; protected:

void doublex(void){x*=x;} public:

One(){x=100;} One(int n){x=n;}

};

278

class Two{ private:

One oneobject; public:

void ShowValues(void);};

void Two:: ShowValues(void)

{

cout<<“\n before oneobject.x=“<< oneobject.x; aone.doublex();

cout<<“\n after oneobject.x=“<< oneobject.x<<“\n”;;

}

main()

{Two twoobject; twoobject.ShowValues(); return 0;}

Як бачимо в прикладі, функція ShowValues класу Two має доступ до поля x об’єкта oneobject та функції doublex() незважаючи на те, що вони є закритими членами класу One.

8.11.2 Дружні функції.

Крім дружніх класів в С++ є дружні функції. Синтаксично для оголошення дружньої до класу функції необхідно в протоколі класу помістити оголошення виду:

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

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

# include<iostream.h> class Two;

class One{

friend void Show(One &c1,Two &c2); private:

char *s1; public:

One(){s1=“testing”;}; class Two{

279

friend void Show (One &c1,Two &c2); private:

char *s2; public:

Two(){s2=“one, two,three”;}; main()

{ One c1; Two c2; Show(c1,c2); return 0;}

void Show(One &c1,Two &c2)

{cout<<c1.s1<<c2.s2<<“\n”;}

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

Як вже відмічалось вище, дружня функція також може бути членом класу.

Синтаксично оголошення її реалізується з використанням операції розширення області видимості:

friend <тип результату>< ім’я класу>::<ім’я функції-члена>(сигнатура);

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

#include <iostream.h> class One;

class Two{

friend void Show (One &c1,Two &c2); private:

char *s2; public:

Two(){s2=“one, two,three”;} void Show(One &c1);

};

class One {

friend void Two::Show(One &c1); private:

char *s1; public:

One(){s1=“testing”;}

};

main()

{

280

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