<< Предыдущая Оглавление Следующая >>


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

Описанные классы CLine, CRect и CEllipse могут быть использованы для хранения и отображения на экране соответствующих графических примитивов. Предположим, что в программе создается три графических объекта: линия, прямоугольник и эллипс, с некоторыми заданными координатами и свойствами. В этом случае алгоритм хранения и отображения объектов может быть представлен следующим образом.

Листинг 6.1. Пример работы с классами CLine, CRect и CEllipse

#include

#define TOTAL_OBJ 3
#define MAX_SIZE 1024

typedef enum type {OBJ_LINE, OBJ_RECT, OBJ_ELLIPSE} TYPE;

class CLine;
class CRect;
class CEllipse;

class CObj
{
protected:
CObj() {}
~CObj() {}

public:
TYPE type_obj;
};

class CPos : public CObj
{
protected:
CPos() {}
CPos(int x1, int y1, int x2, int y2) {SetParam(x1,y1,x2,y2);}
~CPos() {}

public:
void SetParam(int x1, int y1, int x2, int y2)
{
if(x1 >= 0 && x1 <= MAX_SIZE) sp_x = x1;
if(y1 >= 0 && y1 <= MAX_SIZE) sp_y = y1;
if(x2 >= 0 && x2 <= MAX_SIZE) ep_x = x2;
if(y2 >= 0 && y2 <= MAX_SIZE) ep_y = y2;
}

friend CLine;
friend CRect;
friend CEllipse;

private:
int sp_x, sp_y;
int ep_x, ep_y;
};

class CProp
{
protected:
CProp() {}
CProp(int w, int clr) {SetProperty(w,clr);}
~CProp() {}

public:
void SetProperty(int w, int clr)
{
if(w >= 0 && w <= 5) width = w;
if(clr >= 0 && clr <= 15) color = clr;
}

friend CLine;
friend CRect;
friend CEllipse;

private:
int width, color;
};

class CLine : public CPos, public CProp
{
public:
CLine() : CPos(), CProp() {type_obj = OBJ_LINE;}
CLine(int x1,int y1, int x2, int y2, int w, int clr) :
CPos(x1,y1,x2,y2), CProp(w,clr)
{type_obj = OBJ_LINE;}

~CLine() {printf("Удаление объекта линия\n");}

void Draw()
{
printf("Рисование линии с координатами (%d, %d) и (%d, %d)\n", sp_x, sp_y, ep_x, ep_y);
}
};

class CRect : public CPos, public CProp
{
public:
CRect() : CPos(), CProp() {type_obj = OBJ_RECT;}
CRect(int x1,int y1, int x2, int y2, int w, int clr) :
CPos(x1,y1,x2,y2), CProp(w,clr)
{type_obj = OBJ_RECT;}
~CRect() {printf("Удаление объекта прямоугольник\n");}

void Draw()
{
printf("Рисование прямоугольника с координатами (%d, %d) и (%d, %d)\n", sp_x, sp_y, ep_x, ep_y);
}
};

class CEllipse : public CPos, public CProp
{
public:
CEllipse() : CPos(), CProp() {type_obj = OBJ_ELLIPSE;}
CEllipse(int x1,int y1, int x2, int y2, int w, int clr) :
CPos(x1,y1,x2,y2), CProp(w,clr)
{type_obj = OBJ_ELLIPSE;}
~CEllipse() {printf("Удаление объекта эллипс\n");}

void Draw()
{
printf("Рисование эллипса с координатами (%d, %d) и (%d, %d)\n", sp_x, sp_y, ep_x, ep_y);
}
};

int main(int argc, char* argv[])
{
CObj* obj[TOTAL_OBJ];

obj[0] = new CLine(10,10,20,20,1,0);
obj[1] = new CRect(30,30,40,40,1,0);
obj[2] = new CEllipse(50,50,60,60,1,0);

for(int i = 0;i < TOTAL_OBJ;i++)
{
switch(obj[i]->type_obj)
{
case OBJ_LINE:{CLine* line = (CLine *)obj[i];
line->Draw();
break;}
case OBJ_RECT:{CRect* rect = (CRect *)obj[i];
rect->Draw();
break;}
case OBJ_ELLIPSE:{CEllipse* ellipse = (CEllipse *)obj[i];
ellipse->Draw();
break;}
}
}

for(i = 0;i < TOTAL_OBJ;i++)
{
switch(obj[i]->type_obj)
{
case OBJ_LINE:delete (CLine *)obj[i];break;
case OBJ_RECT:delete (CRect *)obj[i];break;
case OBJ_ELLIPSE:delete (CEllipse *)obj[i];break;
}
}

return 0;
}

В данном примере был введен новый класс CObj, который является базовым по отношению к CPos. Но, учитывая, что класс CPos является базовым для классов CLine, CRect и CEllipse, то класс CObj оказывается общим для всех них. Таким образом, получаем иерархию объектов, показанную на рис. 6.2.

Рис. 6.2. Иерархия объектов программы листинга 6.1

Благодаря введению класса CObj в иерархию объектов, классы CLine, CRect и CEllipse можно рассматривать с единых позиций: как объект CObj. Это значит, что любой указатель на эти три класса можно присвоить указателю типа CObj без операции приведения типа, и наоборот, имея указатель типа CObj на один из трех объектов, можно его преобразовать в указатель на конкретный объект CLine, CRect или CEllipse. Однако чтобы выполнить такое преобразование, необходима операция приведения типов, которая в представленной программе выглядит следующим образом:

CLine* line = (CLine *)obj[i];

Именно поэтому в цикле for необходимо использовать оператор switch для определения типа объекта. При этом тип объекта хранится в переменной type_obj класса CObj и означиватся в момент создания экземпляров графических объектов в соответствующих конструкторах. Такой прием программирования позволяет всегда иметь достоверную информацию о типе используемого объекта на разных уровнях иерархии классов. Кроме того, введение класса CObj позволяет создавать удобное однотипное хранилище для разнотипных объектов CLine, CRect и CEllipse, которое в программе представлено в виде массива

CObj* obj[TOTAL_OBJ];

В заключение программы выполняется операция удаления созданных объектов. При этом следует удалять объекты графических примитивов, а не объекты типа CObj. Поэтому здесь также организуется цикл for, в котором определяется тип объекта, а затем используется оператор delete для его удаления.

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

Идея виртуальных функций заключается в том, что они могут быть описаны в базовом классе, а их конкретные реализации в производных. Применительно к задаче работы с графическими рбъектами функцию Draw() целесообразно сделать виртуальной, которая будет задана в базовом классе по отношению к классам CLine, CRect и CEllipse, а реализации находиться в дочерних. В связи с этим, имеет смысл ввести еще один новый класс CObjView, отвечающий за рисование графических примитивов и очевидно, он должен находиться в иерархии объектов, как показано на рис. 6.3.

Рис. 6.3. Иерархия объектов с новыми классом CObjView

Для того чтобы задать виртуальную функцию используется ключевое слово virtual, за которым следует прототип функции. Таким образом, получаем следующее описание класса CObjView с виртуальной функцией Draw():

class CObjView : public CPos, public CProp
{
protected:
CObjView() {}
CObjView(int x1,int y1, int x2, int y2, int w, int clr) :
Pos(x1,y1,x2,y2), CProp(w,clr) {}
~CObjView() {}

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

Затем следует описание класса CLine:

class CLine : public CObjView
{
public:
CLine() {type_obj = OBJ_LINE;}
CLine(int x1,int y1, int x2, int y2, int w, int clr) :
CObjView(x1,y1,x2,y2,w,clr)
{type_obj = OBJ_LINE;}

~CLine() {printf("Удаление объекта линия\n");}

void Draw()
{
printf("Рисование линии с координатами (%d, %d) и (%d, %d)\n", sp_x, sp_y, ep_x, ep_y);
}
};

и аналогично описываются классы CRect и CEllipse. Благодаря использованию виртуальной функции, фрагмент программы для отображения графических примитивов можно записать следующим образом:

for(int i = 0;i < TOTAL_OBJ;i++)
{
CObjView* obj_v = (CObjView *)obj[i];
obj_v->Draw();
}

Цикл for здесь содержит всего две строки программы. Причем они останутся неизменными при любом числе графических объектов, что представляет большое удобство для программиста. Последний пример показывает как ООП позволяет заметно упростить текст программы по сравнению со структурным подходом к программированию.


<< Предыдущая Оглавление Следующая >>