Виртуальные функции. Полиморфизм. Абстрактные классы.
Виртуальные функции.
Когда мы пишем функцию в классе, С++ запоминает к какому классу функция относится. Когда мы вызываем эту функцию через объект, С++ знает какого класса этот объект, и вызывает функцию именно этого класса. Например.
#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
Абстрактные классы
В нашем примере объекты классов ничего не могут делать кроме печати. А поведение у них совсем разное: рабочие яблоки собирают, а вороны воруют. То есть в классе 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
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
Задачи
Задача 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(); } } // Здесь нужно написать код }
Задача 2
К предыдущей задаче добавить класс Customer - наследник от Worker как в прошлых уроках.
Переопределить для него виртуальную функцию act().
В саду N персон. С клавиатуры вводятся числа.
Первое число N - количество персон. Затем вводятся N чисел (положительные, отрицательные и ноль) - количество яблок. Если число 0 - это ворона, если число ≥ 0 - это количество яблок, которое собирает рабочий, если < 0 - это количество яблок, которое покупает покупатель.
Написать программу-модель, которая показывает сколько яблок в корзине каждый час.
Все, кто в саду, должны создаваться оператором new и их указатели должны запоминаться в массив.
Задача 3
Для класса Image создать абстрактный класс Fig. Написать и запрограммировать классы- наследники:
- Rec - прямоугольник
- Line - линия
- Tri - треугольник
- Circle - круг
//...... // Абстрактный класс 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); }