Раздел «Язык Си».OOP-Virt:

Виртуальные функции. Полиморфизм. Абстрактные классы.

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

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

При этом для "обычных" методов (функций) выполняется следующее правило: вызывается метод именно того класса, к типу которого произошло преобразование.

Например.

#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();
    }
  }
}

REFACTOR Задачи

REFACTOR Задача 1

К предыдущему примеру задаче добавить класс Customer - наследник от Worker . Переопределить для него виртуальную функцию act() - забирает яблоки из корзины и кладет сколько-нибудь денег в общий с Worker кошелек.

В саду N персон. Из файла вводятся числа Первое число N - количество персон. Затем вводятся N чисел (положительные, отрицательные и ноль) - количество яблок. Если число 0 - это ворона, если число ≥ 0 - это количество яблок, которое собирает рабочий, если < 0 - это количество яблок, которое покупает покупатель.

Написать программу-модель, которая показывает сколько яблок в корзине каждый час.

Все, кто в саду, должны создаваться оператором new и их указатели должны запоминаться в массив.

REFACTOR Задача 2

Для класса Image создать абстрактный класс Fig. Написать и запрограммировать классы- наследники:

  1. Rec - прямоугольник
  2. Line - линия
  3. Tri - треугольник
  4. 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); 
}

Передача указателя на нестатические методы как параметра.

Слегка усложненная задача Усложним задачу про рабочих, ворон и покупателей. Пусть каждый из них выполниет свои действия в зависимости от времени дня. В своей модели мы будем придерживаться следующего масштаба: 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);

}

REFACTOR Задача 3

Написать интерфейс всех классов для решения "слегка усложненной задачи".

REFACTOR Задача 4

Реализовать все классы для "слегка усложненной задачи" и промоделировать работу сада за 3 дня. Данные на всех получать из файла. Продумать формат файла.

-- TatyanaOvsyannikova2011 - 08 Apr 2016