|
A C++ programozási nyelvet Bjarne Stroustrup fejlesztette ki az AT&T Bell Labs-nál, a 80-as évek elején. Ez a C nyelv továbbfejlesztése, ami három lényeges dologgal egészíti ki a C-t:
A nyelv tervezésénél fontos szempont volt a C-vel való kompatibilitás, ezt oly mértékben sikerült megvalósítani, hogy minden szintaktikailag helyes C program egyben egy szintaktikailag helyes C++ program is.
A változók definiálásának szintakszisa megegyezik a C-beli szintakszissal. A C nyelvhez hasonlóan használhatók a static, auto, volatile illetve register kulcsszók, jelentésük is ugyanaz. Static-ként deklarált belsô változó, illetve külsô változó esetén a kezdôérték 0, amennyiben nem inicializáljuk. Változók inicializálása definiálásukkor is elvégezhetô.
Konstansok definiálása:
Állandók definiálását a típusnév elé írt const típusmódosító szóval jelezhetjük.
A C-hez képest a lényeges újdonság még a scope operátor bevezetése. Ez megoldást nyújt egy elrejtett globális változó (vagy függvény) elérésének problémájára. Erre példa a következô kódrészlet:
const last = 6500; int count(int first, int last) { if (last > ::last) return -1; ... }
A file-ra nézve globális scope-on kívül minden blokk saját scope-al rendelkezik, illetve a namespace kulcsszóval mi is definiálhatunk egy scope-ot, hasonlóan mint egy blokkot:
namespace A { ... void f(char); ... } A::f('a');
A C++-ban a szokásos szekvencia, elágazó, ciklus és ugró utasítások találhatóak meg. Ezek szintaktikája és szemantikája szinte teljesen megegyezik a C nyelvben levôvel.
Különbség csupán ott van, hogy C++-ban nem kötelezô a blokk elején deklarálni a változókat, ezt akárhol megtehetjük, akár például a for ciklus fejlécében:
for (int i=0;i<10;i++) ...
Azonban az így deklarált ciklusváltozó a blokk további részében is látható marad!
Újdonságnak számít, hogy a legutóbbi C++ szabvány a ciklusfejlécben deklarált változók élettartamát a ciklus blokkjának élettartamára csökkenti. Aki esetleg kénytelen olyan C++ fordítót használni, amely ezt a szabványt még nem támogatja, az definiálja a következo makrót:
#define for if(0)else for
A C++ beépített típusai gyakorlatilag megegyeznek a C beépített típusaival. Újdonságot talán csak a referencia típus, illetve az osztály típuskonstrukció. Referencia típust a típusnév után írt hivatkozás (&) operátorral deklarálhatunk. A referencia típusú változók inicializálása kötelezô:
int val = 10; int &refval = val;
A referencia típusú változóra vonatkozó összes művelet az általa referált objektumra vonatkozik, a szintakszis is olyan, mintha magára az objektumról lenne szó. A referencia típusú változót nem lehet "átállítani", hogy egy másik objektumot referáljon.
A C++ erôsen típusos nyelv, ebben eltér a C-tôl. Ha a formális és az aktuális paraméterek között eltérés van, implicit konvertálja a megfelelô típusra, amennyiben ez lehetséges. Amennyiben implicit konverzió nem lehetséges, vagy az argumentumok száma nem megfelelô, hibát jelez a fordító.
A C++ alapvetô típuskonstrukciói megegyeznek a C típuskonstrukcióival. Különbség csupán a rekordok tulajdonságaiban, és az osztály típuskonstrukció bevezetésében van. C++-ban a rekord és unió (struct ill. union típuskonstrukció) törzsében is lehetôségünk van memberfüggvények deklarálására, ilymódon a rekord és az osztály típuskonstrukció csaknem teljesen megegyezik: pusztán annyi a különbség, hogy struct kulcsszó esetén az alapértelmezett hozzáférés publikus, míg class esetén privát.
Az absztrakt adattípus megvalósításához hatékony eszközt nyújt az osztály típuskonstrukció. Errôl részletesebben az OOP-vel foglalkozó részben lesz szó.
A C++-ban a generic megvalósítása a template osztályokkal, illetve függvényekkel lehetséges:
template <class Type> Type min(Type, Type);
A template kulcsszót követi a formális template paraméterlista, < és > jelek között. Ez nem lehet üres. Amennyiben több paraméterbôl áll, ezeket vesszôvel kell elválasztani. A formális template paraméterlistában nem csak típusok, hanem kifejezés paraméterek is lehetnek, ezeket azonban csak konstans kifejezésekkel lehet példányosítani:
template <class Type, int size> class Buffer;
A template osztály deklarációja után teljesen úgy használhatjuk, akár egy "sima" osztályt, de a formális template paramétereket természetesen meg kell adnunk, ez szintén < és > jelek között történik:
Buffer<int,1024>
Tehát template lehet egy osztály vagy egy függvény. Paraméterezhetô típussal, illetve osztállyal, kifejezésekkel. Lehetséges egy template osztályon belül egy másik template osztályt használni.
A kivételkezelés a C++-ban voltaképp hibakezelést jelent. Ehhez három új nyelvi alapszót vezettek be: try, catch, throw. A try kulcsszót követô blokkba kell kerüljenek az esetlegesen hibát okozó utasítások. Hiba esetén a throw utasítással jelezhetjük, hogy hiba történt. Ekkor a megfelelô catch blokk kerül végrehajtásra. Mindez a következô formában adható meg:
try { //veszélyes utasítások } catch( típus [név] ) { //adott kivételtípus esetén a vezérlés ide kerül }
A try blokkot mindig legalább egy catch blokknak követnie kell, de követheti több is. Ha az esetlegesen keletkezô hiba típusára nem vagyunk kiváncsiak, használhatjuk a catch(...) alakú catch blokkot, ez bármely típusú hibát elkap. A különbözô kivételek típusai voltaképp valamely (általában a felhasználó által definiált) típusnak felelnek meg. Mindegyik catch ág specifikálja, hogy milyen típusú kivételeket kezel le. A megfelelô catch ágat a dobott kivétel típusával összevetve választja ki a rendszer. A catch ágak megjelenésük sorrendjében kerülnek megvizsgálásra. Ha nincs megfelelô catch ág, a kivétel továbbadódik az eggyel kijjebb lévô try blokknak. Ha ott sem kezelôdik le, ez folytatódik egészen a legkülsô try blokkig. Ha így sincs megfelelô catch ág, meghívásra kerül a terminate() elôre definiált függvény. (Ez felülbírálható a set_terminate() függvény segítségével.)
A dobott kivételeknek megfelel egy catch ág, ha a következô feltételek közül valamelyik teljesül:
Ha egyszer a rendszer talál egy megfelelô catch ágat, a többi ágat nem vizsgálja meg, még akkor sem, ha ott esetleg tökéletes egyezést találna, ezért a catch ágak sorrendje nem közömbös.
Amint az az elözôekbôl is látszik, a kivételkezelések egymásba ágyazhatók. A kivételkezelô akár át is adhatja a kivételt egy másiknak, a throw paraméter nélküli használatával.
Egy függvény esetében megadható az, hogy a függvény milyen kivételeket tud generálni:
void f(int x) throw(A,B,C);
Eszerint az f függvény csak az A,B és C típusú kivételt generál, vagy azok leszármazottait. Minden más esetben a rendszer meghívja az unexpected() függvényt, melynek alapértelmezett viselkedése a terminate() függvény meghívása. A catch ág lefutása után a try blokk után folytatódik a program végrehajtása.
Mint már az absztrakt adattípusnál említettük, a C++ újdonsága az osztály fogalma. Az osztályban a struktúrához hasonlóan, lehetnek adatok, és memberfüggvények. Az osztályok tagjainak háromfajta elérési szintje lehet: Ezek a private, public és protected. A private tagokat csak az adott osztályon belülrôl érhetjük el. A publikus mezôk bárhonnan elérhetôek, illetve módosíthatóak. A protected mezôket csak az osztályból és leszármazottjaiból lehet elérni. Ha az osztály union kulcsszóval definiáljuk, a mezôk elérése publikus, és a public, private, protected kulcsszavak nem is használhatók. Ilyen osztály nem szerepelhet mint szülô, és nem is származtatható. A mezônevekre való hivatkozás szintakszisa természetesen megegyezik a struktúráknál használt szintakszissal.
Az osztályok egyes példányaihoz automatikusan létrejön a this mutató. Ez mindig az adott példányra mutat, értékét nem lehet megváltoztatni, de explicit módon is használható.
Lehetôségünk van osztályszintű változók, illetve függvények használatára, a static kulcsszó segítségével. Statikus mezôk viszont nem tartalmazhatnak a this mutatóra való utalást, ezért például statikus függvények nem statikus mezôket nem tudnak elérni.
A C++ lehetôséget nyújt konstruktorok és destruktorok írására. A konstruktor az objektum létrejöttének pillanatában automatikusan lefut, a destruktor pedig az objektum felszabadításakor hajtódik végre automatikusan. A konstruktor neve megegyezik az osztály nevével. A destruktor neve pedig az osztály neve, az elején kiegészítve a ~ karakterrel. Konstruktornak és destruktornak nem lehet visszaadott értéke. Ha egy osztályhoz nem definiálunk konstruktort vagy destruktort, akkor automatikusan létrejön egy alapértelmezett, paraméter nélküli konstruktor, illetve destruktor. Ha viszont definiálunk saját konstruktort, nem jön létre ilyen implicit konstruktor. Konstruktoroknak lehetnek alapértelmezett paraméterei, és egy osztályhoz több konstruktor is tartozhat. Destruktorból viszont csak egy lehet, és annak sem lehetnek paraméterei. Az objektumhoz tartozó tárterületek lefoglalását ill. felszabadítását (new, illetve delete) operátorok célszerű a kostruktorban, illetve a destruktorban elvégezni.
Az osztálydefinícióban lehetôségünk van konstans adatmezô létrehozására, ekkor azt a konstruktor segítségével lehet inicializálni.
Az osztályok között az értékadás operátora automatikusan használható, ekkor minden adatmezô a másik objektum megfelelô adatmezôjébe másolódik. Azonban lehetôségünk van ezt a nyelv által automatikusan létrehozott értékadást, vagy valamely más operátort felülbírálni. A ., ::, sizeof, .*, :? operátorok kivételével az összes operátor újradefiniálható. Az operátorfüggvény definíciója:
visszatérési_érték operator=(argumentumok) { ... }
ahol a # jel helyére az átdefiniálni kívánt operátor neve kerül.
Szükség lehet rá, hogy egy függvény, bár nem tagja az osztálynak, mégis hozzáférjen az osztály privát mezôihez. Ezt a friend kulcsszó segítségével tehetjük meg: amennyiben az osztály definíciójába beírjuk a függvény fejlécét a friend kulcsszóval kiegészítve, az adott függvény hozzáfér az osztály privát mezôihez is. Ha egy függvény "barátja" egy osztálynak, akkor az barátja az osztály leszármazottainak is, de csak az eredeti osztályban definiált privát mezôkhöz fér hozzá, az utód osztályokban definiáltakhoz nem. Egy friend függvény nem csak egy osztálynak lehet barátja, hanem akár többnek is, és nem kell hogy egyszerű függvény legyen, lehet egy másik osztály tagfüggvénye, vagy akár egy másik osztály is lehet.
Egy osztályt származathatunk egy ôsosztályból is, ekkor az utódosztály az ôsosztály tulajdonságait (függvényeit, stb.) is sajátjának tudhatja. Az örökölt függvények közül a változtatásra szorulókat újradefiniálhatja. Ekkor a következô módon kell definiálnunk az osztályt:
class utódnév: elérési_mód ôsnév { ... }
Az elérési mód a következô három mód egyike lehet:
Az ôsosztály privát mezôi az utódosztályban is privát mezôk maradnak, függetlenül az öröklés módjától.
Lehetôségünk van többszörös öröklôdés segítségével egyszerre több ôsosztályból egy utód osztályt származtatni. Ekkor a definició alakja a következô:
class utód: mód1 ôs1, mód2 ôs2, ... { ... }
A többszörös öröklôdés engedélyezése azonban bizonyos problémákat is felvet. Tegyük fel például, hogy egy ôsbôl két utódot származtatunk, majd ebbôl a két utódból a következô lépésben egy közös új utódot származtatunk a többszörös öröklôdés segítségével. Ekkor az ôsosztály mezôi két példányban is jelen lennének. Ezt a virtuális öröklés segítségével kerülhetjük el. Az az utód, amelyik virtuálisan örököl, lemond az ôsosztály mezôirôl, amennyiben valamely "testvérében" azok megtalálhatók. Tehát C++-ban nem a többszörösen öröklô utód, hanem annak ôsei döntenek arról, hogy hány példányban legyenek jelen a közös ôs mezôi.
Konstruktorok és destruktorok használata öröklés esetén is megengedett. Konstruktor esetén elôször az ôsosztály, majd az utódosztály konstruktora kerül meghívásra, míg destruktor esetén a sorrend fordított: elôször az utód, majd az ôsosztály destruktora kerül meghívásra.
A virtuális függvények segítségével valósítható meg a polimorfizmus. A virtuális memberfüggvények közül mindig az objektum dinamikus típusának megfelelô hívódik meg. A virtuális függvényt a függvény deklarációja elé írt virtual kulcsszóval jelezhetjük. Lehetôségünk van "absztrakt", azaz üres virtuális függvények írására is:
virtual void f()=0;
Ezeket "pure" virtuális függvényeknek is nevezik.
A destruktorokat általában érdemes virtuális függvényként definiálni.
A C++ nem támogatja a helyességbizonyítást.
A C++ közvetlenül ugyan nem támogatja a párhuzamos végrehajtást, de léteznek hozzá ezt a célt szolgáló könyvtárak, például a PVM. Létezik továbbá a C++-nak párhuzamos kiterjesztése is.