Мегаобучалка Главная | О нас | Обратная связь


Виртуальные методы. Полиморфизм



2019-11-13 399 Обсуждений (0)
Виртуальные методы. Полиморфизм 0.00 из 5.00 0 оценок




Полиморфизм — это способность объекта использовать методы производного класса, который не существует на момент создания базового.

Виртуальные методы переопределяются в классах-наследниках. Например, рассмотрим класс Shape, описывающий некую абстрактную геометрическую фигуру. Свойства x,y,sizeзадают координаты её центра и размер. Метод dist() определяет расстояние от центра фигуры до начала координат. Площадь фигуры для каждого класса-наследника будет вычисляться по-своему. Поэтому метод areaопределим как виртуальный - virtual.

#include <math.h> //описаны sqrt и M_PI

classShape{

public:

   double x, y, size;//центр фигуры и её размер

   double dist() {return sqrt(x*x+y*y);}

   virtual double area() {}

};

class Circle: public Shape {

public:

    // x, y, size, dist() - унаследованы "как есть"

   double area() {

       return M_PI * size * size;

    }

};

class Square: public Shape {

public:

   // x,y,size, dist() - унаследованы "как есть"

   double area() {

       return size*size;

   }

};

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

Примеры классов учебных исполнителей

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

Исполнителя характеризуют:

1. Среда (или обстановка) — это "место обитания" исполнителя. Например, для исполнителя «Робот» из школьного учебника среда — это бесконечное клеточное поле.

2. Система команд. Каждый исполнитель может выполнять команды только из некоторого строго заданного списка — системы команд исполнителя. Для каждой команды должны быть заданы условия применимости (в каких состояниях среды может быть выполнена команда) и описаны результаты выполнения команды.

После вызова команды исполнитель совершает соответствующее элементарное действие.

Задача №184.  Исполнитель «Удвоитель»

Исполнитель «Удвоитель», известный нам из школьного курса информатики, описывается        следующим образом:

1. Исполнитель хранит единственное число. Начальное состояние исполнителя равно 1.

2. Допускается использование двух арифметических операторов «+» и «*», причем первый увеличивает число исполнителя на 1, а второй в два раза.

Например, набор команд +,+,*,+,*,+ произвёдёт изменение состояния исполнителя 2, 3, 6, 7, 14, 15.

Реализуйте исполнитель «удвоитель» как класс с унарными операторами + и *. Добавьте два дополнительных метода restart() для перезапуска исполнителя и print() для вывода его состояния.

Решение

Очевидно, нам для класса doubler(удвоитель) придётся перегружать префиксные операции + и *, т.к. постфиксных таких в C++ нет. Дополнительная сложность задачи заключается в том, что если мы хотим использовать по несколько команд подряд в одной строке, придётся перегружать еще префиксный инкремент, т.к. 2 встреченных подряд знака + будут интерпретироваться именно как инкремент, о чем сообщит компилятор. В остальном решение достаточно тривиально.

#include <iostream>

class doubler{

public:

unsigned long long value; //состояние исполнителя

doubler() {value = 1;}; //конструктор по умолчанию

doubler& operator+() {value++; return *this;};

doubler& operator*() {value *= 2; return *this;};

doubler& operator++() {value += 2; return *this;};

void restart(){value = 1;};

void print(){std::cout << "-->" << value << "\n";}

};

int main() {    //программа-демо для исполнителя «Удвоитель»

doubler x;  //value=1

+x;         //+1

+x;         //+1

*x;         //*2

++*+*x;     //выполняется, начиная от x *2,+1,*2,+1,+1

x.print();  //выведет -->28

x.restart();     //перезапуск

(+*++x).print(); //выведет -->7 

return 0;

}

Таким образом, мы научились использовать и программировать автомат, реализующий функции исполнителя «Удвоитель», непосредственно в коде C++. Рассмотрим чуть более интересный пример с исполнителем «Водолей» для решения задач на переливание, с которым могли познакомиться на уроках информатики и математики.

Задача №185.  Исполнитель «Водолей»

Исполнитель «Водолей» описывает состояние N сосудов с водой (N≤10).

Исполнитель «Водолей» может переливать жидкость из одной емкости в другую вплоть до заполнения первой или опустошения второй

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

Реализовать исполнитель в виде класса, описывающего систему и её сосуды. Для исполнителя команда задаётся парой чисел

Решение

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

structtvessel{                  //вспомогательнаяструктура

unsigned int current,maximum;

};

class aquarius{                 //основнойкласс

public:

static const int maxcount=10; //максимальноечислососудов

unsignedintcount;           //всегоиспользуетсясосудов

boolisclosed;             //системазамкнутая?

tvesselvessel[maxcount+1]; //описание сосудов в системе

aquarius(unsignedintn,...){ //конструкторкласса

count = (&n)[0];          //1 аргумент – числососудов

isclosed = (&n)[1];  //2 аргумент – наличие неогр.

for(inti=1;i<=n;i++){     //остальные аргументы - сосуды

vessel[i].current=(&n)[i*2]; //изначальновсосудеi

       vessel[i].maximum=(&n)[i*2+1]; //максимальновсосудеi

}

};

void print(){               //выводсостояниясистемы

   if(!isclosed)cout<<"(0)неогр. ";

   for(int i=1;i<=count;i++)

       cout<<'('<<i<<')'<<vessel[i].current<<

'/'<<vessel[i].maximum<<" ";

   cout<<"\n";

}

void cmd(unsigned int from, unsigned int to){ //переливание

   if((from!=to)&&(to<=count)&&(from<=count)){ //изfrom вto

       if(!from&& !isclosed){ //изнеограниченнойёмкости

vessel[to].current=vessel[to].maximum;

           return;

       }

       if(!to&& !isclosed){ //внеограниченнуюёмкость

vessel[from].current=0;

           return;

       }

       int water=min(vessel[from].current, //другиеслучаи

               vessel[to].maximum-vessel[to].current);

       vessel[from].current -= water;

       vessel[to].current += water;

}

}

};

Продемонстрируем использование класса на следующей задаче.

Имеется большая бочка с водой и две емкости с 5 и 3 литрами воды. Отмерить ровно 4 литра. Результаты записать в виде команд исполнителя.

Программа может выглядеть так:

int main() {

aquariusw(2,false,5,5,3,3); //незамкнутая система

int a,b,turn = 0;                //с 2 полными сосудами 5 и 3 л

do{

   w.print();

   cout<<++turn<<" ход>";

   cin >> a >> b;

   w.cmd(a,b);

}while (a || b);      //выход из цикла при вводе двух нулей

cout<<"Завершено после " << --turn << " ходов\n";

return 0;

}

Результат работы программы отображается следующим образом:

(0)неогр. (1)5/5 (2)3/3 

1 ход>2 0

(0)неогр. (1)5/5 (2)0/3 

2 ход>1 2

(0)неогр. (1)2/5 (2)3/3 

3 ход>2 0

(0)неогр. (1)2/5 (2)0/3 

4 ход>1 2

(0)неогр. (1)0/5 (2)2/3 

5 ход>0 1

(0)неогр. (1)5/5 (2)2/3 

6 ход>1 2

(0)неогр. (1)4/5 (2)3/3 

7 ход>0 0

Завершено после 6 ходов

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

Двое друзей хотят поделить ровно пополам мёд из полной 12-литровой банки. При этом у них есть еще 8-литровая и 5-литровая банки. Запишите решение в виде команд исполнителя.

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

Для изменения параметров системы нам нужно только переписать первую строку после main().

aquarius w(3,true,12,12,0,8,0,5);

Так будет описана замкнутая система (без неограниченной ёмкости) с тремя сосудами 12 л - полный, 8 л и 5 л - пустые. Результат работы программы выглядит следующим образом:

(1)12/12 (2)0/8 (3)0/5 

1 ход>1 2

(1)4/12 (2)8/8 (3)0/5 

2 ход>2 3

(1)4/12 (2)3/8 (3)5/5 

3 ход>3 1

(1)9/12 (2)3/8 (3)0/5 

4 ход>2 3

(1)9/12 (2)0/8 (3)3/5 

5 ход>1 2

(1)1/12 (2)8/8 (3)3/5 

6 ход>2 3

(1)1/12 (2)6/8 (3)5/5 

7 ход>3 1

(1)6/12 (2)6/8 (3)0/5 

8 ход>0 0

Завершено после 7 ходов

Задача №186.  Исполнитель «Колдун»

Колдун готовит зелье, которое необходимо варить ровно Nминут. При этом у него есть K песочных часов на H1, H2, …, HKминут. Реализуйте исполнителя «Колдун», описывающего систему песочных часов следующим образом:

       T: H1up/H1down, H2up/H2down, … , HKup/HKdown

T – количество минут, прошедших от начала всех действий с песочными часами. Для часов с номером iзапись Hiup/Hidownозначает, что сверху в часах песка на Hiupминут, а снизу на Hidownминут.

Система команд исполнителя (СКИ) включает лишь одну команду – текстовую строку с номерами песочных часов, которые нужно перевернуть в данную минуту. Время на поворот часов не тратится. Команда 0 завершает работу исполнителя.

Решение

Опишем состояние каждых песочных часов с помощью вспомогательной структуры с двумя беззнаковыми целыми величинами upи down. Конструктор класса реализуем в виде функции с переменным числом аргументов, где первый параметр – количество песочных часов в системе исполнителя «Колдун». Метод rotate, переворачивающий часы с заданным номером, реализуется просто – нужно всего лишь обменять значения upи downдля соответствующих часов.

Метод printбез аргументов достаточно тривиален.

Наибольший интерес для нас представляет реализация метода nextбез аргументов. Очевидно, что система является конечным автоматом, для которого Tможет принимать отнюдь не любые значения. Например, для системы с 3 пятиминутными песочными часамина минуте T:

       T: 2/3, 5/0, 0/5

Следующим определённым состоянием системы будет через 2 минуты:

       T+2: 0/5, 3/2, 0/5

Тогда в первых часах сверху весь оставшийся песок высыплется, а во втором останется на 3 минуты. Состояние третьих часов не изменится. Соответственно, необходимо выбрать mintime - наименьшее ненулевое сверху из всех часов. Затем от всех часов, где есть песок в верхней части, пересыпать по mintime из верхней части в нижнюю.

#include<iostream>

#defineHG_COUNT 10

usingnamespacestd;

classwizard{

public:

unsignedinttimer,count; //прошломинут, количествочасов

struct{              //описаниекаждыхчасов

   unsigned int up,down; //пескасверхуиснизу

} hourglass[HG_COUNT+1];

wizard(intn,...){ //конструктор с переменным количеством

count=n;        //аргументов, n–число песочных часов

timer=0;        //начинаем всегда в момент 0 минут

for(int i=1;i<=n;i++){

       hourglass[i].down=(&n)[i];

       hourglass[i].up=0;

   }

}

void rotate(unsigned int num){ //поворотпесочныхчасов

   swap(hourglass[num].up,hourglass[num].down);

}

voidprint(){              //выводсостояниясистемы

cout<<"Прошло "<<timer<< " минЧасы: ";

for(int i=1;i<=count;i++){

       cout<<"("<<i<<")"<<hourglass[i].up+hourglass[i].down

<<" мин="<<hourglass[i].up<<'/'<<hourglass[i].down<<" ";

}

cout<<"\n";

}

voidnext(){     //получение следующего состояния системы

unsigned int mintime=4e9;

for(inti=1;i<=count;i++)//ищемнаименьшеененулевоесверху

if((hourglass[i].up<mintime)&&hourglass[i].up)

           mintime=hourglass[i].up;

if(mintime==4e9)return;//вверхнейчастичасовпесканет

for(int i=1;i<=count;i++)

if(hourglass[i].up){//только для часов, где ещё есть песок

hourglass[i].up -= mintime;

           hourglass[i].down += mintime;

}

timer+=mintime;

}

};

Продемонстрируем использование класса для следующей задачи:

У колдуна есть песочные часы на 7 минут и на 11 минут. Как с их помощью приготовить зелье, которое варится ровно 15 минут?

Сама программа, использующая класс wizard, описывается следующим образом:

int main() {    //программа-демо для исполнителя «Колдун»

wizard zzz(2,7,11); //2 песочных часов на 7 и 11 минут

unsigned int hglass; //номера песочных часов

do{

   zzz.print(); //система перед изменением

   do{

       cin>>hglass;       //читаем строку с номерами часов

       zzz.rotate(hglass); //все переворачиваем

   }while(cin.get()!='\n'); //до перевода строки

   zzz.print(); //система после изменения

   zzz.next(); //запускаем таймер песочных часов

} while (hglass); //продолжаем до ввода 0

cout<<"завершено после "<<zzz.timer<<" минут"<<endl;

return 0;

}

Результат решения задачи будет отображён так:

Прошло 0 мин Часы: (1)7 мин=0/7 (2)11 мин=0/11 

1 2

Прошло 0 мин Часы: (1)7 мин=7/0 (2)11 мин=11/0 

Прошло 7 мин Часы: (1)7 мин=0/7 (2)11 мин=4/7 

1

Прошло 7 мин Часы: (1)7 мин=7/0 (2)11 мин=4/7 

Прошло 11 мин Часы: (1)7 мин=3/4 (2)11 мин=0/11 

1

Прошло 11 мин Часы: (1)7 мин=4/3 (2)11 мин=0/11 

Прошло 15 мин Часы: (1)7 мин=0/7 (2)11 мин=0/11 

0

Прошло 15 мин Часы: (1)7 мин=0/7 (2)11 мин=0/11 

завершено после 15 минут

С помощью класса wizardдля учебного исполнителя «Колдун» можно также решать задачи, где интервал времени отмеряется не от момента старта процесса приготовления зелья, а непосредственно на часах.

Пример

Имеются песочные часы на 8 минут и три минуты. Можно ли с их помощью изготовить эликсир, который варится ровно 7 минут?

Заменим первую строку в главной функции mainдля инициализации исполнителя

wizard zzz(2,8,3); //2 песочных часов на 8 и 3 минуты

Результат работы программы будет отображён в виде

Прошло 0 мин Часы: (1)8 мин=0/8 (2)3 мин=0/3 

1 2

Прошло 0 мин Часы: (1)8 мин=8/0 (2)3 мин=3/0 

Прошло 3 мин Часы: (1)8 мин=5/3 (2)3 мин=0/3 

2

Прошло 3 мин Часы: (1)8 мин=5/3 (2)3 мин=3/0 

Прошло 6 мин Часы: (1)8 мин=2/6 (2)3 мин=0/3 

2

Прошло 6 мин Часы: (1)8 мин=2/6 (2)3 мин=3/0 

Прошло 8 мин Часы: (1)8 мин=0/8 (2)3 мин=1/2 

1

Прошло 8 мин Часы: (1)8 мин=8/0 (2)3 мин=1/2 

Прошло 9 мин Часы: (1)8 мин=7/1 (2)3 мин=0/3 

0

Прошло 9 мин Часы: (1)8 мин=7/1 (2)3 мин=0/3

Очевидно, исполнитель с 9-ой минуты может начинать готовить эликсир, ориентируясь по первым часам, на которых осталось ровно 7 минут

Попробуйте описать в созданной нами среде и СКИ (системе команд исполнителя) «Колдун», а затем самостоятельно решить задачу, когда с помощью 2-х песочных часов на 7 мин и 4 мин требуется отмерить ровно 9 мин времени.

Синглтон и контейнер

Для более глубокого понимания объектно-ориентированного программирования рассмотрим пример таких классов, как синглтон и контейнер.

Синглтон

Синглтон (singleton) – шаблон проектирования, гарантирующий, что в приложении будет единственный экземпляр такого класса.

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

class Singleton

{

private:

static Singleton * p_instance;

// Конструкторы и оператор присваивания недоступны клиентам

Singleton() {}

Singleton( const Singleton& ); 

Singleton& operator=(Singleton&);

public:

static Singleton * getInstance() {

    if(!p_instance)          

       p_instance = new Singleton();

   return p_instance;

}

};

 

Singleton* Singleton::p_instance = 0;

Клиенты запрашивают единственный объект класса через статическую функцию-член getInstance(), которая при первом запросе динамически выделяет память под этот объект и затем возвращает указатель на этот участок памяти. Впоследcтвии клиенты должны сами позаботиться об освобождении памяти при помощи оператора delete.

Последняя особенность является серьезным недостатком классической реализации шаблона Singleton. Так как класс сам контролирует создание единственного объекта, было бы логичным возложить на него ответственность и за разрушение объекта. Этот недостаток отсутствует в реализации Singleton, впервые предложенной Скоттом Мэйерсом.

Singleton Мэйерса

class Singleton

{

private:

Singleton() {}

Singleton(const Singleton&); 

Singleton& operator=(Singleton&);

public:

static Singleton& getInstance() {

   static Singleton instance;

   returninstance;

}   

};

Внутри getInstance() используется статический экземпляр нужного класса. Стандарт языка программирования C++ гарантирует автоматическое уничтожение статических объектов при завершении программы. Статическая функция-член getInstance() возвращает не указатель, а ссылку на этот объект, тем самым, затрудняя возможность ошибочного освобождения памяти клиентами.



2019-11-13 399 Обсуждений (0)
Виртуальные методы. Полиморфизм 0.00 из 5.00 0 оценок









Обсуждение в статье: Виртуальные методы. Полиморфизм

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (399)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.01 сек.)