Miskolci Egyetem Általános Informatikai Tanszék
Bevezetés a C++ programozási nyelvbe
Oktatási segédlet
Összeállította: Ficsor Lajos
2001.
1. A C++ programozási nyelv története A C++ programozási nyelv a jólismert C programozási nyelv objektum orientált kiterjesztése. Az ANSI-C nyelvet - néhány megszorítással - valódi részhalmazaként tartalmazza. A C++ nyelv elsÿ implementációja az AT&T Bell Labs számára készült el Bjarne Stroustrup vezetésével, a nyolcvanas évek elején. Ez egy pre-compiler volt, amely a C++ kódot C kódra fordította, ami aztán a szokásos C fordítóval volt feldolgozható. A C++ rohamosan terjedése és a nyelvet használó programozók tapasztalatai alapján a C++ definíciója is változott, bÿvült, és felmerült az igény a szabványosításra is. Jelenleg a nyelv leghitelesebb, ingyen hozzáférhetÿ definíciójának az ANSI-hoz szabványként való elismerésre benyújtott, 1996. december 2. dátumú szabványtervezet tekinthetÿ. Ez a körülbelül 700 oldalas dokumentum a nyelvi elemek leírásán kívül szabványosításra ajánl osztálykönyvtárakat is. A hivatalos szabvány szám: ISO/EIC 14882. A C++ jelenleg általánosan elterjedt programozási nyelv, gyakorlatilag minden architektúra támogatja, számos elÿre definiált osztálykönyvtár áll rendelkezésre a különbözÿ jelleg feladatok megoldásának támogatására, és sok fejlesztÿeszköz épül erre a nyelvre. A C++ nyelvet a leggyakrabban az alábbi módokon használják: • funkcionális szemlélet programok készítésére, kényelmi szolgáltatásokkal ellátott C nyelvként (az osztály és objektum használata nélkül) • funkcionális szemlélet programok készítésére, tetszÿleges típusokkal bÿvíthetÿ nyelvként (kihasználva a C++ szinte valamennyi nyelvi konstrukcióját) • objektum orientált szemlélet programok implementálására
2. ANSI-C emlékeztetÿ Ebben a fejezetben felhívjuk a figyelmet az ANSI-C nyelv néhány olyan elemére, amelyek a C++ nyelv programokban gyakrabban, vagy nagyobb súllyal szerepelnek, illetve pontos ismeretük a C++ nyelv elsajátításához fontos.
2
2.1. A const minÿsítÿ Névvel ellátott konstans: const int a=3; Az "a" nem változó, így • értéke nem változtatható meg • kötelezô inicializálni
Konstansra mutató pointer: const char *nev = "Ficsór"; A konstans értéke így nem változtatható meg a pointeren keresztül, de a pointer tetszÿlegesen újradefiniálható. Konstans pointer: char *const nev = "Ficsór"; A pointer értéke nem változtatható meg (hiszen konstans).
Konstansra mutató konstans pointer: conts char *const nev = "Ficsor"; Sem a pointer értéke, sem az általa címzett érték nem változtatható meg.
2.2. Függvényekre mutató pointerek Az ANSI-C megengedi, hogy függvényekre mutató pointereket definiáljunk. A függvénypointer használható értékadásban, elhelyezhetÿ tömbben, szerepelhet struktúra mezÿjeként, lehet függvények paramétere vagy visszatérési értéke. A tömbazonosítóval analóg módon a függvény neve a fordítóprogram számára a függvényre mutató konstans mutatót jelent. Egy kis (formális)példa a függvénypointerek deklarálására és használatára:
3
#include <stdio.h> void fgv1 (int); */ int fgv2 (float);
/* Fgv. Prototípusok
void (*fgvpointer) (int); */
/* Ez nem prototípus!
main() { int i=5,j; float f; fgv1(i);
/* Egyszerÿ függvényhívás */
fgvpointer = fgv1; /* A fgv. neve pointer! */ f = 5; j = fgv2(f); fgvpointer(j); /* Fgv. hívás pointeren keresztül */ } void fgv1 (int a) { printf ("%d\n",a); } int fgv2 (float a) { return int(a*a); }
3. Egyszerÿ kiterjesztések Ebben a fejezetben a C++ olyan, az ANSI-C nyelvhez képest új elemeit ismertetjük, amelyek nem igénylik az osztály fogalmát. Ezeket az elemeket a C nyelvi elemekkel együtt alkalmazva egy kényelmesebben használható C nyelvet kapunk. A legtöbb kiterjesztés azonban a késÿbbiekben az objektum orientált szemléletbe illeszkedÿnek fog bizonyulni.
3.1 Megjegyzés A // (dupla törtvonal) olyan megjegyzés elejét jelzi, amelynek a vége a következÿ sorváltás karakter. Bárhol alkalmazható. 3.2. A definíció A definíció végrehajtható utasításnak számít, így bárhol elÿfordulhat, ahol utasítás írható. Alapszabályként a definiált objektum hatásköre a definíciótól az azt magábanfoglaló blokk végéig terjed. Az alapszabály alól van egy kivétel: A for utasításban foglalt elsÿ két kifejezésben, illetve az if, while és switch utasítások feltételében deklarált változók ezen utasításokra nézve lokális változók. Példa:
4
for (int index=1; index<8; index++) { printf ("%d ",index); char betu = index+65; printf ("%c\n",betu); } Ebben a példában a betu és index egyaránt lokális a ciklusmagra nézve. A különbség csak az, hogy mivel a betu definíciója ténylegesen a ciklusmagot képezÿ blokk belsejében van, így a ciklusmag minden végrehajtásakor létrejön, a végén pedig megsz nik Fontos megjegyzés: A most hatályos szabvány elÿtt nem létezett ez a kivétel, így a fenti példában az index változó hatásköre a ciklusutasítást magában foglaló blokk végéig tartott. A jelenlegi fordítóprogramok a régebbi kódok felhasználását elÿsegítve mindkét esetet tudják kezelni. A kétféle értelmezés között kapcsolókkal lehet választani, Nem egységesek azonban abban, hogy mi legyen az alaértelmezett viselkedés: páldául a Visul C++ 6.0 a régi stílust, míg a Borland C 5.5 és a GCC az új stílust tételezi fel. További megjegyzés, hogy a a késÿbb tárgyalandó un. névterek és catch blokkok esetére további szabályok vonatkoznak.
3.3. Hivatkozás típusú változó Ahogyan minden alaptípusból pointer típus származtatható, a C++ ezen kívül hivatkozás típus származtatását is megengedi. Formája: típusnév & A hivatkozás valamely objektum alternatív neveként szerepel, mindig kötelezÿ tehát inicializálni. Például egy hivatkozás típusú változó szabályos definíciója: int valt; int& masik=valt; A hivatkozás típust a leggyakrabban a függvény paraméterének és visszatérési értékének típusaként használjuk.
3.4. Felsorolás, struktúra vagy unió típusú objektumok deklarálása A C++ a fenti típusú objektumok deklarálása esetén nem követeli meg a deklarációban az enum, struct illetve union kulcszavak alkalmazását, az adatszerkezetek deklarációjában szereplÿ nevek önmagukban típusnévként használhatók. Példa: struct datum { int ev; char honap[15]; int nap;
5
}; datum ma,holnap,szulinap;
// a "struct" alapszó // elmaradhat
3.5. Név nélküli unió Struktúra egyik mezÿjeként deklarálható olyan unió, amelynek nincs neve. Ez egyszer síti az unió valamely mezÿjére való hivatkozást, de következetlen használata számos hiba forrása lehet! Példa: struct gepkocsi { float onsuly; union { int szemely; float raksuly; }; }; gepkocsi trabant,ifa; trabant.szemely = 4; ifa.raksuly = 2500.; float s = trabant.raksuly; történik?
// Kérdés: mi
3.6. Típuskonverzió A cast stílusú típuskonverzió helyett használható az alábbi forma is: típusnév(kifejezés) Tehát pl. (char)a helyett char(a) is írható, és a két forma keverhetÿ egy programon belül. 3.7. Dinamikus memóriakezelés A C programokban megszokott memóriakezelÿ függvények (malloc(), free() és társai) helyett a C++ két új operátort vezet be. Ezeknek nem teljeskör ismertetését tartalmazza ez a pont. 3.7.1. A new operátor Formája: new típusnév vagy new típusnév[elemek száma] Az operátor a típusnév (és ha adott, az elemek száma) által meghatározott méret helyet kísérel meg lefoglalni a memóriában. Ha a helyfoglalás sikeres volt,a m velet eredménye egy típusnev* típusú érték, amely a lefoglalt terület kezdÿcímét adja, egyébként az eredmény 0.
6
Egyszer példák: int *p=new int; értéknek int *pp; pp = new int[200]; egész
// helyfoglalás egy egész // helyfoglalás egy 200 elemÿ // tömbnek
3.7.2. A delete operátor Egy objektum törlése és az általa elfoglalt hely felszabadítása a memóriából: delete pointer; Egy tömb törlése: delete [] pointer; Csak a new operátorral értéket kapott pointerrel alkalmazható, egyébként az eredménye definiálatlan. Ez alól kivétel a NULL pointer, amellyel alkalmazva hatástalan. Tömb törlése az egyszer formájú operátorral, vagy egy objektum törlése a tömbökre vonatkozó formával szintén definiálatlan hatású. 3.7.3. Az ANSI-C helyfoglaló függvényei A C++ ismeri az ANSI-C helyfoglaló függvényeit is, tehát (elsÿsorban kompatibilitási okokból) azok is használhatóak. Tilos azonban a két allokációs mechanizmus együttes alkalmazása egy programon belül: ennek hatása definiálatlan.
3.8. Újabb alaptípusok A C++ két alaptípussal bÿvíti a C típusválasztékát. 3.8.1. A bool alaptípus Az alaptípus logikai értékek tárolására szolgál, ebbÿl következik, hogy értéke igaz vagy hamis lehet. A C++ nyelvben a logikai kifejezések végeredménye bool típusú. Integrális típus, és ha szükséges, egy bool típusú érték a C konvenció értelmében a 0 vagy 1 értékre, egy egész érték pedig 0 esetén a hamis, nem 0 esetén az igaz értékre konvertálódik. 3.8.2. A wchar_t alaptípus Az egy byte-nál hosszabb kódú karaktereket alkalmazó karakterkészletek egy elemét tárolni képes típus. Szintén integrális típus.
7
4. Függvényekre vonatkozó kiterjesztések 4.1. Hivatkozás (cím) szerinti paraméterátadás A C++ bevezeti a függvény-paraméterek érték szerinti paraméterátadása mellett a hovatkozás szerinti paraméterátadást is. Ehhez az adott formális paramétert hivatkozási típusúnak kell deklarálni, az aktuális paraméter pedig alaptípusú balérték kell legyen. A hivatkozás szerinti paraméternek a címe adódik át, a függvényben azonban nem kell indirekciót alkalmazni a változó értékének elérésére. Ha a függvény értékadást tartalmaz a formális paraméterrre, akkor azzal az aktuális paraméterének az értékét is megváltoztatja. Egy egyszer példa:
#include <stdio.h> void csere (int &a, int &b); main() { int a=5, b=10; csere (a,b); printf ("%d %d\n",a,b); } void csere (int &a, int &b) { int i; i=a; a=b; b=i; }
4.2. Alapértelmezés szerinti paraméter értékek Ha egy formális paraméterhez = jellel egy kifejezést kapcsolunk, az a paraméter híváskor elmaradhat, de a kifejezésnek megfelelÿ kezdÿértéket veszi fel. (Az alapértelmezés szerinti paraméterérték megadása formálisan is egy inicializálásnak felel meg.) Szabályok: • Ha egy paraméternek van alapértelmezés szerinti (default) értéke, akkor az utána következÿ valamenyi paraméternek kell legyen. • A kifejezés értékének típusa a paraméter típusával kompatibilis kell legyen.
8
• Egy adott paraméter default értékét vagy a prototípusban, vagy a definícióban kell megadni, mindkét helyen tilos! Szokásos eljárás, hogy valamennyi default értéket a prototípusban adunk meg, hiszen az mindig publikus, a függvény definíció pedig nem mindig az. • A kifejezés kiértékelése a hívás során futási idÿben történik. • Default értékkel nem rendelkezÿ formális paraméter helyére kötelezÿ aktuális paramétert írni. Egy példa: int kobtart (int hossz, int szel=3, int mag=5); main () { int x=10, y=12, z=2, k; k = kobtart (x,y,z); k = kobtart (x,y); // k= kobtart(x,y,5; k = kobtart (x); // k = kobtart(x,3,5); k = kobtart (); // Ez hibás! } int kobtart (int hossz, int szel, int mag) { return hossz*szel*mag; }
4.3. Az inline függvény Ha egy függvényt a deklarációjában az inline minÿsítÿvel látunk el, feljogosítjuk a fordítóprogramot arra, hogy a függvény hívását ne a szokásos módon fordítsa (paraméterátadás elÿkészítése, majd ugrás a függvény törzsébÿl generált, elkülönített kódra), hanem a függvény definíciójából és az aktuális paraméterekbÿl készített kódsorozattal helyettesítse. A fordítóprogram ilyenkor hasonlóképpen m ködik, mint a makrófeldolgozó. Az inline függvény függvény törzse tehát annyiszor kerülhet bemásolásra a kódba, ahányszor a hívása szerepel. Ennek a lehetÿségnek a beépítését a C++ nyelvbe az alábbiak indokolják: • A C++-ban gyakori az, hogy nagyon rövid, egyszer a függvény törzse. Ilyenkor a hívás helyén történÿ kódgenerálás rövidebb kódot eredményezhet, mint a paraméterátadás adminisztrálása és az ugrás majd visszatérés. Így a kód összességében éppen hogy rövidülhet a látszólagos ismétlések ellenére. • Jól használható a makrók kiváltására. Az inline függvényt nem az elÿfeldolgozó, hanem a fordítóprogram dolgozza fel, amely képes a típusellenzórzésre. A C++ pedig erÿsebben típusos nyelv, mint a C. Az inline minÿsítÿ nem kötelezi a fordítóprogramot az inline módú hivásfeldolgozásra, tehát a fordítóprogram bonyolultsága határozza meg, hogy milyen optimalizációs stratégiát alkalmaz.
9
4.4. Függvény overloading A függvény overloading azt jelenti, hogy azonos hatáskörben (ennek pontos jelentését csak késÿbb tudjuk tisztázni) azonos névvel, de különbözÿ paraméterszignatúrával különbözÿ függvényeket definiálhatunk. Paraméterszignatúra alatt a formális paraméterek számát és típus-sorrendjét értjük. A függvény neve a híváskor ebben az esetben nem határozza meg, hogy melyik függvény hívásáról van szó. A fordítóprogram az aktuális paraméterek szignatúráját sorban egyezteti az összes azonos nev definíció formális paramétereinek szignatúráival. Ennek az egyeztetésnek az alábbi eredményei lehetnek: • Pontosan egy illeszkedÿt talál: ilyenkor az ennek megfelelÿ függvény hívását fordítja be. • Egyetlen illeszkedÿt sem talál: hibajelzést ad • Több egyformán illeszkedÿt talál: hibajelzést ad Ezt a mechanizmust a legtöbbször tagfüggvények (a fogalmat lásd késÿbb) esetén alkalmazzuk, de bármely függvény esetén használható.
10