Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторные работы по ООП.doc
Скачиваний:
2
Добавлен:
01.05.2025
Размер:
613 Кб
Скачать
☆

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

  1. В чем заключается наследование одного класса другому? В чем разница в организации

наследования полей и методов?

  1. Удачной ли является иерархия классов, при которой некоторый класс Х является производным от большого количества классов с большим числом полей в каждом ( AB  C … X)? Какая существует альтернатива наследованию?

  2. Каким образом производится управление доступом к унаследованным компонентам производных классов? Есть ли в представленном фрагменте программы ошибки?

class Base {

int y;

public:

void setx(int n){x=n;}

void showx() const{ cout <<x<<endl;}

};

class Derived: public Base {

int x;

public:

void sety(int n){y=n;}

void show_sum() const{ cout<<x+y<<endl; }

void show() const {cout<<y<<endl; }

};

  1. Есть ли ошибки в нижеследующих объявлениях:

Shape sh; // класс Shape абстрактный

Shape *psh;

Shape *psh1=new Shape();

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

Цель работы.

Ознакомление с механизмом позднего связывания виртуальных функций.

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

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

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

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

Очень часто класс, содержащей виртуальный метод, называют полиморфным классом. Главная особенность заключается в том, что полиморфные классы допускают обработку объектов, тип которых неизвестен во время компиляции. Если в производном классе виртуальный метод не будет переопределен, то при вызове будет найден метод с таким именем вверх по иерархии классов (т.е. в базовом классе). Схема использования виртуальных функций приведена ниже.

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;

cout<< d1.fb(); //Позднее связывание не работает

//Правильный вызов

Based *pb=0;

pb=new Derived1;

cout<<pb->fb(); // возвращает 5

delete pb;

pb=new Derived2;

cout<<pb->fb(); // возвращает 7

delete pb;

pb=new Derived3;

cout<<pb->fb(); // возвращает 0

if(pb) delete pb;

}

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

Здесь он применяется в программе, работающей с объектами двух разных классов T1 и T2, которые являются производными классами одного базового класса Shape.

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

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

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

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

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

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

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

#include "stdafx.h"

#include <iostream>

using namespace std;

class Point{

public:int x,y;

};

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

public:

Point *arc;

char ID;

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

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

virtual void CreateShape()=0;

virtual ~Shape(){}

};

class Rectangle : public Shape {

public:

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

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

/* методы*/

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

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

~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;

else 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;

}

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

typeid(*p).name(), где p=указатель на базовый класс.

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

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