Виртуальные функции в реальной жизни


Дата добавления: 2014-01-11; просмотров: 5; лекция была полезна: 0 студентам(у); не полезна: 0 студентам(у).
Опубликованный материал нарушает авторские права? сообщите нам...

ПРИМЕР

//можно присвоить адрес cObject //указателю на базовый класс p:

A *p;

//Объявить указателю на базовый класс p типа A*

p= &cObject;

// Присвоить p адрес сObject

Несмотря на то что указатель p типа А*, он может ссылаться на объект типа С, т.к. этот класс выведен из А.

 

Правило, возможно, будет понятнее вам, если вы будете думать о С, как об особом виде объекта А (так, золотая рыбка – особая разновидность рыб, но она все же остается рыбой).

 

эта взаимосвязь указателей работает только в одном направлении.

Объект типа С –особый вид объекта А, но объект типа А не является особым видом объектов В и С.

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

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

Например,

//класс А может объявлет //виртуальную функцию vf():

class A {

public:

virtual void vf();

};

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

Она отличается от обычной функции-члена лишь добавлением ключевого слова virtual.

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

Виртуальные функции м.б. открытыми, защищенными или закрытыми членами класса.

//В классе В, выведенном из А, также //м. объявить виртуальную функцию, //названую опять-таки vf():

class B: public A {

public:

virtual void vf();

// Замещающая виртуальная функция

// в В замещает виртуальную функцию

//с тем же именем в А

};

Когда в классе, подобном В, определяется виртуальная функция, имеющая одинаковое имя с виртуальной функцией класса-предка, такая функция называется замещающей.

 

//В классе С также м. определить //замещающую функцию:

class C: public B {

public:

virtual void vf();

};

В данном случае употреблять ключевое слово Virtual не обязательно, поскольку с находится в конце линии наследования.

Конечно, если еще один класс D будет выведен из класса С и в D потребуется заместит функцию vf(), то в с эта функция должна быть определена с ключевым словом Virtual.

Вернемся к указателю p типа А*, который ссылается на объект cobject типа С* внимательно присмотритесь к оператору, вызывающему виртуальную функцию vf() для объекта на который ссылается p:

p->vf();

Указатель p может хранить адрес объекта типа А, В или С.

во время выполнения этот оператор вызовет виртуальную функцию vf(), принадлежащую классу А, если p ссылается на объект типа А, оператор вызовет функцию vf(), принадлежащую классу В, если p ссылается на объект типа А

один и тот же оператор вызывает функцию класса адресуемого объекта – действие, определяемое во время выполнения.

Еще один классический пример поможет уяснить их практическое значение в реальном мере программирования.

Вообразите, что вы пишете графическую программу и хотите разработать различные классы для таких фигур, как круги, квадраты и линии. Поскольку все эти классы взаимосвязаны (все они - фигуры), //сначала вы создаете базовый класс //shape следующим образом:

class Shape {

public:

// …. Разнообразные члены

virtual void Draw();

};

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

Конечно, вам известно, что необходимо где-то хранить коллекцию объектов Shape, вероятно, в массиве указателей (предположим, он объявлен глобальным, и все указатели инициализированы нулевым значением):

Shape *picture[100];

//Массив указателей на объекты Shape

//массив picture м. хранить адреса //100 объектов Shape.

//Каждый элемент массива – //указатель типа shape*, который, м. //ссылаться на объект типа shape или //любого производного от него //класса.

Поскольку в классе shape определена виртуальная функция Draw(), м. запрограммировать цикл, вызывающий Draw() для указателей из массива picture:

int i = 0;

while (i< 100 && picture[i] ! = 0) {

picture[i]->Draw();

// Вызвать виртуальную функцию Draw()

i++;

}

//код, рисующий картинку до того, //как у вас появилась хотя бы одна //существующая фигура для //прорисовки!

 

этот код не требует точного задания типов данных объектов, на которые ссылаются указатели picture, требуется только чтобы эти объекты были производными от shape. Объекты определяют во время выполнения, какую функцию Draw() следует вызвать.

Предположим, что классы Circle (круг) и Line (линия) – потомки класса Shape:

class Circle: public Shape {

public:

virtual void Draw();

};

class line: public shape {

public:

virtual void Draw();

};

также необходимо запрограммировать замещающие функции Draw в классах Circle и line для отрисовки соответствующих фигур.

При выполнении программы рисующий цикл вызывает необходимые виртуальные функции, исходя из типа адресуемого объекта. Например, вы можете в массив picture фигуры, подобные следующим:

picture[0] = new Circle;

picture[1] = new Circle;

picture[2] = new Line;

picture[3] = new Circle;

picture[4] = new Line;

Или же вы можете читать объекты shape из файла, лежащего на диске, или добавлять новые фигуры в массив такими, такими их нарисует на экране пользователь. Очевидно, в подобных случаях невозможно предугадать, какие виде объектов будет содержать массив picture, но все это не имеет не какого значения для цикла отрисовки, который способен нарисовать любой объект класса, производного от класса shape.