(toc)
A polimorfizmus és a virtuális függvények segÃtségével olyan szoftverek
fejleszthetÅ‘k, melyek könnyű módszerrel bÅ‘vÃthetÅ‘ek. Ezek a programok az
osztály hierarchia minden objektumát az Å‘sosztály objektumaiként kezelik, Ãgy
az újonnan hozzáadott osztályok objektumait is rögtön kezelni tudják. Csak
azoknál a programrészeknél kell módosÃtást végrehajtani, melyek specifikus
információkat használnak az újonnan hozzáadott osztályról.
Switch
A különbözÅ‘ adattÃpusok használatára a switch az egyik alternatÃv módszer, hiszen ezzel
más-más utasÃtásokat lehet végrehajtatni az objektum tÃpusának függvényében.
Például a formák osztályának hierarchiájában kiválasztható, hogy a melyik
tÃpusú formához melyik print() függvényt hÃvjuk meg. A switch-el azonban problémák is vannak. A
programozó kifelejthet egy lehetséges esetet, például mikor egy új osztállyal
bÅ‘vÃti a programot elfelejti beÃrni annak print() esetét a switch-be. UgyanÃgy
ha megváltoztatunk vagy kitörlünk egy osztályt, akkor mindenik switch-ből ki
kell törölni annak esetét és, különben egy hibákkal teli program lesz az
eredmény.
Virtuális függvények
A switch logikájának
automatizálását a virtuális függvények valósÃtják meg. Legyen egy Shape Å‘sosztály, melynek deriváltjai a Point, Circle, Triangle, Rectangle és Square. Mindenik osztálynak van egy printShapeName függvénye, mely az adott forma nevét Ãrja
ki, ezért különbözőek. Jó lenne mégis, ha mindenik forma azonos módon kezelhető
lenne, hisz egy ősosztályból származnak. Más szóval, ha az ősosztály szintjén
meghÃvjuk a printShapeName függvényt,
akkor a program dinamikus módon (futás közben) határozza meg, melyik derivált
osztály függvényéről van szó. Ehhez a printShapeName függvényt virtuálisnak kell deklarálni az
Å‘sosztályban, és mindenik örökös felül kell Ãrja a maga módján.
#include <iostream>
using namespace std;
class Shape{
public:
virtual void
printShapeName() const{
cout
<< "Shape::printShapeName()"
<< endl;
}
void init() const{
cout
<< "Shape::init()" <<
endl;
}
};
class Point : public
Shape{
public:
void printShapeName() const{
cout
<< "Point::printShapeName()"
<< endl;
}
void init() const{
cout
<< "Point::init()" <<
endl;
}
};
int main()
{
Shape
shape;
cout
<< "A Shape függvényei Shape
objektummal:" << endl;
shape.printShapeName();
shape.init();
Shape*
shapePtr = new Shape();
cout
<< "\nA Shape függvényei Shape
mutatóval:" << endl;
shapePtr->printShapeName();
shapePtr->init();
Point*
pointPtr = new Point();
shapePtr
= pointPtr;
cout
<< "\nA Shape függvényei Point mutatóval,
1. eset:" << endl;
shapePtr->printShapeName();
//polimorfizmus
shapePtr->init(); //nem polimorfizmus
Point
point;
shapePtr
= &point;
cout
<< "\nA Shape függvényei Point mutatóval,
2. eset:" << endl;
shapePtr->printShapeName();
//polimorfizmus
shapePtr->init(); //nem polimorfizmus
return 0;
}
A kimenet:
A Shape
fuggvenyei Shape objektummal:
Shape::printShapeName()
Shape::init()
A Shape
fuggvenyei Shape mutatoval:
Shape::printShapeName()
Shape::init()
A Shape
fuggvenyei Point mutatoval, 1. eset:
Point::printShapeName()
Shape::init()
A Shape fuggvenyei
Point mutatoval, 2. eset:
Point::printShapeName()
Shape::init()
Az ősosztály printShapeName függvénye mutatóval vagy a pont
operátorral is elérhető, akár egy sima függvény. Ugyanez érvényes a derivált
osztály függvényeire is. Ha viszont az Å‘sosztály tÃpusú mutató egy derivált
osztály objektumára mutat (shapePtr = pointPtr vagy shapePtr = &point),
akkor a program automatikusan derivált osztályban lévÅ‘ printSapeName függvényt veszi számÃtásba (merthogy az
Å‘sosztályban virtuálisnak volt deklarálva). Ezt angolul „dynamic binding” azaz
dinamikus csatolásnak vagy köteléknek nevezik.
Absztrakt ősosztályok
Amikor az osztályra, mint adattÃpusra gondolunk, akkor arra számÃtunk,
hogy az osztályt használó alkalmazásban lesznek majd ilyen tÃpusú objektumok.
Vannak viszont olyan körülmények, mikor az adott osztálynak nem deklarálunk
objektumokat. Ezek az absztrakt osztályok és általában ősosztályok, ezért
absztrakt Å‘sosztályoknak hÃvjuk. Az ilyen osztályokból tehát nem deklarálunk
objektumokat, ellenben ennek deriváltjaiból már igen, és ezért őket konkrét
osztályoknak hÃvjuk. Az absztrakt osztályok túl alaposztályok, túl általánosak
ahhoz, hogy bármit is kezdeni lehessen az objektumaival. Az ősosztály akkor lesz
absztrakt, hogyha egy vagy több virtuális függvénye tiszta. Ez azt jelenti,
hogy nincsen tartalma és ezt az =0
taggal jelzik a deklarálásnál: virtual
void earnings() const = 0; . Ha ezt a függvényt a derivált osztály se Ãrja felül, akkor Å‘ is
absztrakt osztálynak számÃt.
Polimorfizmus
Lehetővé teszi, hogy a különböző osztályok objektumai, melyeket öröklés
köt össze, másképp válaszoljanak az adott függvényre. Ezt a virtuális
függvények teszik lehetővé, amint azt a fenti példában is láttuk. Természetesen
a nem virtuális függvények is felülÃrhatók, erre ad példát az init() függvény. Minden esetben az Å‘sosztály tagfüggvénye hÃvódott
meg, mert bár a sharePtr a Point objektumra mutat, a tÃpusa akkor is Shape. A tÃpusátalakÃtás nem
segÃthet, hiszen nem a tÃpus miatt nem működik, hanem a kiÃrás miatt, ugyanis minden
esetben a Shape szemszögébÅ‘l tesszük: shapePtr->init();. Ebben az esetben nincs polimorfizmus. A polimorfizmus elÅ‘segÃti a program
bÅ‘vÃthetÅ‘séget. Az ilyen stÃlusban Ãrt alkalmazás független azoktól az
objektumoktól akiknek üzenetet küld, azaz nem kell figyeljen arra, hogy a megfelelő
tagfüggvény hÃvódjék meg és ezért nem is kell módosÃtani a program
alapszerkezetét. Az új osztályok vagy objektumok hozzáadásakor nem kell újra
végigmenni a teljes kódon, csak egyszerűen újra kell fordÃtani a fordÃtó
segÃtségével. Ha absztrakt Å‘sosztályok is vannak a kódban, akkor nem
deklarálhatunk nekik objektumokat, viszont mutatókat és referenciákat igen.
Ezek felhasználhatók a derivált objektumok polimorfikus kezelésére. A
polimorfizmus akkor is hasznosnak bizonyul, ha a fejlesztő nem tudja előre,
hogy milyen osztályok kellenek majd a program végső verziójáig, mert új
osztályokat a dynamic binding segÃtségével rögtön alkalmazni tudja majd. A
virtuális függvényt használó objektum tÃpusa nem fontos a fordÃtás során, majd
a futásnál a virtuális függvény az őt használó objektum tagfüggvényeként lesz
azonosÃtva.
Virtuális destruktorok
Amikor polimorfizumst használunk a dinamikusan lefoglalt objektumokon,
akkor problémák léphetnek fel azok lebontásánál. Amikor egy derivált objektumot,
amelyet éppen az ősosztály használ, explicit módon lebontunk a delete operátorral, akkor az ősosztály
destruktora hÃvódik meg attól függetlenül, hogy milyen tÃpusú objektumról van
szó. Hogy ez ne történjen meg, az ősosztály destruktorát is virtuálisnak
deklaráljuk. Ettől aztán az összes derivált osztály destruktora virtuálissá
válik még akkor is, ha más nevük van. A destruktorokkal ellentétben, a
konstruktorokat nem lehet virtuálisnak deklarálni, de nincs is amiért.
Példa
A következő programban a Shape
ősosztálynak a Point,
a Circle és a Cylinder lesznek a leszármazottjai. A Shape absztrakt ősosztály, mert a printShapeName és a print virtuális függvényei tiszták. Van még két
virtuális függvénye, az area és
a volume, amelyek nullát térÃtenek vissza, azaz
önmagukban nem sokat érnek, de a Point
osztálynak éppen megfelelnek, hiszen úgysem veszi hasznukat. A Circle a Point leszármazottja lesz és felülÃrja az area függvényt. A Cylinder a Circle leszármazottja és
Å‘ felülÃrja a volume függvényt.
shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
public:
virtual double area()
const {return
0.0;}
virtual double
volume() const {return
0.0;}
virtual void
printShapeName() const = 0; // tiszta
virtual void print() const = 0; // tiszta
};
#endif
point.h
#ifndef POINT_H
#define POINT_H
#include <iostream>
using std::cout;
#include "shape.h"
class Point : public
Shape
{
public:
Point(int = 0, int = 0);
void setPoint(int, int);
virtual void
printShapeName() const {cout << "Point: ";}
virtual void print() const;
private:
int x, y; // a pont
koordinátái
};
#endif
point.cpp
#include <iostream>
#include "point.h"
Point::Point(int
a, int b){
setPoint(a,
b);
}
void Point::setPoint(int
a, int b){
x =
a;
y =
b;
}
void Point::print() const{
cout
<< '[' << x << ", " << y << ']';
}
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.cpp"
class Circle : public
Point
{
public:
Circle(double r = 0.0, int x
= 0, int y = 0);
void setRadius(double);
virtual double area()
const;
virtual void
printShapeName() const {cout << "Circle: ";}
virtual void print() const;
protected:
double radius;
};
#endif
circle.cpp
#include <iostream>
using std::cout;
#include "circle.h"
Circle::Circle(double
r, int a, int
b) : Point(a, b){
setRadius(r);
}
void Circle::setRadius(double
r){
radius
= (r > 0 ? r : 0);
}
double Circle::area() const{
return 3.14159 * radius * radius;
}
void Circle::print() const
{
Point::print();
cout
<< "; Sugár = " <<
radius;
}
cylinder.h
#ifndef CYLINDER_H
#define CYLINDER_H
#include "circle.h"
class Cylinder : public
Circle
{
public:
Cylinder(double h = 0.0, double
r = 0.0,
int x = 0, int y =
0);
void setHeight(double);
virtual double area()
const;
virtual double
volume() const;
virtual void
printShapeName() const {cout << "Cylinder: ";}
virtual void print() const;
private:
double height;
};
#endif
cylinder.cpp
#include <iostream>
using std::cout;
#include "cylinder.h"
Cylinder::Cylinder(double
h, double r, int
x, int y) : Circle(r, x, y){
setHeight(h);
}
void Cylinder::setHeight(double
h){
height
= (h >= 0 ? h : 0);
}
double Cylinder::area() const{
return 2 * Circle::area() + 2 * 3.14159 * radius *
height;
}
double Cylinder::volume() const{
return Circle::area() * height;
}
void Cylinder::print() const
{
Circle::print();
cout
<< "; Magasság = " <<
height;
}
test_shape.cpp
#include<iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "cylinder.h"
void virtualViaPointer(const
Shape*);
void virtualViaReference(const
Shape&);
int main()
{
cout
<< setiosflags(ios::fixed | ios::showpoint) << setprecision(2); //double-nek
Point
point(7, 11); // x = 7, y = 11
Circle
circle(3.5, 22, 8); // radius = 3.5,
x = 22, y = 8
Cylinder
cylinder(10, 3.3, 10, 10); // height = 10, radius =
3.3, x = 10, y = 10
point.printShapeName();
//static binding
point.print();
//kiÃrja a
koordinátákat
cout
<< endl;
circle.printShapeName();
//static binding
circle.print();
//kiÃrja a
koordinátákat + a sugarat
cout
<< endl;
cylinder.printShapeName();
//static binding
cylinder.print(); //kiÃrja a
koordinátákat + a sugarat + a magassgot
cout
<< '\n';
Shape
*arrayOfShapes[3]; //3 elemű tömb, melyben Shape mutatók vannak
arrayOfShapes[0]
= &point; //Az
első a point objektumra mutat
arrayOfShapes[1]
= &circle; //A
második a circle objektumra mutat
arrayOfShapes[2]
= &cylinder;//A harmadik a cylinder objektumra
mutat
//Mindenik tagra meghÃvjuk a virtuális függvényeket kiÃró függvényt
cout
<< "\nA Shape virtuális függvényei
mutatókkal meghÃvva\n";
for(int i = 0; i <
3; i++) virtualViaPointer(arrayOfShapes[i]);
//Mindenik tagra meghÃvjuk a virtuális függvényeket kiÃró függvényt
cout
<< "A Shape virtuális függvényei
referenciákkal meghÃvva\n";
for(int i = 0; i <
3; i++) virtualViaReference(*arrayOfShapes[i]);
return 0;
}
//MeghÃvja a virtuális függvényeket
a mutató segÃtségével
void virtualViaPointer(const
Shape* baseClassPtr)
{
baseClassPtr->printShapeName();
//dynamic binding
baseClassPtr->print(); //dynamic binding
cout
<< "\nTerület = " <<
baseClassPtr->area() << "\nTérfogat =
" << baseClassPtr->volume() << "\n\n";
}
//MeghÃvja a virtuális függvényeket
a referencia segÃtségével
void virtualViaReference(const
Shape& baseClassRef)
{
baseClassRef.printShapeName();
//dynamic binding
baseClassRef.print(); //dynamic
binding
cout
<< "\nTerület = " <<
baseClassRef.area() << "\nTérfogat =
" << baseClassRef.volume() << "\n\n";
}
A kimenet:
Circle: [22, 8];
Sugár = 3.50
Terület = 38.48
Térfogat = 0.00
Cylinder: [10,
10]; Sugár = 3.30; Magasság = 10.00
Terület = 275.77
Térfogat =
342.12
A Shape
virtuális függvényei referenciákkal meghÃvva
Point: [7, 11]
Terület = 0.00
Térfogat = 0.00
Circle: [22, 8];
Sugár = 3.50
Terület = 38.48
Térfogat = 0.00
Cylinder: [10,
10]; Sugár = 3.30; Magasság = 10.00
Terület = 275.77
Térfogat =
342.12
A virtuális függvényeket meghÃvó virtualViaPointer és virtualViaReference függvények esetén a
fordÃtó nem tudja meghatározni, hogy milyen tÃpusú lesz a baseClassPtr objektum, ami akár problémák a forrása is lehet.
Ikuti AltairGate.com pada Aplikasi GOOGLE NEWS : FOLLOW (Dapatkan Berita Terupdate tentang Dunia Pendidikan dan Hiburan). Klik tanda ☆ (bintang) pada aplikasi GOOGLE NEWS.