Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 400107.doc
Скачиваний:
5
Добавлен:
30.04.2022
Размер:
568.32 Кб
Скачать

3.2.7. Виртуальные функции

Пример 1. Виртуальные функции позволяют порожденным классам опредлять различные версии функций базового класса (переопределить их; не путать с перезагрузкой, рассмотренной в предыдущем параграфе!). Как известно, в С++ функции-члены класса с различным числом и/или типом параметров есть действительно разные функции, даже если они имеют одно имя. Виртуальные функции позволяют переопределять в порожденном классе функции, введенные в базовом классе, даже если число и тип аргументов те же самые. Для виртуальных функций нельзя переопределять тип функций. Виртуальная функция обязательно является членом класса и не может быть статическим членом класса. Конструктор не может быть виртуальным, виртуальный деструктор допускается.

class B

{

virtual void vf1 ();

virtual void vf2 ();

virtual void vf3 ();

void f ();

…….};

class D :public B

{

virtual void vf1 (); //виртуальная функция

void vf2 (int); //невиртуальная функция

//другой список аргументов

char vf3 (); //ошибка, изменен тип функции

};

void exf (); //внешняя функция

main ()

{ D d; //объявление объекта d класса D

B bp=&d; //стандартное преобразование из B* в B*

bp->vf1 (); //вызов D::vf1

bp->vf2 (); // вызов B::vf2

bp->f (); // вызов B::f

}

Спецификатор virtual в описании функции vf () класса D не обязателен: он присваивается автоматически, если в порожденном классе переопределяется функция базового класса (совпадает состав параметров). Если класс D, порожденный B, содержит функцию vf1 () того же самого типа, то vf1 () может быть вызван для объекта d класса D (D::vf1 ()) с использованием указателя или ссылки на В.

Пример 2.

#include <stdio.h>

class Base

{

public:

virtual void virt() // виртуальная функция

{

printf(“Hello from base Virt \n”);

}

void nonVirt() // невиртуальная функция

{

printf(“Hello from Base nonVirt \n”);

} };

class Derived :public Base

{

public:

void virt() //переопределение виртуальной функции

//базового класса

{

printf(“Hello from base Virt \n”);

}

void nonVirt()

{

printf(“Hello from Base nonVirt \n”);

} };

int main(void)

{

Base *bp=new Derived;

bp->virt(); //вызывается virt() класса Derived, потому что

//значением указателя bp является адрес Derived

bp->nonVirt(); //вызывает nonVirt() класса Base, потому что

//указатель bp по определению связан с Base

return 0;

}

Пример 3. Приведем в этом примере одну и ту же программу в двух вариантах (с использованием виртуальных методов и без них).

Без виртуальных методов.

#include<conio.h>

#include<iostream.h>

class Location

{

protected:

int X,Y;

public:

Location(int X1, int Y1);

void Position (int NX, int NY);

};

Location::Location(int X1, int Y1)

{ X=X1;

Y=Y1; }

void Location::Position(int NX, int NY)

{X=NX;

Y=NY; }

class Ch::public Location

{

protected:

char C;

public:

Ch(char C1, int i, int k):Location (i,k) {C=C1};

/*virtual*/ void Show(void);

void SetC(char NC);

void MoveTo(int NX, int NY);

};

void Ch::Show()

{

cprintf(“%c”,C);

getch();

}

void Ch::SetC(char NC)

{ C=NC; }

void Ch::MoveTo(int NX, int NY)

{ X=NX;

Y=NY;

clrscr();

gotoxy(NX,NY);

Show();

}

class St::public Ch

{

protected:

char S;

public:

St(char S1, int j, int i, char C1):Ch(C1,i,j)

{ S=S1; };

/*virtual*/ void Show(void);

{ void SetS(char S2); };

void St::Show()

{ cout<<S; };

void St::SetS(char S2)

{ S=S2; };

int main()

{St MySt(‘a’,10,15,’b’); //A

St *Ptr=&MySt; //B

Ptr->SetS(‘c’); //C

Ptr->MoveTo(5,10); //D

return 0;

}

В строке //A объявляется объект MySt класса St, а это означает запуск конструктрора с заданными значениями параметров. В конструкторе St вызывается конструктор Ch, а из него, в свою очередь, конструктор Location. В результате переменные получат следующие значения:

S=’a’; C=’b’; X=10; Y=15;

В строке //В объявляется указатель Ptr на объект класса St и ему присваивается адрес MyStr. В результате выполнения //C переменная S=’с’. В строке //D предусмотрен вызов метода MoveTo(), которого нет в классе St. Поэтому поск этого метода продолжается в классе-предшественнике Ch и запускает метод Ch::MoveTo. В нем имеется вызов метода Show(), но имеется два метода под этим названием Ch::Show и St::Show. Так как в ходе выполнения программы был выполнен переход в класс Ch, то запускается Ch::Show() и на экране появится символ ‘b’.

С виртуальными методами.

#include<conio.h>

#include<iostream.h>

class Location

{

protected:

int X,Y;

public:

Location(int X1, int Y1);

void Position (int NX, int NY);

};

Location::Location(int X1, int Y1)

{ X=X1;

Y=Y1; }

void Location::Position(int NX, int NY)

{X=NX;

Y=NY; }

class Ch::public Location

{

protected:

char C;

public:

Ch(char C1, int i, int k):Location (i,k) {C=C1};

virtual void Show(void);

void SetC(char NC);

void MoveTo(int NX, int NY);

};

void Ch::Show()

{

cprintf(“%c”,C);

getch();

}

void Ch::SetC(char NC)

{ C=NC;

void Ch::MoveTo(int NX, int NY)

{ X=NX;

Y=NY;

clrscr();

gotoxy(NX,NY);

Show();

}

class St::public Ch

{

protected:

char S;

public:

St(char S1, int j, int i, char C1):Ch(C1,i,j)

{ S=S1; };

virtual void Show(void);

{ void SetS(char S2); };

void St::Show()

{ cout<<S; };

void St::SetS(char S2)

{ S=S2; };

int main()

{St MySt(‘a’,10,15,’b’);

St *Ptr=&MySt;

Ptr->SetS(‘c’);

Ptr->MoveTo(5,10);

return 0;

}

Программа выполняется аналогично предыдущей, за исключением метода MoveTo. Благодаря виртуальности вызывается St::Show() и на экране появится символ ‘c’.

Пример 4. Модифицируем пример, приведенный в п. 3.2. Если внимательно посмотреть на функцию Point::MoveTo и Circle::MoveTo, то не трудно видеть, что они совпадают, разница только в том, какие функции Hide() и Show() они вызывают (для объектов класса Point или Circle). Использование виртуальных функций позволяет решить эту проблему и исключить функцию Circle::MoveTo. Аналогичный пример на Turbo Pascal дан в п. 3.4. Для чего функциям Show() и Hide() в классах Point и Circle добавим атрибут virtual и исключим функцию MoveTo из класса Circle. Предоставим это сделать читателю самостоятельно.