Лабораторная работа 3. Виртуальные функции. Выбор типа объектов



Цель работы.

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

Основные сведения

Продолжим обсуждение использования  виртуальных функций, начатое в предыдущей работе.  Там инструкции создания объектов  производных классов  записаны явно,  в  тексте программы (см. конструктор  Garage в листинге 2.2); виртуальные функции  определяют поведение  объектов, подстраиваясь под их тип.

В этой работе механизм виртуальных функций используется для создания программ, в которых выбор типа объекта производится во время выполнения программы по запросу пользователя. Это идея паттерна проектирования программ под названием «Фабричный метод» (Faсtory Method) [2].

Еще немного  о виртуальных функциях. Когда говорят об их вызове, обычно имеют в виду т.н. позднее связывание объекта класса и функции. Дело в том, что наличие виртуальной функции в иерархии классов приводит к созданию для каждого класса таблицы, в которую заносятся адреса реализаций виртуальных функций этого класса. Таким образом, адреса разных реализаций одной виртуальной функции иерархии различны. Объект класса при создании получает адрес таблицы своего класса. Указателем, обращающимся ко всем  таблицам иерархии, является указатель на базовый класс. Если при выполнении программы в какой-либо точке  этот  указатель получает адрес объекта одного их производных классов, то он через соответствующее поле объекта обращается к таблице этого класса и вызывает соответствующую реализацию виртуальной функции. Поэтому   конкретная реализация виртуальной функции   определяется  динамически во время выполнения программы. Поэтому же вызов виртуальных функций обходится дороже, чем вызов обычной - выполняется дополнительная операция адресации.

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

Упрощенная схема программы с выбором типа  объектов во время выполнения программы приведена ниже.

Листинг 3.1

class Based {

public:

virtual int fb(){return 0;}

//При наличии в классе виртуальных функций необходим виртуальный деструктор

virtual ~Based(){}

};

class Derived1:public Based {

public:

int fb() {return 5;}

~Derived1(){}

};

class Derived2:public Based {

int fb() {return 7;}

~Derived2(){}

};

class Derived3:public Based {

// fb не определена

};

 

void main()

{

Derived1 d1;в

Based *pb=0;

int id=1;

cout<<"Id?"<<endl;

cin>>id;

switch(id)

{case 1:  pb=new Derived1; break;

case 2: pb=new Derived2; break;

case 3: pb=new Derived3; break;

default: pb=new Derived3;

}

cout<<pb->fb();

delete pb;

}

 

В лабораторной работе должна быть создана программа, случайным образом выбирающая два класса (T1, T2) из семи производных классов и выполняющей некоторые действия над объектами этих двух классов. Базовым является  класс Shape. 

Производные классы должны создавать плоские объекты следующих типов: квадрат, треугольник, прямоугольник, параллелограмм, трапеция, правильный шестиугольник, правильный восьмиугольник (square, triangle, rectangle, parallelogram, trapeze, hexagon, octagon). Для каждого типа фигуры вычисляются площадь, центр тяжести  и другие атрибуты [4], а также должны быть предусмотрены виртуальные методы Вращения (Rotate) и Перемещения (Move). 

Операции над объектами этих классов могут быть такими (функции с 2-мя аргументами Shape* obj1, Shape* obj2):

- сравнить два объекта по площади - Compare;

- определить факт пересечения  объектов – IsIntersect;

- определить факт включения одного объекта в другой – IsInclude.

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

Ниже приводится рекомендуемый вариант программы (упрощенный). В ней из трех фигур выбираются и обрабатываются две  – объекты классов Triangle  и Rectangle.

//Листинг 3.2

#include "stdafx.h"

#include <iostream>

using namespace std;

struct Point{

int x,y;

};

class Shape { // Абстрактный базовый класс

public:

Point *arc;

char ID;

/* Поля и методы */

virtual int GetArea(){return 0;}//Вычисление площади фигуры

virtual void CreateShape()=0;

virtual ~Shape(){}

};

class Rectangle : public Shape {

 public:

Rectangle (){arc=new Point[4]; ID='R';}

void CreateShape(){cout<<"Input Rectangle coordinate points "<<endl;

cin>>arc[0].x; /*вводим  все координаты*/}

/* методы*/

int GetArea(){ /*вычисление конкретной площади*/return 4;}

 ~Rectangle(){delete[] arc;}

};

class Triangle : public Shape {

public:

Triangle (){arc=new Point[3]; ID='T';}

 void CreateShape(){cout<<"Input Triangle coordinate points"<<endl;

cin>>arc[0].x; /*вводим  все координаты*/}

/*.....*/

 int GetArea(){/*вычисление конкретной площади*/return 3;}

~Triangle(){delete[] arc;}

};

class Octagon : public Shape {

public:

Octagon(){arc=new Point[8]; ID='O';}

void CreateShape(){/*...*/}

/* .....*/

int GetArea(){/*вычисление конкретной площади*/return 8;};

~Octagon(){delete[] arc;}

};

class Operation

{ //Класс, инкапсулирующий методы обработки

public:

void Compare(Shape* s1, Shape* s2)

 {// Проверка правильности подбора типов фигур

    if ((s1->ID=='T' && s2->ID=='R')||(s2->ID=='T' && s1->ID=='R'))

cout<<"Correct choice"<<endl;

 else {cout<<"Not such operation"<<endl; return;}

//Вычисления

if (s1->GetArea()> s2->GetArea()) cout<<"Area "<<s1->ID<<" > "<<s2->ID<<endl;

 if (s1->GetArea()==s2->GetArea()) cout<<"Area "<<s1->ID<<" = "<<s2->ID<<endl;

if (s1->GetArea()< s2->GetArea()) cout<<"Area "<<s1->ID<<" < "<<s2->ID<<endl;    

}

  void IsInclude(Shape* s1, Shape* s2)

{/* */}

void IsIntersect(Shape* s1, Shape* s2)

{/* */}

};

 

class FactoryShape{// Класс–фабрика объектов, производных от Shape

public:

Shape* generator()

{ switch(rand() % 3) {

   case 0:

       return new Triangle;

   case 1:

       return new Rectangle;

   case 2:

       return new Octagon;

      /*         */

}

}

};

int _tmain(int argc, _TCHAR* argv[])

{

//Создаем 2 указателя на базовый класс

Shape *p1=0,*p2=0;

//Создаем заданные объекты

FactoryShape f;

cout<<"Input 1-t shape"<<endl;

char cond='y';

while (cond=='y')

{

  if (p1) delete p1;

 p1=f.generator();

 cout<<p1->ID<<endl;

 cout<<"Continue choice?(y/…)"<<endl;

 cin>>cond;

}

p1->CreateShape();

cout<<"Input 2-d shape"<<endl;

cond='y';

while (cond=='y')

{if (p2) delete p2;

 p2=f.generator();

 cout<<p2->ID<<endl;

 cout<<"Continue choice? (y/…)"<<endl;

 cin>>cond;

}

p2->CreateShape();

Operation op;

op.Compare(p1,p2); //Сравниваем площади

  

  if(p1) delete p1;

 if(p2) delete p2;

    

return 0;

}

Замечания.

1. Можно для идентификации класса текущего объекта воспользоваться библиотекой typeinfo (#include <typeinfo>) и его компонентом typeid. Название класса выводится на консоль функцией typeid(*p).name(), где p-указатель на базовый класс.

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

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

Ответы на вопросы, связанные со свойствами геометрических фигур, можно получить в [3,4].

 

Варианты задания

Варианты заданий приведены в следующей таблице:

№ T1                       T2                   

1. Треугольник      Квадрат              

2. Треугольник      Прямоугольник 

3. Треугольник      Параллелограмм

4. Треугольник   Трапеция        

5. Треугольник       Шестиугольник

6. Треугольник       Восьмиугольник

7. Квадрат            Прямоугольник

8. Квадрат                 Параллелограмм

9. Квадрат            Трапеция          

10. Квадрат             Шестиугольник

11. Квадрат             Восьмиугольник

12. Прямоугольник Параллелограмм

13. Прямоугольник    Трапеция             

14. Прямоугольник     Шестиугольник

15. Прямоугольник     Восьмиугольник

16. Параллелограмм   Трапеция

17. Параллелограмм    Шестиугольник

18. Параллелограмм   Восьмиугольник

19. Трапеция                 Шестиугольник

20. Трапеция                 Восьмиугольник

21. Шестиугольник      Восьмиугольник

 

Контрольные вопросы

1. Различают 4 элемента определения функции: тип, имя, список параметров, тело. Какими элементами могут отличаться экземпляры одной виртуальной функции, находящиеся в разных производных классах?

2. Что возвращает switch в листинге 3.1 при case 3?

3. В каких случаях применяется фабрика объектов?

4. Что изменится в поведении программы 3.2, если в базовом классе лишить метод GetArea атрибута virtual?

5. Можно ли в программе 3.2 в каком-либо производном классе не определять метод CreateShape?

 

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

7. В нижеследующей программе выполните правильное обращение к функции speak производного класса, для чего замените одиночные знаки «?» корректными символами или вообще уберите их, а сдвоенные знаки «??» замените именами классов.

class Based {

public: virtual void speak() {cout<<“Hello, Based”<<endl;}

}; //Based

class Derived: public Based {

public: void speak(){cout<<“Hello, Derived”<<endl;}

}; //Derived

void print ( Based* p){p->speak();}

void main()

{ ?? ?d1;

?d1=new ??;

print (?d1);

delete d1;

}

8. Как изменятся операторы в функции main, если после выполнения предыдущего задания изменить определение функции print на следующее

void print ( Based& p){p.speak();}

 

 


Дата добавления: 2018-04-15; просмотров: 948; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!