Class Z: public X, public Y

{

protected;

int z;

public:

z(int i, int j); /* конструктор с параметром*/

~z(void); /* деструктор */

void make_z(void);

void show(void);

};

X::x(int i)

{

x=i;

cout<<” конструктор X \n”;

}

x::~x(void){ cout<<” деструктор X \n”;}

void x::show(void) { cout<<” X=”<<x<<endl;}

Y::y(int i)

{

y=j;

cout<<” конструктор Y \n”;

}

y::~y(void){ cout<<” деструктор Y \n”;}

void y::show(void){ cout<<” Y=”<<y<<endl;}

/* конструктор класса Z передает значение своих параметров */

z::z(int i, in j):Y(j), X(i) { cout<<” конструктор Z \n”;}

z::~x(void){ cout<<” деструктор Z \n”;}

void Z::make_z(void){ z=x*y;}

void Z::show(void){ cout<<z<<” =”<<x<<”*”<<y<<endl;}

Main(void)

{

z zobj(3, 5); /* создание и инициализация объекта*/

zobj.make_z();

zobj.show();

zobj.x::show();

zobj.y::show(); /* осуществление вызова функции из разных классов*/

zobj.put_x(7);/* задание новых значений переменных */

zobj.put_x(9);

zobj.make_z();

zobj.show();

zobj.x::show();

zobj.y::show();

return 0;

}


 


Виртуальное наследование

При множественном наследовании базовый класс не может быть задан в производном классе более одного раза. На практике возникает ситуация, когда производный класс будет содержать несколько копий одного базового класса. Чтобы избежать многократного включения в производный класс компонент базового класса, используется виртуальное наследование.

При виртуальном наследовании производный класс описывается так:

сlass <имя производного класса>:

virtual <вид наследования> <имя базового класса>

{...};

Включение в производный класс полей базового класса осуществляется один раз, а их инициализация происходит в производном классе, который не является прямым потомком базового класса. Вызов конструкторов при этом происходит в следующем порядке:

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

2. конструкторы базовых классов, в порядке их перечисления при объявлении

3. конструкторы объектных полей

4. конструктор производного класса

Деструкторы вызываются в обратном порядке.

Виртуально наследованный класс должен содержать конструктор без параметров. Он активизируется при выполнении конструкторов классов, являющихся прямыми потомками виртуально наследованного класса.

Пример 1

Class fixed

{

protected:

int fix;

public:

fixed(void) /* конструктор без параметров */

{ cout<<” вызов конструктора \n”;}

fixed(int fix)::fix(fix) /* конструктор с параметром */

{ cout<<” вызов конструктора fixed int \n”;}

};

class derived_1: virtual public fixed /* виртуальное наследование */

{

public:

int one;

derived_1(void) { cout<<”Вызов конструктора 1 \n”;};

};

class derived_2: virtual public fixed /* виртуальное наследование */

{

public:

int two;

derived_2(void) { cout<<” Вызов конструктора 2 \n”;};

};

class derived: public derived_1, public derived_2 /* объявление производного класса непрямого потомка */

{

public:

derived(void) {cout<< “вызов конструктора derived” <<endl;}

derived(int fix);

fixed(fix) { cout<<”вызов конструктора derived int“<<endl;}

void out() { cout<<”Fix = ”<< fix; }

 


 


Глава 7. Полиморфизм

Полиморфизм

Существует 2 вида полиморфизма: простой полиморфизм (статический), базирующийся на механизме раннего связывания, и сложный полиморфизм (динамический), использующий механизм позднего связывания. Простой полиморфизм поддерживается языком C++ на этапе компиляции (раннее связывание) и реализуется с помощью механизма переопределения функции. Эти полиморфные функции в C++ называются переопределенными. В соответствии с общими правилами они могут различаться типом возвращаемого параметра и сигнатурой (количеством и типом передаваемых параметров).

Динамический полиморфизм – выбор функции производится по используемому при выводе имени, типу вызываемого объекта и типу используемый параметров. Если объявленный тип не совпадает с типом использованного объекта, тип этого объекта идентифицируется и проверяется во время исполнения программы, что позволяет осуществить правильный выбор соответствующей функции. Это и называется динамическим полиморфизмом (поздним связыванием). Динамическое связывание приводит к небольшим издержкам памяти и времени выполнения программы, поэтому в C++ его используют только в случае явного указания, посредством ключевого слова virtual. Динамическое связывание используется путем объявления виртуальной функции. Виртуальная функция может реализовываться алгоритмами, которые, как правило, отличаются между собой. Если коды функции различны, то механизм виртуальности не включается. При использовании виртуальной функции, каждый аспект виртуальной функции, вызываемой из базового класса, определяется на этапе выполнения программы, когда известно для какого объекта вызван метод: базового класса или производного. Если вызов функции осуществляется через указатель на базовый класс, нужный аспект функции вызывается в зависимости от того, адрес объекта какого класса будет содержать указатель.

 

Пример 1

Class Window

{

public:

Window(const Point &upper.Dest, const Point &size, const string &title);

~Window();

void move(const Point &new_upper_left);

Point upper_left() const;

Point lower_right() const;

virtual void change_size(const Point fnew_size);

//Если новый размер point меньше минимального, сократить до этого минимального размера

int move cursor(contst Point &where);

Point cursor() const;

Display char character()const;

string Tile() const;

void add(Display char c);

void add(const string &str);

void clear();

void scroll_up();

void scroll_down();

private:

//данные для Window

};

/* Функция collapse, которая использует только общие для всех типов окон функции, что обеспечивает возможность её применения для окон любого типа. */

void collapse(Window &w)      

{

w.clear();

w.change_size(Point(5,5));

//метод изменения размера change.size

}

main(int argc, char *argv[])

{

window w(Point(10,6), Point(30,6), AWIndow);

shell_Window sw(Point(3, [1]), Point(10, 10), "A shell_window")

collapse(w);

collapse(sw);

return 0;

}

Функция, объявленная виртуальной, остается таковой, сколько бы производных классов не образовывалось. Виртуальная функция обязательно должна быть компонентой некоторого класса. Она может быть объявлена дружественной другому классу, но не может быть статической.


 


Виртуальные методы

Раннее связывание

Работа с объектами чаще всего производится через указатели. Указателю на базовый класс можно присвоить значение адреса объекта любого производного класса.

Пример 1

// Описывается указатель на базовый класс:

monstr *p:

// указатель ссылается наобъект производного класса:

p = new daemon;

 

Вызов методов объекта происходит в соответствии с типом указателя, а не фактическим типом объекта, на который он ссылается, поэтому при выполнении оператора, например, p->draw(1. 1, 1, 1); будет вызван метод класса monstr, а не класса daemon, поскольку ссылки на методы разрешаются во время компоновки программы. Этот процесс называется ранним связыванием или статическим полиморфизмом. Чтобы вызвать метод класса daemon, можно использовать явное преобразование типа указателя: (daemon * p)->draw(l, 1, 1, 1);

Это не всегда возможно, поскольку в разное время указатель может ссылаться на объекты разных классов иерархии, и во время компиляции программы конкретный класс может быть неизвестен. В качестве примера можно привести функцию, параметром которой является указатель на объект базового класса. На его место во время выполнения программы может быть передан указатель на любой производный класс. Другой пример — связный список указателей на различные объекты иерархии, с которым требуется работать единообразно.



Позднее связывание

В C++ реализован механизм позднего связывания, когда разрешение ссылок на метод происходит на этапе выполнения программы в зависимости от конкретного типа объекта, вызвавшего метод. Этот механизм реализован с помощью виртуальных методов и рассмотрен в следующем разделе.

Для определения виртуального метода используется спецификатор virtual.

Пример 2

virtual void draw(int x, int y, int scale, int position);

Рассмотрим правила описания и использования виртуальных методов.

· Если в базовом классе метод определен как виртуальный, метод, определенный в производном классе с тем же именем и набором параметров, автоматически становится виртуальным, а с отличающимся набором параметров — обычным.

· Виртуальные методы наследуются, то есть переопределять их в производном классе требуется только при необходимости задать отличающиеся действия. Права доступа при переопределении изменить нельзя.

· Если виртуальный метод переопределен в производном классе, объекты этого класса могут получить доступ к методу базового класса с помощью операции доступа к области видимости.

· Виртуальный метод не может объявляться с модификатором static, но может быть объявлен как дружественный.

· Если в классе вводится описание виртуального метода, он должен быть определен хотя бы как чисто виртуальный. Чисто виртуальный метод содержит признак = 0 вместо тела.

Пример 3

virtual void f(int) = 0;

Чисто виртуальный метод должен переопределяться в производном классе (возможно, опять как чисто виртуальный).

Если определить метод draw в классе monstr как виртуальный, решение о том, ме­тод какого класса вызвать, будет приниматься в зависимости от типа объекта, на который ссылается указатель:

Пример 4

monstr *r,*p;

r = new monstr;

//

Создается объект

Класса monstr

р = new daemon;

//

Создается объект

Класса daemon

r->draw(1, 1, 1, 1);   //

Вызывается метод

Monstr::draw

p->draw(1, 1, 1, 1);   //

Вызывается метод

Daemon::draw

p-> monstr::draw(1, 1, 1, 1); // Обход механизма виртуальных методов

Если объект класса daemon будет вызывать метод draw не непосредственно, а косвенно (то есть из другого метода, определенного в классе monstr), будет вызван метод draw класса daemon.

Итак, виртуальным называется метод, ссылка на который разрешается на этапе выполнения программы (перевод красивого английского слова virtual — в данном значении всего-навсего «фактический», то есть ссылка разрешается по факту вызова).




Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: