A C++ programozás alapjai- segédlet
1 Rev 4.
Tartalomjegyzék 1 Bevezetés........................................................................................................5 1.1 POSIX specifikáció.....................................................................................6 1.2 Fordítók és fejlesztői környezetek.............................................................7 1.2.1 GNU-LINUX GCC.................................................................................8 1.2.2 Windows Cygwin................................................................................8 1.2.3 Windows: MinGW...............................................................................8 1.2.4 Az Eclipse IDE.....................................................................................8 1.2.5 Telepítés Windows alatt......................................................................9 1.2.6 Az Eclipse használatának alapjai........................................................9 1.3 Egy egyszerű példa.................................................................................10 1.3.1 Hello World forrás.............................................................................10 1.3.2 Megjegyzések, kommentek..............................................................11 1.3.3 Egyszerű input / output....................................................................11 1.4 Elemi adattipusok...................................................................................12 1.4.1 Adattípusok, változók, és jellemzőik................................................12 1.4.1.1 Beépített alaptípusok................................................................13 1.4.1.2 Egész számok............................................................................14 1.4.1.3 Lebegőpontos számok...............................................................15 1.4.1.4 Karakterek.................................................................................16 1.4.1.5 Logikai típus..............................................................................17 1.4.1.6 Stringek.....................................................................................18 1.4.1.7 Típuskonverzió.........................................................................19 1.4.1.8 Változó nevek............................................................................20 1.4.1.9 Szimbolikus konstansok.............................................................21 1.4.1.10 Példák......................................................................................22 2 Kifejezések....................................................................................................23 2.1 Aritmetikai operátorok............................................................................24 2.2 Relációs operátorok................................................................................25 2.3 Logikai operátorok..................................................................................26 2.4 Bitenkénti operátorok.............................................................................27 2.5 Auto inkrement - auto dekrement...........................................................28 2.6 Értékadó operátorok...............................................................................29 2.7 Feltételes operátorok..............................................................................30 2.8 Vessző operátor......................................................................................31 2.9 sizeof operátor........................................................................................32 2.10 Pointerek...............................................................................................33 2.11 Bővebben az elemi stringekről.............................................................34 2.12 Precedencia..........................................................................................35 3 Utasítások, vezérlési szerkezetek..................................................................36 3.1 Utasítások...............................................................................................37 3.2 Egyszerű feltételvizsgálat.......................................................................38 3.3 Egymásba ágyazott, többszintű feltételvizsgálat...................................39 3.4 Elágazásos feltételvizsgálat....................................................................40 3.5 Ciklusszervezés while utasítással...........................................................41 3.6 Ciklusszervezés do-while utasítással......................................................42 3.7 Ciklusszervezés for utasítással...............................................................43 2 Rev 4.
4
5 6
7
8
9
3.8 Ciklusszervezés tartományra for utasítással..........................................44 3.9 A continue utasítás.................................................................................45 3.10 A break utasítás....................................................................................46 3.11 A goto utasítás......................................................................................47 3.12 A return utasítás...................................................................................48 Függvények...................................................................................................49 4.1 A függvények részei:..............................................................................50 4.2 Függvény deklaráció...............................................................................51 4.3 Függvény paraméterek és argumentumok.............................................52 4.4 Hatókör...................................................................................................54 4.5 Argumentumok alapértelmezett értéke..................................................55 4.6 Parancssori argumentumok....................................................................56 4.7 Függvények túlterhelése.........................................................................57 4.8 Függvény pointerek................................................................................58 Összetett adattípusok....................................................................................59 5.1 Tömbök...................................................................................................60 5.2 Struktúrák...............................................................................................62 Az osztályokról általában...............................................................................63 6.1 Tagfüggvények, vagy metódusok...........................................................64 6.2 Adattagok, vagy tagváltozók..................................................................66 6.3 Hozzáférési engedélyek..........................................................................67 6.4 Konstruktorok..........................................................................................68 6.5 Destruktorok...........................................................................................69 6.6 Osztálytagok - osztályváltozók...............................................................70 6.7 A this változó..........................................................................................71 6.8 Inicializációs lista....................................................................................72 6.9 Operátor túlterhelés...............................................................................73 Öröklődés (származtatás, bővítés)................................................................74 7.1 Barátosztályok (friendship).....................................................................77 7.2 Többszörös öröklődés..............................................................................79 7.3 Polimorfizmus.........................................................................................80 7.4 Absztrakt osztály:....................................................................................81 7.5 Osztály template-ek................................................................................82 7.6 Implicit típuskonverzió............................................................................83 7.7 inline Függvények...................................................................................84 További ismeretek az adattipusokról.............................................................85 8.1 Union-ok.................................................................................................86 8.2 Enumeráció.............................................................................................87 8.3 A fájlkezelés alapjai................................................................................88 8.4 Típusdefiníció..........................................................................................89 8.5 Dinamikus memória kezelés...................................................................90 8.6 Tárolási osztályok és típusminősítők.......................................................91 Egyéb osztálytulajdonságok..........................................................................92 9.1 Bináris operátor túlterhelésről részletesen.............................................93 9.2 Konstans objektumváltozók....................................................................94 9.3 Konstans osztályváltozók........................................................................95 9.4 Statikus tagfüggvények..........................................................................96 9.5 Tagobjektumok........................................................................................97 9.6 Objektum tömbök...................................................................................98
3 Rev 4.
9.7 Osztály láthatóság..................................................................................99 9.8 Egyéb, fontosabb osztályok..................................................................100 9.8.1 String osztály.................................................................................100 10 Preprocesszor............................................................................................101 10.1 Lépései:..............................................................................................102 10.2 Direktívák:..........................................................................................103
4 Rev 4.
1 Bevezetés
5 Rev 4.
1.1 POSIX specifikáció Portable Operating System Interface – olyan specifikáció, amely leírja egy általános Unix szerű operációs rendszer paramétereit. Alapvetően két témakörből áll: 1. POSIX Alapdefiníciók: – System interfészek és parancsok – Real-time szolgáltatások – Thread szolgáltatások – Biztonsági interfészek – Hálózatkezelés – Védelmi és vezérlő parancsok – Batch kezelő programok 2. POSIX megfelelőségi tesztek
6 Rev 4.
1.2 Fordítók és fejlesztői környezetek
7 Rev 4.
1.2.1 GNU-LINUX GCC Debian / Ubuntu disztribúciók esetén: – cli:sudo apt-get install gcc g++ – synaptic package manager – software centre
1.2.2 Windows Cygwin www.cygwin.com Eredeti fejlesztője a Cygnus Solutions. Feladata: Teljes POSIX réteg megvalósítása könyvtárak és rendszerhívások emulálására. Ennél a megoldásnál a POSIX réteg folyamatosan fut a Windows-ban, ami többlet CPU kapacitást igényel. A Cygwin segítségével lefordított programokhoz a kompatibilitási réteget biztosító lib-et is mellékelni célszerű, hogy futtathatók legyenek. Felépítése: – Library, ami a Unix (POSIX) rendszerhívásokat Win32-nek megfelelő rendszerhívásokat (API) valósítja meg – GNU development toolchain – nagy számú, Unix-ra jellemző parancsot megvalósító alkalmazás (mount, sshd, Apache, grep, emacs, vi, sed, stb).
1.2.3 Windows: MinGW www.mingw.org Minimalist GNU for Windows - a Cygwin első változatából ágazott le. A kompatibilitást direkt WIndows hívásokra fordítja le. Nem biztosít teljes POSIX kompatibilitást, a teljes Unix programkínálat nem fordítható le a segítségével. Erőforrás igénye kisebb, nem igényli a kompatibilitási réteg folyamatos jelenlétét.
1.2.4 Az Eclipse IDE www.eclipse.org Eredeti fejlesző: IBM Canada Kezelője 2004 óta az Eclipse Foundation. Java alapú fejlesztő környezet. Licence: EPL (Eclipse Public Licence) - FSF szerint free licence, de nem GPL kompatibilis. Felépítés: – Workspace – Plugin rendszer – Az Eclipse IDE 8 Rev 4.
1.2.5 Telepítés Windows alatt 1. Java runtime install – Download http://www.oracle.com/technetwork/java/javase/downloads/java-se-jre-7download-432155.html 2. MinGW (Minimal GCC for Windows) letöltés – www.mingw.org Downloads Installer: mingw32-base mingw32-gcc-g++ – www.mingw.org – PATH_ba beírni a MINGW/bin-t: 1. file explorer-ben ráálni a c:MINGW/bin-re, jobb gomb és copy 2. control panel: system and security -> system ->advanced fül, és environment variable. 3. PATH után beírni ; után a bemásolt útvonalat! 3. Eclipse CDT letöltés – Átnevezés a hosszú filenév elkerülése miatt eclipse.zip-re – Eclipse indítás. – New project -> C++ -> Hello world – Project properties -> c/c++ build -> Tool Chain Editor-ban MINGW-t beállítani Cross GCC helyett – Ha internal build helyett make-re állítjuk, a Project Properties -> C/C++ Build-ben a make-et migw32-make -re kell módosítani.
1.2.6 Az Eclipse használatának alapjai
9 Rev 4.
1.3 Egy egyszerű példa 1.3.1 Hello World forrás #include
// preprocesszor direktíva
int main(void) // főprogram, függvény neve { // utasításblokk kezdete std::cout << "Hello World\n"; //kiíratás } // utasításblokk vége #include - preprocesszor direktíva – A fordítás előtt bevágja a megadott fájlt mint szövegállományt a forrásba. – Az iostream.h egy u.n. header fájl, amely az iostream-mel kapcsolatos deklarációkat és definíciókat tartalmazza. Régebbi környezetekben iostream.h, újabban iostream. int main() - a program legmagasabb szintű végrehajtandó függvénye – A függvényeknek lehet 0, vagy több paramétere. – A void azt jelöli, hogy nincs paraméter. – Jelen esetben a függvény visszatérő értékének típusa integer. – Minden C++ programnak egy, és csak egy main() függvénye van. A programvégrehajtás a main()-en kezdődik.
10 Rev 4.
{ - a függvény testének a kezdete (utasításblokk kezdete) – A következő sor a main()-t alkotó utasítások sora. – Minden utasítást ;-vel kell lezárni. "Hello World\n" string kiíratása a cout output stream-re. – \n - újsor karakter jelölése. – std:: - Standard IO névtere. – cout - Standard output stream. – << - túltelített operátor. Balértékként az output stream-et, jobbértékként egy kifejezéstkell megadni. } - A függvény testének a vége (utasításblokk vége) – A kiterjesztés a UNIX szerű környezetben .cc, Windows környezetben cpp. (Helloworld.cc vagy Helloworld.cpp) – Fordítás: g++ HelloWorld.cc – A kimenő fájl jelen esetben a.out, de a -o fordítási opcióval tetszőleges kimenő fájlnév megadható.
1.3.2 Megjegyzések, kommentek // általában tört soros megjegyzés /* általában hosszabb, akár több soros megjegyzés */ Alapvető szabályok: – A megjegyzések nem egymásba ágyazhatóak. – A megjegyzés legyen érthetőbb, mint a programrészlet, amit leír! – Túl sok megjegyzés csökkenti a program olvashatóságát. – Beszélő szimbólumok használata csökkenti a megjegyzések számát. – A megjegyzéseket általában angolul írjuk! – "Mérőszám: WTF/minute"
1.3.3 Egyszerű input / output Operátorok: – << – kiírás stream-be – >> – beolvasás stream-ből A stream-ek az vagy az include fájlok segítségével érhetők el. – cout – kiviteli stream – cin – beviteli stream Példa: Workhours3.cpp
11 Rev 4.
1.3.4 Fordítási lépések
1.4 Elemi adattipusok
1.4.1 Adattípusok, változók, és jellemzőik A változók adatok átmeneti tárolására szolgálnak. A fordító a RAM-ban foglalt memóriatartományban helyezi el őket. Ez természetesen nem áll a const előtagú szimbólumokra. (Lásd később.) A változókat két attríbútummal jellemezzük. – Típus - a változó definiálásakor adandó meg, a továbbiakban nem lehet megváltoztatni. – Érték - tetszőleges időpontban újraírható. Definíció és értékadás – A változó első értékadása az inicializálás. – A példában a definíció és az értékadás elkülönül, de inicializáláskor ez együttesen is elvégezhető. – Inicializálás nélkül elvileg lehetséges használni a változókat, de ilyenkor az értékük véletlenszerű. Értékadás Ennek során a válozóhoz konkrét értéket rendelünk. a = 3; int a (3); int a {3}; // -std=c++11 vagy -std=gnu++11
12 Rev 4.
1.4.1.1 Beépített alaptípusok
Alaptípusok – int – egész – float – lebegőpontos – double – dupla pontosságú lebegőpontos – bool – logikai – char – karakter – wchar_t – „széles” karakter – void – típus nélküli Módosítók: – signed – előjeles – unsigned – előjeltelen – short – rövid tárolási mód – long – hosszú tárolási mód
13 Rev 4.
1.4.1.2 Egész számok
Típusok: (Alapértelmezés: signed) – short int vagy short – 2byte – int – legalább annyi, vagy több byte, mint a short – 4byte – long int vagy long – legalább annyi, vagy több byte, mint az int – 4byte – long long – 8byte – unsigned... – signed... Literalok: Számérték valamilyen számrendszerben kifejezett alakja. 19 – int (alapértelmezés) 19L – long 19LL – long long 19U – unsigned 19LU – long unsigned ...
14 Rev 4.
1.4.1.3 Lebegőpontos számok
Típusok: (Alapértelmezés: signed) – float – 4byte – double – legalább annyi, vagy több byte, mint a float – 8byte – long double – legalább annyi, vagy több byte, mint a double – 8 vagy 10 byte Literalok: 0.06F- float szimpla pontosság 0.06 – double dupla pontosság 0.06L – long long double 2.132E-3 – exponenciális alak Számrendszerek ábrázolása 45 – decimális 045 – oktális 0X45 – hexa
15 Rev 4.
1.4.1.4 Karakterek
A karakter kódja - numerikus érték, a kódrendszertől függ. Legelterjedtebb: ASCII. Pl 'A' = 65, 'a' = 97 Alapértelmezett a signed, de lehet unsigned is. Típusok: (Alapértelmezés: signed) – char – 1byte – wchar_t – 2 vagy 4 byte Előfordulhatnak még: – char8_t – char16_t – char32_t – uchar –… Példák: signed char letter = 'A'; unsigned char row = 2, column = 4; Literalok: 'A' '\r' - carriage return '\n' - new line '\t' - vízszintes tabulator '\v' - függőleges tabulator '\b' - backspace '\f' - formfeed '\\' - backslash '\'' - aposztróf '\"' - idézőjel '\ooo' - oktális számmal ábrázolt ascii kód '\0' - null kódú karakter '\a' - alert '\?' - kérdőjel
16 Rev 4.
1.4.1.5 Logikai típus
Valójában int-tel egyenértékű típus. Értéke 0, vagy nem 0, de a jobb olvashatóság érdekében használható helyette a vele egyenértékű bool. Típusok: – bool - 1 byte Literalok: true false
17 Rev 4.
1.4.1.6 Stringek
A string változó értéke az a memória cím, ahol a string kezdődik. A stringet mindig \0 karakter zárja le: H E L L O \0 Típusok: (Alapértelmezés: signed) – char* – A hossz tetszőleges. Literalok: "Hello World\n" "Name\tAddress\tPhone" Több soros literal: "This is an example \ of a multi line literal" Figyelem! "A" nem azonos 'A'-val! "" valójában egy \0, de a típusa nem azonos a karakter típussal.
18 Rev 4.
1.4.1.7
Típuskonverzió
Példa: int i; float f = 3.14; i = (int) f; // vagy ... i = int (f); // C++-ban ez is elfogadott
19 Rev 4.
1.4.1.8 Változó nevek
A változó névnek a következő követelményeknek kell eleget tenne: – Tartalmazhat betűket 'A'-'Z' és 'a'-'z' tartományban. – Tartalmazhat számjegyeket '0'-'9' tartományban, de nem az első helyen. – Tartalmazhat '_' karaktert. – Nem tartalmazhat kulcsszavakat és műveleti jeleket. – Hossza elvileg nem limitált, de gyakorlatban általában 255 karakter.
20 Rev 4.
1.4.1.9 Szimbolikus konstansok
– – – –
const double PI = 3.141592654; Definíciókor inicializálni! Ismételten már nem kaphat új értéket. Érvényes a const SIZE = 100; is, ekkor az alapértelmezett típus int. A konstanst a fordító több módszerrel kezelheti fordításidőben: immediate értékként, vagy regiszterben tartott változatlan értékként (pl. 0 értékű regiszter az avr-g++ esetén)
Pointerekkel kapcsolatos megjegyzések: const int* size = 38 // integer konstansra mutató pointer változó int* const age = 42 // konstans pointer, ami integer változóra mutat *size = 40; // Hibás age = 0L; // Hibás age = size; // Hibás *age = 40; // Helyes size = 0L; // Helyes size = age; // Helyes Függvényekkel kapcsolatos megjegyzések: függvény bármelyik paramétere lehet konstans: int power (const int base, const int exponent); Stilisztikai megjegyzés: A konstansokat általába a header file-okban definiáljuk, hogy az értékük ismert legyen.
21 Rev 4.
1.4.1.10 Példák ■
HelloWorld.cc
■
WeeklyPay_1.cc
■
WeeklyPay_2.cc
■
WeeklyPay_3.cc
22 Rev 4.
2 Kifejezések Bármilyen számítási sorozat, aminek kiértékelhető eredménye van. Rendszerint a lényeg a kiértékelés során kapott eredmény, de a kiértékelés során más is történik: 1. kiszámítódik a számítási sorozat eredménye, 2. mellékhatások: néhány operátor a rendszer állapotában is változást idéz elő (pl. értékadás, inkrementálás, dekrementálás. Példa: cout << “Hello “ << “World“; Balról jobbra lesz végrehajtva. cout << “Hello “ mellékhatása nyomtatás, visszatérő értéke a cout stream címe. Így a
23 Rev 4.
(cout << “Hello “ ) << “World“ //második lépésben már egyenértékű lesz ezzel: cout << “World“
2.1 Aritmetikai operátorok Operátorok: – + összeadás – - kivonás – * szorzás – / osztás – % maradék Figyelem! – %-ot kivéve minden operátor int és real tetszőleges kombinációját elfogadja. % mindkét paramétere int. – Ha mindkét operandus int, az eredmény int. – Az egész osztás hozhat nem várt eredményt: 9 / 2 eredménye 4 (NEM 4.5)! -9 / 2 eredménye -5 (NEM -4)! – Ha legalább az egyik operandus real, az eredmény is real (pontosabban double). – Ha real eredményt akarunk: type cast (típusmódosítás), ellenkező esetben egészosztás történik. int dist = 100; int time = 10; double speed = dist / (double) time; – Ügyelni kell a túlcsordulásra! A következő művelet rossz eredményt ad unsigned char a = 64 * 4 // az eredmény 0 lesz. – 0-val osztás: fordítási időben az operandusokat nem ellenőrzi a fordító, 0-val osztás futásidejű hibát, és általában leállást eredményez.
2.2 Relációs operátorok A relációs operátor operandusai számértékkel kell, hogy rendelkezzenek. Kiértékelés után egy bool értékkel ekvivalens.
24 Rev 4.
Operátorok: – == egyenlőség – != nem egyenlőség – <= kisebb egyenlő (sorrend!) – >= nagyobb egyenlő (sorrend!) – < kisebb – > nagyobb Példa: 'A' < 'F' // helyes "HELLO" < "WORLD" // nem helyes
2.3 Logikai operátorok Nincs valódi beépített bool típus, eléggé elterjedt, hogy int-et használnak helyette. Operátorok: – ! negálás (unáris operátor) – && Logikai ÉS – || Logikai VAGY Figyelem! – Értéke valójában 0 vagy nem 0 lehet. – Kiértékelés balról: ha a && baloldali feltétel nem teljesül, vagy a || bal oldali feltétel teljesül, nem folytatódik a kiértékelés. Tehát a jobb oldali kifejezésnek ne szánjunk mellékhatást, mert nem biztos, hogy végre lesz hajtva! Példák: !130 // értéke 0 1000 && 3200 // értéke 1 1000 || 3200 // értéke 1 (0xaa & 0x55) && 255 // értéke 0 (false) if ( (i<10) && (++i
2.4 Bitenkénti operátorok Operátorok: – ~ bitenkénti negálás – & bitenkénti ÉS – | bitenkénti vagy – ^ kizáró vagy – << léptetés balra – >> léptetés jobbra Mivel a léptetés az implementáció függő előjelet nem tudja egységesen 25 Rev 4.
kezelni, célszerű eleve előjeltelen vátozókon használni.
26 Rev 4.
Példák: unsigned char x = '\011'; unsigned char y = '\045'; x: 00010001 y: 00100101 ~x: 11101110 x & y: 00000001 x & y: 00110101 x ^ y: 00110100 x << 1: 001000010 x >> 1 000010000
2.5 Auto inkrement - auto dekrement Prefix-ként: először műveletvégzés, majd az így nyert változót értékadáshoz használjuk Postfix-ként, elöször értékadás, majd művelet a változón. – ++ inkrement (prefix / postfix) – -- dekrement (prefix / postfix) Példák: int k = 10; int j = ++k; // j: 11, k: 11 int j = k++ // j: 10, k: 11
2.6 Értékadó operátorok Érték letárolása valamilyen módon megadott memória címre. Az értékadó operátor egy olyan kifejezés, melynek értékét a bal oltali operandusban tárolni kívánjuk. – lvalue – írhatónak kell lennie, memória cím - egyelőre csak változó nevet láttunk példának. – rvalue – lehet változó, de lehet konstans (literal) is Tehát (lvalue = rvalue) hatására: 1. rvalue átmásolódik lvalue-be 2. visszatés az átmásolt értékkel, ami ismét használható rvalue-ként. Így lehetséges többszörös értékadás is. Az értékadást megelőzheti egy művelet (mellékhatással). – = egyszerű értékadás – += hozzáadás – ...ennek mintájára valamennyi bináris operátor használható az = jel bal oldalán! Példa:
27 Rev 4.
// a többszörös értékadás alapja, hogy az értékadés mellékhatásán kívül // visszatérő értéke is van. int j, k, l; j = k = l =10; // ( j = (k = (l = 10))); j += k = l = 10 // j = j + (k = l = 10);
2.7 Feltételes operátorok Operátorok: operandus1 ? operandus2 : operandus3 – ha op1 nem 0: op2 kifejezést kell kiértékelni, és ez lesz a visszatérő érték, – ha op1 nulla: op3 kifejezést kell kiértékelni. és ez lesz a visszatérő érték. Példák: int j = 1, k = 2; int min = j < k ? j : k; // Mivel a feltételes operátorok végül egy kifejezést képeznek, // több is egymásba ágyazható. int j = 1, k = 2, l = 3; int min = j < k ? (j < l ? j : l) : (k < l ? k : l);
2.8 Vessző operátor Több kifejezés kiértékelése egyszerre. Visszatérő érték a legutolsó kif. értéke. Azoknak a kifejezéseknek, melyek nem visszatérő értékek, természetesen csak akkor van értelmük, ha van mellékhatásuk. Példa: int j, k, min; int jCtr = 0, kCtr = 0; min = j < k ? jCtr++, j : kCtr++, k;
2.9 sizeof operátor Operandusa egy típusnév, vagy egy kifejezés. A visszatérő érték implementáció függő.
2.10 Pointerek Egy bizonyos memória hely címe. Értékére ritkán vagyunk kiváncsiak, többnyire hivatkozásként használjuk. Operátorok: – * operátor jelentése: "point to" – & operátor jelentése "address of" Példa: 28 Rev 4.
int* ptr; char* ptrChar; int num; ptr = # Figyelem! – A * mint dereferencia operátor: argumentuma egy pointer, ami feloldja a hivatkozást, tehát *ptr ekvivalens lesz a num-mal. – A pointer definiálásakor meg kell adni, hogy milyen típusra mutat. – A pointer típusától függetlenül lehet neki 0 értéket adni. (neve: null pointer) – A pointer típusát lehet módosítani (type cast) pl. ptrChar = (char*) ptr; (csak indokolt esetben) – típusfüggetlen pointer: void* anyPtr;
2.11 Bővebben az elemi stringekről Hozzunk létre két stringet: char* str1 = "firstname"; char* str2 = "lastname"; Az eredmény: két /0-val lezárt karakterlánc a memóriában, és két pointer típusú változó, amely a karakterláncok kezdőcímét tartalmazza. Tehát: str1 = str2; nem a string tartalmát tölti át, csak egyik pointert a másikba. Ugyanakkor: str1 = "middlename"; Memóriában létrehoz egy stringet "middlename" és a címét str1-be tölti. A memóriában levő string átírása a következő függvénnyel végezhető el: <string.h> strcpy(char* dest, char* source) Figyelem! – Létezik egy standard string osztály is, ami a std névtérben a <string> header segítségével érhető el.
29 Rev 4.
2.12 Precedencia
30 Rev 4.
3 Utasítások, vezérlési szerkezetek 3.1 Utasítások Szekvenciális műveletek mellékhatással. 1. Egyszerű utasítás: egyetlen utasítás ;-vel lezárva. Példa: int i; // egyszerú utasítás: vált definíció ++i; // művelet (mellékhatással) i + 8; // ; -vel lezárt kifejezés - művelet (mellékhatás nélkül) ; // null utasítás 2. Összetett utasítás, vagy blokk: {} közé zért egyszerű utasítások. – Lehetővé teszik, hogy több utasítás írjunk oda, ahová egyébként csak egyet lehetne – Korlátozzák az érvényességi kört (scope) Típusok: – deklarációk – hozzárendelés (értékadás) műveletek – feltételes elágazások – ciklusszervező utasítások – végrehajtás vezérlők
3.2 Egyszerű feltételvizsgálat if (kifejezés) utasításblokk1; else utasításblokk2; Példa: if (account > 0) { interest = account * debitPercentage; account = account + interest; // kiemelhető } else { interest = account * creditPercentage; account = account + interest; } Példa feltételes kifejezéssel egyszerűsítve: account += account ? ( account * debitPercentage : account * creditPercentage); Példa további egyszerűsítéssel: account += account * (account ? debitPercentage : creditPercentage);
31 Rev 4.
3.3 Egymásba ágyazott, többszintű feltételvizsgálat if (kifejezés1) { utasítás1; } else { if (kifejezés2) { utasítás2; } } Vagy: if (kifejezés1) utasítás1; else if (kifejezés2) utasítás2;
32 Rev 4.
3.4 Elágazásos feltételvizsgálat Valójában használható lenne helyette az if - else if szerkezet, de a switch olvashatóbb. switch (kifejezés) { case constant1: utasítások1; break; case constant2: utasítások2; break; ... default: utasításokn; break; } Példa: A, S, K, L mint kurzormozgató billentyűk: switch (key) { case 'a': ++y; break; case 's': --y; break; case 'k': ++x; break; case 'l'; --x; break; default: cout << "Error\n"; break; }
33 Rev 4.
Vigyázat! – A break elhagyása nehezen ellenőrízhető hibát okoz. – Az utolsó break után beírt utasítást nem jelzi hibának - de nem is hajtja végre.
3.5 Ciklusszervezés while utasítással while (kifejezés) { utasítások; } – kifejezés: ciklusfeltétel – utasítások: ciklus törzs Figyelem! Végtelen ciklust a rendszer nem ellenőrzi
3.6 Ciklusszervezés do-while utasítással do { utasítások; } while (kifejezés); – ritkábban használt – akkor célszerű, ha mindenképp le kell egyszer futtatni a ciklust Figyelem! Végtelen ciklust a rendszer nem ellenőrzi
3.7 Ciklusszervezés for utasítással Leggyakoribb felhasználás: lépéses ciklus - ilyenkor inkább javasolt, mint while for (kifejezés1; kifejezés2; kifejezés3) { utasítások; } 1. kifejezés1 kiértékelésre kerül 2. Minden futási ciklusban ha kifejezés2 kiértékelésre kerül, 3. ha kifejezés2 nem 0, kifejezés3 és utasítások végrehajtása. Példa: long sum; // hatóköre kívül van a for ciklus törzsön. for (short i = 1; i < n; ++i) { // i hatóköre kívü a cilustörzsön. sum += i; }
34 Rev 4.
– ciklusváltozó hatóköre nem a ciklusmag, hanem maga a ciklus. – bármelyik kifejezés lehet üres – for utasítások egymásba ágyazhatóak – minden kifejezés helyére több is beírható, ha használjuk a ”,” (vessző) operátort. Természetesen csak az utolsó ad vissza értéket, de pl. több ciklusváltozót így lehet egyszerre inicializálni. Példák: for ( ; i != 0; ) // megfelel: while (i != 0) for (;;) // megfelel: (üres feltétel = true) végtelen ciklus for (i = 0, j = 0; i + j < n; ++i, ++j) { utasítások; } Vigyázat, gyakori hiba: for (...); // a ";" miatt a ciklus további része csak egyszer fut le. { ... } – CycleDemo.cc – Sum.cc
3.8 Ciklusszervezés tartományra for utasítással Megadható egy halmaz, amin elemenként megy végig. for ( declaration : range ) statement; Példa: string str {"Hello!"}; for (char c : str) { std::cout << c; } std::cout << '\n';
3.9 A continue utasítás Kihagyja a ciklusmag további végrehajtását és új ciklusfejet kezd. Példa: // Páros számok összeadása n-ig for (i = 0; i < n; ++i) { if ((n % 2) != 0) // elég lenne if (n % 2) is, de így olvashatóbb continue; sum += i; }
35 Rev 4.
3.10 A break utasítás Megszakítja a ciklus, vagy a switch futását. Példa: "gondoltam egy számot - 3 kisérlet" for (i = 0; i < 3; ++i) { cin >> i; if (i == n) { break; } cout << "Wrong!\n"; } if (i == n) cout << "gotja\n"; else cout << "looser\";
3.11 A goto utasítás Alacson szintű ugró utasítás. Példa: for (i = 0; i < try; ++i) { cout << "Enter password: "; cin >> pw; if (Verify(pw)) // check password for correctness goto out; // drop out of the loop cout << "Wrong!\n"; } out: …
3.12 A return utasítás Visszatérő érték generálása. Eddig csak a main() ben használtuk. return kifejezés;
36 Rev 4.
4 Függvények Műveletszekvenciák csoportosítása.
4.1 A függvények részei: – prototípus, vagy interfész (visszatérő típus, név, paraméterek vagy aláírás) – törzs (utasítások) Használat: hívás. – argumentumok kiértékelése (minden argumentum egy a megfelelő paraméterrel azonos típusú kifejezés) – a fenti kifejezések eredményeinek hozzárendelése a megfelelő paraméterekhez – függvénytörzs végrehajtása – visszatérő érték átadása a hívónak Figyelem! – nem-void függvény: kifejezés – void függvény: utasítás. Példa int power (int base, unsigned int exp) // prototype: base és exp lokális változók { int result = 1; // lokális változó for (int i = 0; i < exp; ++i)// végrehajtás result *= base; return result; // visszatérő érték } int main (void) { cout << "2 on the power of 3 is" power (2, 3); // függvényhívás }
4.2 Függvény deklaráció – A függvényt mindíg csak akkor szabad használni, ha már deklarálva volt. – Definíció maga is deklaráció, ha elől van, de javasolt kigyűjtött deklarációk használata. – Deklaráció - prototype-pal. – Deklaráció interfészébe konkrét változó nevek nem kellenek, de javasoltak. – A paraméter listában minden paraméternek kell típus, vesszővel nem lehet több azonos típusú paramétert felsorolni.
37 Rev 4.
4.3 Függvény paraméterek és argumentumok Két paraméter típus C++ -ban (vegyesen is használható egy paraméter listában): – Érték szerinti átadás: a függvény lemásolja az argumentumot (változások nem hatnak vissza). – Referencia szerinti átadás: pointerrel, vagy referenciával. A függvény hozzáférést kap a paraméterhez, és változtathat rajta. Az alábbi példákban az increment függvény feladata, hogy a neki átadott változó értékét megnövelje. Erre a célra érték szerinti paraméterátadást nem használhatunk, mert az inkrementálás a függvénytörzsben csak az argumentum másolatán történik meg. Példa - átadás érték szerint változóban – ROSSZ! void increment (int number); int main() { int num = 4; increment(num); cout << num << '\n'; } void increment (int number) fog dolgozni { ++(number); } Példa - pointer szerinti átadás void increment (int* nptr); int main() { int num = 4; int* numptr = # increment(numptr); cout << num << '\n'; } void increment (int* nptr) { ++(*nptr); }
38 Rev 4.
// kiíratás: 4 // rossz, mert itt number másolatán
Példa - referencia szerinti átadás void increment(int& number); int main() { int num = 4; increment(num); cout << num << '\n'; } void increment(int& number) { ++number; }
4.4 Hatókör Globális: – Élettartamuk a program teljes futási ideje. – Minden ami a program hatókörében van definiálva (tehát nem függvényben, vagy osztályban, vagy szeparált blokkban. – Inicializálatlan globális változók értéke 0-ra van automatikusan beállítva. – Mivel a teljes programból láthatók, névadás legyen egyedi! – Lokális: – Élettartamuk csak a blokk elejétől a végéig tart. – Minden, ami utasításblokkon belül van. – Függvényparaméterek a függvénytörzsön belül lokálisak. – Névadásnak csak a blokkon belül kell egyedinek lenni. – Ha a hatókörök egymásba ágyazódnak, a belső névadása felülírja a külsőt. – Ha felülírt globálisra akarunk hivatkozni, azt a ::név jelöléssel lehet. Példa: int xyz; // xyz globális void fnc (int xyz) // xyz lokális a fnc-n belül { if (xyz > 0) { double xyz; // xyz lokális a blokkon belül //... } ::xyz = 0; // hívatkozás a globális változóra }
4.5 Argumentumok alapértelmezett értéke A default argumentumokat mindíg a paraméterlista végén kell felsorolni! Célszerű meghatározás a deklarációban. Lehet a definícióban is, de soha 39 Rev 4.
nem mindkét helyen! Példa: int main() { int a = 4; int b = 5; cout << add(a, b) << ", "; // teljes paraméterlista cout << add(a) << endl; // második argumentum elhagyva } int add(int number1, int number2) { return number1 + number2; }
40 Rev 4.
4.6 Parancssori argumentumok Parancssori konzol segítségével indított programnak szüksége lehet paraméterekre. A main() függvénynek két féle definíciója létezik: int main(void); int main(int argcj, char* argv[]); Az argc azt adja meg, hogy hány paramétert kapott a program a parancssorban. A második paraméter egy string tömb, aminek első, tehát 0 indexű eleme magának a programnak a neve, a többi pedig a parancssorban beérkezett paraméterek. Például a sum 2 3 4 parancs: argc = 3; argv[0] értéke: "sum", argv[1] értéke: 2 stb. Példa: int main(int argc, int *argv[]) { int sum = 0; for (int i = 1, i < argc; ++i) sum += argv[i]; cout << sum << '\n'; return 0; }
4.7 Függvények túlterhelése A programot ovashatóbbá teszi, ha az azonos feladatot ellátó függvények azonos nevet kapnak. A C++ megengedi a függvények túlterhelését, azaz ugyanazon függvénynéven több függvény definiálását. A feltétel, hogy a függvényeket az argumentumok és a visszatérő érték típusai, tehát az u.n "aláírás" egyértelműen megkülönböztessék. Példák: long getTime(); void getTime(long* time); void getTime(int* hours, int* minutes); void getTime(int& hours, int& minutes);
41 Rev 4.
4.8 Függvény pointerek Lehetséges a függvény címét letárolni és hívatkozni rá. 1. Legyen több olyan függvényünk, aminek két int paramétere van és inttel tér vissza: int f1 (int a, int b) {...} int f2 (int c, int d) {...} 2. Definiáljunk egy pointer típusú változót, ami egy ilyen függvényre mutat: int ( * fptr) (int, int) 3. Adjunk az fptr-nek értéket: fptr = &f1; De elegendő: fptr = f1; 4. Hívás: int result = fptr(5, 7); Példa: int increment(int a) {return a+1;} int main() { int (*fncPtr)(int); // fncPtr = &increment; helyett elegendő: fncPtr = increment; cout << fncPtr(3) << endl; // eredmény: 4 return 0; }
42 Rev 4.
5 Összetett adattípusok 5.1 Tömbök Azonos típusú elemek halmaza. – Hivatkozás sorrend alapján: tomb[index] – Index 0...size-1 – Neve csak a tömbnek van, az elemeknek nincs. – Elemek összes lehetséges száma: dimenzió - nem keverendő össze a dimenziók számával. – A dimenzió futás közben nem változtatható, mert fordításidőben kerül a memória lefoglalásra. – Dimenzió túllépés eredménye: jó esetben futásidejű hiba generálódik, rossz esetben kiszámíthatatlan. Definíció: int a[3]; Figyelem! Dimenzióban ugyanaz, mint int* a; de ezt nem lehet definícióként használni, mert nem tudni, hogy mennyi memóriát kell foglalni. Inicializáció int a[3] = {2, 4, 6}; int a[3] = {2, 4}; // a[2] értéke 0 int a[] = {2, 4, 6}; // a[] mérete 3 Hivatkozás A tömb neve index nélkül egy pointert ad vissza, ami a tömb első elemére mutat, de csak ha ez az egyszerűsítés nem egy olyan elemre mutat, ami maga is pointer (pl. két dimenziós tömb). Példa: int a[] = {2, 4, 6, 8}; int* aptr = a; cout << a[0] << ',' << *aptr << endl; cout << a[1] << ',' << *++aptr << endl; // a két kiíratás ugyanazt adja cout << a[1] << ','<< *aptr + 1 << endl; // nem ugyanaz, mert * prioritásosabb, mint + cout << a[1] << ',' << *(aptr + 1) << '\n'; // így már ugyanazt adja Stringek A string egy karaktertömb. Lehetséges inicializálás:
43 Rev 4.
char name[] = {'H', 'E', 'L', 'L', 'O'}; // mérete 5 - nem string!!!! char name[] = {"HELLO"} // mérete 6, mert a '/0' is a végére kerül. Ez a tényleges string. Többdimenziós tömb A több dimenziós tömb tárolása valójában sorfolytonosan történik. int a[2][3] = { 2, 4, 6, 8, 10, 12, }; Sorfolytonos tárolás int a[2][3] = {2, 4, 6, 8, 10, 12}; Pointer aritmetika A pointerhez lehet egész számot hozzáadni, vagy abból elvenni.
5.2 Struktúrák Valójában hagyaték a C nyelvből. struct tipusnév { típus1 vátozónév1; típus2 változónév2; ... } objektumnév ; – A struktúra létrehoz egy új típusnév típust. – Ha csak egy példányt akarunk, nem kell típusnév, az objektum azonnal létrehozható (objektumnév). Ekkor természetesen nem kapunk típusnevet, ami a továbbiakban használható lenne újabb ugyanilyen típusú objektum létrehozására. Példa: Person postman = { "Robert", 55}; // hívatkozás struktúra mezőre struktúrán keresztül: cout << postman.name << endl; // hívatkozás struktúra mezőre pointeren keresztül: Person *ptrPostman = &postman; cout << (*ptrPostman).name << ", " << ptrPostman->age << endl;
44 Rev 4.
6 Az osztályokról általában Objektum: az OOP alapeszköze. Az OOP egyik alapelve a "becsomagolás" vagy "encapsulation". Lényege: egy objektumot alkozó összes tulajdonságot egy egységbe tömörítjük. Az objektumokat nem egyenként hozzuk létre, hanem egy előre definiált osztály példányosításával. Az osztály egy típust hoz létre, ami a beépített típusokkal azonos módon használható. A struktúra és a union lényegében speciális osztályok. C++-ban osztály a struct és a class kulcsszóval egyaránt létrehozható. Alkotóelemei: – tagfüggvények, vagy metódusok – adattagok, vagy tagváltozók. Felépítés: – Fejléc (header): Osztálynév és alaposztályok (ezekből származtathatjuk az új osztályt). – Törzs (body): Az osztály tagjainak (members) felsorolása
45 Rev 4.
Példa: class Point { private: // a default értelmezés: private int xCoord, int yCoord; bool active; public: void set(int, int); void move(int, int) }; – Az osztályokat rendszerint a header file-ok tartalmazzák. – A változóktól eltérően az extern minősítő használata osztályoknál nem szükséges. – A jó olvashatóságot segíti, ha a deklarációk az osztályban (.h modulban), de a definíció a .cc modulban van. – Nem kötelező, de egyre elterjedtebb gyakorlat az egy osztály - egy modul elv.
6.1 Tagfüggvények, vagy metódusok Az osztályra jellemző műveletek. – Alkalmazhatók default argumentumok, és túlterhelhetőek, de vigyázni kell, hogy a híváskor a default argumentumok elhagyásával ne álljon elő olyan forma, ami az egyik túlterhelt változatot is kielégíti. – A tagfüggvényeket az osztályban deklaráljuk. A definíció történhet ekkor is, vagy később, az osztályon kívül. Példa osztályon kívüli definícióra: void Point::set(int x, int y) { xCoord = x; yCoord = y; } void Point::move(int x, int y) { xCoord += x; yCoord += y; } // hívatkozás Point pt; pt.set( 15, 20); pt.move(10, 10); // azonban pt.xCoord = 10; // nem szabályos, mert xCoord privát változó
46 Rev 4.
Egyszerűbb metódusokat célszerű inline-nak definiálni! // Osztály a header (.h) fájlban: class Point { private: // a default értelmezés: private int xCoord, yCoord; bool active; public: inline void set(int x, int y) // inline függvény definiálható az osztályban { xCoord = x; ycoord = y; } void move(int, int); inline int getX() {return xCoord;} // osztálydeklarációban definiálva inline int getY() {return yCoord;} // osztálydeklarációban definiálva }; // osztályon kívüli definíció a forrás (.cpp) fájlban void Point::move(int x, int y) { xCoord += x; yCoord += y; }
6.2 Adattagok, vagy tagváltozók A változó definíciókkal azonos szintaxis.
6.3 Hozzáférési engedélyek – public: bárki által hozzáférhető – private: csak az osztályon belül hozzáférhető – protected: csak az osztályon, és a belőle származtatott osztályokon belül hozzáférhető – Ha az osztályt a class kulcsszóval hozzuk létre, az alapértelmezés: private, – ha struct kulcsszóval, akkor az alapértelmezés public.
6.4 Konstruktorok Az objektumpéldány létrejöttekor automatikusan lefut. – Neve ugyanaz, mint az osztályé. – Paraméterlistája lehet változó (túlterhelt).
47 Rev 4.
– Visszatérő értéke nincs – Hozzáférés: public Példa: Point::Point(int x, int y) { xCoord = x; yCoord = y; } Point::Point() { xCoord = 0; yCoord = 0; } // hívatkozás: Point p(10, 25); Point p();
6.5 Destruktorok Jellemzően akkor használjuk őket, ha az objektumnak a megszűnése előtt meg kell szüntetnie az objektumon belül végzett memóriafoglalásokat (new - delete operátorok), vagy le kell zárni az objektum által megnyitott erőforrásokat. Destruktorból csak egy van.
6.6 Osztálytagok - osztályváltozók Az osztály tagjai deklarálhatók úgy is, mint static. Ebben az esetben az osztály példányosítása előtt, a definíció pillanatában létrejön egyetlen példány ezekből a tagokból. A továbbiakban minden létrejött példány ugyanazon egy osztályváltozót fogja használni. – Hívatkozás: mivel nem példányhoz (objektumhoz) kötött, nem lehet rá objektumon keresztül hívatkozni, ezért az osztály nevén keresztül kell :: operátorral. – Tagfüggvényre is megadható, de ennek csak akkor van értelme, ha az osztályt egyáltalán nem akarjuk példányosítani, hanem csak enkapszulációt akarunk. Egyetlen előnye, hogy a példányosítás esetenként néhány bájttal több memóriát igényel. 8 bites mikrokontrollereknél elterjedt vélemény hogy memóriát takarít meg - nem feltétlenül igaz. Akkor célszerű alkalmazni, ha kimondottan az osztályhoz kapcsolódóan akarjuk használni. Pl. példányok számlálása:
48 Rev 4.
Példa: class Point { public: static int objCtr; // példány számláló int xCoord, yCoord; Point(int x, int y) // objCtr-t itt nem szabad inicializálni!!! { objCtr++; // számláljuk a példányt xCoord = x; yCoord = y; } } // A static változót külön meg kell definiálni, mert a példányosításból kimarad. // Ez ugyanabban a szkópban lehetséges, ahol a tagfüggvények definíciója. int Point::objCtr = 0; // automatikusan is erre van beállíttva // példányosítás: Point p1; Point p2; Point*ptrP2 = &p2; cout << p1.xCoord << '\n'; // objektumváltozó közvetlen hívatkozás cout << ptrP2->xCoord << '\n'; // objektumváltozó indirekt hívatkozás cout << Point::objctr << '\n'; // osztályváltozó hívatkozás
6.7 A this változó Az adott objektumon belül használható private változó.
49 Rev 4.
– Típusa: pointer, ami olyan konkrét típusú, mint maga az objektum. – Az adott példányra mutat. – Csak tagfüggvényekben érhető el.
6.8 Inicializációs lista class Point { private: int xCoord, yCoord; public: Point (int x, int y) { xCoord = x; yCoord = y; } }
50 Rev 4.
// Konstruktor
Helyette célszerűbb: class Point { private: int xCoord; int yCoord; public: Point(int x, int y) : xCoord(x), yCoord(y) {} } – Az inicializációs listát meg lehet adni az osztály deklarációban, vagy a konstruktor osztályon kívüli definíciójában is.
6.9 Operátor túlterhelés Az elemi operátorok kibővíthetők, hogy osztályokon végezzenek műveletet: – Unáris operátor esetén az újradefiniált operátor nem kap argumentumot, hanem a tagváltozókat értékeli ki. – Ha egy operátornak csak unáris vagy, csak bináris formája van, akkor nem terhelhető túl, a másik formában. Példa ex66_class_operator:
51 Rev 4.
class Point { public: int xCoord, yCoord; // public, hogy ki lehessen íratni Point(int x, int y): xCoord(x), yCoord(y) {} // összeadás operátor Point operator + (Point &point) { return Point(xCoord + point.xCoord, yCoord + point.yCoord);} // negálás (unáris) operátor túlterhelése // az unáris operátor nem kap argumentumot, csak az osztálytagokon dolgozik Point operator - () { return Point(-xCoord, -yCoord);} }; int main() { Point p1(2, 2); Point p2(4, 4); Point p3 = p1 + p2; cout << p3.xCoord << ", " << p3.yCoord << endl; p3 = -p3; cout << p3.xCoord << ", " << p3.yCoord << endl; return 0; }
52 Rev 4.
7 Öröklődés (származtatás, bővítés) Új osztályok hozhatók létre egy kiválasztott alaposztály tagjainak a bővítésével. Ehhez megadjuk az alaposztályt, és a hozzáadandó új tagokat. Ezenközben az alaposztályhoz történő hozzáférést lehet korlátozni. class új_osztály : public/protected/private alap_osztály { … } Az alaposztály legmagasabb szintű hozzáférései a megadott szintre lesznek bekorlátozva. – public: Az alaposztály public tagjai public lesznek. – protected: Az alaposztály public tagjai protected lesznek. – private: Az alaposztály minden tagja private lesz. Példa ex64_class_inheritence: class Polygon { protected: int width, height; public: void set(int w, int h) {width=w; height=h;} }; class Rectangle: public Polygon { public: int area () {return width * height;}
53 Rev 4.
}; class Triangle: public Polygon { public: int area () {return width * height / 2;} }; int main () { Rectangle rect; Triangle trgl; rect.set(4, 5); trgl.set(4, 5); cout << rect.area() << '\n'; cout << trgl.area() << '\n'; return 0; } A származtatott osztály nem örökli az alaposztálytól: – a konstruktorokat, destruktorokat, – a túlterhelt operátorokat, – a barát osztályokat. Bár a konstruktorok nem öröklődnek, azok mégis meghívódnak – legalábbis az alapértelmezés szerinti, vagyis paraméter nélküli változat. Ha más konstruktorra van szükség, azt külön inicializáló listával kell hívni. Példa ex65_class_inher_initlist: class Polygon { protected: int width, height; public: Polygon(int w, int h): width(w), height(h) {} // konstruktor void set(int w, int h) {width=w; height=h;} }; class Rectangle: public Polygon { public: Rectangle(int w, int h): Polygon(w, h) {} int area () {return width * height;} }; class Triangle: public Polygon { public: 54 Rev 4.
Triangle(int w, int h): Polygon(w, h) {} int area () {return width * height / 2;} }; int main () { Rectangle rect(4, 5); Triangle trgl(4, 5); cout << rect.area() << '\n'; cout << trgl.area() << '\n'; return 0; }
7.1 Barátosztályok (friendship) A barátosztály olyan – egyébként teljesen független - osztály, melynek hozzáférése van a private vagy protected szintű tagokhoz. class Square; // kell, mert a Rectangle mindjárt hivatkozik rá // Téglalap osztály class Rectangle { private: int width, height; public: int area () {return (width * height);} void convert (Square square); // a téglalapot egy megadott négyzettel azonos méretűre változtatja }; // Négyzet osztály class Square { friend class Rectangle; private: int side; public: Square (int a) : side(a) {} }; void Rectangle::convert(Square square) { width = square.side; // side-hoz csak azért férhet hozzá, mert friend 55 Rev 4.
height = square.side; } int main () { Rectangle rect; Square sqr (5); rect.convert(sqr); cout << rect.area() << endl; return 0; } A példában a rect.convert-nek megadható egy square objektum, minek hatására a rect-et egy ugyanilyen oldalhosszúságú négyzetté konvertálja. A négyzet (square) oldala ugyan private, de a friendship miatt a rect.convert hozzáférhet.
7.2 Többszörös öröklődés A C++ lehetővé teszi több osztály tulajdonságainak az átszármaztatását is: class új_osztály : public/protected/private alapo1, public/protected/private alapo2 { ... } Figyelem! – Amennyiben a két alaposztályban ugyanaz a tag előfordul, mindenütt az alapo1::tag illetve alapo2::tag módon kell rá hivatkozni!
7.3 Polimorfizmus Ha egy osztály metódusa a virtual kulcsszóval kerül definiálásra, akkor a származtatott osztályokban ez a metódus újradefiniálható.
56 Rev 4.
class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area () { return (0); } }; // származtatott osztály: class Rectangle: public Polygon { public: int area () // úradefiniálva { return (width * height); } };
7.4 Absztrakt osztály: Megtehetjük, hogy area()-t a Polygon-ban egyáltalán nem definiáljuk. Ekkor ezt jelölni kell:
57 Rev 4.
virtual int area () =0; // tisztán virtuális függvény Ez esetben Polygon u.n. absztrakt osztály, a származtatott osztályokban pedig mindenképp kell definiálni a metódust.
7.5 Osztály template-ek Az osztályok generálását automatikusan is elvégezhetjük olyan módon, hogy ehhez típusnevet adunk meg paraméternek. template definíció: template class Coordinate { public: T xCoord, yCoord; // a nyomtatás érdekében public Coordinate (T xarg, T yarg) // konstruktor { xCoord = xarg; // fontos, hogy = legyen definiálva T-re! xCoord = yarg; } void stepUp () {++xCoord;} // fontos, hogy ++ legyen definiálva T-re! }; Hivatkozás: Coordinate intCoord (5, 5); intCoord.stepUp(); cout << intCoord.xCoord << endl; Megtehetjük azt is, hogy bizonyos típusokra másképp definiáljuk az osztálytemplate-et. Pl. double tipusú paraméter esetén stepUp() helyett legyen jumpUp(): template <> class Coordinate <double> { public: double xCoord, yCoord; Coordinate(double xarg, double yarg ) { xCoord = xarg; yCoord = yarg; } void jumpUp() { xCoord += 10.0;} }; Hivatkozás:
58 Rev 4.
Coordinate<double> doubleCoord(5.0, 5.0); doubleCoord.jumpUp(); cout << doubleCoord.xCoord << endl;
7.6 Implicit típuskonverzió Publikus öröklés esetén implicit típuskonverzió megy végbe, tehát: class Ember {}; class Benzinkutas: public Ember {}; // a benzinkutas ember class Parkoloor: protected Ember {}; // a parkolóőr nem ember – Nem publikus öröklés esetén lehetséges az explicit típuskonverzió, de ekkor a programozó felelőssége, hogy ezt helyesen tegye.
7.7 inline Függvények Abszolutérték számításra a (n > 0 ? n : -n) kifejezés használható. Ha ezt többször használjuk, célszerű ezt egy függvényben definiálni: int Abs (int n) { return n > 0 ? n : -n; } A fenti megoldás helyett célszerűbb az inline int Abs (int n) { return n > 0 ? n : -n; } Ha a függvény inline-ként van definiálva, a fordító megpróbálja befordítani minden egyes előforduláshoz a függvényt, tehát bemásolni annak végrehajtható kódját, és nem hívással intézni. Ezáltal a hívással járó adminisztráció megtakarítható. Figyelem! – C++-ban az osztályon belül deklarált és definiált függvény alapértelmezése inline. – A compiler figyelmen kívül hagyhatja az inline kulcsszót, ha túl nagy függvényről van szó. – inline függvényt csak akkor érdemes definiálni, ha az nagyon rövid végrehajtható kódot eredményez, ellenkező esetben a hívással járó adminisztrációt ugyan megtakarítjuk, de a hosszabb végrehajtható kódot sokszorosítjuk. – az inline függvény minden hívást megelőzően kell, hogy definiálva legyen. Ennek legjobb helye a header fájl, ami viszont rontja a program szerkezetét, és sérti az „encapsulation” elvet.
59 Rev 4.
8 További ismeretek az adattipusokról 8.1 Union-ok Speciális struktúra, melyben a tagok ugyarra a memória címre vannak elhelyezve. union DataField{ int data; unsigned char d[4]; } mydata; mydata.data = 2014; // használat int - ként cout << mydata.d[0]; // hozzáférés byteonként – Létrehoz egy új DataField tipust – Ha csak egy példányt akarunk, nem kell típusnév, az objektum azonnal létrehozható (mydata). Ekkor természetesen nem kapunk típusnevet, ami a továbbiakban használható lenne újabb ugyanilyen típusú objektum létrehozására.
8.2 Enumeráció Szimbolikus konstansok elfogadandó halmazának a definíciója. Az enumerációs konstansoknak nincs memória allokálva, fordítás időben léteznek. – Értékek rendre: 0, 1, 2, 3, 4 enum {north, south, east, west}; – Default értékek felülírhatók: enum {north = 360, south = 180, west = 90, east = 15}; Létrehozható típus és változó is enumeráció alapján: enum Direction {north, south, west, east} mydir; – Létrehoz egy új Direction tipust – Ha csak egy példányt akarunk, nem kell típusnév, az objektum azonnal létrehozható (mydir). Ekkor természetesen nem kapunk típusnevet, ami a továbbiakban használható lenne újabb ugyanilyen típusú objektum létrehozására. – Switch szerkezetben célszerű enumerációt használni:
60 Rev 4.
switch(d) { case north: .. break; case south: .. break; }
8.3 A fájlkezelés alapjai A fájlműveletek a következő headereken keresztül érhetők el: – ifstream – stream osztály olvasási műveletekhez – ofstream – stream osztály írási műveletekhez – fstream – stream osztály irás/olvasás műveletekhez Használata: #include #include using namespace std; int main() { ofstream myOutFile("hello.txt"); if( myOutFile.is_open()) { myOutFile << "Hello World" << endl; myOutFile << "Hello World Again" << endl; myOutFile.close(); } char buffer[100]; ifstream myInFile("hello.txt"); if( myInFile.is_open()) { while( myInFile.getline(buffer, 100)) { // getline() a stream ref-t adja vissza, // vagy 0-t, ha vége cout << buffer << endl; } myInFile.close(); } else cout << "Cannot open input file" << endl; }
61 Rev 4.
8.4 Típusdefiníció Létrehozhatók új, saját típusnevek. typedef unsigned char int8_t; typedef char buffer[20]; Újabb jelölés: using unsigned char = int8_t; using char = buffer[20]; – Lényegében mindkét jelölés ugyanaz, de vannak esetek, amikor csak a using használható (template-ek) – Igazi hasznuk akkor jelentkezik, amikor a típusdefiníció túl összetett lenne. Ilyenkor javítja az olvashatóságot. typedef int (*fncPtr)(int) prinFunctionPointer;
8.5 Dinamikus memória kezelés A programstacken kívül futásidőben igénybe vehető egy másik dinamikus memória terület, az u.n. heap is. A new operátor – Memória területet foglal, – argumentum: típus – visszatérő érték: pointer a lefoglalt memória területre. – A visszatérő érték pointere az argumentumban adott típusra mutat.
62 Rev 4.
int *intPtr = new int; Vigyázat! – A lefoglalt memória nem az automatikus változók szabályai szerint működik! void fnc(void) { char* text = new char[10]; } // fnc() végrehajtása után a text változó megszűnik, de a lefoglalt memória nem. A delete operátor – Felszabadítja a memória területet. delete intPtr; // felszabadít egy objektumot delete [] text // felszabadít egy objektumokból álló vektort
8.6 Tárolási osztályok és típusminősítők. Tárolási osztályok – auto int i; // Automatikus változó: a lokális változók élettartama korlátozott, ezért dinamikusan jönnek létre és pusztulnak el. Ritkán használjuk explicit módon, mert default a lokális változók esetében. – register int i; // Legyen ez a változó az élettartama alatt regiszterben, HA LEHETSÉGES! Explicit módon nem használjuk, mert az optimalizálók ezt csinálják. (Bármikor utólag is beírható a programba.) – static int func(void); // függvények esetén a forrásfile-ra szűkíti a hozzáférést. – static int i; // változók esetén meggátolja az automatikus kezelést. – extern int i; // globális változó bármelyik modulban definiálható. Extern esetén a fordító nem keresi az adott file-ban, hanem csak elteszi a hivatkozást, ami később lesz feloldva, ha minden egyéb előfordulás, és a definíció is megvan. – const - lásd később
63 Rev 4.
9 Egyéb osztálytulajdonságok 9.1 Bináris operátor túlterhelésről részletesen Legyen egy osztályunk: class Element { public: Element(int value) : value(value) {} int getValue() const { return value; } private: int value; }; Bináris operátor túlterhelhető négy féle módon: 1. Különálló függvény hozzáféréssel a public adattagokhoz: Element operator+(const Element& a, const Element& b) { return Element(a.getValue() + b.getValue()); } Az operandusok: e1 + e2 == operator+(e1, e2)
64 Rev 4.
2. Tagfüggvényként: class Element { public: // Left operand is 'this'; right is 'other'. Element operator+(const Element& other) const { return Element(value + other.value); } // ... }; Az operandusok: e1 + e2 == e1.operator+(e2) 3. friend függvényként class Element { public: // Left operand is 'a'; right is 'b'. friend Element operator+(const Element& a, const Element& b) { return a.value + b.value; } // ... }; Az operandusok: e1 + e2 == operator+(e1, e2) 4. Friend függvény, az osztályon kívül definiálva. U.az mint 3. class Element { public: friend Element operator+(const Element&, const Element&); // ... }; Element operator+(const Element& a, const Element& b) { return a.value + b.value; } e1 + e2 == operator+(e1, e2)
9.2 Konstans objektumváltozók Egy objektumváltozót lehet const-ként definiálni.
65 Rev 4.
class Origo { const int xCoord; const int yCoord; //... } Viszont az inicializálás nem lehetséges deklaráláskor. class Origo { const int xCoord = 0; // HIBÁS const int yCoord = 0; // HIBÁS //... } A magyarázat az, hogy mire az értékadás lefut, a változót a default konstruktor már létrhozza, de csak olvashatóként. Megoldás: Konstruktor és inicializáló lista, mert az előbb hajtódik végre, mint a konstruktor törzse. class Origo { Origo(int x, int y): xCoord(x), yCoord(y); const int xCoord; const int yCoord; } Figyelem! Konstans objektumváltozóval nem lehet tömbméretet megadni, mivel csak futásidőben kap értéket.
9.3 Konstans osztályváltozók Gyakorlati jelentősége jóval nagyobb. Olyan állandókat definiálhatunk vele, melyet az osztály biztosít szabályozott hozzáféréssel.
66 Rev 4.
class Parser { public: static const char NEWLINE = '\n'; static const char C_RETURN = '\r'; } // hivatkozás: cout << "Hello World" << Parser::NEWLINE; cout << "Again" << pars.NEWLINE;
9.4 Statikus tagfüggvények Akkor célszerű használni, ha egy osztályt példányosítás nélkül akarunk használni. Bár hozzáférhető az objektumon keresztül is az Obj.func() operátorral, helyes hozzáférés az osztályon keresztül: Class::func() class Point { //... static void Draw(); //... }
9.5 Tagobjektumok Az osztályon belül tetszőleges típus felhasználható tagok definiálására. Így pl. másik oszályból képezett objektum is lehet tag. (Természetesen elhagyható, ha a tagobjektum nem vár inicializálást - pl. default argumentum.) class Rectangle { public: // konstruktor: Rectangle ( int left, int top, int right, int bottom ); private: Point topLeft; Point bottomRight; } // konstruktor definíció: // a tagobjektumokat is inicializálnia kell Rectangle::Rectangle (int left, int top, int right, int bottom ) : topLeft(left, top), bottomRight(right, bottom) {}
9.6 Objektum tömbök Ugyanúgy, mint az elemi típusoknál: Ha Point-nak létezik argumentum nélküli konstruktora: 67 Rev 4.
Point triangle[3]; Ha muszály paraméterezett konstruktort használni, akkor a tömböknél szokásos értékadással, de az értékek helyére a paraméterezett konstruktort kell beírni: Point triangle[3] = { Point(50, 50), Point(10, 20) }; Ha a konstruktornak csak egy paramétere van, akkor elegendő a paramétereket felsorolni: Set set[5] = {1, 2, 5, 6}; Objektum tömb dinamikus memóriafoglalás szintén lehetséges: Point *pentagon = new Point[5]; Mivel az objektum tömb futásidőben jön létre, a létrehozáskor nem lehet inicializálni. Ezért kell egy argumentum nélküli konstruktor! Ha emellett további inicializáció kell, akkor azt explicit módon kell végrehajtani! pentagon[0].Point(10, 20); Törléshez kell a [] !!! delete [] pentagon; // ha a [] elmarad, a destruktor csak az első elemre fut le!
9.7 Osztály láthatóság Az osztály létrehoz egy blokkot, amin belülre korlátozza a láthatóságot. Minden tag az osztály láthatóságán belül van, Ha az osztályból egy globális szimbólumra akarunk kifelé hívatkozni, a :: -val tehető egyértelművé. Az osztály láthatósága lehet: ○ globális osztály: a legtöbb osztály C++_ban - erre valamennyi láthatósági körből lehet hívatkozni ○ beágyazott osztály: osztályon belül létrehozott osztály, ○ lokális osztály: blokkon belúl, vagy függvényen belül van létrehozva. A létrehozás módjának a megválasztásánál azt tartsuk szem előtt, hogy milyen körben akarjuk az osztályt létrehozni! Célszerű mindíg a legszűkebb láthatóságot használni!
68 Rev 4.
9.8 Egyéb, fontosabb osztályok 9.8.1 String osztály Az elemi char* tipusú string helyett érdemes az összetett string osztályt használni. Példa: #include #include <string> using namespace std; int main() { string mystring1, mystring2, mystring3; mystring1 = “Hello “; mystring2 = “World“; mystring3 = mystring1 + mystring2; cout << mystring3 << endl; }
69 Rev 4.
10 Preprocesszor A GNU GCC által használt preprocesszor a GPP. Viszonylag jól használható a legtöbb nyelvhez, de vannak korlátai, ami miatt pl. a makrózási lehetőségek a C++ esetében leginkább csak fordításvezérlésre javasoltak.
10.1 Lépései: 1. Trigraph elemek behelyettesítése karakterekkel - nem minden implementációban. 2. Többsoros utasítások tördelésének megszüntetése. 3. Tokenizásció: a forrást preprocesszor tokenektre és wihtespace-ekre fordítja. Kommenteket wihitespace-ekkel helyettesíti. 4. Makro kifejtés és direktíva kezelés.
10.2 Direktívák: File-ok beszúrása: #include <stdio.h> #include "ownheader.h" Behelyettesítti a direktívát a megadott headerfile tartalmával. Makro definíció: objektum makró: #define # define DEBUGLEVEL 3 függvény makró: #define (<paraméter lista>) #define RADTODEG(x) ((x) * 57.29) Zárójelezés kell, mert pl. a RADTODEG(r + 1) kifejtése zárójel nélkül: r + 1 * 57.29 lenne, ami miatt a szorzás előbb lenne elvégezve. Vigyázzunk, hogy ne zárjuk le a sort ; - vel! Definíció feloldása: #undef Feltételes fordítás: #if #ifdef #ifndef #else #elif #endif Fordító specifikus direktíva: 70 Rev 4.
#pragma (<paraméter lista>)
71 Rev 4.