Az öröklés egy kód-újrahasznosítási módszer, ahol az új osztályok a már
megírt osztályok alapján készülnek felhasználván azok tulajdonságait és
algoritmusait, átírván némely algoritmust és létrehozván pár új tulajdonságot
és algoritmust. Nem csak kódot, de időt is takarít meg, mert a már létező
osztályokat nem kell újra tesztelni. Adatokat és tagfüggvényeket örökölni egy
vagy több osztálytól is lehet. Mindhárom adattípust (public, private,
protected) lehet örökölni, de
legtöbbször a publikus öröklést használjuk. Ebben az esetben ugyanis a derivált
(örökölt) osztály objektumai bárhol használhatók az ősosztály objektumaiként
is. A fordított eset viszont nem igaz, azaz az ősosztály objektumai nem
objektumai a derivált osztálynak is. Tulajdonképpen ez a legalapvetőbb
különbség az osztályok kombinálása (amikor más osztályok objektumait használjuk
tagváltozóként) és az öröklés között. A derivált osztály objektumai az
ősosztály objektumaiként is kezelhetőek. Az angolban ezt „is a” (öröklés) és „has a” (kombináció) típusú osztály kapcsolatként emlegetik. Akár egy külső
függvény, a derivált osztály sem fér hozzá az ősosztályának privát tagjaihoz,
hacsak nincsenek ehhez hozzáférést nyújtó publikus vagy protected tagfüggvények
az ősosztályban. Az öröklés egyik fölösleges tulajdonsága, hogy örökölnek a nem
használt publikus tagfüggvények is. Ha az ősosztály valamely tagfüggvényének
működése nem felel meg a derivált osztálynak, akkor azt újra lehet deklarálni a
derivált osztályban. Az ősosztály friend függvényei nem öröklődnek. Az öröklésre
példa az Absztrakció című bejegyzésben található.
1. Mutató típusú
objektumok átalakítása az osztályok között
A mutató típusú objektumokat az ősosztály és a derivált osztály között cast-tal lehet átalakítani vigyázva, hogy a
mutató és az új objektum típusa találjon.
point.h
#ifndef POINT_H
#define POINT_H
#include <iostream>
using std::ostream;
class Point
{
friend ostream& operator<<(ostream&,
const Point&);
public:
Point(int = 0, int = 0); //konstruktor és a parametér hiányában fellépő értékek
void setPoint(int, int); //Beállítja x és y protected tagváltozókat
int getX() const { return x; }
int getY() const { return y; }
protected: //csak a derivált osztály érheti el
int x, y; //egy pont koordinátái
};
#endif
point.cpp
#include <iostream>
#include "point.h"
//konstruktor - beállítja a
tagváltozókat
Point::Point(int
a, int b)
{
setPoint(a,
b);
}
// a tagváltozók beállítása
void Point::setPoint(int
a, int b)
{
x =
a;
y =
b;
}
// Kiírja az objektumot egyéni
formában
ostream& operator<<(ostream&
output, const Point& p)
{
output
<< '[' << p.x << ", " << p.y << ']';
return output; //fűzér mód
}
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
using std::ostream;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "point.h"
class Circle : public
Point //A Point publikus deriváltja
{
friend ostream& operator<<(ostream&,
const Circle&);
public:
Circle(double r = 0.0, int x
= 0, int y = 0);
void setRadius(double);
double getRadius() const;
double area() const;
protected:
double radius;
};
#endif
circle.cpp
#include "circle.h"
// Előbb a Point konstruktorát hívja meg
Circle::Circle(double
r, int a, int
b) : Point(a, b)
{
setRadius(r);
// és csak utána állítja be a saját tagváltozóit
}
void Circle::setRadius(double
r)
{
radius
= (r > 0 ? r : 0); //a sugár csak pozitív lehet
}
double Circle::getRadius() const
{
return radius;
}
double Circle::area() const
{
return 3.14159 * radius * radius;
}
// Kiírja az objektumot egyéni
formában: Központ = [x, y]; Sugár = #.##
ostream& operator<<(ostream&
output, const Circle& c)
{
output
<< "Központ = " << static_cast<Point>(c)<< ";";
output
<< "Sugár = " <<
setiosflags(ios::fixed | ios::showpoint)
<< setprecision(2) << c.radius;
return output;
}
test_point_circle.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
#include "circle.h"
int main()
{
Point
*pointPtr = 0, p(30, 50);
Circle
*circlePtr = 0, c(2.7, 120, 89);
cout
<< "A p pont: " << p
<< endl;
cout
<< "A c kör: " << c
<< endl;
//a Circle objektum címét egy Point típusó mutatóba írja
pointPtr
= &c; //upcasting
cout
<< "\nA c kör a Point osztály
szemszögébôl: " << *pointPtr << endl;
//A Circle mutató felveszi a Point mutató értékét, ami
Circle objektumra mutat
circlePtr
= static_cast<Circle*>(pointPtr); //downcasting
cout
<< "\nA c kör a Circle osztály
szemszögébôl:\n" << *circlePtr << endl;
//területszámítás
cout
<< "A c kör területe:
circlePtr->area() = " << circlePtr->area() <<
endl;
//a Point objektum címét egy Point típusó mutatóba írjuk
pointPtr
= &p;
//A
Circle mutató felveszi a Point mutató értékét, ami Point objektumra mutat
circlePtr
= static_cast<Circle*>(pointPtr);
cout
<< "\nA p pont a Circle osztály
szemszögébôl:\n" << *circlePtr << endl;
//teruletszámítás
cout
<< "A c kör területe:
circlePtr->area() = " << circlePtr->area() <<
endl;
return 0;
}
A kimenet:
A p pont: [30,
50]
A c kör: Központ
= [120, 89];Sugár = 2.70
A c kör a Point
osztály szemszögébôl: [120, 89]
A c kor a Circle
osztály szemszögébôl:
Központ = [120,
89];Sugár = 2.70
A c kör
területe: circlePtr->area() = 22.90
A p pont a
Circle osztály szemszögébôl:
Központ = [30,
50];Sugár = 0.00
A c kör
területe: circlePtr->area() = 0.00
A Point ősosztály publikus interfésze tartalmazza a setPoint, getX és getY tagfüggvényeket. Az x és y tagváltozók viszont protected típusúak, hogy az osztály közvetlen felhasználói ne férjenek hozzá, de a
derivált osztályok igen. Ha private típusúak lennének, akkor a derivált osztály is csak a Point publikus függvényeivel érhetné el. A Circle osztály a Point ősosztály publikus deriváltja: class Circle : public Point. Ez azt jelenti, hogy a Point
minden public és protected tagja átöröklődött a Circle osztályba. Azt is jelenti, hogy a Circle publikus interfésze nem csak az area, setRadius és getRadius tagfüggvényekből áll, hanem magába foglalja a Point ősosztály publikus tagfüggvényeit is. Az
öröklésnél fontos, hogy a konstruktorok a megfelelő sorrendben hívódjanak meg.
Előbb az ős és utána az utód tagváltozóit kell inicializálni. Legegyszerűbb a
paraméterlista után kettősponttal jelezni, hogy a paraméterek közül melyek
inicializálják a Pointer osztály tagváltozóit: Circle::Circle(double
r, int a, int
b) : Point(a, b). Ebben az
esetben az a és a b lesz amit a Point konstruktor megkap. Ha ezt nem írjuk oda,
akkor is a Point konstruktor fut le hamarább, csakhogy az inicializált értékekkel,
melyek ebben az esetben nullák: Point(int = 0, int = 0) . Ha ez az inicializálás sincs megírva, akkor a fordító hibát jelez. A Point *pointPtr = 0, p(30,
50); programkód létrehoz egy NULL
mutatót és egy p objektumot. Ugyanígy
készül a Circle objektum is.
Mindét osztálynak van egy operátorfüggvénye, ami túlterheli a << operátort és saját stílusban írja ki az
objektumokat. A Circle
derivált osztály használhatja a Point
ősosztály operátorfüggvényét is, ám az csak a saját maga által ismert adatokat
tudja kiírni. Hogy a Point
operátorfüggvénye hívódjon meg, Point
típusú mutatót kell kiíratni, tehát a pointPtr fel kell vegye a Circle típusú objektum címét. A pointPtr = &c; műveletet upcasting-nak nevezik, mert a
derivált objektumot kezeljük ősobjektumként. Ez azt jelenti, hogy csak azok az
adatok elérhetőek az objektumból amelyek az ősosztályból valók, ebben az
esetben az x és y koordináta. Ennek ellenkezője a
downcasting: circlePtr
= static_cast<Circle*>(pointPtr);. Az upcasting esetén a Circle objektum mutatója Point típusú lett. A downcasting esetén visszaalakítás
történik, melyben az értéket csakis egy Circle
típusú mutató veheti fel. A program végül megadja a Point objektum címét a pointPtr mutatónak, majd átalakítja Circle mutatóvá. Ennek eredménye, hogy kiíráskor
a Circle operátorfüggvénye fog meghívódni és nem
fogja tudni, hogy mennyi a Point
objektum területe és sugara, ezért az inicializált nullákkal helyettesíti az
ismeretlen adatokat.
Annak ellenére, hogy a derivált objektum az ősosztályból származik,
típusa mégis különbözhet. A publikus öröklés esetén a derivált objektumokat
lehet ősi objektumként kezelni, hiszen a tagok hozzáférési típusa nem változik.
Az öröklés célja, hogy az alaptulajdonságok mellett új tulajdonságokat
soroljunk fel, ezért a derivált osztály mindig több taggal rendelkezik mint az
ősosztály. Az ősosztályban ezért is nem lehet a derivált objektumokra
hivatkozni, mert az ismeretlen tagok definiálatlanul maradnának. A publikus
öröklés lehetővé teszi, hogy a derivált osztály mutatója átalakítható legyen,
hogy az ősosztály objektumára mutasson, ahogyan ezt a fenti példában láttuk. Tulajdonképpen
négyféleképp kombinálhatóak a mutatók és az objektumok a derivált és az
ősosztály között:
1. Az
ősobjektumra való hivatkozás egy ősmutató segítségével
pointPtr = &p;
2. A
derivált objektumra való hivatkozás egy derivált mutató segítségével
circlePtr = &c;
3. A
derivált objektumra való hivatkozás egy ősmutató segítségével. Ez lehetséges,
hiszen a derivált objektum az ősosztály objektuma is, viszont csak az ősosztály
tagjait használhatjuk, különben a fordító hibát jelez.
pointPtr = &c;
4. Az
ősobjektumra való hivatkozás egy derivált mutató segítségével. Közvetlenül nem
lehet, csak ha átalakítjuk a derivált mutatót ősmutatóvá.
circlePtr
= static_cast<Circle*>(pointPtr);
2. Tagfüggvények
használata
A derivált és az ősosztályok tagfüggvényei között olykor csak kevés
eltérés van, mint például az előző példában szereplő operátorfüggvények esetén.
Ilyenkor nem érdemes a függvény nevét megváltoztatni, ugyanis felülírható az
ősosztály tagfüggvénye. Egyszerűen újra kell deklarálni az adott függvényt a
derivált osztályba, és onnantól az lesz az elsődlegesen érvényes függvény.
Továbbra is használható marad az ősosztály tagfüggvénye, ám használni kell a
tartományoperátort.
employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
class Employee
{
public:
Employee(const char*, const char*);
void print() const; // Kiírja a vezeték- és keresztnevet
~Employee();
private:
char* firstName;//dinamikusan
lefoglalt string
char* lastName; //dinamikusan
lefoglalt string
};
#endif
employee.cpp
#include <iostream>
using std::cout;
#include <cstring>
#include <cassert>
#include "employee.h"
Employee::Employee(const
char* first, const
char* last)
{
firstName
= new char[strlen(first)
+ 1]; // dinamikus memórialefoglalás
assert(firstName
!= 0); //
abort ha nem sikerül lefoglalni
strcpy(firstName,
first); //
bemásolja a bejövő paramétert a tagváltozóba
lastName
= new char[strlen(last)
+ 1];
assert(lastName
!= 0);
strcpy(lastName,
last);
}
void Employee::print() const
{
cout
<< firstName << ' ' <<
lastName;
}
Employee::~Employee()
{
delete[] firstName; //memória
felszabaditás
delete[] lastName;
//memória felszabaditás
}
hourly.h
#ifndef HOURLY_H
#define HOURLY_H
#include "employee.h"
class HourlyWorker : public
Employee
{
public:
HourlyWorker(const char*,const char*,double, double);
double getPay() const;
void print() const; //az ososztály tagfüggvényének felülírása
private:
double wage;
double hours;
};
#endif
hourly.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "hourly.h"
HourlyWorker::HourlyWorker(const char* first, const char* last,
double initHours, double
initWage) : Employee(first, last)
{
hours
= initHours;
wage
= initWage;
}
//A munkások fizetése
double HourlyWorker::getPay() const
{
return wage*hours;
}
//A felülírt függvény: kiírja a
fizetéseket is
void HourlyWorker::print() const
{
cout
<< "HourlyWorker::print():"
<< endl;
Employee::print();
//az eredeti függvény
cout
<< " egy munkás ember havi "
<<
setiosflags(ios::fixed | ios::showpoint)
<<
setprecision(2) << getPay() << "
EUR minimálbérrel." << endl;
}
test_hourlyworker.cpp
#include"hourly.h"
int main()
{
HourlyWorker
h("Bob", "Smith",
40.0, 10.00);
h.print();
return 0;
}
A kimenet:
HourlyWorker::print():
Bob Smith egy
munkás ember havi 400.00 EUR minimálbérrel.
Az Employee osztály
definíciója két char* privát tagváltozóból és három
tagfüggvényből áll. A konstruktor két karakterláncot kap paraméternek,
melyeknek dinamikusan lefoglal egy-egy char táblát a memóriában. Ha a helylefoglalás sikertelen, az assert hibaüzenettel lépik ki a programból.
Használható a try/catch szekvencia is,
mely bad_alloc kivételt
generál ha a new nem működött
megfelelően. Mivel az Employee osztály tagváltozói (vagy adattagjai) privátak, az egyetlen módszer az
elérésükhöz a print függvény, amely
kiírja őket. Az osztály destruktora felszabadítja a tagváltozóknak dinamikusan
lefoglalt memóriát elkerülve ezzel a memory leak nevű jelenséget.
Az HourlyWorker osztály az Employee osztályból van származtatva publikus
típusú örökléssel. Felülírja a print az ősosztályban is létező print függvényt, így két print függvényhez van hozzáférése. Ilyen
esetben általában a derivált változat alkalmazza az ősi változatot is, hogy
kiírja az ott elérhető információkat, ehhez azonban eléje kell írja az
ősosztály nevét: Employee::print();
3. A public, protected
és private típusú tagok öröklődése
Az osztályok származtatásakor az ősosztályt lehet public, private vagy protected típusú örökléssel származtatni. A private és protected nagyon ritka, inkább a public típust használjuk. A következő
táblázatban fel van sorolva mindenik hozzáférési típusú származtatás és hogy
mihez ad hozzáférést az ősosztályból.
Az ősosztály tagjainak hozzáférési
típusa, amelyet a derivált osztály elérhet
|
Öröklés típusa
|
||
public
|
protected
|
private
|
|
public
|
Az ősosztály public tagjai public hozzáférésűek a derivált osztályban is. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
Az ősosztály public tagjai protected hozzáférésűek a derivált osztályban. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
Az ősosztály public tagjai private hozzáférésűek a derivált osztályban. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
protected
|
Az ősosztály protected tagjai protected hozzáférésűek a derivált osztályban is. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
Az ősosztály protected tagjai protected hozzáférésűek a derivált osztályban is. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
Az ősosztály protected tagjai private hozzáférésűek a derivált osztályban. Közvetlenül
elérhetőek bármilyen nem statikus tagfüggvénnyel vagy osztályon kívüli friend
függvénnyel.
|
private
|
Az ősosztály private tagjai nem elérhetőek a derivált
osztályban. Közvetlenül elérhetőek bármilyen nem statikus tagfüggvénnyel vagy
osztályon kívüli friend függvénnyel, ha az eléréshez az ősosztály függvényeit
használjuk.
|
Az ősosztály private tagjai nem elérhetőek a derivált
osztályban. Közvetlenül elérhetőek bármilyen nem statikus tagfüggvénnyel vagy
osztályon kívüli friend függvénnyel, ha az eléréshez az ősosztály függvényeit
használjuk.
|
Az ősosztály private tagjai nem elérhetőek a derivált
osztályban. Közvetlenül elérhetőek bármilyen nem statikus tagfüggvénnyel vagy
osztályon kívüli friend függvénnyel, ha az eléréshez az ősosztály függvényeit
használjuk.
|
Látható, hogy a public típusú öröklésnél minden public tag public marad, a protected pedig protected a származtatott osztályban is. A protected öröklés mindent protected típussá tesz, a private öröklés pedig mindent private típussá. Ez utóbbival az a baj, hogy minden tagfüggvény az ősosztályból
segédfüggvénnyé válik a derivált osztályban. Egyik típusú öröklés sem enged
hozzáférést az ősosztály private hozzáférésű tagjaihoz. Ha a
derivált osztályból is deriválunk osztályokat, akkor nem kell a legősibb
osztályig felsorolni mindent, csupán azt az osztályt ami egy absztrakciós
szinttel felette van. Ebben az esetben a legősibb osztály közvetett őse a
derivált osztálynak.
4. A derivált osztályok
konstruktorai és a destruktorai
A derivált osztály objektumai létrehozásakor az ősosztály konstruktorai
futnak le előbb. Ezek és az operátorfüggvények nem öröklődnek át, viszont
használhatóak a derivált osztály konstruktoraiban és operátor függvényeiben. A
destruktorok fordított sorrendben hívódnak meg, azaz előbb a derivált osztály
objektumai bomlanak le, aztán az ősosztály objektumai.
Tegyük fel, hogy az ősosztály és a derivált osztály is egy másik, „idegen”
osztály objektumait tartalmazza. Amikor létrehozunk egy objektumot a derivált
osztályban, először az ősosztályban lévő „idegen” objektumok konstruktora
hívódik meg, majd az ősosztály konstruktora, majd a derivált osztályban lévő
„idegen” objektumok konstruktora, végül a derivált osztály konstruktora. A
destruktorok fordított sorrendben futnak le.
point2.h
#ifndef POINT2_H
#define POINT2_H
class Point
{
public:
Point(int = 0, int = 0);
~Point();
protected: //csak a
derivált osztály látja
int x, y;
};
#endif
point2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "point2.h"
Point::Point(int
a, int b)
{
x =
a;
y =
b;
cout
<< "Point konstruktor: "
<< '['
<< x << ", " <<
y << ']' << endl;
}
Point::~Point()
{
cout
<< "Point destruktor: "
<< '['
<< x << ", " <<
y << ']' << endl;
}
circle2.h
#ifndef CIRCLE2_H
#define CIRCLE2_H
#include "point2.h"
class Circle : public
Point
{
public:
Circle(double r = 0.0, int x
= 0, int y = 0);
~Circle();
protected:
double radius;
};
#endif
circle2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "circle2.h"
Circle::Circle(double
r, int a, int
b) : Point(a, b)
{
radius
= r;
cout
<< "Circle konstruktor: a sugár: "
<< radius << " [" << x << ", " << y << ']' << endl;
}
Circle::~Circle()
{
cout
<< " Circle
destruktor: a sugár: "
<< radius << " [" << x << ", " << y << ']' << endl;
}
test_point2_circle2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "circle2.h"
int main()
{
//Konstruktor és destruktor teszt
{
Point
p(11, 22);
}
cout
<< endl;
Circle
circle1(4.5, 72, 29);
cout
<< endl;
Circle
circle2(10, 5, 5);
cout
<< endl;
return 0;
}
A kimenet:
Point
konstruktor: [11, 22]
Point
destruktor: [11, 22]
Point
konstruktor: [72, 29]
Circle
konstruktor: a sugár: 4.5 [72, 29]
Point
konstruktor: [5, 5]
Circle
konstruktor: a sugár: 10 [5, 5]
Circle
destructor: a sugár: 10 [5, 5]
Point
destruktor: [5, 5]
Circle
destructor: a sugár: 4.5 [72, 29]
Point
destruktor: [72, 29]
A Circle osztály a Point publikus derivált osztálya, melynek van egy radius tagváltozója az örökölt tagváltozók
mellett. A Circle konstruktora
meghívja a Point konstruktorát, hogy
inicializálja az ősosztály x és y tagváltozóit. A főprogramban a p objektum külön blokkban van deklarálva,
így amikor a blokk végére ér, meghívódik a Point destruktora. Ezek után a circle1 és circle2 objektumok következnek, melyeknél látható, hogy
előbb a Point konstruktora fut
le.
5. A „uses a” és a „knows
a” típusú kapcsolat
Az osztályok öröklése és a
kombinálása a kód-újrahasznosítást szorgalmazza olyan osztályok révén, melyeknek
közös tagjaik vannak. Ahogyan egy „személy” típusú objektum nem „jármű” típusú
objektum ugyanúgy nem is lehet annak leszármazottja vagy őse sem. A „személy”
objektumnak viszont használhat egy (uses
a) „jármű” objektumot. Egy objektum egy mutatóval, referenciával vagy közvetlen
módon használhat egy másik objektumot és annak nem-privát tagfüggvényeit. Egy
objektum tudhat egy (knows a) másik
objektum létezéséről, azaz tartalmazhat egy mutatót vagy referenciát egy másik
objektumra. A különbség a „használ” és „tud róla” között, hogy míg a „használ”
esetben mindkét objektum létezése azonos időtartamú, addig a „tud róla” esetben
az objektumok egymástól függetlenül is létezhetnek. A következő példában a Cylinder és a Circle osztályok a Point osztályból származnak közvetetten és
közvetlenül.
point.h
#ifndef POINT_H
#define POINT_H
#include <iostream>
using std::ostream;
class Point
{
friend ostream& operator<<(ostream&,
const Point&);
public:
Point(int = 0, int = 0);
void setPoint(int, int);
int getX() const { return x; }
int getY() const { return y; }
protected:
int x, y;
};
#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;
}
ostream& operator<<(ostream&
output, const Point& p)
{
output
<< '[' << p.x << ", " << p.y << ']';
return output; //fűzér mód
}
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
using std::ostream;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "point.h"
class Circle : public
Point
{
friend ostream& operator<<(ostream&,
const Circle&);
public:
Circle(double r = 0.0, int x
= 0, int y = 0);
void setRadius(double);
double getRadius() const;
double area() const;
protected:
double radius;
};
#endif
circle.cpp
#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::getRadius() const
{
return radius;
}
double Circle::area() const
{
return 3.14159 * radius * radius;
}
ostream& operator<<(ostream&
output, const Circle& c)
{
output
<< "A kör közepe = "
<< static_cast<Point>(c)
<< "\nA
kör suagara = "
<< setiosflags(ios::fixed |
ios::showpoint)
<< setprecision(2) << c.radius;
return output; //fűzér mód
}
cylinder.h
#ifndef CYLINDER_H
#define CYLINDER_H
#include <iostream>
using std::ostream;
#include "circle.h"
class Cylinder : public
Circle
{
friend ostream& operator<<(ostream&,
const Cylinder&);
public:
Cylinder(double h = 0.0, double
r = 0.0,
int x = 0, int y =
0);
void setHeight(double);
double getHeight() const;
double area() const;
double volume() const;
protected:
double height;
};
#endif
cylinder.cpp
#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::getHeight() const
{
return height;
}
double Cylinder::area() const
{
return 2 * Circle::area() +
2 *
3.14159 * radius * height;
}
double Cylinder::volume() const
{
return Circle::area() * height;
}
ostream& operator<<(ostream&
output, const Cylinder& c)
{
output
<< static_cast<Circle>(c)
<< "\nA
henger magassága = " << c.height;
return output; //fűzér mód
}
test_cylinder.cpp
#include<iostream>
using std::cout;
using std::endl;
#include "cylinder.h"
int main()
{
Cylinder
cyl(5.7, 2.5, 12, 23);
cout
<< "x = " <<
cyl.getX() //
A Point interfészből
<< "\ny
= " << cyl.getY() // A Point interfészből
<< "\nsugár
= " << cyl.getRadius() //A Circle
interfészből
<< "\nmagasság
= " << cyl.getHeight() << "\n\n";
// A sajat interfészből
cyl.setHeight(10); // A saját
interfészből
cyl.setRadius(4.25); // A Circle
interfészből
cyl.setPoint(2,
2); // A Point
interfészből
cout
<< "cyl:\n" << cyl
<< '\n';
cout
<< "A henger területe: "
<< cyl.area() << '\n'; // A saját interfészből
Point&
pRef = cyl;
cout
<< "\nA cyl a Point osztály
szemszögéből: " << pRef << "\n\n";
Circle&
circleRef = cyl;
cout
<< "A cyl a Circle osztály
szemszögéből:\n" << circleRef << '\n';
cout
<< "A henger területe: "
<< circleRef.area() << endl;
return 0;
}
A kimenet:
x = 12
y = 23
sugár = 2.5
magasság = 5.7
cyl:
A kör közepe =
[2, 2]
A kör suagara =
4.25
A henger
magassága = 10.00
A henger
területe: 380.53
A cyl a Point
osztály szemszügéből: [2, 2]
A cyl a Circle
osztály szemszögéből:
A kör közepe =
[2, 2]
A kör suagara =
4.25
A kör területe:
56.74
A Cylinder osztály a Circle osztály deriváltja, amely a Point osztály deriváltja. Ez azt jelenti, hogy
a Point és a Cyrcle interfésze elérhető a Cylinder osztályban, amely még hozzáteszi a setHeight, getHeight, volume
és area tagfüggvényeket.
Ezek közül az area megtalálható a Circle osztályban is, azonban felülíródik. A Cylinder konstruktora meg kell hívja a Circle konstruktorát. A Point konstruktorát a Circle konstruktora hívja meg. A derivált
osztály konstruktora csak a közvetlen ősosztály konstruktorának meghívásáért
felelős. A főprogramban a cyl egy
Cylinder típusú objektum, melyre az összes létező
interfészt alkalmazva van. Ezután deklarálva van egy Point típusú referencia, mely a cyl objektumra hivatkozik: Point& pRef = cyl;. A pRef így
az objektumból csak a Point
tudásának eleget tevő koordinátákat látja. Hasonló a helyzet a Circle& circleRef = cyl; esetben is, ahol a circleRef referencia csak a Circle által ismert adatokat tudja kiírni. Ebben
az esetben az area az eredeti és nem a
felülírt függvényt fogja jelenti. A szemszögek kiírásához ugyanilyen módon jó
lett volna egy print() függvény is, azonban az << operátor átdefiniálása olvashatóbbá teszi a kódot.
6. A többszörös öröklés
A derivált osztálynak több
ősosztálya is lehet. A következő példában a Derived osztály a Base1 és a Base2 osztályok leszármazottja.
base1.h
#ifndef BASE1_H
#define BASE1_H
class Base1
{
public:
Base1(int x){ value = x; }
int getData() const {
return value; }
protected:
int value;
};
#endif
base2.h
#ifndef BASE2_H
#define BASE2_H
class Base2
{
public:
Base2(char c){ letter = c; }
char getData() const
{ return letter; }
protected:
char letter;
};
#endif
derived.h
#ifndef DERIVED_H
#define DERIVED_H
#include <iostream>
using std::ostream;
#include "base1.h"
#include "base2.h"
class Derived : public
Base1, public Base2
{
friend ostream& operator<<(ostream
&, const Derived&);
public:
Derived(int, char, double);
double getReal() const;
private:
double real;
};
#endif
derived.cpp
#include "derived.h"
Derived::Derived(int
i, char c, double
f) : Base1(i), Base2(c), real(f)
{}//az f paramétert a
real privát tagváltozóba teszi
double Derived::getReal() const
{
return real;
}
ostream& operator<<(ostream&
output, const Derived& d)
{
output
<< "value = " <<
d.value //A Base1-ből
<< "\nletter
= " << d.letter //A Base2-ből
<< "\nreal
= " << d.real; //A sajátból
return output;
}
test_derived.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "derived.h"
int main()
{
Base1
b1(10), *base1Ptr = 0;
Base2
b2('Z'), *base2Ptr = 0;
Derived
d(7, 'A', 3.5);
cout
<< "b1 tartalma: " <<
b1.getData()
<< "\nb2
tartalma: " << b2.getData()
<< "\nd
tartalma:\n" << d << "\n\n";
cout
<< "A Derived tagjai egyenként is
elérhetőek:"
<< "\nvalue:
" << d.Base1::getData()
<< "\nletter:
" << d.Base2::getData()
<< "\nreal:
" << d.getReal() << "\n\n";
cout
<< "d mint az ősosztályok
objektuma:\n";
base1Ptr
= &d; // A Base1 objektuma
cout
<< "base1Ptr->getData() = "
<< base1Ptr->getData() << '\n';
base2Ptr
= &d; // A Base2 objektuma
cout
<< "base2Ptr->getData() = "
<< base2Ptr->getData() << '\n';
return 0;
}
A kimenet:
b1 tartalma: 10
b2 tartalma: Z
d tartalma:
value = 7
letter = A
real = 3.5
A Derived tagjai egyenként
is elérhetőek:
value: 7
letter: A
real: 3.5
d mint az ősosztályok
objektuma:
base1Ptr->getData() = 7
base2Ptr->getData() = A
A többszörös öröklés az ősosztályok
felsorolása révén valósul meg: class Derived : public Base1, public Base2. A derivált osztály konstruktorában mindenik ősosztály konstruktorát meg kell
hívni, hogy inicializálódjanak az onnan örökölt értékek is. A főprogram
mindenik osztálytípusból deklarál egy-egy objektumot, majd kiírja ezek
tartalmát a beépített tagfüggvények és az operátorfüggvény segítségével. A
tagfüggvények nevei nem téveszthetők össze, hisz más-más objektumhoz tartoznak.
Amikor egyenként vannak kiírva, akkor is ott áll az adatok előtt az
osztályazonosító (pl. d.Base1::getData()). Ha
egyedi nevük lenne, nem lenne szükség osztályazonosítóra. Végül pedig két
upcasting látható az ősosztályokra, valamint a mutatók alkalmazása a függvények
megkülönböztetésére.