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ó.
Related Articles
1. Mutató típusú objektumok átalakítása az osztályok között
2. Tagfüggvények használata
3. A public, protected és private típusú tagok öröklődése
4. A derivált osztályok konstruktorai és a destruktorai
5. A „uses a” és a „knows a” típusú kapcsolat
6. A többszörös öröklés
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
using std::ostream;
class Point
{
friend ostream& operatorconst 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
#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& operatoroutput, const Point& p)
{
output
'[' ", " ']';
return output; //fűzér mód
}
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include
using std::ostream;
#include
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "point.h"
class Circle : public
Point //A Point publikus deriváltja
{
friend ostream& operatorconst 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& operatoroutput, const Circle& c)
{
output
"Központ = " static_cast
output
"Sugár = " setiosflags(ios::fixed | ios::showpoint)
return output;
}
test_point_circle.cpp
#include
using std::cout;
using std::endl;
#include
#include "circle.h"
int main()
{
Point
*pointPtr = 0, p(30, 50);
Circle
*circlePtr = 0, c(2.7, 120, 89);
cout
"A p pont: "
cout
"A c kör: "
//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: "
//A Circle mutató felveszi a Point mutató értékét, ami
Circle objektumra mutat
circlePtr
= static_cast
cout
"\nA c kör a Circle osztály
szemszögébôl:\n"
//területszámítás
cout
"A c kör területe:
circlePtr->area() = " 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
cout
"\nA p pont a Circle osztály
szemszögébôl:\n"
//teruletszámítás
cout
"A c kör területe:
circlePtr->area() = " 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
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
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
using std::cout;
#include
#include
#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
' ' lastName;
}
Employee::~Employee()
{
delete[] firstName; //memória
felszabaditás
delete[] lastName;
//memória felszabaditás
}
hourly.h
#ifndef HOURLY_H
#define HOURLY_H