Раздел «Язык Си».ClassVirt:

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

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

Когда мы пишем функцию в классе, С++ запоминает к какому классу функция относится. Когда мы вызываем эту функцию через объект, С++ знает какого класса этот объект, и вызывает функцию именно этого класса.

Например.

#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!!!

Как только мы преобразовали объект b к классу Person ( и через объект, и через указатель), вызывается функция print() класса 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(). Однако все функции работают по-разному. Это явление называется полиморфизм

Абстрактные классы

В нашем примере объекты классов ничего не могут делать кроме печати. А поведение у них совсем разное: рабочие яблоки собирают, а вороны воруют. То есть в классе Anybody не получается описать функцию act() для описания поведения - объекты Anybody ничего не делают.

Функцию act() в классе Anybody можно объявить, но не реализовывать. Тогда эту функцию обязательно нужно реализовывать для наследников.

Классы, в которых есть хотя бы одна нереализованная функция - абстрактные классы . Абстрактные класс не может иметь объектов. Но указатели на объекты абстрактного класса использовать можно.

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

>./myprog
0 час:
basket: 49
basket: 16
basket: 38

1 час:
basket: 87
basket: 54
basket: 76

2 час:
basket: 125
basket: 92
basket: 114

3 час:
basket: 163
basket: 130
basket: 152

REFACTOR Задачи

REFACTOR Задача 1

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

Например, 4
30
10
0
2

Это означает, что всего 4 песоны. Сначала первый человек собирает 30 яблок, затем второй - 10, затем прилетает ворона и крадет 12 яблок, последний человек собирает 2 яблока.

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

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

// код для классов уже написан

// Пусть указатель на Anybody будет называться pAny
typedef Anybody* pAny;
int main(){
// для чисел с клавиатуры
  int a;
   cin>>a;
// создадим массив указателей на Anybody. a  - размер массива
   pAny *p = new pAny[a];
// 
  for( int i = 0; i<a; i++){
// числа будем считывать в c
    int c;
    cin>>c;
// Если c > 0 - то это рабочий
    if(c>0)
     {
// создаем объект Worker. c - количество яблок
       Worker *w = new Worker(c);
// запомнили указатель на этот объект в массив
       p[i] = w;
// печать рабочего
       w->put();
     }else{
Если с <= 0, то это ворона
// создаем объект Crow.
       Crow *cr = new Crow();
// запомнили указатель на этот объект в массив
       p[i] = cr;
// печать вороны
       cr->put();
     }
  } 
// Здесь нужно написать код
}

REFACTOR Задача 2

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

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

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

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

REFACTOR Задача 3

Для класса 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;
};

class Line:public Fig{
  wxPoint one,sec; // концы отрезка
public:
// Конструктор с указателем на картинку
   Line(Image*);
// Установка концов отрезка
   void setPoints(wxPoint a, wxPoint b);
// Цвет линии
   void setColor(wxColor cl);
// Переопределение виртуальной функции Draw()
   void Draw();
};

// Получаем указатель на картинку
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