Az 1990-es évek óta az objektum-orientált programozási stílus előtérbe
került a strukturált programozással szemben. A strukturált program adatból és
algoritmusból épül fel, míg az objektum-orientált program emellett absztrakciót
is végez. Az absztrakció az objektumok révén valósul meg, melyek
leegyszerűsítik az adatok és algoritmusok kezelését. Az objektumokat
tulajdonságaik alapján osztályozni lehet, az adott osztályhoz tartozó
objektum pedig az osztály példányának tekinthető.
1. Osztályok és objektumok
Példaképp adott egy négyzethálós kép. Minden négyzet egy objektum,
melynek van egy pozíciója és egy színe. Ezzel a két adattal művelet hajtható
végre, például megváltoztatható a szín vagy újra rajzolható a négyzet. Mivel minden négyzetnek
van színe és pozíciója, ezért egy osztályba sorolható, a Négyzet osztályba.
A Négyzet osztály létrehozása egy adattípus létrehozásával ér fel, mert lehetővé teszi a Négyzet típusú változók deklarálását. Ezeket a változókat a Négyzet osztály objektumainak hívjuk. Például a
Négyzet obj utasítás az obj objektumot deklarálja, amely Négyzet típusú, és rendelkezik a szín és pozíció tagváltozókkal és a Színez, Rajzol tagfüggvényekkel.
2. Öröklés
Nem csak négyzetek, hanem háromszögek is részesei lehetnek a képnek.
Ezeknek ugyanazok a tulajdonságai vannak és
ugyanazok a műveletek végezhetőek el velük, de mégsem teljesen ugyanazok, mert például az újrarajzoló algoritmus
más. Továbbá beiktatható egy új tulajdonság ami méginkább megkülünbözteti a háromszöget a négyzettől: a csúcsok
száma.
A hasonlóságok miatt ezt a két osztályt egy felsőbb absztrakciós szinten közös osztályba lehet sorolni: a Formák osztályba.
A Formák
osztály még absztraktabb a Négyzet és
Háromszög osztályoknál, mert
méginkább kiiktatja a kevésbé fontos információkat. Nem foglalkozik azzal, hogy
a forma négyzet-e vagy háromszög, az majd lentebb eldől. Magába foglalja a
közös tulajdonságokat, így ezeket nem kell külön deklarálni mindenik alsó
osztályban. Az alsó osztályok (Négyzet és
Háromszög) megöröklik a Formák tulajdonságait. Ami nem egyforma
azt felülírja mindenik a maga osztályában (például a csúcs tagváltozót és a Rajzol tagfüggvényt). A Négyzet és a Háromszög
osztályok származtatott (vagy derivált) osztályai a Formák
osztálynak. A Formák osztály a
származtatott osztályok ősosztálya (vagy alaposztálya). Az öröklődés célja, hogy közös
tulajdonságok általánosabbá váljanak kezelhetőbbé téve az
információt.
3. Struktúrák
A hagyományos strukturált programozási nyelv a C. A Pascal, a Deplphi és
a C++ csupán támogatják az objektum-orientált módszereket, azonban alapjában
ezek is strukturált programozási nyelvek. Tisztán objektum-orientált például a
Java és a C#. A C++-ban
használt osztályok a C-ben használt struktúrákból fejlődtek ki. Összesített
adattípust jelentenek, azaz különböző típusú adatokra és függvényekre lehet
egyetlen változóval hivatkozni, amit objektumoknak hívunk.
Példa:
#include <iostream>
using namespace std;
struct Time
{
int hour; //0-23
int minute; //0-59
int second; //0-59
void printShort();
void printLong();
};
void Time::printShort() //13:30
{
cout
<< (hour < 10 ? "0" : "") << hour
<< ":"
<< (minute < 10 ? "0" : "")
<< minute;
}
void Time::printLong() //1:30:10 PM
{
cout
<< ((hour == 0 || hour == 12) ? 12 : hour % 12)
<< ":"
<< (minute < 10 ? "0" : "")
<< minute
<< ":"
<< (second < 10 ? "0" : "")
<< second
<< "
"
<< (hour < 12 ? "AM" : "PM");
}
int main()
{
Time
timeObject, // Time típusú objektum
*timePtr, // Time típusú mutató
&timeRef = timeObject;// Time típusú referencia
timeObject.hour
= 6;
cout
<< "timeObject.hour = "
<< timeObject.hour << endl;
cout
<< "timeRef.hour = "
<< timeRef.hour << endl;
timePtr
= &timeObject;
cout
<< "timePtr->hour = "
<< timePtr->hour << endl << endl;
Time
Lunch;
Lunch.hour
= 13;
Lunch.minute
= 30;
Lunch.second
= 10;
cout
<< "Ebéd idő: ";
Lunch.printShort();
cout
<< " azaz ";
Lunch.printLong();
return 0;
}
A fenti programban egy Time nevű struktúra van definiálva, mely 3
változóval és 2 függvénnyel rendelkezik. A függvényeket lehetne a struktúrán
belül is definiálni, azonban szebb ha külön, a struktúrán kívül definiáljuk.
Mivel több struktúrának is lehet azonos nevű függvénye, a függvény neve elé
dupla kettősponttal oda kell írni a struktúra nevét is ahová tartozik.
A program elején három darab Time típus
van deklarálva: egy objektum, egy mutató és egy referencia. A struktúra
definiálása önmagában nem foglal semmi memóriát, ha nem használjuk akkor
szabadon marad a neki szánt tárhely. Amikor azonban egy Time típusú objektumot
deklarálunk, akkor lefoglalódnak a struktúrában deklarált változók helyei. A
struktúra tagjai a pont (.) és a nyíl (->) operátorokkal érhetőek el. A
pontot az objektumon vagy az objektum referenciáján keresztül való elérésénél
használjuk, míg a nyilat akkor, ha mutató segítségével hivatkozunk az
objektumra. A program kimenete a következő:
timeObject.hour
= 6
timeRef.hour = 6
timePtr->hour
= 6
Ebéd idő: 13:30
azaz 1:30:10 PM
A struktúrák a lehetőségek szempontjából korlátozottabbak
az osztályoknál, például nem lehet privát változókat deklarálni, melyeket csak
a tagfüggvények érhetnének el, mert alapból minden publikus, azaz kívülről
hozzáférhető. A programírás felügyelete pedig sokkal korlátlanabb a
struktúráknál, például megengedi, hogy inicializálatlanul hagyjuk a változókat
vagy hogy ne ellenőrizzük le annak helyességét (pl. egy interfészen keresztül). Az
osztályok esetén az ilyen hibákra már a fordításnál fény derül és ezzel
csökken a hibás programok írásának esélye. A gyakorlott programozók a
struktúrákkal is meg tudják oldani mindazt amit az osztályokkal lehet, ám sokkal
nagyobb erőfeszítést és figyelmet igényel.
4. Osztályok
Az osztályok implementálása nagyon hasonló a struktúrákéhoz. Lássuk az
előző programot osztállyal:
#include <iostream>
using namespace std;
class Time
{
public: // bárhol elérhető
Time(); //
konstruktor
void printShort();
void printLong ();
void setTime(int, int, int);
private: // csak a tagfüggvények látják
int hour;
int minute;
int second;
};
Time::Time()
{
hour
= minute = second = 0; //inicializálás
}
void Time::printShort()
{
cout
<< (hour < 10 ? "0" : "") << hour
<< ":"
<< (minute < 10 ? "0" : "")
<< minute;
}
void Time::printLong()
{
cout
<< ((hour == 0 || hour == 12) ? 12 : hour % 12)
<< ":"
<< (minute < 10 ? "0" : "")
<< minute
<< ":"
<< (second < 10 ? "0" : "")
<< second
<< "
"
<< (hour < 12 ? "AM" : "PM");
}
void Time::setTime(int
h, int m, int
s)
{
hour
= (h >= 0 && h < 24) ? h : 0;
minute
= (m >= 0 && m < 60) ? m : 0;
second
= (s >= 0 && s < 60) ? s : 0;
}
int main()
{
Time
t; // a tagváltozók lenullázódnak a konstruktorban
cout
<< "\nRövid változat: ";
t.printShort();
cout
<< "\nHosszú változat: ";
t.printLong();
t.setTime(13,27,6);
cout
<< "\nRövid változat a setTime után:
";
t.printShort();
cout
<< "\nHosszú változat a setTime után:
";
t.printLong();
return 0;
}
A struktúráktól eltérően az osztályok
rendelkeznek private, public és protected specifikációkkal. Ezek a tagokhoz
való hozzáférést határozzák meg. A privát tagokhoz csak a tagfüggvények férnek
hozzá (a főprogramban érhetőek el), a
publikushoz mindenki hozzáfér aki az objektumot is eléri, a protected tagokat pedig csak az örökös osztályok érhetik el. A tagfüggvényeket közvetlenül a
deklarálásnál is lehet definiálni, de szebb, ha csak a prototípust (deklarálást) írjuk az
osztályba, a definiálást pedig azon kívül, akár a struktúrák esetében. A tagfüggvények
összessége az osztály interfészét képezi, hiszen csak azokon keresztül érhetőek
el és módosíthatók a privát tagváltozók. Publikus tagváltozókat ritkán
használunk. A tagfüggvények listájába tartozik az osztály nevével azonos nevű
függvény, a konstruktor. Ez minden alkalommal automatikusan meghívódik
valahányszor létrehozunk egy objektumot, soha nem térít vissza semmit, ezért
ebbe a függvénybe a tagváltozók inicializálását szokás beírni.
A fenti program a következőt írja ki:
Rövid változat:
00:00
Hosszú változat:
12:00:00 AM
Rövid változat a setTime után: 13:27
Hosszú változat a setTime után: 1:27:06 PM
Pluszban van a setTime interfész tagfüggvény, ami a privát tagokhoz ad
hozzáférést és közben ellenőrzi, hogy a megadott értékek a megfelelő
tartományban vannak-e. A tagfüggvények olyan módon is megírhatók, hogy azok
közvetlen hozzáférést adjanak a privát változókhoz, ez viszont helytelen taktika,
ugyanis kiiktatja a változók ellenőrzését. Tegyük be az int& badSetHour(int); függvényt a publikus tagfüggvények. Ez egy referenciát fog visszatéríteni,
a hour változó referenciáját:
int& Time::badSetHour(int
h)
{
int& ora = hour; // az "ora" a hour tagváltozó referenciája
ora =
(h >= 0 && h < 24) ? h : 0; // ellenőrzés
return ora;
}
A hour
tagváltozó kiirásához a következő publikus tagfüggvény használható:
int Time::getHour(){ return hour; }
Ezek segítségével a main()függvény végén közvetlenül lehet módosítani a hour tagváltozót akár hibás értékkel is:
int& hourRef = t.badSetHour(20); // itt még van belső ellenőrzés
hourRef = 87; //a
hour tagváltozó módosítása - ellenőrzés nélkül
cout << "\nA módosított óra: " <<
t.getHour();
A program kimenete:
Rövid változat:
00:00
Hosszú változat:
12:00:00 AM
Rövid változat a setTime után: 13:27
Hosszú változat a setTime után: 1:27:06 PM
A módosított óra: 87
5. Kódrendezés
A programozásban általában, főként az objektum-orientált programozásban
fontos, hogy a függvényeket és azok használatát külön felületre, más fájlokba helyezzük. Az osztályok esetén az osztály deklarálását, definícióját
és alkalmazását is elválasztják egymástól. Rendszerint az osztályokat header (fejléc) fájlokban deklarálják (.h), melyet aztán az include segítségével láthatóvá lehet
tenni a program számára. A fenti programot a következőképp lehet felbontani:
time.h
#ifndef TIME_H
#define TIME_H
class Time
{
public: // bárhol elérhető
Time(); //
konstruktor
void printShort();
void printLong ();
void setTime(int, int, int);
private: // csak a tagfüggvények látják
int hour;
int minute;
int second;
};
#endif
time.cpp
#include <iostream>
#include "time.h"
using std::cout;
Time::Time()
{
hour
= minute = second = 0; //inicializálás
}
void Time::printShort()
{
cout
<< (hour < 10 ? "0" : "") << hour
<< ":"
<< (minute < 10 ? "0" : "")
<< minute;
}
void Time::printLong()
{
cout
<< ((hour == 0 || hour == 12) ? 12 : hour % 12)
<< ":"
<< (minute < 10 ? "0" : "")
<< minute
<< ":"
<< (second < 10 ? "0" : "")
<< second
<< "
"
<< (hour < 12 ? "AM" : "PM");
void Time::setTime(int
h, int m, int
s)
{
hour
= (h >= 0 && h < 24) ? h : 0;
minute
= (m >= 0 && m < 60) ? m : 0;
second
= (s >= 0 && s < 60) ? s : 0;
}
test_time.cpp
#include <iostream>
#include "time.h"
using std::cout;
int main()
{
Time
t; // a tagváltozók lenullázódnak a konstruktorban
cout
<< "\nRövid változat: ";
t.printShort();
cout
<< "\nHosszú változat: ";
t.printLong();
t.setTime(13,27,6);
cout
<< "\nRövid változat a setTime után:
";
t.printShort();
cout
<< "\nHosszú változat a setTime után:
";
t.printLong();
return 0;
}
A főprogram csak a time.h fájlt látja, ami az osztály deklarációját tartalmazza. Az osztálydefiníciót, ami a time.cpp fájlban van, a linker találja meg. Ez a kompilálásnál kezdődik, amikor minden .cpp kiterjesztésű fájlból a kompilátor egy .o kiterjesztésű obejktumfájlt generál. Ezekben külső és belső szimbólumok találhatók. A test_time.o-ban például a printShort() külső szimbólum, mert definíciója nem ott van. A time.o-ban ugyanez belső szimbólumként szerepel. A linker feladata, hogy a szimbólumokat listázza és annak alapján összekapcsolja az objektumfájlokat, létrehozván a futtatható programot. A test_time.o vizsgálata során észreveszi, hogy a printShort() egy megoldatlan külső szimbólum, így keresgélni kezd az eddig megtalált szimbólumok táblázatában. A hiányzó szimbólumot a time.o objektumfájlban fogja megtalálni, ezért összekapcsolja a test_time.o-val.
Az include tulajdonképpen a "foglald bele" szószoros jelentése, vagyis ugyanaz, mintha az itt megadott fájl teljes tartalma szerepelne ebben a sorban.
A tagfüggvénynek olyan függvényeket írunk, amilyent a feladat megkövetel, azonban az átláthatóság kedvéért ezeket is csoportosítani lehet:
Függvények, melyek lekérdezik a tagváltozók értékeit (getter), pl. void getHour().
Függvények, melyek értéket adnak a tagváltozóknak (setter), pl. void setHour(int).
Függvények, melyek az osztály szolgáltatásait végzik, azaz műveleteket végeznek a tagváltozókkal.
Függvények, melyek alapműveleteket végeznek: inicializálás, osztályok összekapcsolása, típuskonverzió, memóriakezelés, stb.
Segítő függvények, melyeket a többi tagfüggvény használ. Ezek privát függvények, például a lekérdező vagy az értékadó függvények használhatják az isAM()vagy isPM() segítő függvényeket.
Az include tulajdonképpen a "foglald bele" szószoros jelentése, vagyis ugyanaz, mintha az itt megadott fájl teljes tartalma szerepelne ebben a sorban.
A tagfüggvénynek olyan függvényeket írunk, amilyent a feladat megkövetel, azonban az átláthatóság kedvéért ezeket is csoportosítani lehet:
Függvények, melyek lekérdezik a tagváltozók értékeit (getter), pl. void getHour().
Függvények, melyek értéket adnak a tagváltozóknak (setter), pl. void setHour(int).
Függvények, melyek az osztály szolgáltatásait végzik, azaz műveleteket végeznek a tagváltozókkal.
Függvények, melyek alapműveleteket végeznek: inicializálás, osztályok összekapcsolása, típuskonverzió, memóriakezelés, stb.
Segítő függvények, melyeket a többi tagfüggvény használ. Ezek privát függvények, például a lekérdező vagy az értékadó függvények használhatják az isAM()vagy isPM() segítő függvényeket.
A következő program példát ad néhány függvénytípusra a
felsoroltak közül:
sales.h
#ifndef SALES_H
#define SALES_H
class SalesPerson
{
public:
SalesPerson(); // alapművelet: inicializálás
void getSalesFromUser(); // lekérdező
void setSales(int, double); // értékadó
void printAnnualSales(); // szolgáltatás
private:
double totalAnnualSales(); // segítő
double sales[12];
};
#endif
sales.cpp
#include <iostream>
#include <iomanip>
#include "sales.h"
using namespace std;
SalesPerson::SalesPerson() // kezdetben
a tömb minden eleme nulla
{
for(int i = 0; i <
12; i++)
sales[i]
= 0.0;
}
void SalesPerson::getSalesFromUser() // beolvassa a havi eladásokat
{
double salesFigure;
for(int i = 1; i
<= 12; i++)
{
cout
<< "Az eladások értéke a(z) "
<< i << ". hónapban: ";
cin
>> salesFigure;
setSales(i,
salesFigure);
}
}
void SalesPerson::setSales(int
month, double amount) //
beteszi a tömbbe a beolvasott értéket
{
if(month >= 1 && month <= 12 &&
amount > 0)
sales[month
- 1] = amount; //a
tömb indexe 0-tól indul
else
cout
<< "A hónap vagy az eladások száma
hibás!" << endl;
}
void SalesPerson::printAnnualSales() //beállítja a tizedes pontosságot és kiírja az összeget
{
cout
<< setprecision(2) << setiosflags(ios::fixed | ios::showpoint)
<< endl
<< "Az évi eladások száma: " <<
totalAnnualSales() << " EUR"
<< endl;
}
double SalesPerson::totalAnnualSales() // összeadja a tömb értékeit
{
double total = 0.0;
for(int i = 0; i <
12; i++)
total
+= sales[i];
return total;
};
test_sales.cpp
#include "sales.h"
int main()
{
SalesPerson
s;
s.getSalesFromUser();
s.printAnnualSales();
return 0;
}
A SalesPerson osztálynak van
egy 12 elemű privát tömbje, melyben a havi eladások értékeit tárolja. A
konstruktor ezt nullákkal inicializálja és a setSales értékadó
függvény a billentyűzetről beolvasott értékekkel tölti fel. A printAnnualSales osztályszolgáltatást végző függvény a totalAnnualSales segítő függvényt használja az összeadás elvégzésére. A
program eredménye a következő:
Az eladások értéke a(z) 1. hónapban: 34.25
Az eladások értéke a(z) 2. hónapban: 57.61
Az eladások értéke a(z) 3. hónapban: 14
Az eladások értéke a(z) 4. hónapban: 32.09
Az eladások értéke a(z) 5. hónapban: 55.58
Az eladások értéke a(z) 6. hónapban: 35.77
Az eladások értéke a(z) 7. hónapban: 76
Az eladások értéke a(z) 8. hónapban: 42
Az eladások értéke a(z) 9. hónapban: 35
Az eladások értéke a(z) 10. hónapban: 46
Az eladások értéke a(z) 11. hónapban: 67.15
Az eladások értéke a(z) 12. hónapban: 38
Az évi eladások száma:
533.45 EUR
6. Konstruktorok és
destruktorok
A konstruktor egy olyan tagfüggvény, ami alapműveleteket végez, nem
térít vissza semmit és automatikusan meghívódik az objektum létrehozásakor.
Ugyanaz a neve mint az osztálynak, átdefiniálható (túlterhelhető) és többnyire a tagváltozók
inicializálására használják. A time programban ez a legegyszerűbb módon
történt:
time.h
Time();
time.cpp
Time::Time()
{
hour
= minute = second = 0; //inicializálás
}
A kezdeti értékek megadhatók a deklarálásnál is:
time.h
Time(int = 0, int = 0, int = 0);
time.cpp
Time::Time(int
hr, int min, int
sec)
{
setTime(hr, min, sec);
}
test_time.cpp
Time t1(12, 25, 42) //hour=12, minute=25, second=42
t2(21, 34); //hour=21, minute=34, second=0
t3; //hour=0,
minute=0, second=0
A header fájlban a konstruktor
prototípusában már nullázni lehet a kezdeti paramétereket, ezáltal ha ezek
hiányoznának az objektum létrehozásakor, akkor nullának lesznek értelmezve és a
setTime három nullával hívódik meg. A konstruktor
használata nem kötelező, a fordító úgyis létrehoz egyet, ám nem minden fordító inicializálja a tagváltozókat.
A destruktor szintén egy tagfüggvény és
szintén az osztály nevével azonos névvel rendelkezik, melyet a ~ jel előz meg. Ez akkor hívódik meg
automatikusan, mielőtt az objektum törlődne a memóriából, azaz amikor a program
elhagyja azt a tartományt ahol az objektum deklarálva volt. A destruktor tehát
nem törli az objektumot, hanem lehetőséget ad néhány művelet elvégzésére az
objektum törlődése előtt. Nincs paramétere és nem térít vissza semmit. Ritkán
használunk destruktorokat, mert csak olyan esetekben hasznosak amikor az
objektumok dinamikusan lefoglalt memóriarészekre utaló referenciákat (is)
tartalmaznak.
Ha több objektumot hozunk létre, akkor a
konstruktorok a létrehozás sorrendjében hívódnak meg, a destruktorok pedig
néhány kivétel esetében fordított sorrendben. A globális szinten deklarált
objektumok konstruktorai minden más függvény előtt meghívódnak, ide értve a main-t is. Ezen objektumok destruktorai a main végeztével futnak le, vagy amikor
meghívódik az exit függvény. Az abort esetén nem hívódik meg a destruktor. Ha az objektum statikus (static), akkor a
konstruktor csak egyetlen egyszer hívódik meg és csakis az exit meghívásakor vagy a main befejezésekor bontódik le az objektum. A következő
program különböző kódtartományokban deklarál és bont le objektumokat:
create.h
#ifndef CREATE_H
#define CREATE_H
class CreateAndDestroy
{
public:
CreateAndDestroy(int); //konstruktor
~CreateAndDestroy(); //destruktor
private:
int data;
};
#endif
create.cpp
#include <iostream>
#include "create.h"
using namespace std;
CreateAndDestroy::CreateAndDestroy(int value) // konstruktor
{
data
= value;
cout
<< "Konstruktorban: "
<< data;
}
CreateAndDestroy::~CreateAndDestroy() // destruktor
{
cout
<< "Destruktorban: "
<< data << endl;
}
test_create.cpp
#include <iostream>
#include "create.h"
using namespace std;
void Create();
CreateAndDestroy first(1); // legutoljára
bontja le, mert globális
int main()
{
cout
<< " (Globálisan kreált objektum a main
előtt)" << endl;
CreateAndDestroy
second(2);
cout
<< " (Helyi automatikus objeutm)"
<< endl;
static CreateAndDestroy third(3);
cout
<< " (Helyi statikus objektum)"
<< endl;
Create();
//egy globális függvény
CreateAndDestroy
fourth(4);
cout
<< " (Helyi automatikus objektum a
Create() után)" << endl;
return 0;
}
void Create()
{
CreateAndDestroy
fifth(5);
cout
<< " (Create - helyi automatikus
objeutm)" << endl;
static CreateAndDestroy sixth(6);
cout
<< " (Create - helyi statikus
objektum)" << endl;
CreateAndDestroy
seventh(7);
cout
<< " (Create - helyi automatikus
objektum a függvény végén)" << endl;
}
A protram kimenete a következő:
Konstruktorban:
1 (Globálisan kreált objektum a main előtt)
Konstruktorban:
2 (Helyi automatikus objeutm)
Konstruktorban:
3 (Helyi statikus objektum)
Konstruktorban:
5 (Create - helyi automatikus objeutm)
Konstruktorban:
6 (Create - helyi statikus objektum)
Konstruktorban:
7 (Create - helyi automatikus objektum a függvény végén)
Destruktorban: 7
Destruktorban: 5
Konstruktorban:
4 (Helyi automatikus objektum a Create() után)
Destruktorban: 4
Destruktorban: 2
Destruktorban: 6
Destruktorban: 3
Destruktorban: 1
A first
objektum globális szinten van, ezért ez lesz lebontva legutoljára, miután a main is véget ért. A main-ben három objektum van: second, third, fourth. Ezek sorban
épülnek fel a konstruktorral és fordított sorrendben bontódnak le. A Create függvényben további három objektum szerepel: fifth, sixth, seventh. Ezek is
sorrendbe épülnek fel, azonban a statikus sixth mindvégig a memóriában marad, míg a main be nem zárul. Ugyanez érvényes a szintén
statikus third és a globális first objektumra is.
7. Objektumok másolása
Az „=” hozzárendelő operátor használható az objektumok másolására. Tagot
taggal másol, azaz mindenik tag az egyik objektumból átmásolódik a másik
objektum azonos nevű tagjaiba. Ugyanez történik akkor is, amikor függvény kap
objektum paramétert, vagy mikor objektumot térít vissza. Mindkét művelet során
értékátadás történik, vagyis az objektumnak egy másolatát küldjük vagy kapjuk
vissza. Vigyázni kell arra, hogy ne manipuláljunk túl nagy terjedelmű objektumot
ilyen módon, mert az negatív hatással lesz a program teljesítményére. Ilyen
esetben referenciával vagy mutatóval kell az objektumokat átadni, ez viszont
biztonsági szempontból előnytelen, mert az adott függvénynek közvetlen
hozzáférése lesz az eredeti objektumhoz.
#include <iostream>
using std::cout;
using std::endl;
class Date
{
public:
Date(int = 1, int = 1, int = 1990);
void print();
private:
int day;
int month;
int year;
};
//hibalellenőrzés nélkül
Date::Date(int
d, int m, int
y)
{
day =
d;
month
= m;
year
= y;
}
void Date::print()
{
cout
<< day << '-' << month
<< '-' << year;
}
int main()
{
Date
date1(9, 3, 2016), date2;
cout
<< "date1 = ";
date1.print();
cout
<< endl << "date2 = ";
date2.print();
date2
= date1; //tagot-taggal
való másolás
cout
<< endl << endl << "Objektum
másolás után, date2 = ";
date2.print();
cout
<< endl;
return 0;
}
A fenti program az „=” hozzárendelő
műveleti jellel másolta át a date1 objektumot a date2 objektumba. A kimenet:
date1 = 9-3-2016
date2 = 1-1-1990
Objektum másolás után, date2 = 9-3-2016
Objektum másolás után, date2 = 9-3-2016
8. Konstans objektumok és
konstans tagfüggvények
A program stabilitása és biztonsága érdekében használhatók konstans
objektumok és konstans tagfüggvények. Konstans
tagfüggvénynek például a get
típusú függvényeket (getter) deklarálják, hogy a fordító már előre jelezze, ha a
lekérdező függvény módosítani próbál az objektumokon vagy tagváltozón:
int Time::getHour() const { return hour };
A konstruktor és a destruktor kivételes esetek, ők szabad módosítsák az objektumokat, hisz az inicializálás és a törlés
fontos művelet. A konstansok használata nem csak programozás szempontjából jó,
hanem a program teljesítményén is javít, mert a fordító optimizálja a
konstansnak deklarált változókat.
time.h
#ifndef TIME_H
#define TIME_H
class Time
{
public:
Time(int = 0, int = 0, int = 0); //konstruktor -
kezdő értékek
// setter függvények
void setTime(int, int, int);
void setHour(int);
void setMinute(int);
void setSecond(int);
// getter függvények - konstansok
int getHour() const;
int getMinute() const;
int getSecond() const;
// kiíró függvények - konstansok
void printShort() const;
void printLong ();
private:
int hour; //0-23
int minute; //0-59
int second; //0-59
};
#endif
time.cpp
#include <iostream>
using std::cout;
#include "time.h"
Time::Time(int
hr, int min, int
sec)
{
setTime(hr,
min, sec);
}
void Time::setTime(int
h, int m, int
s)
{
setHour(h);
setMinute(m);
setSecond(s);
}
void Time::setHour(int
h)
{
hour
= (h >= 0 && h < 24) ? h : 0;
}
void Time::setMinute(int
m)
{
minute
= (m >= 0 && m < 60) ? m : 0;
}
void Time::setSecond(int
s)
{
second
= (s >= 0 && s < 60) ? s : 0;
}
int Time::getHour() const
{ return hour; }
int Time::getMinute() const
{ return minute; }
int Time::getSecond() const
{ return second; }
void Time::printShort() const
{
cout
<< (hour < 10 ? "0" : "") << hour << ":" << (minute < 10 ? "0" : "")
<< minute;
}
void Time::printLong()
{
cout
<< ((hour == 0 || hour == 12) ? 12 : hour % 12)
<<
":" << (minute < 10 ? "0" : "")
<< minute
<<
":" << (second < 10 ? "0" : "")
<< second
<<
(hour < 12 ? " AM" : " PM");
}
test_time.cpp
#include "time.h"
int main()
{
Time
wakeUp(6, 45, 0); // nem konstans
objektum
const Time noon(12, 0, 0); //
konstans objektum
wakeUp.setHour(18);
//nem konstans objektum - nem konstans függvény
//noon.setHour(12); //konstans objektum - nem konstans
függvény ----- HIBA!
wakeUp.getHour(); //nem konstans
objektum - konstans függvény
noon.getMinute(); //konstans
objektum - konstans függvény
noon.printShort(); //konstans objektum
- konstans függvény
//noon.printLong(); //konstans objektum - nem konstans
függvény ----- HIBA!
return 0;
}
A fordító rögtön hibát ad amint észleli,
hogy nem konstans függvénnyel próbálunk műveletet végezni konstans objektumon.
Ilyen esetben az objektum inicializálása kötelező a konstruktorban. Ha
egy tagváltozó a konstans, akkor a konstruktor definícióját módosítani kell
hozzá adván a tagváltozók inicializáló listáját:
#include <iostream>
using std::cout;
using std::endl;
class Increment
{
public:
Increment(int c = 0, int i =
1);
void addIncrement(){ count += increment; }
void print();
private:
int count;
const int increment; //konstans tagváltozó
};
Increment::Increment(int
c, int i) : increment(i) // a konstans tagváltozókat itt inicializáljuk
{
count
= c; // a nem-konstans tagváltozót pedig a
megszokott módon
}
void Increment::print()
{
cout
<< "count = " << count
<< ", increment = " <<
increment << endl;
}
int main()
{
Increment
value(10, 5); //konstans 5-tel inkrementálunk végig.
cout
<< "Kezdő értékek: ";
value.print();
for(int j = 0; j <
3; j++)
{
value.addIncrement();
cout
<< j+1 << ". inkrementálás után:
";
value.print();
}
return 0;
}
A kimenet:
Kezdő értékek:
count = 10, increment = 5
1. inkrementálás
után: count = 15, increment = 5
2. inkrementálás
után: count = 20, increment = 5
3. inkrementálás
után: count = 25, increment = 5
A konstruktor paraméterei után lévő : increment(i) azt
jelzi, hogy az increment tagváltozó a paraméterek közül az i-t fogja megkapni értékként. Tulajdonképpen az
összes tagváltozót inicializálhatjuk ilyen módon, ám a konstansoknál
kötelezően így helyes (ellenkező esetben a fordító hibát jelez).
9. Osztályok illesztése
Sokszor szükség lehet egy osztály objektumának használatára egy másik
osztályon belül. Nem öröklésről van szó, hanem például az Employee osztálynak szüksége van dátum kezelésre
és erre a Date osztály éppen megfelelő. Az Employee a következő információkat tartalmazza az alkalmazottakról: name, birthdate, hiredate.
Ezekből a birthdate és a hiredate a Date osztály konstans objektumai és
tartalmazzák a month, day és year tagváltozókat.
date.h
#ifndef DATE_H
#define DATE_H
class Date
{
public:
Date(int = 1, int = 1, int = 1990);
void print() const;
~Date();
private:
int day; //1-12
int month;//1-31
int year;
int checkDay(int); // segítő függvény az ellenőrzéshez
};
#endif
date.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "date.h"
Date::Date(int
d, int m, int
y)
{
if(m > 0 && m <= 12)
month
= m;
else
{
month
= 1;
cout
<< m << " az hibás hónap. Hónap =
1.\n";
}
year
= y;
day =
checkDay(d); //lehet-e ilyen nap a hónapban
cout
<< "Date konstruktor: ";
print();
cout
<< endl;
}
void Date::print() const
{ cout << day << '-' <<
month << '-' << year; }
Date::~Date()
{
cout
<< "Date destruktor: ";
print();
cout
<< endl;
}
int Date::checkDay( int
testDay )
{
static const int daysPerMonth[13] = {0, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
if( testDay > 0 && testDay <=
daysPerMonth[month]) return testDay;
// szökőév:
if(month == 2 && testDay == 29 &&
(year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))) return testDay;
// ha idáig ér a program, akkor a nap hibás volt.
cout
<< testDay << " az hibás nap. Nap
= 1.\n";
return 1;
}
employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include"date.h"
class Employee
{
public:
Employee(char*, int, int, int, int, int, int);
void print() const;
~Employee();
private:
char Name[25];
const Date birthDate;
const Date hireDate;
};
#endif
employee.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <cstring>
#include "employee.h"
Employee::Employee(char*
name, int bday, int
bmonth, int byear, int
hday,
int hmonth, int
hyear) : birthDate(bday, bmonth, byear), hireDate(hday, hmonth, hyear)
{
int length = strlen(name);
length
= (length < 30 ? length : 29); // a név minimum 29 hosszú
strncpy(
Name, name, length);
Name[length]
= '\0'; // az
utolsó karakter a befejező karakter
cout
<< "Employee kosntruktor: "
<< Name << endl;
}
void Employee::print() const
{
cout
<< Name << "\nAlkalmazás: ";
hireDate.print();
cout
<< " Születés: ";
birthDate.print();
cout
<< endl;
}
Employee::~Employee()
{
cout
<< "Employee destruktor: "
<< Name << endl;
}
test_composition.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "employee.h"
#include"date.h"
int main()
{
char fullname[12] = "Chloe
Price";
Employee
e(fullname, 11, 3, 1994, 30, 1, 2015);
cout
<< '\n';
e.print();
cout
<< "\nA Date konstruktor tesztelése
hibás napra es hónapra:\n";
Date
d(35, 14, 2000);
cout
<< endl;
return 0;
}
A kimenet:
Date
konstruktor: 11-3-1994
Date
konstruktor: 30-1-2015
Employee
kosntruktor: Chloe Price
Chloe Price
Alkalmazás:
30-1-2015 Születés: 11-3-1994
A Date
konstruktor tesztelése hibás napra es hónapra:
14 az hibás
hónap. Hónap = 1.
35 az hibás nap.
Nap = 1.
Date
konstruktor: 1-1-2000
Date destruktor:
1-1-2000
Employee
destruktor: Chloe Price
Date destruktor:
30-1-2015
Date destruktor:
11-3-1994
Akár a konstans tagváltozókat, az
objektumokat is a konstruktor fejlécében kell inicializálni. A paraméterlistát
követő kettőspont után a Date
objektumokat inicializáltuk a listában szereplő változókkal. Az Employee osztály látja a Date
osztályt , hisz az elején ott van az #include Date.h. Ha nem
inicializáljuk az objektumokat a paraméterlistában, akkor azok konstruktora
úgyis inicializálja őket az alapértelmezett értékekkel. Ha viszont a
konstruktor is hiányzik a Date
osztályból, akkor azt már jelzi a fordító. A program kimenetén látható, hogy a
konstruktorok közül a Date
osztály építi fel hamarább az objektumait, hiszen ezeket hamarább
inicializálódnak.
10. Friend függvények és
friend osztályok
A friend, azaz a barát függvény nem része az osztály családjának, az osztály tartományán kívül van
definiálva, viszont hozzáfér az osztály private és protected tagjaihoz. Ugyanígy egy egész osztályt is lehet friend-nek deklarálni egy
másik osztály számára. A friend függvényeket akkor használják, ha bizonyos
műveletek nem valósíthatók meg tagfüggvényként (mint például az operátorok átdefiniálása). A következő programban a setX függvény friend függvénye lesz a Count osztálynak és módosítani fogja annak
privát tagváltozóját.
#include <iostream>
using std::cout;
using std::endl;
class Count
{
friend void
setX(Count&, int); //
friend függvény
public:
Count(){
x = 0; }
void print() const {
cout << x << endl; }
private:
int x;
};
void setX( Count& c, int
val ) { c.x = val; }
int main()
{
Count
counter;
cout
<< "x = ";
counter.print();
setX(counter,
8);
cout
<< "setX után x = ";
counter.print();
return 0;
}
A kimenet:
x = 0
setX után x = 8
A setX függvény nem tagfüggvény, ezért kell a hívásnál az objektumot is a
paraméterek közé tenni. A szintaxis szempontjából nem számít, hogy a friend
függvényeket a private, public vagy protected zónába írjuk, de általában rögtön a
kapcsos zárójel után teszik őket, így publikusak lesznek. A friend osztályok esetén is ugyanez a
szabály:
#include"time.h"
class Count
{
friend class Time; // friend osztály
public:
Count(){
x = 0; }
void print() const {
cout << x << endl; }
private:
int x;
};
11. A this mutató
Minden objektum hozzáférhet a saját memóriacíméhez a this mutató segítségével. Ez a mutató nem
része az objektumnak, hanem az fordító tudja őt úgy értelmezni mint első számú
argumentumot, amikor az objektum tagfüggvényeit vagy tagváltozóit használjuk. A
this mutató típusa függ az objektum típusától
és hogy az általa használt tagfüggvény konstans-e vagy sem. Ha például az Employee osztály nem konstans tagfüggvényét
használjuk, akkor a this
típusa Employee* const. Ha
konstans tagfüggvényt hívunk meg, akkor a this típusa const Employee* const lesz. A következő program a this mutató explicit használatát mutatja be:
#include <iostream>
using std::cout;
class Test
{
public:
Test(
int = 0);
void print() const;
private:
int x;
};
Test::Test( int
a ) { x = a; }
void Test::print() const
{
cout
<< "x = " << x;
cout
<< "\nthis->x = "
<< this->x;
cout
<< "\n(*this).x = " <<
(*this).x;
}
int main()
{
Test
testObject(12);
testObject.print();
return 0;
}
A kimenet:
x = 12
this->x = 12
(*this).x = 12
Az x tagváltozó háromféleképp van
kiíratva: közvetlenül és a this
mutatóval nyíl illetve pont segítségével. Az utolsó esetben a zárójel kötelező, különben a fordító *(this.x)-nek értelmezi az utasítást. A this mutató egyik érdekes tulajdonsága, hogy
ha *this-t tértít vissza egy függvény és a típusa Osztály&, akkor az a függvény láncba fűzhető a pont operátor segítségével:
t.setHour(18).setMinute(30).setSecond(22);. Erre látható egy példa a következő programban:
time.h
#ifndef TIME_H
#define TIME_H
class Time
{
public:
Time(int = 0, int = 0, int = 0);
Time
&setTime(int, int,
int);
Time
&setHour(int);
Time
&setMinute(int);
Time
&setSecond(int);
void print() const;
private:
int hour; //0-23
int minute; //0-59
int second; //0-59
};
#endif
time.cpp
#include <iostream>
using std::cout;
#include "time.h"
Time::Time(int
hr, int min, int
sec) { setTime(hr, min, sec); }
Time &Time::setTime(int h, int m, int s)
{
setHour(h);
setMinute(m);
setSecond(s);
return *this; //fűzér mód
}
Time &Time::setHour(int h)
{
hour
= (h >= 0 && h < 24) ? h : 0;
return *this; //fűzér mód
}
Time &Time::setMinute(int m)
{
minute
= (m >= 0 && m < 60) ? m : 0;
return *this; //fűzér mód
}
Time &Time::setSecond(int s)
{
second
= (s >= 0 && s < 60) ? s : 0;
return *this; //fűzér mód
}
void Time::print() const
{
cout
<< ((hour == 0 || hour == 12) ? 12 : hour % 12)
<<
":" << (minute < 10 ? "0" : "")
<< minute
<<
":" << (second < 10 ? "0" : "")
<< second
<<
(hour < 12 ? " AM" : " PM");
}
test_time.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "time.h"
int main()
{
Time
t;
t.setHour(18).setMinute(30).setSecond(22);
t.print();
cout
<< endl;
t.setTime(20,
20, 20).print();
return 0;
}
A kimenet:
6:30:22 PM
8:20:20 PM
12. Az osztályok statikus
tagjai
Egy osztály minden objektumának megvan a saját másolata az osztály
tagjairól. Egyes esetekben csak egyetlen másolatot szükséges fenntartani minden
objektum számára és erre jó a static
típusú tag. Ez a tag az információt osztály szinten és nem objektum szinten
tárolja, azaz globálisnak tekinthető az osztály objektumai számára. Akkor is
létezik, ha nincs egyetlen objektum se deklarálva. A statikus tagváltozókat
csak a statikus tagfüggvények érhetik el. A következő program egy statikus
tagváltozót használ az osztály objektumainak számolására:
employee2.h
#ifndef EMPLOYEE2_H
#define EMPLOYEE2_H
class Employee
{
public:
Employee(const char*, const char*);
~Employee();
const char
*getFirstName() const;
const char
*getLastName() const;
static int
getCount(); // statikus tagfüggvény
private:
char *firstName;
char *lastName;
static int count; // statikus
tagváltozó
};
#endif
employee2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <cstring>
#include <cassert>
#include "employee2.h"
//statikus tagok inicializálása:
int Employee::count = 0;
int Employee::getCount() { return count; }
Employee::Employee(const
char* first, const
char* last)
{
firstName
= new char[strlen(first)
+ 1]; // memória lefoglalás
assert(firstName
!= 0); //
hiba ha nem sikerült lefoglalni
strcpy(firstName,
first); // különben bemásolja a tagváltozóba
lastName
= new char[strlen(last)
+ 1];
assert(lastName
!= 0);
strcpy(lastName,
last);
++count;
//statikus változó növelése
cout
<< "Konstruktor: " <<
firstName << ' ' << lastName
<< endl;
}
Employee::~Employee()
{
cout
<< "Destruktor: "<<
firstName << ' ' << lastName
<< endl;
delete [] firstName; // memória felszabadítás
delete [] lastName;
--count;
//statikus változó csökkentése
}
const char
*Employee::getFirstName() const { return firstName;}
const char
*Employee::getLastName() const { return lastName; }
test_employee2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "employee2.h"
int main()
{
cout
<< "count = " <<
Employee::getCount() << endl; // objektum
létrehozás előtt
Employee
*e1Ptr = new Employee("Mark",
"Jefferson");
Employee
*e2Ptr = new Employee("Rachel",
"Amber");
cout
<< "count = " <<
e1Ptr->getCount(); // objektum létrehozás után
cout
<< "\n\n1.Alkalmazott: "
<< e1Ptr->getFirstName() << "
" << e1Ptr->getLastName();
cout
<< "\n2.Alkalmazott: "
<< e2Ptr->getFirstName() << "
" << e2Ptr->getLastName();
cout
<< endl << endl;
delete e1Ptr; // ettől elindul a destruktor
e1Ptr
= 0;
delete e2Ptr;
e2Ptr
= 0;
cout
<< "count = " <<
Employee::getCount() << endl; // objektum törlése után
return 0;
}
A kimenet:
count = 0
Konstruktor:
Mark Jefferson
Konstruktor:
Rachel Amber
count = 2
1.Alkalmazott:
Mark Jefferson
2.Alkalmazott:
Rachel Amber
Destruktor: Mark
Jefferson
Destruktor:
Rachel Amber
count = 0
Amíg egyetlen objektum se volt létrehozva,
addig a count statikus
tagváltozót csak a getCount statikus
tagfüggvény érte el. Az objektumok létrehozása után a getCount az objektumokon keresztül is elérhetővé vált. A mutatók és a delete parancs segítségével törölni lehet az objektumokat még a program végezte előtt, s így kiíratható a count értéke a destruktorok lefutása után is. A
nem statikus tagoktól eltérően a statikus tagoknak nincs this mutatójuk, mert függetlenek az osztály
objektumaitól. Az objektumok létrehozása a new operátorral történt, mely
dinamikusan foglalta le a memóriát és lehetővé tette a lefoglalt terület
dinamikus törlését a delete operátorral. Tulajdonképpen ez a dinamikus memóriakezelés lényege, hogy
mutatók segítségével memóriát foglalunk le (ellenőrzéssel) és szabadítunk fel
amikor szükséges.
13. Proxy osztályok
Sok esetben előírás, hogy az osztály forráskódjának részletei rejtve
maradjanak a felhasználók elől, hogy azok ne tudjanak hozzáférni a privát
adatokhoz és a program logikájához. A proxy osztály lehetővé teszi ezt, mert
csupán a publikus interfészét ismeri a titkos osztálynak. A következő programban az Implementation osztály a privát adatokkal rendelkező titkos osztály és az Interface a proxy osztály. Az Interface osztálynak ugyanaz a publikus interfésze
mint az Implementation osztálynak
és van egy privát tagváltozója, ami az Implementation osztályra mutat. Ennek segítségével a felhasználó hozzáfér az Implementation osztályhoz, de annak forráskódja rejtve marad előtte.
implementation.h
#ifndef IMPLEMENTATION_H
#define IMPLEMENTATION_H
class Implementation
{
public:
Implementation(int v) { value = v; }
void setValue(int v)
{ value = v; }
int getValue() const
{ return value; }
private:
int value;
};
#endif
interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
class Implementation; //
forward deklarálás
class Interface
{
public:
Interface(int);
void setValue(int);
int getValue() const;
~Interface();
private:
Implementation
*ptr;
};
#endif
interface.cpp
#include "interface.h"
#include "implementation.h"
Interface::Interface(int
v) : ptr( new Implementation(v)){}
void Interface::setValue(int
v) {ptr->setValue(v);}
int Interface::getValue() const
{return ptr->getValue();}
Interface::~Interface() {delete ptr;}
test_interface.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "interface.h"
#include "interface.h"
int main()
{
Interface
i(5);
cout
<< "Az interfész tartalma: "
<< i.getValue() << " a setValue
előtt." << endl;
i.setValue(10);
cout
<< "Az interfész tartalma: "
<< i.getValue() << " a setValue
után." << endl;
return 0;
}
A kimenet:
Az interfész
tartalma: 5 a setValue előtt.
Az interfész
tartalma: 10 a setValue után.
Az interface.h-ban lévő forward deklarálásra az Implementation típusú mutató miatt volt szükség.
Helyette használhattuk voln az #include "implementation.h" direktívát is, ám ebben az esetben
praktikusabb a forward deklarálás, mert csupán egyetlen mutató mutat arra az
osztályra és a fordítónak is könnyebb dolga lesz. Mivel a felhasználó csak
az interface.h-t használhatja, nem láthatja a proxy és
az Implementation osztály
közreműködését.
Nincsenek megjegyzések:
Megjegyzés küldése