Professional Documents
Culture Documents
Vissza Előre
III.1.1. Alapelemek
Először ismerkedjünk meg az objektum-orientált témakör alapelemeivel! A megértéshez nem szükséges
mély programozási ismeret megléte.
Osztály (class)
Az osztály (class) meghatározza egy dolog (objektum) elvont jellemzőit, beleértve a dolog jellemvonásait
(attribútumok, mezők, tulajdonságok) és a dolog viselkedését (amit a dolog meg tud tenni, metódusok
(módszerek), műveletek, funkciók).
Azt mondhatjuk, hogy az osztály egy tervrajz, amely leírja valaminek a természetét. Például, egy
Teherautó osztálynak tartalmazni kell a teherautók közös jellemzőit (gyártó, motor, fékrendszer,
maximális terhelés stb.), valamint a fékezés, a balra fordulás stb. képességeket (viselkedés).
Osztályok önmagukban biztosítják a modularitást és a strukturáltságot az objektum-orientált
számítógépes programok számára. Az osztálynak értelmezhetőnek kell lennie a probléma területén
jártas, nem programozó emberek számára is, vagyis az osztály jellemzőinek „beszédesnek” kell lenniük.
Az osztály kódjának viszonylag önállónak kell lennie (bezárás – encapsulation). Az osztály beépített
tulajdonságait és metódusait egyaránt az osztály tagjainak nevezzük (C++-ban adattag, tagfüggvény).
Objektum (object)
Az osztály az objektum mintája (példája). A Teherautó osztály segítségével minden lehetséges teherautót
megadhatunk, a tulajdonságok és a viselkedési formák felsorolásával. Például, a Teherautó osztály
rendelkezik fékrendszerrel, azonban az énAutóm (objektum) fékrendszere elektronikusvezérlésű (EBS)
vagy egyszerű légfékes is lehet.
Példány (instance)
Az objektum szinonimájaként az osztály egy adott példányáról is szokás beszélni. A példány alatt a
futásidőben létrejövő aktuális objektumot értjük. Így elmondhatjuk, hogy az énAutóm a Teherautó osztály
egy példánya. Az aktuális objektum tulajdonságértékeinek halmazát az objektum állapotának (state)
nevezzük. Ezáltal minden objektumot az osztályban definiált állapot és viselkedés jellemez.
Metódus (method)
Metódusok felelősek az objektumok képességeiért. A beszélt nyelvben az igéket hívhatjuk
metódusoknak. Mivel az énAutóm egy Teherautó, rendelkezik a fékezés képességével, így a Fékez() ez
énAutóm metódusainak egyike. Természetesen további metódusai is lehetnek, mint például az Indít(), a
GáztAd(), a BalraFordul() vagy a JobbraFordul(). A programon belül egy metódus használata általában
csak egy adott objektumra van hatással. Bár minden teherautó tud fékezni, a Fékez() metódus
aktiválásával (hívásával) csak egy adott járművet szeretnénk lassítani. C++ nyelven a metódus szó
helyett a tagfüggvény kifejezést használjuk.
III.1. ábra - Az énAutóm objektum (a Teherautó osztály példánya)
#include <iostream>
#include <string>
class Teherauto {
protected:
string gyarto;
string motor;
string fekrendszer;
string maximalis_terheles;
public:
double teher) {
gyarto = gy;
motor = m;
fekrendszer = fek;
maximalis_terheles = teher;
void Indit() { }
void GaztAd() { }
void BalraFordul() { }
void JobbraFordul() { }
};
public:
};
public:
void Navigal() {}
};
int main() {
class OsztályNév {
public:
típus4 Függvény1(paraméterlista1) { }
típus5 Függvény2(paraméterlista2) { }
protected:
típus3 adat3;
private:
típus2 adat2;
};
Az class és a struct osztályok deklarációját a C++ programban bárhol elhelyezhetjük, ahol deklaráció
szerepelhet, azonban a modul szintű (fájl szintű) megadás felel meg leginkább a modern
programtervezési módszereknek.
struct Alkalmazott{
int torzsszam;
string nev;
float fizetes;
};
a.ber *= (1 + szazalek/100);
A struktúra tagjait a pont, illetve a nyíl operátorok segítségével érhetjük el, attól függően, hogy a fordítóra
bízzuk a változó létrehozását, vagy pedig magunk gondoskodunk a területfoglalásról:
int main() {
Alkalmazott mernok;
mernok.torzsszam = 1234;
mernok.ber = 2e5;
BertEmel(mernok,12);
pKonyvelo->torzsszam = 1235;
pKonyvelo->ber = 3e5;
BertEmel(*pKonyvelo,10);
cout << pKonyvelo->ber << endl;
delete pKonyvelo;
Természetesen a fent bemutatott módon is lehet strukturált felépítésű, hatékony programokat fejleszteni,
azonban ebben a fejezetben mi tovább megyünk.
struct Alkalmazott {
int torzsszam;
string nev;
float ber;
ber *= (1 + szazalek/100);
};
Első látásra feltűnik, hogy a BertEmel() függvény nem kapja meg paraméterként az osztály típusú
változót (objektumot), hiszen alapértelmezés szerint az objektumon végez műveletet. Az Alkalmazott
típusú objektumok használatát bemutató main () függvény is valamelyest módosult, hiszen most már a
változóhoz tartozó tagfüggvényt hívjuk:
int main() {
Alkalmazott mernok;
mernok.torzsszam = 1234;
mernok.ber = 2e5;
mernok.BertEmel(12);
pKonyvelo->torzsszam = 1235;
pKonyvelo->ber = 3e5;
pKonyvelo->BertEmel(10);
delete pKonyvelo;
}
III.2.1.3. Adatrejtés
Az osztály típusú változók (objektumok) adattagjainak közvetlen elérése ellentmond az adatrejtés
elvének. Objektum-orientált megoldásoknál kívánatos, hogy az osztály adattagjait ne lehessen
közvetlenül elérni az objektumon kívülről. A struct típus alaphelyzetben teljes elérhetőséget biztosít a
tagjaihoz, míg a class típus teljesen elzárja a tagjait a külvilág elől, ami sokkal inkább megfelel az
objektum-orientált elveknek. Felhívjuk a figyelmet arra, hogy az osztályelemek elérhetőségét a private,
protected és public kulcsszavak segítségével magunk is szabályozhatjuk.
A public tagok bárhonnan elérhetők a programon belül, ahonnan maga az objektum elérhető. Ezzel
szemben a private tagokhoz csak az osztály saját tagfüggvényeiből férhetünk hozzá. (A protected
elérést a III.3. szakasz tárgyalt öröklés során alkalmazzuk.)
Az osztályon belül tetszőleges számú tagcsoportot kialakíthatunk az elérési kulcsszavak (private,
protected, public) alkalmazásával, és a csoportok sorrendjére sincs semmilyen megkötés.
A fenti példánknál maradva, a korlátozott elérés miatt szükséges további tagfüggvényeket megadnunk,
amelyekkel ellenőrzött módon beállíthatjuk (set), illetve lekérdezhetjük (get) az adattagok értékét. A
beállító függvényekben a szükséges ellenőrzéseket is elvégezhetjük, így csak érvényes adat fog
megjelenni az Alkalmazott típusú objektumokban. A lekérdező függvényeket általában konstansként
adjuk meg, ami azt jelöli, hogy nem módosítjuk az adattagok értékét a tagfüggvényből. Konstans
tagfüggvényben a függvény feje és törzse közé helyezzük a const foglalt szót. Példánkban a GetBer()
konstans tagfüggvény.
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
public:
ber *= (1 + szazalek/100);
torzsszam = tsz;
nev = n;
ber = b;
return ber;
};
int main() {
Alkalmazott mernok;
mernok.BertEmel(12);
pKonyvelo->BertEmel(10);
delete pKonyvelo;
Mivel a későbbiek folyamán a fenti forma használhatóságát további megkötések korlátozzák (nem lehet
származtatott osztály, nem lehetnek virtuális tagfüggvényei), ajánlott az inicializálást az osztályok
speciális tagfüggvényeivel, az ún. konstruktorokkal elvégezni.
III.2.1.4. Konstruktorok
Az osztályokat használó programokban egyik leggyakoribb művelet az objektumok létrehozása. Az
objektumok egy részét mi hozzuk részre statikus vagy dinamikus helyfoglalással (lásd fent), azonban
vannak olyan esetek is, amikor a fordítóprogram készít ún. ideiglenes objektumpéldányokat. Hogyan
gondoskodhatunk a megszülető objektumok adattagjainak kezdőértékkel való (automatikus) ellátásáról?
A választ a konstruktornak nevezett tagfüggvények bevezetésével találjuk meg.
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
public:
Alkalmazott() { // default
torzsszam = 0;
nev = "";
ber = 0;
torzsszam = tsz;
nev = n;
ber = b;
torzsszam = a.torzsszam;
nev = a.nev;
ber = a.ber;
ber *= (1 + szazalek/100);
void SetNev(string n) {
nev = n;
return ber;
};
int main() {
Alkalmazott dolgozo;
dolgozo.SetNev("Kiss Pista");
mernok.BertEmel(12);
fomernok.BertEmel(50);
pDolgozo->SetNev("Kiss Pista");
delete pDolgozo;
Alkalmazott *pKonyvelo;
pKonyvelo->BertEmel(10);
delete pKonyvelo;
pFomernok->BertEmel(50);
delete pFomernok;
A fenti példában – a függvénynevek túlterhelését alkalmazva - készítettünk egy paraméter nélküli, egy
paraméteres és egy másoló konstruktort. Látható, hogy a konstruktor olyan tagfüggvény, amelynek neve
megegyezik az osztály nevével, és nincs visszatérési típusa. Az osztály konstruktorát a fordító minden
olyan esetben automatikusan meghívja, amikor az adott osztály objektuma létrejön. A konstruktor nem
rendelkezik visszatérési értékkel, de különben ugyanúgy viselkedik, mint bármely más tagfüggvény. A
konstruktor átdefiniálásával (túlterhelésével) többféleképpen is inicializálhatjuk az objektumokat.
A konstruktor nem foglal tárterületet a létrejövő objektum számára, feladata a már lefoglalt adatterület
inicializálása. Ha azonban az objektum valamilyen mutatót tartalmaz, akkor a konstruktorból kell
gondoskodnunk a mutató által kijelölt terület lefoglalásáról.
Egy osztály alapértelmezés szerint két konstruktorral rendelkezik: a paraméter nélküli (default) és a
másoló konstruktorral. Ha valamilyen saját konstruktort készítünk, akkor a paraméter nélküli
alapértelmezett (default) konstruktor nem érhető el, így azt is definiálnunk kell. Saját másoló konstruktort
általában akkor használunk, ha valamilyen dinamikus tárterület tartozik az osztály példányaihoz.
A paraméter nélküli és a paraméteres konstruktort gyakran összevonjuk az alapértelmezés szerinti
argumentumok bevezetésével:
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
public:
torzsszam = tsz;
nev = n;
ber = b;
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
public:
: torzsszam(a.torzsszam), nev(a.nev),
ber(a.ber) { }
class Szam
private:
int n;
public:
n = x;
Szam( float x) {
};
int main() {
Az a objektum létrehozásakor az explicit konstruktor hívódik meg, míg a b objektum esetén a float
paraméterű. Az explicit szó elhagyásával mindkét esetben az első konstruktor aktiválódik.
III.2.1.5. Destruktor
Gyakran előfordul, hogy egy objektum létrehozása során erőforrásokat (memória, állomány stb.)
foglalunk le, amelyeket az objektum megszűnésekor fel kell szabadítanunk. Ellenkező esetben ezek az
erőforrások elvesznek a programunk számára.
A C++ nyelv biztosít egy speciális tagfüggvényt - a destruktort - amelyben gondoskodhatunk a lefoglalt
erőforrások felszabadításáról. A destruktor nevét hullám karakterrel (~) egybeépített osztálynévként kell
megadni. A destruktor, a konstruktorhoz hasonlóan nem rendelkezik visszatérési típussal.
Az alábbi példában egy 12-elemű, dinamikus helyfoglalású tömböt hozunk létre a konstruktorokban, az
alkalmazottak havi munkaidejének tárolására. A tömb számára lefoglalt memóriát a destruktorban
szabadítjuk fel.
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
int *pMunkaorak;
public:
torzsszam = tsz;
nev = n;
ber = b;
torzsszam = a.torzsszam;
nev = a.nev;
ber = a.ber;
pMunkaorak[i]=a.pMunkaorak[i];
~Alkalmazott() {
delete[] pMunkaorak;
ber *= (1 + szazalek/100);
pMunkaorak[honap-1]=oraszam;
return ber;
};
int main() {
mernok.BertEmel(12);
mernok.SetMunkaora(3,192);
Alkalmazott *pKonyvelo;
pKonyvelo = new Alkalmazott(1235, "Gazdag Reka", 3e5);
pKonyvelo->BertEmel(10);
pKonyvelo->SetMunkaora(1,160);
pKonyvelo->SetMunkaora(12,140);
delete pKonyvelo;
A lefordított program minden olyan esetben meghívja az osztály destruktorát, amikor az objektum
érvényessége megszűnik. Kivételt képeznek a new operátorral dinamikusan létrehozott objektumok,
melyek esetén a destruktort csak a delete operátor segítségével aktivizálhatjuk. Fontos megjegyeznünk,
hogy a destruktor nem magát az objektumot szűnteti meg, hanem automatikusan elvégez néhány
általunk megadott „takarítási” műveletet.
A példaprogram futtatásakor az alábbi szöveg jelenik meg:
224000
330000
Ebből láthatjuk, hogy először a *pKonyvelo objektum destruktora hívódik meg a delete operátor
használatakor. Ezt követően a main () függvény törzsét záró kapcsos zárójel elérésekor automatikusan
aktiválódik a mernok objektum destruktora.
Amennyiben nem adunk meg destruktort, a fordítóprogram automatikusan egy üres destruktorral látja el
az osztályunkat.
Alkalmazott *pKonyvelo;
mernok.BertEmel(12);
pKonyvelo->BertEmel(10);
Felvetődik a kérdés, honnan tudja például a BertEmel() függvény, hogy a hívásakor mely adatterületet
kell elérnie?
Erre a kérdésre a fordító nem látható tevékenysége adja meg a választ: minden tagfüggvény, még a
paraméter nélküliek is, rendelkeznek egy nem látható paraméterrel (this), amelyben a hívás során az
aktuális objektumra mutató pointer adódik át a függvénynek. A fentieken kívül minden adattag-hivatkozás
automatikusan az alábbi formában kerül be a kódba:
this->adattag
A this (ez) mutatót mi is felhasználhatjuk a tagfüggvényeken belül. Ez a lehetőség jól jön, amikor egy
paraméter neve megegyezik valamely adattag nevével:
class Alkalmazott{
private:
int torzsszam;
string nev;
float ber;
public:
this->torzsszam = torzsszam;
this->nev = nev;
this->ber = ber;
};
A this mutató deklarációja normál tagfüggvények esetén Osztálytípus* constthis, illetve const
Osztálytípus*const this a konstans tagfüggvényekben.
III.2.2. Az osztályokról bővebben
Az előző alfejezetben eljutottunk a struktúráktól az osztályokig. Megismerkedtünk azokkal a
megoldásokkal, amelyek jól használható osztályok kialakítását teszik lehetővé. Bátran hozzáfoghatunk a
feladatok osztályok segítségével történő megoldásához.
Jelen fejezetben kicsit továbblépünk, és áttekintjük az osztályokkal kapcsolatos - kevésbé általános -
tudnivalókat. A fejezet tartalmát akkor javasoljuk feldolgozni, ha az Olvasó már jártassággal rendelkezik
az osztályok készítésében.
#include <iostream>
#include <iomanip>
#include <cmath>
class Math {
public:
private:
public:
eMode = mode; }
};
A példában látható módon, az osztályon belül egy felsorolást is elhelyezhetünk. Az így elkészített Egyseg
típusnévre és a felsorolt (fok, radian) konstansokra az osztálynévvel minősített név segítségével
hivatkozhatunk. Ezek a nevek osztály hatókörrel rendelkeznek, függetlenül az enum kulcsszó után
megadott típusnévtől (Math::Egyseg): Math::radian, Math::fok.
A statikus adattagok kezelésére általában statikus tagfüggvényeket használunk (Math::Sin(), Math::Cos(),
Math::Mertekegyseg()). A statikus tagfüggvényekből azonban a normál adattagokhoz nem férhetünk
hozzá, mivel a paramétereik között nem szerepel a this mutató. A nem statikus tagfüggvényekből az
osztály statikus tagjait korlátozás nélkül elérhetjük.
A Math osztály lehetséges alkalmazását az alábbiakban láthatjuk:
int main() {
y = Math::Sin(30);
y = Math::Sin(Math::Pi/6);
Math m; // oszálypéldány
m.Mertekegyseg(Math::fok); // vagy
m.Mertekegyseg(m.fok);
y = m.Sin(30);
m.Mertekegyseg(m.radian); // vagy
m.Mertekegyseg(Math::radian);
y = m.Sin(Math::Pi/6);
m.KiirPI();
class Pont {
private:
int x,y;
public:
Pont(int a = 0, int b = 0) { x = a; y = b; }
void SetX(int a) { x = a; }
void SetY(int a) { y = a; }
};
class Pont {
#ifndef __PONT_H__
#define __PONT_H__
class Pont {
private:
int x,y;
public:
};
#endif
#include <iostream>
#include "Pont.h"
Pont::Pont(int a, int b) {
x = a; y = b;
return x;
}
int Pont::GetY() const {
return y;
void Pont::SetX(int a) {
x = a;
void Pont::SetY(int a) {
y = a;
x = a; y = b;
x = p.x; y = p.y;
cout<<"("<<x<<","<<y<<")\n";
#ifndef __PONT_H__
#define __PONT_H__
class Pont {
private:
int x,y;
public:
};
cout<<"("<<x<<","<<y<<")\n";
#endif
class AOsztaly;
class BOsztaly {
public:
};
class COsztaly {
// ...