Programozási Nyelvek (C++) Összefoglaló Somogyi Krisztián gondozásában 2009-12-22
1. tétel: Fordítás [fordítási egység; warning; error; g++ kapcsolók]
A teljes programot általában lehetetlen egy fájlban tárolni, mert például a szabványos könyvtára és az operációs rendszer forrása nem szokott szerepelni a program forráskódjában. Fordítási egység az, amit a fordítóprogram önállóan le tud fordítani. Tehát a felhasználó a fordítóprogramnak egy fordítási egységet, egy forrásfájlt ad át. Először a fájl előfordítása történik, azaz végrehajtódik a makrófeldolgozás, és az #include utasítások beépítik a fájlállományokat; majd a fordítóprogram ténylegesen lefordítja és elkészíti a tárgykódot. A külön lefordított részeket a szerkesztőprogram, a linker kapcsolja össze. Törekedni kell minél kevesebbet hagyatkozni a preprocesszorra, mert a C++ hibaüzenetei jobbak. A warning olyan dolgokra figyelmeztet, melyek nem akadályozzák a lefordulást, de később gondot okozhatnak. Ha valami akadályozza a lefordulást, akkor az error szól. A mi fordítóprogramunk a g++, ez tehát egy C++ forrásfájlból futtatható állományt készít. Lássuk a g++ legfontosabb kapcsolóit: -o
-c -I <path> -l <path> -Wall -W -Werror -g -ansi -pedantic -Oβ
Ez lesz a lefordított fájl neve. Csak object file-t készít, nem linkel. Beállítja az include könyvtárat. Beállítja a library könyvtárat. Maximálisra állítja a warning szintet. Hibánkénti figyelmeztetések. Debug információkat is beletesz a bináris file-ba. Letiltja az ansi szabvánnyal ellentétes kiterjesztéseket. Letilt minden nem szabványos kiterjesztést. Optimizációs szintet állít: β ∈ {0, 1, 2, 3}.
2. tétel: Típusok [erősen típusos nyelv; a C++ alaptípusai és méretei; konvertálás; const]
A memóriában létrehozott tárolókat névvel látjuk el, amely segítségével hivatkozhatunk rájuk. Ezeket a tárolókat változóknak nevezzük. A számítógéptudományban és a programozáselméletben az erősen típusos (strong typing) kifejezést olyan esetekben használják, ahol a programozási nyelv megkötést tesz arra vonatkozóan, hogy a különböző adattípusú értékek hogyan keverhetőek egymással. A C++ erősen típusos nyelv, ami két dolgot von maga után: 1. Minden egyes változónak meg kell adni a típusát deklaráláskor. 2. Pontosan meg van szabva, hogy a különböző típusok mennyire képesek egymással együttműködni. 1
C++-ban minden kifejezésnek és részkifejezésnek fordítási időben ismert a típusa. A típus meghatározza a vele elvégezhető műveleteket. (Lásd 6. tétel.) Megkülönböztetünk alap- és származtatott típusokat. Előbbiekhez a karakter, az előjeles és előjel nélküli egészek, valamint a lebegőpontos típusok tartoznak. Utóbbiakat az alaptípusok felhasználásával felépített tömb-, mutató-, stb. típusok tartalmazzák. A C++-ban leggyakrabban használt alaptípusok: • Egész számok típusa: int, short int, long int • Lebegőpontos számok típusa: float, double, long double • Logikai típus: bool • Karakter típus: char • Karakterlánc típus: string Az alapvető adattípusok mérete fordító és platformfüggő, de a következők adottak: • egység = |char| ≤ |short| ≤ |int| ≤ |long| • egység ≤ |bool| ≤ |long| • |char| ≤ |wchar_t| ≤ |long| • |float| ≤ |double| ≤ |long double| • |N| = |signed N| = |unsigned N|, ahol N integrális (int, char vagy enum). Konvertálás: Felülről lefelé haladva, az első olyan szabály alapján, aminek igaz a feltétele. Egyik operandus long double double float unsigned long long int long int long int unsigned akármi
Másik operandus akármi akármi akármi akármi unsigned int unsigned int akármi akármi bármi
Konverzió akármi → long double akármi → double akármi → float akármi → unsigned long |unsigned int| ≤ |long int| ⇒ unsigned int → long int |long int| ≤ |unsigned int| ⇒ long int → unsigned int akármi → long int akármi → unsigned akármi, bármi → int
Const Konstansoknak azokat a változókat nevezzük, amelyeknek pontosan egyszer, kötelezően a definícióban adunk értéket. Ezt a típusnév elé írt const típusminősítővel jelezzük.
3. tétel: Vezérlési szerkezetek [szekvencia; elágazás; switch; ciklusok]
A szekvencia az egymás után végrehajtott utasításokat jelenti, s ezzel a legegyszerűbb vezérlési szerkezet. utasitas1; utasitas2; utasitas3; stb. 2
Azonban vannak speciális utasítások, melyeket önmagukban szemügyre véve: más és más vezérlési szerkezeteket valósítanak meg. Amikor a program a futásakor egy elágazáshoz ér, megvizsgál egy feltételt, és ennek függvényében folyik tovább a végrehajtás. A feltétel teljesülésére és nem teljesülésére is adhatunk külön-külön végrehajtást. if (feltetel) { szekvencia1; } else { szekvencia2; }; Bonyolultabb, többszörös feltételt is vizsgálhatunk: if (feltetel1) { szekvencia1; } else if (feltetel2) { szekvencia2; } else { szekvencia3; }; A feltétel több szempontból való megvizsgálására a C++ a switch szerkezetet adja; de ez korlátozott, csak integrális típust lehet vizsgálni. switch(N) { case eset1: szekvencia1; case eset2: szekvencia2; case eset3: szekvencia3; stb. default: szekvencia //ha semelyik sem igaz, ez legyen }; Ciklust akkor használunk, ha ugyanazt a feladatot többször kell elvégezni egymás után. Három fajta ciklus létezik (melyek lényegében megegyeznek egymással): Elöltesztelő: while (feltetel) { szekvencia; //ciklusmag }; Hátultesztelő: do { szekvencia; //ciklusmag } while (feltetel); Számlálós: for (inicializalas; teszteles; inkrementalas) { szekvencia; //ciklusmag }; A feltételes (tesztelős) ciklusokat olyankor használjuk, amikor nem tudjuk előre, hogy hányszor kell a ciklusnak lefutnia. Az elöltesztelő ciklus először megvizsgálja, hogy a feltétel fennáll-e. Ha igen, akkor lefuttatja a ciklusmagot, és újból kezdődik; ha nem, akkor a program a ciklus utáni ponton folytatódik, azaz a ciklusmag kimarad. Lehetséges tehát, hogy az elöltesztelő ciklus egyszer sem fog lefutni. 3
4. tétel: Függvények [függvények deklarációja; túlterhelés, elfedés; paraméterátadás; függvények definiálása]
A függvény a program olyan névvel ellátott egysége, amely a program bármely pontjából meghívható. Általában olyan utasítássorozatokat tartalmaz, amelyekre gyakran van szükség. Egy függvényt először deklarálnunk kell. Ekkor tájékoztatjuk a fordítót a függvény nevéről, a visszatérési értékének típusáról – ha nincs visszatérési értéke, akkor ez void), és ha van neki, akkor a paramétereinek számáról és azok típusairól. Egy függvény szignatúrája a nevéből, a paramétereinek számából, valamint azok típusaiból áll. Hogy a deklaráció más forrásfájlból is látható legyen, használjuk az extern előatagot! extern tipus-ve fvnev(tipus-p1, tipus-p2, stb.); Akkor beszélünk túlterhelésről, ha azonos látókörben több azonos nevű függvény van deklarálva, különböző szignatúrával. Elfedésről akkor beszélünk, ha külső látókörben több azonos nevű függvény van deklarálva, különböző szignatúrával. Túlterhelt függvény hívásakor a fordító kiválasztja a látható függvények közül a legjobban illeszkedőt, szignatúra alapján. A feloldási szabály meglehetősen bonyolult, így óvatosan kell túlterhelt nevet bevezetni. C++-ban kétféle paraméterátadás van: érték szerinti, melynél az átadott típusból másolat készül a memóriában, így az eredeti értéket nem módosítja a függvény; valamit cím szerinti, melynél paraméterként az átadott típus referenciája szerepel, így a függvény módosíthatja a paramétereket. Nagyobb objektumot ha érték szerint adunk át, akkor az több memóriát foglal, ezért ebben az esetben a cím szerinti átadás javasolt. A deklaráció után rátérhetünk a függvény definíciójára is, ahol megadjuk, hogy mit csináljon a függvény. Hogy a definíció más forrásfájlból is látható legyen, használjuk az extern előatagot! extern fvnev(p1, p2, stb.) { szekvencia; //függvény belseje }; A függvények belsejében deklarált változókat lokális változóknak hívjuk. Ez a gyakorlatban azt jelenti, hogy láthatóságuk és élettartamuk a függvényen belülre korlátozódik. Viszont, ha a függvény egy függvényen kívüli változóval dolgozik, akkor azt a változót globális változónak nevezzük.
5. tétel: I/O kezelés [cin; cout; cerr; clog]
A konzol kezeléséhez a műveleteket külön fájlban, az iostream-ben találjuk, ezért ezt csatolnunk kell a programunkhoz: #include. Az iostream a standard névtérbe (std) tartozik. std::cin: A szabványos bemenet, amely általában a billentyűzet. std::cout: A szabványos kimenet, amely általában a képernyő. std::cerr: A szabványos hibakimenet, amely általában a képernyő. std::clog: A std::cerr teljesen bufferált változata. int n; std::cout << "Kérem, írjon be egy természetes számot." << std::endl; std::cin >> n; std::cout << "Ennek a 13-mal való osztási maradéka: " << n%13 << "." << std::endl;
4
6. tétel: Beépített típusok operátorai [operátorok; operátorok összefoglalása]
A legtöbb programozási nyelv tartalmaz operátorokat. Ezek speciális függvények, precedenciával és sokszor sajátos írásmóddal, az általános függvényforma nélkül. Az operátorokat csoportosíthatjuk létrehozásuk célja szerint, így vannak aritmetikai, összehasonlító, bitszintű és egyéb operátorok. A C++ átvette a C összes operátorát, valamint bevezetett újakat és néhánynak a jelentését is megváltoztatta. Az utolsó oldalon található az operátorok összefoglaló táblázata. Most alaptípusonként közöljük az azokon elvégezhető műveleteket: • Egész számok típusán: +, -, *, /, %, ==, !=, <, <=, >, >= • Lebegőpontos számok típusán: +, -, *, /, ==, !=, <, <=, >, >= • Logikai típuson: &&, ||, !, ==, != • Karakter típuson: ==, !=
7. tétel: Standard könyvtár elemei [STL konténerek; I/O könyvtár (5. tétel)]
Az std névtérben definiálták a standard könyvtár szolgáltatásait. Sokszor szabványos tárolóknak hívják az STL (Standard Template Library) konténereket. A tároló olyan objektum, ami más objektumokat tartalmaz. Kétféle tárolótípus van: 1. Szekvenciális tárolók: vector, deque, list. 2. Asszociatív tárolók: set, multiset, map, multimap. vector: Olyan, mint az egyszerű tömb a C++-ban, de a mérete dinamikusan nő. Gyorsan elérhető tetszőleges eleme, és gyorsan megy a végéhez egy új elem beszúrása, vagy egy onnan való elem törlése is. Viszont minden más helyre beszúrni, vagy törölni lassú. Példa: int-eket tartalmazó vector: vector v; deque: Ez a „kévégű” sor. Gyorsan (bár egy kicsit lassabban, mint vector esetén) elérhető tetszőleges eleme, és az elejéhez meg a végéhez gyors egy új elem beszúrása, vagy egy onnan való elem törlése is. Viszont minden más helyre beszúrni, vagy törölni lassú. Példa: int-eket tartalmazó deque: deque d; list: Ez a kétirányú láncolt lista. Gyorsan lehet tetszőleges helyre beszúrni, vagy tetszőleges helyről törölni. Az elején és a végén lévő elemek elérése is gyorsan megy, de minden más helyen lassú. Példa: int-eket tartalmazó list: list l; Az előbbiekben a gyors azt jelentette, hogy konstans időben végrehajtható, s a lassú meg azt, hogy „csak” lineáris időben. set: Ez a matematikai halmaz. Példa: int-eket tartalmazó set: set s; 5
multiset: A zsák olyan objektum, ami annyiban különbözik a halmaztól, hogy egy elem többször is szerepelhet benne. Minden elemnek megvan a multiplicitása, hogy tudjuk, hányszor szerepel. Példa: int-eket tartalmazó multiset: multiset ms; map: Kulcs és érték párokat tárol. Kívülről úgy túnik, mint egy tömb, de indexei tetszőleges típusok lehetnek! Példa: Olyan map, amiben string a kulcs, az érték meg int. map<string, int> m; multimap: Ez egy olyan objektum, ami annyiban különbözik a map-tól, hogy egy elem többször is szerepelhet benne. Példa: Olyan multimap, amiben string a kulcs, az érték meg int. multimap<string, int> mm;
8. tétel: Osztályok [motiváció; adattagok; tagfüggvények; konstruktor; destruktor]
Az objektum-orientált programozás alapja, hogy az összetartozó adatokat egy egységként kezeljük. A C++ nyelv osztályai azt a célt szolgálják, hogy a programozó a beépített adattípusokkal azonos védelmi szinten használható, új adattípusokat hozhasson létre. Ezenkívül az öröklődés és a sablonok segítségével úgy szervezhetjük az egymással kapcsolatban álló osztályokat, hogy hatékonyan használhassuk ki kapcsolataikat. Az osztály egy felhasználói típus. Az adattípus és a függvények közötti kapcsolatot úgy hozhatjuk létre, hogy a függvényeket tagfüggvényként adjuk meg. Az osztálydefiníción belül deklarált függvényt nevezzük tagfüggvénynek. Minden tagfüggvényt definiálni is kell valahol. Az osztályon belül definiált tagfüggvényt inline függvénynek nevezzük. Egy osztály bármely tagfüggvénye hozzáfér az adattagokhoz, bármilyen legyen is annak elérése. Egy osztály adattagjának háromféle elérhetősége lehet: public: Mindenki számára elérhető. private: Csak az osztályon belülről lehet elérni, illetve barát osztályok és függvények részére még elérhető. protected: A származtatott osztályok számára közvetlen elérhetőséget biztosít. A struktúra olyan osztály, melynek minden adattagja nyilvános. Bármely adattag lehet static tárolási osztályú. A statikus adattag az osztály valamennyi objektuma számára egy példányban létezik, azok osztottan használják. Most jöjjön egy osztály, mely az előzőekben bevezetett fogalmakat példázza. class Osztaly { private: static int adattag1; //Nyilvános és statikus egész típusú adattag. float adattag2; //Nyilvános és nem statikus lebegőpontos számtípusú adattag. bool tagfv1(float); //Nyilvános tagfüggvény, melynek visszatérési értéke logikai //típusú, és az egy paramétere lebegőpontos számtípusú, melyet //érték szerint adunk át. public: char adattag3; //Nem nyilvános és nem statikus karakter típusú adattag. int tagfv2(int); //Nem nyilvános tagfüggvény, melynek visszatérési értéke //és paramétere egész típusú. }
6
Konstruktor Az objektumok kezdeti értékadása nincs lefektetve, és ez hibák forrása lehet. Erre jó megoldás egy függvény létrehozása, amelynek célja az objektumok előkészítése. Ennek a speciális függvénynek a neve konstruktor. Arról ismerjük meg, hogy ugyan az a neve, mint az osztálynak. A konstruktor szintén lehet public, private, vagy protected elérésű. A csak private elérésű konstruktorral rendelkező osztályt rejtett osztálynak nevezzük. Destruktor Az objektumok kezdőállapotát a konstruktorok állítják be, vagyis létrehozzák azt a környezetet, ahol a tagfüggvények működnek. Ez erőforrás-lefoglalással járhat, amit használat után fel kell szabadítani. Tehát némely osztálynak kell olyan függvény, ami biztosan meghívódik, amikor egy objektum megsemmisül; hasonlóan, ahogy a konstruktor is biztosan meghívódik, amikor az objektum létrejön. Ennek a speciális függvénynek a neve destruktor. Hasonlóan a konstruktorhoz a destruktornak sincs visszatérési értéke, azonban a destruktornak nem lehetnek paraméterei a konstruktorral ellentétben. A destruktort onnan ismerjük meg, hogy a neve, mint az osztálynak, plusz egy ∼ karakter. Végül jöjjön egy példa. Ebben egy komplex osztály komplex összeadással, és egy konkrét összeadás kiszámítása lesz bemutatva. #include class Komplex { private: double re; double im; public: //konstruktor Komplex(double r=0, double i=0) { re=r; im=i; } double real() { return re; } double imaginary() { return im; } }; //komplex összeadás cím szerinti paraméterátadással Komplex operator+ (Komplex& egyik, Komplex& masik) { return Komplex(egyik.real()+masik.real(), egyik.imaginary()+masik.imaginary()); } //(3+4i)+(2-7i)=? int main() { Komplex a(3, 4); Komplex b(2, -7); Komplex c=a+b; std::cout << c.real() << "+" << c.imaginary() << "i" << std::endl; }
7
Prec. 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 5 5 5 6 6 7 7 8 8 8 8 9 9 10 11 12 13 14 15 16 16 16 16 16 16 16 16 16 16 16 17
Operátor a::b (a) a[] ptr->b() a.b() a++ a-!a ∼a ++a --a -a +a *ptr &a (b)a sizeof(a) a->*b() a.*b() a*b a/b a%b a+b a-b a<>b ab a>=b a==b a!=b a&b a^b a|b a&&b a||b logkif?kif:kif a=b a+=b a-=b a*=b a/=b a%=b a&=b a^=b a|=b a<<=b a>>=b a, b
Rövid leírás Hatókör-operátor Csoportosítás Tömb-elérés Mutatón keresztüli tag-elérés Objektumon keresztüli tag-elérés Posztfix növelés Posztfix csökkentés Logikai tagadás Bitenkénti negálás Prefix növelés Prefix csökkentés Negatív előjel Pozitív előjel Deferálás Objektum címe Konverzió típusa Méret Tagkiválasztás mutatón Tagkiválasztás objektumon Szorzás Osztás Maradékos osztás Összeadás Kivonás Bitenkénti eltolás balra Bitenkénti eltolás jobbra Kisebb Kisebb-egyenlő Nagyobb Nagyobb-egyenlő Egyenlő Nem egyenlő Bitenkénti és Bitenkénti kizáró vagy Bitenkénti megengedő vagy Logikai és Logikai vagy if-then-else operátor Értékadás Összeadás, majd értékadás Kivonás, majd értékadás Szorzás, majd értékadás Osztás, majd értékadás Maradékos osztás, majd értékadás Bitenkénti és, majd értékadás Bitenkénti kizáró vagy, majd értékadás Bitenkénti megengedő vagy, majd értékadás Eltolás balra, majd értékadás Eltolás jobbra, majd értékadás Szekvencia operátor 8
Kiért. iránya nincs Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Balról jobbra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Jobbról balra Balról jobbra