Лекции по обектно-ориентирано програмиране


Виртуални функции и полиморфизъм



страница16/16
Дата25.07.2016
Размер0.95 Mb.
#6568
ТипЛекции
1   ...   8   9   10   11   12   13   14   15   16

31. Виртуални функции и полиморфизъм.

с помощта на виртуални функции и полиморфизъм, могат да се разработват и реализират програми за обобщена обработка на обекти от всички съществуващи в йерархията класове, като обекти от базовия клас; ако по време на разработката на такава програма трябва да се добави нов клас в йерархията, това може да стане с незначителни изменения на самата програма;


един от начините за специфична обработка на обекти от различни класове е използването на оператор switch; във връзка с това, обаче, възникват различни проблеми – например липса на проверка за типовете, на проверка за всички възможни случаи; освен това, добавянето или изключването на клас води до промяна на съответния оператор switch;

от друга страна, програмистът може да използва механизма на виртуалните функции за автоматично изпълнение на логиката на оператора switch, избягвайки горепосочените проблеми и неудобства;

използването на виртуални функции и полиморфизъм води до създаване на програми, които имат по-малко логически разклонения и по този начин се улеснява проверката и съпровождането на тези програми;
да предположим, че редицата класове за форми, такива като Circle, Triangle, Square и т.н. са производни от базовия клас Shape; всеки клас може да има своя собствена функция Draw; в този случай е удобно при рисуване на всяка форма да можем да извикваме функцията Draw за базовия клас Shape и тогава програмата динамично, т.е. по време на своето изпълнение, да определи коя от функциите Draw на съответния производен клас да се изпълни; за да се създаде такава възможност, трябва функцията Draw да се декларира като виртуална в базовия клас и след това да се предефинира във всеки от производните класове, така че да рисува съответната форма;
една функция се декларира като виртуална с помощта на ключовата дума virtual, записана преди прототипа на функцията в базовия клас; например в базовия клас можем да запишем:

virtual void Draw () const;


ако една функция веднъж е декларирана като виртуална, то тя остава виртуална на всяко по-ниско ниво в йерархията; ако производният клас няма собствена реализация на виртуалната функция, то се използва реализацията, описана в базовия клас; ако функцията Draw от базовия клас е декларираната като virtual и ако след това тя се извика чрез указател от базовия клас, сочещ към обект от производния клас, например

shapePtr -> Draw ();

то програмата динамично, т.е. по време на изпълнение, ще избере съответната функция от производния клас; това се нарича динамично свързване;
предефинирата виртуална функция в производния клас трябва да има същия тип на резултата и същата сигнатура, както виртуалната функция в дефиницията в базовия клас; в противен случай се дава съобщение за синтактична грешка;
ако виртуална функция се извиква чрез обръщение по име и при това се използва операцията за достъп до елемент ‘.’, например

squareObject.Draw();

то извикването се обработва по време на компилация и това се нарича статично свързване;
полиморфизмът е възможност за обектите от различни класове, свързани с помощта на наследяване, да реагират по различен начин при обръщение към една и съща функция елемент; полиморфизмът се реализира с помощта на виртуални функции;
понякога функция елемент, която не е дефинирана като виртуална в базовия клас се предефинира в производния клас;

ако такава функция елемент е извикана чрез указател от базовия клас, то се използва версията на функцията в базовия клас, ако е извикана чрез указател от производния клас, то се използва версията на функцията от производния клас; това не е полиморфно поведение;

например:

имаме базов клас Employee и производен клас HourlyWorker

HourlyWorker h, *hPtr = &h; //дефинираме обект и указател от

//производния клас;

Employee *ePtr = &h; //дефинираме указател от базовия клас
ще напомним, че функцията елемент print е невиртуална, дефинирана в базовия клас и предефинирана в производния клас;

hPtr -> print (); //извиква се функцията елемент print от производния

//клас

ePtr -> print (); //извиква се функцията елемент print от базовия клас


причината, че това не е полиморфно поведение – функциите не са виртуални и имат еднаква сигнатура; ако функцията print беше дефинирана като virtual в базовия клас, то при обръщението

ePtr -> print ();

щеше да се извика функцията елемент print от производния клас;
по време на компилация не е необходимо да се знае типа на обекта, за да се генерира извикване на виртуална функция; по време на изпълнението на програмата, чрез динамично свързване, извикването на виртуална функция елемент се преобразува в извикване на вариант на виртуалната функция от съответния клас;

за целта се използва таблица на виртуалните методи; всеки клас, който съдържа виртуални функции има своя таблица на виртуалните методи; за всяка виртуална функция елемент от класа, таблицата има елемент, съдържащ указател към вариант на виртуалната функция, който се използва с обектите от дадения клас; всеки обект от клас, който съдържа виртуални функции, има указател към таблицата на виртуалните методи на този клас; при динамичното свързване, чрез този указател се отива в таблицата на виртуалните методи и там се намира нужният указател към вариант на виртуалната функция от съответния клас;



32. Абстрактни базови класове.

има случаи, в които е полезно да се дефинират класове, за които програмистът няма да създава обекти; такива класове се наричат абстрактни класове; тъй като те се използват като базови класове в процеса на наследяване, обикновено се наричат абстрактни базови класове; обектите на абстрактните базови класове не могат да бъдат реализирани, т.е. не съществуват обекти от абстрактни базови класове; единственото предназначение на абстрактните базови класове е да могат да предават интерфейс и реализация на производните от тях класове; класове, чиито обекти могат да бъдат реализирани се наричат конкретни класове;

например, можем да имаме абстрактен базов клас TwoDimensionalObject и производни конкретни класове Square, Circle, Triangle и т.н.;

абстрактните базови класове са твърде общи за дефиниране на реални обекти; за това са предназначени конкретните класове – те притежават необходимата определеност и специфика за дефиниране на реални обекти;


един клас става абстрактен чрез дефиниране на една или повече виртуални функции като чисто виртуални; една виртуална функция става чисто виртуална, когато в нейната декларацията тялото е дефинирано като 0, т.е. инициализатор = 0; например:

virtual float earnings () const = 0;

ако един клас е производен от клас с чисто виртуална функция и ако тази чисто виртуална функция не е реализирана в този клас, тогава функцията остава чисто виртуална и производният клас автоматично става абстрактен;
при опит за създаване на обект от абстрактен клас се дава съобщение за синтактична грешка;
абстрактният базов клас определя интерфейса за различни типове обекти в йерархията на класовете; всички обработки в йерархията могат да прилагат един и същи интерфейс, използвайки полиморфизъм – дефинират се указатели от абстрактния базов клас и след това те се използват за полиморфно опериране с обектите на производните конкретни класове;
полиморфизмът е особено ефективен при реализацията на системно програмно осигуряване; например при операционните системи всеки тип физическо устройство може да работи по съвършено различен начин; независимо от това, командите за четене ‘read’ и командите за писане ‘write’ могат да бъдат подобни; например съобщението ‘write’, изпратено на обект драйвер за устройство се интерпретира по различен начин в зависимост от типа на драйвера и от това по какъв начин той управлява съответното устройство; въпреки това, извикването ‘write’ е почти едно и също за различните устройства – при това извикване просто се копират определен брой байтове от паметта в конкретното устройство; обектно-ориентираните операционни системи могат да използват абстрактни базове класове за да се реализира интерфейс, пригоден за драйверите на всички устройства; след това, с помощта на наследяване, тези абстрактни класове могат да образуват производни класове; откритият интерфейс към драйверите на устройствата се предава с помощта на чисто виртуални функции; реализацията на тези виртуални функции в производните класове съответства на конкретните типове драйвери за устройства;

полиморфизмът се използва и при класове итератори – например при обхождане в свързан списък на обекти от различни нива в йерархията; всички указатели в такъв списък могат да бъдат указатели от базовия клас;


конструкторите не могат да бъдат виртуални; типична грешка е дефинирането на конструктор като виртуална функция;
при използването на полиморфизъм за обработка на динамично разпределени обекти може да възникне един проблем – ако обекта се унищожава с явно използване на операцията delete, приложена към указател от базовия клас, сочещ към този обект, то в случая се извиква деструктора на базовия клас за този обект, независимо от типа на обекта; съществува просто разрешение на проблема – деструкторът от базовия клас се дефинира като виртуален и това автоматично прави виртуални деструкторите на производните класове, въпреки че те имат имена, различни от името на деструктора на базовия клас; по този начин операцията delete, приложена към указател от базовия клас, сочещ към обект от производен клас, ще активира деструктора на производния клас; както знаем, когато обект от производен клас се унищожава, след изпълнение на деструктора на производния клас се извиква деструктора на базовия клас за да се унищожи частта на обекта от базовия клас;
ако един клас има виртуални функции, добър стил на програмиране е създаването на виртуален деструктор, дори той да не е необходим;
благодарение на виртуалните функции и полиморфизма, програмистът може да управлява широк спектър от обекти без да знае за тяхните типове; управлението по време на изпълнението на програмата автоматично ще отчита спецификата на тези обекти;

33. Пример за използване на виртуални функции и полиморфизъм.

в примера ще се пресмята заплата на служители, като се отчита техния тип; базовият клас е Employee, производните класове са – клас Boss (с фиксирана заплата), клас CommissionWorker (с базова заплата и комисионен процент), клас PieceWorker (със заплата, пропорционална на изработеното количеството), клас HourlyWorker (със заплата, пропорционална на часовете за работа);

функцията earning се използва за всички служители, но начинът за пресмятане на заплата е различен, затова тя се декларира като виртуална в базовия клас, а след това е реализирана във всеки от производните класове; за да се пресметне заплатата, в програмата се използва указател от базовия клас, който сочи към обект на конкретен служител при извикване на функцията earning;
#include

#include

#include

#include

class Employee {

public:


Employee (const char *, const char *);

~Employee ();

const char *getFirstName () const;

const char *getLastName () const;

virtual float earnings () const = 0;

virtual void print () const = 0;

private:

char *firstName;

char *lastName;

} ;


Employee::Employee (const char *first, const char *last)

{ firstName = new char [strlen (first) + 1];

assert (firstName != NULL);

strcpy (firstName, first);

lastName = new char [strlen (last) + 1];

assert (lastName != NULL);

strcpy (lastName, last);

}

Employee::~Employee ()



{ delete []firstName;

delete []lastName;

}

const char *Employee::getFirstName () const



{ return firstName;}

const char *Employee::getLastName () const

{ return lastName;}

class Boss: public Employee {

public:

Boss ( const char *, const char *, float = 0);



void setWeeklySalary (float);

virtual float earnings () const;

virtual void print () const;

private:


float weeklySalary;

} ;
Boss::Boss (const char *first, const char *last, float s) :

Employee (first, last)

{ setWeeklySalary (s); }

void Boss::setWeeklySalary (float s)

{ weeklySalary = (s > 0) ? s : 0; }

float Boss::earnings () const

{ return weeklySalary; }

void Boss::print () const

{ cout << “Постоянна заплата: “ << getFirstName () << ‘ ‘



<< getLastName ();

}

class CommissionWorker: public Employee {



public:

CommissionWorker ( const char *, const char *, float = 0, float = 0,

unsigned = 0);

void setSalary (float);

void setCommission (float);

void setQuantity (unsigned);

virtual float earnings () const;

virtual void print () const;

private:

float salary;

float commission;

unsigned quantity;

} ;

CommissionWorker:: CommissionWorker (const char *first, const char *last, float s, float c, unsigned q) : Employee (first, last)



{ setSalary (s); setCommission (c); setQuantity (q); }

void CommissionWorker::setSalary (float s)

{ salary = (s > 0) ? s : 0; }

void CommissionWorker::setCommission (float c)

{ commission = (c > 0) ? c : 0; }

void CommissionWorker::setQuantity (unsigned q)

{ quantity = (q > 0) ? q : 0; }

float CommissionWorker::earnings () const

{ return salary + commission*quantity; }

void CommissionWorker::print () const

{ cout << “Комисионна заплата: “ << getFirstName () << ‘ ‘

<< getLastName ();

}

class PieceWorker: public Employee {



public:

PieceWorker ( const char *, const char *, float = 0, unsigned = 0);

void setWagePerPiece (float);

void setQuantity (unsigned);

virtual float earnings () const;

virtual void print () const;

private:

float wagePerPiece;

unsigned quantity;

} ;


PieceWorker:: PieceWorker (const char *first, const char *last, float w,

unsigned q) : Employee (first, last)

{ setWagePerPiece (w); setQuantity (q); }

void PieceWorker::setWagePerPiece (float w)

{ wagePerPiece = (w > 0) ? w : 0; }

void PieceWorker::setQuantity (unsigned q)

{ quantity = (q > 0) ? q : 0; }

float PieceWorker::earnings () const

{ return wagePerPiece*quantity; }

void PieceWorker::print () const

{ cout << “Производствена заплата: “ << getFirstName () << ‘ ‘

<< getLastName ();

}

class HourlyWorker: public Employee {



public:

HourlyWorker ( const char *, const char *, float = 0, float = 0);

void setWage (float);

void setHours (float);

virtual float earnings () const;

virtual void print () const;

private:

float wage;

float hours;

} ;


HourlyWorker:: HourlyWorker (const char *first, const char *last, float w,

float h) : Employee (first, last)

{ setWage (w); setHours (h); }

void HourlyWorker::setWage (float w)

{ wage = (w > 0) ? w : 0; }

void HourlyWorker::setHours (float h)

{ hours = (h > 0 && h <= 168) ? h : 0; }

float HourlyWorker::earnings () const

{ return (hours < 40) ? wage*hours: 40*wage + 1.5*(hours-40)*wage; }

void HourlyWorker::print () const

{ cout << “Почасова заплата: “ << getFirstName () << ‘ ‘

<< getLastName ();

}

void main ()



{

cout << setiosflags (ios::showpoint | ios::showpoint) << setprecision (2);

Employee *ptr;

Boss b (“Иван”, “Христов”, 800);

ptr = &b;

ptr -> print ();//динамично свързване

cout << “ $” << ptr -> earnings () << endl;//динамично свързване

b.print ();//статично свързване

cout << “ $” << b.earnings () << endl;//статично свързване

CommissionWorker c (“Елена”, “Кирова”, 200, 3, 150);

ptr = &c;

ptr -> print ();

cout << “ $” << ptr -> earnings () << endl;

c.print ();

cout << “ $” << c.earnings () << endl;

PieceWorker p (“Калоян”, “Николов”, 200, 12);

ptr = &p;

ptr -> print ();

cout << “ $” << ptr -> earnings () << endl;

p.print ();

cout << “ $” << p.earnings () << endl;

HourlyWorker h (“Първан”, “Първанов”, 200, 12);

ptr = &h;

ptr -> print ();

cout << “ $” << ptr -> earnings () << endl;

h.print ();

cout << “ $” << h.earnings () << endl;

}
в базовия клас Employee, функциите getFirstName и getLastName връщат константни указатели към скритите данни елементи, което не позволява на клиентите да модифицират тези данни; за да се избегне получаването на неопределен указател, клиентът трябва да прекопира получените низове, преди те да бъдат унищожени от деструктора на класа;



34. Пример за наследяване на интерфейса и неговата реализация.

ще разгледаме йерархията на формите Point, Circle и Cylinder; допълнително във върха на йерархията ще създадем абстрактен базов клас Shape, който съдържа четири виртуални функции – две чисто виртуални за отпечатване на формите и две виртуални за лице и обем, реализирани да връщат стойност 0; например класът Point не предефинира функциите за лице и обем, тъй като те имат стойност 0 за точка; ще отбележим, че макар клас Shape да е абстрактен базов клас, той съдържа реализацията на някои функции и тези реализации се наследяват; клас Shape предоставя за наследяване виртуални функции, които ще влизат на всички нива в йерархията; в примера се акцентира върху това, че класовете могат да наследяват интерфейса и реализацията на базовия клас;

в йерархия, проектирана за наследяване на реализация, стремежът е да се осигури функционална възможност на възможно по-високо ниво за да може всеки нов производен клас да наследява функции елементи, описани в базовия клас и да използва тези описания;

в йерархия, проектирана за наследяване на интерфейс, стремежът е да се осигури функционална възможност на възможно по-ниско ниво; в този случай базовият клас декларира функции, които трябва да се извикват идентично за всеки обект в йерархията, т.е. извикванията да имат еднаква сигнатура; производните класове сами осигуряват реализации на тези функции;


#include

#include

class Shape {

public:


virtual float area () const { return 0; }

virtual float volume () const { return 0; }

virtual void printShapeName () const = 0;

virtual void print () const = 0;

} ;

class Point : public Shape {



friend ostream &operator << (ostream &, const Point &);

public:


Point (float = 0, float = 0);

void setPoint (float, float);

float getX () const { return x; }

float getY () const { return y; }

virtual void printShapeName () const { cout << “Точка: “; }

virtual void print () const;

private:

float x, y;

} ;

Point::Point (float a, float b)



{ setPoint (a, b); }

void Point::setPoint (float a, float b)

{ x = a; y = b; }

void Point::print () const

{ cout << ‘[‘ << x << “, “ << y << ‘]’; }

ostream &operator<< (ostream &output, const Point &p)

{ output << ‘[‘ << p.x << “, “ << p.y << ‘]’; return output; }

class Circle : public Point {

friend ostream &operator<< (ostream &, const Circle &);

public:


Circle (float = 0, float = 0, float = 0);

void setRadius (float);

float getRadius () const;

virtual float area () const;

virtual void printShapeName () const

{ cout << “Кръг: “; }

virtual void print () const;

private:


float radius;

} ;


Circle::Circle (float r, float a, float b) : Point (a, b)

{ setRadius (r); }

void Circle::setRadius (float r)

{ radius = (r > 0) ? r : 0; }

float Circle::getRadius () const

{ return radius; }

float Circle::area () const

{ return 3.14159*radius*radius; }

void Circle::print () const

{ cout << ‘[‘ << getX() << “, “ << getY() << “]; Радиус = “



<< radius; }

ostream &operator<< (ostream &output, const Circle &c)

{ output << ‘[‘ << c.getX() << “, “ << c.getY() << “]; Радиус = “

<< c.radius; return output; }

class Cylinder : public Circle {

friend ostream& operator<< (ostream &output, const Cylinder &);

public:


Cylinder (float = 0, float = 0, float = 0, float = 0);

void setHeight (float h) { height = ( h > 0) ? h : 0; }

float getHeight () const { return height; }

virtual float area () const

{ return 2*3.14159*getRadius()*height + 2*Circle::area(); }

virtual float volume () const

{ return height*Circle::area(); }

virtual void printShapeName () const

{ cout << “Цилиндър: “; }

virtual void print () const;

private:

float height;

} ;

Cylinder::Cylinder (float h, float r, float a, float b) : Circle (r, a, b)



{ setHeight (h); }

void Cylinder::print () const

{ Circle::print ();

cout << “; Височината = “ << height;

}

ostream &operator<<(ostream &output, const Cylinder &c)



{ output << '[' << c.getX() << ", " << c.getY() << "]; Радиус = "

<< c.getRadius() << "; Височина = " << c.height;

return output;

}

void main ()



{ cout << setprecision (2) << setiosflags (ios::fixed | ios::showpoint);

Point point (7, 11);

Circle circle (3.5, 22, 8);

Cylinder cylinder (10, 3.3, 10, 10);

point.printShapeName ();

cout << point << endl;

circle.printShapeName ();

cout << circle << endl;

cylinder.printShapeName ();

cout << cylinder << endl;

Shape *array[3];

array [0] = &point; array[1] = &circle; array[2] = &cylinder;

for (int i = 0; i < 3; i++) {

array[i] -> printShapeName ();

array[i] -> print ();

cout << “\nЛице = “ << array[i] -> area ()



<< “\nОбем = “ << array[i] -> volume () << endl;

}

}



Край
09 юни 2002 г.


Сподели с приятели:
1   ...   8   9   10   11   12   13   14   15   16




©obuch.info 2024
отнасят до администрацията

    Начална страница