Виртуальные функции. Полиморфизм. Абстрактные классы.
Видео
Виртуальные функции.
Объекты классов-наследников могут быть преобразованы к типу родительского класса. Также указатели на объекты классов-наследников могут быть преобразованы к указателям типа родительского класса.
При этом для "обычных" методов (функций) выполняется следующее правило: вызывается метод именно того класса, к типу которого произошло преобразование.
Например.
#include <iostream>
#include <cstdlib>
using namespace std;
class Person{
public:
void print();
};
class Worker:public Person{
public:
void print();
};
void Person::print(){
cout<<" Person!!!"<<endl;
};
void Worker::print(){
cout<<" Worker!!!"<<endl;
};
int main(){
Person a;// персона
Person *p;// укзатель на объект класса Person
Worker b;// рабочий
// печать от персоны
// вызов функции print() класса Person
a.print();
// печать от рабочего
// вызов функции print() класса Worker
b.print();
// преобразование b к классу Person
// и печать. Работает функция
// класса Person
((Person)b).print();
// передача адреса b укзателю на
// объект класса Person
// и печать. Работает функция
// класса Person
p = &b;
p->print();
}
|
>./myprog
Person!!!
Worker!!!
Person!!!
Person!!!
|
Однако часто возникает необходимость как-то опредеить тип исходного класса объекта и вызвать метод именно этого класса независимо от типа указателя на объект.
Для этого используются
виртуальные функции .
Виртуальные функции объявляются виртуальными в классе-родителе. Информация про них запоминается в
таблицу виртуальных функций .
Это позволяет С++ при вызове через указатель использовать функцию именно исходного класса независимо от того к какому типу указателя (в рамках наследования) произошло преобразования.
Например.
#include <iostream>
#include <cstdlib>
#include <fstream>
using namespace std;
// Родительский класс для
// классов Worker и Crow
class Anybody{
int apple;
protected:
static int basket;
public:
Anybody();
Anybody(int);
int getApple();
// функция печати объявлена виртуальной
// она становится виртуальной для всех
// классов-наследников
virtual void put();
};
class Worker:public Anybody{
public:
// у класса Worker не будет
// конструктора по-умолчанию
Worker(int);
// переопределение виртуальной функции
void put();
};
class Crow:public Anybody{
public:
// у класса Crow не будет
// конструктора с парамерами
Crow();
// переопределение виртуальной функции
void put();
};
// Реализация функций родительского класса
Anybody::Anybody(){
apple = rand() %50;
};
Anybody::Anybody(int a){
apple = abs(a) % 50;
};
int Anybody::getApple(){
return apple;
};
void Anybody::put(){
cout<<"Anybody - apple: "<<apple<<endl;
};
// реализация функций класса Worker
// в конструкторе с параметром можно сразу
// указать значение параметра "по-умолчанию"
// тогда, если писать параметр при создании
// объекта, парметр будет равен этому
// значению "по-умолчанию"
Worker::Worker(int a = 49 ):Anybody(a){};
// переопределение виртуальной функции для класса Worker
void Worker::put(){
cout<<"Рабочий: я собираю по "<<getApple()<<" яблок"<<endl;
};
// реализация методо класса Crow
Crow::Crow(){};
// переопределение виртуальной функции для класса Crow
void Crow::put(){
cout<<"Ворона: я ворую по "<<getApple()<<" яблок"<<endl;
};
// Как работают виртуальные функции
int main(){
Anybody a;
// указатель на родительский класс
Anybody *p;
// конструктора "по-умолчанию" нет
// но мы определили занчение обязательного
// параметра "по-умолчанию"
// теперь можно его не указывать
Worker w,
// а здесь указали параметр
Worker w1(22);
Crow cr;
// указателю на Anybody передали адрес a (Anybody)
p = &a;
// печать (вызов put() класса Anybody
p->put();
// указателю на Anybody передали адрес w1 (Worker)
// тип указателя Anybody
p = &w1;
// вызов виртуальной функции "по-указателю"
// вызывается функция класса Worker
p->put();
// указателю на Anybody передали адрес w1 (Crow)
// тип указателя Anybody
p = &cr;
// вызов виртуальной функции "по-указателю"
// вызывается функция класса Crow
p->put();
// Преобразование объекта w к классу Anybody
// и вызов печати
// вызовется функция класса Anybody
((Anybody)w).put();
}
>./myprog
Anybody - apple: 33
Рабочий: я собираю по 22 яблок
Ворона: я ворую по 36 яблок
Anybody - apple: 49
Заметим, что для всех функий печати, у нас одинаковый вызов
p->put(). Однако все функции работают по-разному. Это явление называется
полиморфизм
Абстрактные классы
Обычно при наследовании предполагается что у классов будет значительная общая часть. Но часто нужно чтобы совпадал лишь интерфейс классов. А сами реализованные функции имеют никак с друг другом алгоритмически не связаны.
Для объявления такого интерфейса и предоставления возможности обращаться к виртуальным функциям совершенно непохожих объектов существуют
Абстрактные классы
Классы, в которых есть хотя бы одна нереализованная функция -
абстрактные классы .
В
абстрактных классах объявлются имена и интерфейс функций, но их реализация не предполагается.
Все функции должны быть реализованы в классах - наследниках.
Из-за того что, в абстрактных классах существует хотя бы одна нереализованная функция, эти классы не могут порождать объекты. Но можно использовать указатели на эти классы.
#include <iostream>
#include <cstdlib>
#include <fstream>
using namespace std;
// Абстрактный класс
class Anybody{
int apple;
protected:
static int basket;
public:
Anybody();
Anybody(int);
int getApple();
// не виртуальная функция
void put();
// виртуальная функция, которая не реализуется в этом классе
virtual void act() = 0;
};
class Worker:public Anybody{
public:
Worker(int);
// будет работать как обычная функция
void put();
// обязательно нужно реализовать
// будет работать как виртуальная
void act();
};
class Crow:public Anybody{
public:
Crow();
// будет работать как обычная функция
void put();
// обязательно нужно реализовать
// будет работать как виртуальная
void act();
};
int Anybody::basket = 0;
Anybody::Anybody(){
apple = rand() %50;
};
Anybody::Anybody(int a){
apple = abs(a) % 50;
};
int Anybody::getApple(){
return apple;
};
// просто печатает что в корзине
void Anybody::put(){
cout<<"basket: "<<basket<<endl;
};
Worker::Worker(int a = 49 ):Anybody(a){};
// функция в наследнике просто так же называется как и у
// родителя.
void Worker::put(){
cout<<"Рабочий: я собираю по "<<getApple()<<" яблок"<<endl;
};
// реализация виртуальной функции
void Worker::act(){
basket += getApple();
};
Crow::Crow(){};
// функция в наследнике просто так же называется как и у
// родителя.
void Crow::put(){
cout<<"Ворона: я ворую по "<<getApple()<<" яблок"<<endl;
};
// реализация виртуальной функции
void Crow::act(){
basket -= getApple();
if(basket < 0) basket = 0;
};
int main(){
// указатель на Anybody
// объектов быть не может.
Anybody *p;
// два рабочих
Worker w,w1(22);
// ворона
Crow cr;
// передача адреса указателю на w1
// преобразование к типу Anybody
p = &w;
// вызов виртуальной функции
// работает функция Worker
p->act();
// печать. работает функция Worker
w.put();
// put() - не виртуальная.
// работает функция Anybody
p->put();
p = &w1;
p->act();
w1.put();
p->put();
p = &cr;
p->act();
cr.put();
p->put();
}
>./myprog
Рабочий: я собираю по 49 яблок
basket: 49
Рабочий: я собираю по 22 яблок
basket: 71
Ворона: я ворую по 33 яблок
basket: 38
Можно использовать массив указателей на класс
Anybody. Тогда
main() будет выглядеть совсем просто
int main(){
// Массив указателей на 3 Anybody
Anybody *p[3];
Worker w,w1(22);
Crow cr;
// первый работает рабочий
p[0] = &w;
// второй прилетает ворона
p[1] = &cr;
// третий работает рабочий
p[2] = &w1 ;
// все в саду 4 часа
for(int h = 0; h<4;h++){
cout<<endl<<h<<" час:"<<endl;
// обращаемя к каждому Anybody из массива
// по-очереди
for(int i = 0; i< 3; i++){
// что-то делает
p[i]->act();
// печать
p[i]->put();
}
}
}
Задачи
Задача 1
К предыдущему примеру задаче добавить класс
Customer - наследник от
Worker .
Переопределить для него виртуальную функцию
act() - забирает яблоки из корзины и кладет сколько-нибудь денег в общий с
Worker кошелек.
В саду
N персон. Из файла вводятся числа
Первое число
N - количество персон. Затем вводятся
N чисел (положительные, отрицательные и ноль) - количество яблок. Если число 0 - это ворона, если число ≥ 0 - это количество яблок, которое собирает рабочий, если < 0 - это количество яблок, которое покупает покупатель.
Написать программу-модель, которая показывает сколько яблок в корзине каждый час.
Все, кто в саду, должны создаваться оператором
new и их указатели должны запоминаться в массив.
Передача указателя на нестатические методы как параметра.
Слегка усложненная задача
Усложним задачу про рабочих, ворон и покупателей. Пусть каждый из них выполниет свои действия в зависимости от времени дня. В своей модели мы будем придерживаться следующего масштаба: 1 секунда - один час.
Рабочие начинают работу в саду в 8.00 и уходят в 17.00, вороны прилетаю в 6.00 и улетают в 18.00, покупатели приходят за яблоками с 10.00 и до 17.00. Причем, каждый персонаж выпоняет свои действия с собственной периодичностью.
Таким образом, для каждого мы должны иметь по три
будильника, и будильник должен каким-то образом знать в какой момент какие методы вызывать для объекта.
Рассмотрим пример реализации передачи пармеров на метод класса
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
class Z;
class Al;
// Объявим Fun как указатель на функцию класса Z возвращающую void и
// не имеющую параметров
// оператор ::* объявляет указатель на функцию класса
typedef void (Z::*Fun)();
// абстрактный класс Z
class Z{
public:
virtual void f1()=0;
virtual void f2()=0;
};
// наследник Z
class Z1:public Z{
public:
void f1();
void f2();
};
// наследник Z
class Z2:public Z{
public:
void f1();
void f2();
};
// Будущий будильник
class Al{
// указатель на объект,
// чьи функции будем вызывать
Z* pz;
public:
// кнструктор
Al(Z*);
// функция класса Al c парамером - указателем на функцию класса Z
void go(Fun);
};
// реализация абстрактных функций
void Z1::f1(){
cout<<"11AAAAA\n";
};
void Z1::f2(){
cout<<"12RRRzzz\n";
};
void Z2::f1(){
cout<<"21AAAAA\n";
};
void Z2::f2(){
cout<<"22RRR\n";
};
// конструктор будущего будильника
Al::Al(Z* a){
pz = a;
};
// реализация функции, вызывающей функцию
// класса Z по указателю на нее
void Al::go(Fun fptr){
// функции мы определили виртуальными,
// значит вызовутся функции соответствующих классов
(pz->*fptr)();
};
int main(){
Z1 az;
Z2 ax;
Al d1(&az);
Al d2(&ax);
d1.go(&Z::f2);
d1.go(&Z::f2);
d2.go(&Z::f1);
d2.go(&Z::f2);
}
Задача 2
Написать интерфейс всех классов для решения "слегка усложненной задачи".
Задача 3
Реализовать все классы для "слегка усложненной задачи" и промоделировать работу сада за 3 дня.
Данные на всех получать из файла. Продумать формат файла.
Задача 4
Нужно работать с картинками.
Необходимо иметь класс, который:
- рисует прямоугольник
- рисует треугольник
- рисует окружность
- рисует линию
- рисует точку
- сохраняет картинку в графический файл
У нас есть реализованный раньше класс
Image
#include <wx/wx.h>
#include <wx/image.h>
#include <fstream>
#include <iostream>
#include <string.h>
using namespace std;
// Класс Image - наследник "системного" класса wxImage
// wxImage "умеет" работать с картинками
class Image:public wxImage{
wxColor pen; // цвет линий
wxColor fill; // цвет красить
int w,h; // размер картинки
public:
// Создать картину из файла (имя - в строке wxString)
Image(string);
// создать пустую картину, с фоном back
Image(int w, int h, wxColor back);
// деструктор
~Image();
// рисование линии по двум точкам
void DrawLine(wxPoint one, wxPoint sec);
// установить цвет линий
void setPen(wxColor a);
// установить цвет заливки
void setFill(wxColor a);
// закрасить точку p цветом a
void ColorPoint(wxPoint p, wxColor a);
// закрасить прямоугольник цветом а, внутри которого точка p
void FillRec(wxPoint p, wxColor a);
// сохранить картинку в файл. Имя файла в строке
void saveToFile(string);
};
// Реализация
Image::Image( string file):wxImage(wxString(file.c_str(), wxConvUTF8),wxBITMAP_TYPE_PNG){
w = GetWidth();
h = GetHeight();
};
Image::Image(int w, int h, wxColor back):wxImage(w,h){
wxInitAllImageHandlers();
wxPoint one(1,1),sec(w-1,h-1);
wxRect rec(one,sec);
this->SetRGB(rec, back.Red(),back.Green(),back.Blue());
};
void Image::setPen(wxColor a){
pen = a;
};
void Image::setFill(wxColor a){
fill = a;
};
void Image::ColorPoint(wxPoint p, wxColor c){
wxRect rc(p,p);
this->SetRGB(rc,c.Red(),c.Green(),c.Blue());
};
void Image::FillRec(wxPoint p, wxColor c){
wxColor place(GetRed(p.x,p.y),GetGreen(p.x,p.y),GetBlue(p.x,p.y));
wxColor check(GetRed(p.x,p.y - 1),GetGreen(p.x,p.y - 1),GetBlue(p.x,p.y - 1));
int yh,yl,xl,xp;
int x,y;
// cout << (place != c)<<endl;
for(y = p.y-1 ; place == check; y--){
check.Set(GetRed(p.x,y), GetGreen(p.x,y), GetBlue(p.x,y));
};
yh = y +1;
check.Set(GetRed(p.x,p.y + 1), GetGreen(p.x,p.y + 1), GetBlue(p.x,p.y + 1));
for(y = p.y + 1 ; place == check; y++){
check.Set(GetRed(p.x,y), GetGreen(p.x,y), GetBlue(p.x,y));
};
yl = y - 1;
check.Set(GetRed(p.x - 1,p.y ), GetGreen(p.x - 1,p.y), GetBlue(p.x - 1,p.y));
for(x = p.x - 1 ; place == check; x--){
check.Set(GetRed(x,p.y), GetGreen(x,p.y), GetBlue(x,p.y));
};
xl = x + 1;
check.Set(GetRed(p.x + 1,p.y), GetGreen(p.x + 1,p.y), GetBlue(p.x + 1,p.y));
for( x = p.x + 1 ; place == check; x++){
check.Set( GetRed(x,p.y), GetGreen(x,p.y), GetBlue(x,p.y));
};
xp = x - 1;
wxRect rec(wxPoint(xl,yh), wxPoint(xp,yl));
this->SetRGB(rec, c.Red(), c.Green(), c.Blue());
};
void Image::DrawLine(wxPoint one, wxPoint sec){
int b,f;
if (one.x < sec.x){
b = one.x;
f = sec.x;
}else{
f = one.x;
b = sec.x;
}
for(int x = b; x < f ; x++){
int y=((sec.y - one.y ) * x + (sec.x * one.y - one.x * sec.y )) / (sec.x - one.x );
// cout<<"x="<<x<<" y="<<y<<endl;
wxPoint fr(x,y);
wxRect rec(fr,fr);
this->SetRGB(rec, pen.Red(), pen.Green(), pen.Blue());
}
if (one.y < sec.y){
b = one.y;
f = sec.y;
}else{
f = one.y;
b = sec.y;
}
for(int y = b; y < f ; y++){
int x = ((sec.x - one.x ) * y + (sec.y * one.x - one.y * sec.x )) / (sec.y - one.y);
// cout<<"x="<<x<<" y="<<y<<endl;
wxPoint fr(x,y);
wxRect rec(fr,fr);
this->SetRGB(rec, pen.Red(), pen.Green(), pen.Blue());
}
};
void Image::saveToFile(string file){
wxString st1(file.c_str(), wxConvUTF8);
this->SaveFile(st1,wxBITMAP_TYPE_PNG);
};
Image::~Image(){
wxImage::Destroy();
};
// Пример использования
int main(){
// Создать "пустую" желтую картинку размером 200х200
Image im(200,200,wxColor(255,255,0));
// имя файла картинки
string s = "f1.png";
// установить цвет линий - белый
im.setPen(wxColor(255,255,255));
// нарисовать линию от точки (20,20) до точки (60,20)
im.DrawLine(wxPoint(20,20),wxPoint(60,20));
im.DrawLine(wxPoint(20,20),wxPoint(20,60));
im.DrawLine(wxPoint(20,60),wxPoint(60,60));
im.DrawLine(wxPoint(60,20),wxPoint(60,60));
// Закрасить прямоугольник, в котором точка (30,30)
im.FillRec(wxPoint(30,30), wxColor(255,0,0));
// Закрасть одну точку внутри прямоугольника
im.ColorPoint(wxPoint(40,40),wxColor(0,255,0));
// нарисовать "косую" линию
im.DrawLine(wxPoint(20,20),wxPoint(30,60));
// сохранить картинку в файл
im.saveToFile(s);
}
Для указаний цветов здесь используются объекты класса
wxColor. Для указания точек на картинке используются объекты класса
wxPoint
Написать класс - наследник
Image. Нужно добавить функции:
- рисования прямоугольника
- рисования треугольника
- рисования наклонного креста
- рисования окружности
- ** если на картинке только прямоугольники, подсчитывать сколько прямоугольников
Для компиляции такой программы нужно использовать файл
*comp*
Первая команда (ОДИН РАЗ!!)
Теперь каждый раз, когда нужно компилировать.
cpp в названии файла с текстом программы не указывать!!
Задача 3
Для класса
Image создать абстрактный класс
Fig. Написать и запрограммировать классы- наследники:
- Rec - прямоугольник
- Line - линия
- Tri - треугольник
- Circle - круг
Создать несколько объектов, поместить указатели на них (
Fig) в массив и нарисовать их все на черной катринке.
Пример для класса
Line
//......
// Абстрактный класс
class Fig{
wxPoint center;
protected:
// объекту нужен указатель на картинку (на чем будет рисовать)
Image *im;
public:
Fig(Image*);
// Виртуальные функции
virtual void Draw() = 0;
virtual void moveTo(wxPoint)=0;
virtual void Fill(wxColor)=0
};
class Line:public Fig{
wxPoint one,sec; // концы отрезка
public:
// Конструктор с указателем на картинку
Line(Image*);
// Установка концов отрезка
void setPoints(wxPoint a, wxPoint b);
// Цвет линии
void setColor(wxColor cl);
// Переопределение виртуальной функции Draw()
void Draw();
//переопределить самостоятельно
void moveTo(wxPoint);
void Fill(wxColor);
};
// Получаем указатель на картинку
Fig::Fig(Image* m){
im = m;
};
// Конструктор. Передача адреса картинки
Line::Line(Image* a):Fig(a){};
// Установка точек
void Line::setPoints(wxPoint a, wxPoint b){
one = a;
sec = b;
};
// Установка цвета
void Line::setColor(wxColor cl){
im->setPen(cl);
};
// переопределение виртуальной функции
// рисование
void Line::Draw(){
im->DrawLine(one,sec);
};
// Пример использования
int main(){
// Создать "пустую" желтую картинку размером 200х200
Image im(200,200,wxColor(255,255,0));
// Создаем Line. передаем адрес картинки
Line f1(&im);
// установка точек на картинке
f1.setPoints(wxPoint(10,100),wxPoint(190,100));
// установка цвета
f1.setColor(wxColor(255,0,255));
// рисование
f1.Draw();
string s="vpic.png";
im.saveToFile(s);
}
--
TatyanaOvsyannikova2011 - 08 Apr 2016