C++
A Wikipédiából, a szabad enciklopédiából.
| Paradigma: | többelvű: generic, objektum-orientált, imperatív |
|---|---|
| Tervezte: | Bjarne Stroustrup |
| Utolsó kiadása: | 2003, ISO/IEC 14882:2003 / |
| Típusosság: | static, strong, unsafe, nominative |
| Fordítóprogram: | GNU Compiler Collection, Microsoft Visual C++, Borland C++ Builder |
| Dialektusok: | ANSI C++ 1998, ANSI C++ 2003 |
| Kiindulási nyelv: | C, Simula, BCPL, CLU, Algol |
| Befolyásolt nyelvek: | C#, Java, PHP, Python |
| Operációs rendszer: | Unix, Linux, Windows |
A C++ egy általános célú magasszintű programozási nyelv. Támogatja a procedurális-, az objektumorientált- és a generikus programozást, valamint az adatabsztrakciót. Napjainkban már szinte minden operációs rendszer alá létezik C++ fordító. A nyelv főbb felhasználási területei pl. matematikai számítások, telekommunikáció, rendszerprogramozás, szórakoztató (főleg játék) programok. A nyelv a C hatékonyságának megőrzése mellett törekszik a könnyebben megírható, fenntartható és újrahasznosítható kód írására, ez azonban sok kompromisszummal jár, erre utal a (nem hivatalos) mid-level minősítése is.
Tartalomjegyzék
|
[szerkesztés] Története
Bjarne Stroustrup kezdte el a C++ programozási nyelv fejlesztését a C programozási nyelv kiterjesztéseként, illetve más nyelvekből is vett át megoldásokat (Simula67, Algol68), ötleteket (ADA). A nyelv első nem kisérleti körülmények közt való használatára 1983-ban került sor, 1987-ben pedig nyilvánvalóvá vált, hogy a C++ szabványosítása elkerülhetetlen. Ez a folyamat 1991 júniusában kezdődött el, amikor az ISO szabványosítási kezdeményezés részévé vált. A C++ programozási nyelv szabványát 1998-ban hagyták jóvá ISO/IEC 14882:1998 néven, az aktuális, 2003-as változat kódjelzése ISO/IEC 14882:2003.
[szerkesztés] Érdekesség
Mire a nyelvet szabványosították, már rengeteg C++ nyelvű kód készült, került használatra. Mivel a szabvány fejállományok némileg eltértek az eddigiektől a bizottság érdekes megoldást választott a kompatibilitás megtartására:
- A régi C++ fejállományok (pl.: "iostream.h") továbbra is használhatóak (bár hivatalosan nem támogatottak), de tartalmuk nincs benne a standard névtérben.
- Az új, hivatalos fejállományok ("iostream") szinte megegyeznek a régiekkel, de tartalmuk szerepel a standard névtérben.
- A szabvány C fejállományok (pl.: "stdio.h") továbbra is támogatottak, de tartalmuk nincs a standard névtérben.
- A C könyvtár helyettesítésére létrehozott új fejállományok majdnem ugyanazt kínálják mint a régiek, de nevükből eltávolították a ".h" kiterjesztést és egy "c" betüt illesztettek az elejére (pl.: "stdio.h" -ból "cstdio" lett). Tartalmuk szerepel a standard névtérben.
[szerkesztés] Fordítók, fejlesztőeszközök
Windows operációs rendszeren tanuláshoz megfelelő - és ingyenes - eszköz a Dev - C++, illetve a Code::Blocks. Haladó szinten kényelmes választás a Visual C++ Express Edition, amely ingyen letölthető a Microsoft oldaláról, de több helyen bevallottan eltér a szabványtól.
Linux/UNIX alatt gyakran kényelmesebb konzolból fordítani (ez a lehetőség Windows-zal is megvan). Erre a GNU g++ programot használjuk, illetve grafikus fejlesztőeszközként rendelkezésünkre áll a KDevelop.
A legtöbb fordító - ha nem adjuk meg külön - néhány esetben eltér a szabványtól, így optimalizáltabb kódot hozhatnak létre. Természetesen minden esetben lehetőség van a szabvány szerinti fordításra.
[szerkesztés] A név eredete
Nevét Rick Mascitti találta ki. A C++ név kifejezi, hogy a nyelv a C kibővítése: az inkrementáló operátorra utal a ++ [1].
[szerkesztés] A C++ alapelemei
[szerkesztés] Jelkészlet
A kis- és nagybetűs angol ABC, általános írásjelek és a matematikai operátorok, jelek.
[szerkesztés] Azonosítók
A nyelv bizonyos összetevőire (változók, konstansok, függvények, etc.) névvel hivatkozunk. A legtöbb fordító maximum 32 karaktert enged egy névnek. A név első karaktere betű, vagy "aláhúzásjel (_)", ettől kezdődően már számok is szerepelhetnek. A C++ "case-sensitive", azaz különbséget tesz kis- és nagybetűk közt (b nem egyenlő B).
[szerkesztés] Kulcsszavak
Ezek a kifejezések a nyelv részei, önmagukban nem használhatóak névként.
| asm | do | if | return | typedef |
| auto | double | inline | short | typedid |
| bool | dynamic_cast | int | signed | typename |
| break | else | long | sizeof | union |
| case | enum | mutable | static | unsigned |
| catch | explicit | namespace | static_cast | using |
| char | export | new | struct | virtual |
| class | extern | operator | switch | void |
| const | false | private | template | volatile |
| const_chast | float | protected | this | wchar_t |
| continue | for | public | throw | while |
| default | friend | register | true | |
| delete | goto | reinterpret_cast | try |
[szerkesztés] Megjegyzések
A megjegyzések olyan karaktersorozatok, amelyeket a program dokumentálása érdekében használunk. A fordító nem veszi figyelembe a programban elhelyezett kommenteket.
// egysoros megjegyzés /* Itt kezdődik ................ és itt a vége */
[szerkesztés] Operátorok
Az operandusok a nyelv azon elemei, amelyeken az operátorok kifejtik hatásukat. Az operátorokat csoportosíthatjuk az operandusok száma szerint:
- Egyoperandusú (unáris), ekkor az általános alak:
- operator op (prefixes alak)
- op operator (postfixes alak)
- Kétoperandusú (bináris), ide tartozik az operátorok többsége
- Háromoperandusú, a C++ -ban csak a feltételes operátor ilyen:
c = a > b ? a : b; //az a és b közül a nagyobbik lesz c értéke
[szerkesztés] Precedencia
Az elsőbbségi (precedencia) – szabályok a kifejezések kiértékelésének helyes sorrendjét írják elő. A kiértékelés során először mindig a magasabb precedenciájú kifejezés értékelődik először.
[szerkesztés] Aritmetikai operátorok
A négy alapműveleten kívül a maradékképzést (%) is tartalmazza.
11 % 2 // a kifejezés értéke 1
[szerkesztés] Összehasonlító és logikai operátorok
Összehasonlító operátorok:
| C++ kifejezés | jelentés |
|---|---|
| a < b | a kisebb mint b |
| a <= b | a kisebb egyenlő mint b |
| a > b | a nagyobb mint b |
| a >= b | a nagyobb egyenlő mint b |
| a == b | a egyenlő b -vel |
| a != b | a nem egyenlő b -vel |
Minden fenti kifejezés logikai(bool) típusú, automatikusan konvertálódik egészre, és értéke 1, ha igaz, 0, ha hamis.
Logikai operátorok:
| C++ kifejezés | jelentés |
|---|---|
| !a | a tagadása, vagy a == 0 |
| a || b | a vagy b kifejezés igaz |
| a && b | a és b kifejezés igaz |
[szerkesztés] Az operátorok összefoglalása
| Precedencia | Operátor | Rövid leírás | Kiértékelés iránya |
|---|---|---|---|
| 1 | :: | Hatókör-operátor | nincs |
| 2 | () [] -> . ++ -- |
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 |
Balról jobbra |
| 3 | ! ~ ++ -- - + * & (típus) sizeof |
Logikai tagadás Bitenkénti negálás Prefix növelés Prefix csökkentés Előjel - Előjel + Dereferálás Objektum címe Konverzió típusra Méret |
Jobbról balra |
| 4 | ->* .* |
Tagkiválasztás mutatón/objektumon | Balról jobbra |
| 5 | * / % |
Szorzás Osztás Maradékszámítás |
Balról jobbra |
| 6 | + - |
Összeadás Kivonás |
Balról jobbra |
| 7 | << >> |
Bitenkénti eltolás balra Bitenkénti eltolás jobbra |
Balról jobbra |
| 8 | < <= > >= |
Kisebb Kisebb-egyenlő Nagyobb Nagyobb-egyenlő |
Balról jobbra |
| 9 | == != |
Egyenlő Nemegyenlő |
Balról jobbra |
| 10 | & | Bitenkénti ÉS | Balról jobbra |
| 11 | ^ | Bitenkénti kizáró VAGY | Balról jobbra |
| 12 | | | Bitenkénti megengedő VAGY | Balról jobbra |
| 13 | && | Logikai ÉS | Balról jobbra |
| 14 | || | Logikai(megengedő) VAGY | Balról jobbra |
| 15 | ? : | if-then-else operátor | Jobbról balra |
| 16 | = += -= *= /= %= &= ^= |= <<= >>= |
Értékadás Összeadás és értékadás Kivonás és értékadás Szorzás és értékadás Osztás és értékadás Maradékképzés és értékadás Bitenkénti ÉS és értékadás Bitenkénti kizáró VAGY és értékadás Bitenkénti megengedő VAGY és értékadás Eltolás balra és értékadás Eltolás jobbra és értékadás |
Jobbról balra |
| 17 | , | Szekvencia operátor | Balról jobbra |
[szerkesztés] Operátorok túlterhelése
A közönséges függvényekhez hasonlóan a legtöbb operátort is túl lehet terhelni, amely a felhasználói típusok kényelmesebb használatát teszi lehetővé(jellemző például az <<(eltoló) operátor túlterhelése, melyet a kimeneti folyamok használnak kimenetként). Értékadó(=, += stb.), &(címe) operátort csak tagfüggvényként lehet megírni, minden mást érdemesebb globálisként deklarálni, ha lehet(ha nem kell hozzáférniük a tagokhoz).
struct Complex { //Barátfüggvény deklarációja, hogy hozzáférjen a tagokhoz friend std::ostream& operator<<(std::ostream& stream, const Complex& z); //Konstruktor, taginicializációs listával Complex(double a, double b): x(a), y(b) { } Complex& operator+=(const Complex& rhs) //hozzáadó operátor tagfüggvény... {re += rhs.re; im += rhs.im;} private: double re, im; }; Complex operator+(const Complex& lhs, const Complex& rhs) //összeadó operátor viszont globális {return Complex(lhs) += rhs;} //az ostream definíciójához nem férünk hozzá, de //operator<<(ostream&, const complex&)-t definiálhatunk std::ostream& operator<<(std::ostream& stream, const Complex& z) { return (stream << "(" << z.x << ", " << z.y << ")"); } //Ezután az operátort egyszerűen használhatjuk: Complex c(1.0, 4.6); std::cout << c; //A kimeneten megjelenik: (1.0, 4.6)
[szerkesztés] Típusok, változók, konstansok
A C++ -ban minden felhasznált névről meg kell mondanunk, hogy mi az amit képvisel, tudatnunk kell a fordítóprogrammal a típusát. Megkülönböztetünk alap- és származtatott típusokat. Előbbiekhez a char, az előjeles és előjel néküli egészek és a lebegőpontos típusok tartoznak. Utóbbiak az alaptípusok felhasználásával felépített tömb-, mutató-, etc. típusokat tartalmazza.
Az alapvető adattípusok mérete fordító és platformfüggő, de a következők adottak:
- 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)
- 1 <= sizeof(bool) <= sizeof(long)
- sizeof(char) <= sizeof(wchar_t) <= sizeof(long)
- sizeof(float) <= sizeof(double) <= sizeof(long double)
- sizeof(N) == sizeof(signed N) == sizeof(unsigned N) (ahol N integrális típus)
Az itt megadott méretek a "megszokott" 32 bites asztali környezetben általánosak, de nem garantáltak.
| Adattípus | Értékkészlet | Méret(byte) | Pontosság |
|---|---|---|---|
| bool | false, true | 1 | |
| char | -128..127 | 1 | |
| signed char | -128..127 | 1 | |
| unsigned char | 0..255 | 1 | |
| wchar_t | 0.65535 | 2 | |
| int | -2147483648..2147483647 | 4 | |
| unsigned int | 0..4294964295 | 4 | |
| short | -32768..32767 | 2 | |
| unsigned short | 0..65535 | 2 | |
| long | -2147483648..2147483647 | 4 | |
| unsigned long | 0..4294964295 | 4 | |
| long long | -9223372036854775808..9223372036854775807 | 8 | |
| unsigned long long | 0..18446744073709551615 | 8 | |
| float | 3.4E-38..3.8E+38 | 4 | 6 |
| double | 1.7E-308..1.7E+308 | 8 | 15 |
| long double | 3.4E-4932..3.4E+4932 | 10 | 19 |
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.
Konstansoknak azokat a változókat nevezzük, amelyeknek pontosan egyszer a definícióban adhatunk értéket (ekkor kötelező), és a típusnév elé írt const típusmínősítővel jelezzük:
const int x; //Hiba! const int y = 10; //Jó y = 10; //Hiba!
[szerkesztés] A felsorolt típus (enum)
Az "enum" olyan adattípust jelöl, melynek lehetséges értékei egy konstanshalmazból kerülnek ki.
enum Animals { bear, wolf, rabbit };
A fordító balról jobbra haladva nullával kezdve egész értékeket feleltet meg a felsorolt konstansoknak. Ha egy konstasnak egész értéket adunk, akkor a következő elemek ettől a kiindulási értéktől kapnak értéket.
enum Animals { bear = 10, wolf, rabbit }; //wolf és rabbit értéke rendre 11 és 12
A felsorolásban azonos értékek is szerepelhetnek. A deklarációban megadott név típusnévként is felhasználható:
enum Animals { bear, wolf, rabbit }; Animals x = bear;
[szerkesztés] Típuskonverziók
Előfordulhat, hogy valamely kétoperandusú operátor különböző típusú operandusokkal rendelkezik. Ekkor, hogy a művelet elvégezhető legyen, a fordítónak azonos típusra kell alakítania az operandusokat. Megkülönböztetünk implicit és explicit típuskonverziót.
Implicit típuskonverzió Ez a fajta konverzió a programozó beavatkozása nélkül automatikusan megy végbe, a C++ definíciójában szereplő szabályok alapján. A "szűkebb" típus adatvesztés nélkül konvertálódik a "szélesebb" típusra, ha lehetséges.
int x = 10; float f = 1.23; x + f //A kifejezés float típusú lesz, 10.00 + 1.23 alakban értékelődik ki
A fordított írányban adatvesztés léphet fel:
float f = 1.23; int x = f; //x értéke 1 lesz, a törtrész elvész
Explicit típuskonverzió
Feltétel nélküli típusátalakítás:
(típusnév)kifejezés (pl.: (char *)c)
Temporális érték létrehozása:
típusnév(kifejezés) (pl.: int(x))
Explicit konverziót a programozó előírhat a típuskonverziós operátorok használatával is.
Dinamikus típusátalakítás:
dynamic_cast<típus>(ptr)
Futásidejű konverziókat végezhetünk vele. A "típus" osztályra mutató pointer, referencia, vagy void* típusú kifejezés, míg a "ptr" pointer vagy referencia típusú kifejezés lehet. bad_cast kivételt vált ki, ha nem létezik ilyen konverzió.
Fordítási idejű átalakítások
static_cast <típus>(arg)
Visszafordítható típuskonverzió egymásba konvertálható típusok között. A "típus" és "arg" típusának fordítási időben ismertnek kell lennie.
reinterpret_cast<típus>(arg)
Úgy, mint a (típus)arg kifejezés.
Konstans típusátalakítás:
const_cast<típus>(arg)
Akkor használjuk, ha fel akarjuk oldani a const, vagy volatile típusmódosítók hatását egy adott változón. A "típus" és "arg" azonos típusúak kell legyenek, kivéve a fent említett típusmódosítókat.
[szerkesztés] Pointerek
Amikor egy változót definiálunk, a memóriában létrejön egy megfelelő méretű tároló, amelybe bemásolódik a kezdőérték.
int * p;
A fenti példában egy int típusú változó címének tárolására használható tároló jön létre. A címet a referecia operátorral (&) érhetjük el.
int x = 7; p = &x;
Most az "x" név és a "*p" (a "p" által mutatott tároló) érték ugyanarra a memóriaterületre hivatkozik.
*p = x + 6;
A kifejezés hatására "x" értéke 13 lesz. A referencia típus felhaszálásával már létező változókra is hivatkozhatunk.
int x = 10; int &r = x;
Ebben az esetben nem új változó jön létre, hanem a fordító egy második nevet ad az "x" változónak.
[szerkesztés] Névterek
A fordító a programban használt neveket különböző névtereken (namespace) tárolja. Egy névtérben lévő neveknek egyedieknek kell lenniük, azonban a különböző névtereken azonos néven is szerepelhetnek. Egy névtérben logikailag összefüggő változókat, függvényeket, típusokat tárolunk. Egy osztály/struktúra egyben a nevével azonos nevű névteret is definiál.
// Az Állat névtéren belül szerepel a Farkas és Medve osztályok definíciója namespace Allat{ class Farkas{...}; class Medve{...}; }
Egy névtér elemeire háromféleképpen hivatkozhatunk:
- Globális névfeloldással, ekkor a névtér összes eleme használható:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { cout << "Globális névfeloldás" << endl; return 0; }
- Explicit névfeloldással, közvetlenül a feloldani kívánt elem esetében:
#include <iostream> int main(int argc, char *argv[]) { std::cout << "Explicit névfeloldás" << std::endl; return 0; }
- Megadva a feloldani kívánt elemet:
#include <iostream> using std::cout; int main(int argc, char *argv[]) { cout << "Csak a cout lett feloldva" << std::endl; return 0; }
Létezik az úgynevezett névtelen névtér, amit arra használhatunk, hogy ne szemeteljük tele a globális névteret, megvédjük magunkat a többértelműségektől.
namespace { //amit el akarunk keríteni }
[szerkesztés] A C++ programok szerkezete
[szerkesztés] Hello World!
Az ún. "Hello World" programot először Brian Kernigham és Dennis Richie alkalmazták A C programozási nyelv című könyvükben példaprogramként. Mindössze annyit csinál, hogy a képernyőre írja az üdvözletet.
#include <iostream> int main(int argc, char *argv[]){ std::cout << "Hello World!" << std::endl; return 0; }
A kettőskereszttel (#) jelzett sorok az előfordítónak (precompiler) szóló utasítások. Az "include" utasítás behelyettesíti a hívás helyére a megadott file tartalmát. Az "iostream" fejállomány tartalmazza a megfelelő IO utasításokat a kiiratáshoz. A "main" egy függvény, ez a program belépési pontja. Minden C++ nyelven írt programnak tartalmaznia kell. Paraméterei közül az "argc" a paraméterek számát adja meg, míg az "argv[]" egy nullpointerrel terminált, karaktermutatókat tartalmazó tömb amelyben a paraméterek vannak C-stílusú stringként. A C++ -ban a tömböket nullától indexeljük, az argv nulladik eleme a futtatható állomány neve, első eleme pedig az első paraméter.
./program elso masodik (vagy program.exe elso masodik) argv[0] == "program" argv[1] == "elso" argv[2] == "masodik"
A két kapcsos zárójel ({}) közti részt blokknak nevezzük. A "cout" a C++ standard kimenete, az "std::" pedig arra szolgál, hogy a fordító a standard névtérben keresse a "cout" definícióját. A "::" az ún. hatókör operátor. Az "endl" egy újsor karakter ír. A "return" visszaadja a vezérlést az őt hívó függvénynek, jelen esetben ez a program futásának befejezését jelenti, ezért az operációs rendszernek. A "return" mögé írt szám a visszatérési érték a "0" azt jelenti, hogy a program rendben lefutott. A main()-ben ez nem kötelező, ha elhagyjuk akkor 0-t ad vissza.
A program futásának eredménye:
./program Hello World!
[szerkesztés] Standard IO
A C++ megkülönböztet standard- inputot, outputot illetve errort. A standard output(cout) amire ír, ez alapértelmezés szerint a képernyő. A standard input(cin) ahonnan a bejövő adatokat fogadja, alapesetben a billentyűzet. A standard error(cerr) az az eszköz, ahová a hibaüzenetek érkeznek, alapértelmezetten szintén a képernyő.
std::cout << "Standard kimenet!"; char ch; std::cin >> ch; // A standard bemenetről beolvasunk a ch változóba std::cerr << "Standard error!";
[szerkesztés] Az előfordító
A C++ megörökölte a C előfordítóját. Az előfordító a tényleges fordítás előtt fut le. Feladatai közé tartoznak a forráskód megfelelő átalakítása, szimbólumok és makrók definiálása, illetve felhasználható feltételes fordításra is. Az előfordító által definiált szimbólumok és makrók típus nélküli értékekkel dolgoznak, ezért míg a C - gyengén típusos nyelv lévén - erősen épít az előfordítóra a C++ nyelvben gyakorta (fordítási és futási) hibák forrásai lehetnek.
[szerkesztés] Vezérlési szerkezetek
[szerkesztés] Szekvencia
A szekvencia az egymás után végrehajtott utasításokat jelenti:
utasítás1; utasítás2; ...
[szerkesztés] Elágazás
Amikor a program a futásakor egy elágazáshoz ér, megvizsgál egy feltételt, és ha igaz akkor végrehajtja a megfelelő utasítást.
if(feltétel) utasítás; else utasítás; //az utasítások lehetnek blokkok({}) is, az else ág elhagyható
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 (int, char és enum) típust lehet vizsgálni.
void Eldontendo(char c) { switch(c) { case 'i': case 'I': cout << "Igen\n"; break; //break; nélkül "átesne" a következő case-be is case 'n': //itt viszont szándékosan nincs break; case 'N': cout << "Nem\n"; break; default: cout << "I/i vagy N/n!\n"; break; //ez nem kötelező, ha semelyik sem igaz, ide ugrik } }
Ha mégis bonyolultabb többszörös feltételt kell megvizsgálnunk, akkor használhatjuk az else if szerkezetet, azaz az else ágban nyitott if -et.
if(feltétel_1) { utasítás_1; } else if(feltétel_2) { utasítás_2; } else if(...) { } . . . else utasítás;
Egy elágazásban pontosan egy if, bármennyi else-if és pontosan egy else ág lehet.
A feltételek kiértékelése balról jobbra történik, és csak addig megy, amíg biztosan igaz vagy hamis az eredmény (lusta kiértékelés):
bool l = false; bool k = true; if(l && k) { utasítás; }
A fenti példában a logikai és operátort használtuk, ez a feltétel akkor lesz igaz, ha "l" és "k" is igaz. Mivel "l" hamis ezért a program csak "l" -t fogja vizsgálni.
[szerkesztés] Ciklus
Ciklust akkor használunk, ha ugyanazt a feladatot többször kell elvégezni egymás után. Háromféle ciklus áll rendelkezésre:
Számláló ciklus (for)
for(int i = 0;i < 10;++i) { std::cout << "i = " << i << std::endl; }
A ciklusfeltételben felveszünk egy ciklusváltozót, kezdőértéket adunk neki, és megadjuk meddig menjen a számláló. A "++" a C++ ún. inkrementáló operátora, megvan a párja ("--", dekrementáló) is. Két alakja létezik:
- változó++ -> postfixes alak, csak a kifejezés kiértékelése után növeli az értékét és letrehoz egy átmeneti változót.
- ++változó -> prefixes alak, nincs átmeneti változó, azonnal növeli az értékét, általában ezt használjuk.
Elöltesztelős ciklus:
while(feltétel) { utasítás; }
Az elöltesztelős ciklus előbb vizsgálja a ciklusfeltételt, aztán hajtja végre az utasításokat. Ez azt jelenti, hogy nem biztos, hogy akér egyszer is lefut.
Hátultesztelős ciklus:
do { } while(feltétel);
Ez a ciklus biztos, hogy egyszer legalább lefut.
[szerkesztés] Break, continue
Speciális vezérlőszavak a break és a continue, a break kilép a legbelső ciklusból(nincs többszörös break), a continue pedig a ciklus végére ugrik, azaz a feltételvizsgálathoz, átugorva a ciklusmag hátralévő részét.
[szerkesztés] A goto
A goto mint utasítás speciális a vezérlők között, a vezérlés szerkezetének felborítását végzi, egyszerű ugróutasítás. Szerkezete: goto cimke; ahol a cimkét cimke: alakban vezetjük be. Túlzott használata átláthatatlanná teheti a kódot, néha mégis alkalmazzuk, például ha többszörösen beágyazott ciklusból kell kilépni.
[szerkesztés] Adatszerkezetek
[szerkesztés] Tömb
A C stílusú tömb azonos típusú adatok halmaza, amelyek a memóriában folytonosan helyezkednek el. Csak alapértelmezett konstruktorral rendelkező típusokból lehet tömböt létrehozni(a beépített típusok ilyenek). A tömb elemeire a tömb nevével és az indexelő operátorral ([]) hivatkozhatunk:
int t[10]; //10 elemű statikus tömb, másnéven vektor for(int i = 0;i < 10;++i) { t[i] = i; //az i -edik index értéke legyen i }
A C++ -ban a tömböket nullától indexeljük. A C++ fordító nem ellenőrzi a tömbindexeket, ezért hibás indexeléssel is lefordul a program, de futás közben az operációs rendszer hibát fog dobni (jellemzően ez a segmentation fault(szegmenshiba)).
int t[10]; // 10 elemű tömb létrehozása // tömb feltöltése std::cout << t[10]; //lefordul, de hibás mivel nulla az első elem indexe, t[9] a legmagasabb hivatkozható elem
Létrehozhatunk többdimenziós tömböt is:
int t[méret1][meret2]...[meretN];
A leggyakrabban használt a kétdimenziós tömb, azaz a mátrix:
int sizeN, sizeM; sizeN = sizeM = 5; int t[sizeN][sizeM]; //5*5 mátrix for(int i = 0;i < sizeN;++i) { for(int j = 0;j < sizeM;++j) { t[i][j] = i + j; // az i -edik sor j -edik oszlopa legyen i+j } }
A tömb neve kifejezésben konvertálódik az nulladik elemre mutató pointerré, de nem egyezik meg vele.
[szerkesztés] Struktúrák
A programozás során gyakran találkozhatunk olyan esetekkel, amikor több különböző adatot egy egységként kell kezelnünk, pl. egy könyvnek van szerzője, címe, kiadója, stb. A C++ nyelvben a struktúra (struct) több különböző típusú adatok együttese:
//a Book struktúra definíciója struct Book { std::string title; std::string writer; int year; };
A "string" típus karaktersorozatot jelöl, és a standard névtérben helyezkedik el. A struktúrák adattagjaira a pont operátor (.) segítségével hivatkozhatunk és adhatunk nekik értéket:
//Book struktúra megadása tagonként Book b; //típusként kezelhetjük b.title = "A C++ programozási nyelv"; b.writer = "Bjarne Stroustrup"; b.year = 2005; //Book kezdőértékadása Book c = {"A C++ programozási nyelv", "Bjarne Stroustrup", 2005}; //c == b
Struktúrákat a C nyelvben is lehet használni. A C++ -ban a struktúrákat kibővítették, hogy alkalmasak legyenek az objektum-orientált programozás megvalósítására:
- Rendelkezhetnek konstruktorral és deskruktorral
- Lehetnek tagfüggvényeik
- Szabályozható az adattagok elérése
A struktúra minden tagjának láthatósága alapértelmezetten nyilvános (public), ettől eltekintve lényegében ekvivalens a class szerkezettel, ahol az alapértelmezett elérés zárt (private).
[szerkesztés] Uniók, Bitmezők
A C nyelv kidolgozásakor a takarékos memóriahasználat érdekében több megoldást is beépítettek a nyelvbe:
Az uniók (union) lényege, hogy ugyanazt a memóriaterületet több változó közösen használja (persze nem egyidejüleg). Leggyakrabban gépfüggő adatkonverziók megvalósítására használják.
A bitmezők az egy byte -nál kisebb helyfoglalású változókat egyetlen byte -on tárolják. Gyakorlati hasznuk főleg a hardverek vezérlésénél van.
[szerkesztés] Osztályok
A C++ az objektum-orientált programozás megvalósításához egyrészt kibővítí strukturákat, másrészt bevezeti a class típust. Mindkettő alkalmas osztály definiálására. Egy osztály (class) adattagjának háromféle elérhetősége lehet:
- public, nyivános mindenki számára elérhető
- private, privát csak az osztályon belülről lehet elérni, illetve barát osztályok és függvények
- protected, védett a származtatott osztályok számára közvetlen elérhetőséget biztosít. A private tagok a leszármazottakból csak az ősosztály tagfüggvényeiből (metódusok) elérhetőek.
//Egy egyszerű osztály class SimpleBook { public: SimpleBook(std::string param_cim){ cim = param_cim; } private: std:string cim; };
A programozó által definiált híján minden osztály és struktúra rendelkezik alapértelmezett konstruktorral(típusnév()), másolókonstruktorral(típusnév(const típusnév&)) és értéadó operátorral(operator=(const típusnév&)), ami egyszerű adatszerkezet esetén általában megfelelő és hatékony.
[szerkesztés] Függvények
A függvény (function) a program olyan névvel ellátott egysége, amely a program bármely pontjából hívható. Általában olyan utasítássorozatokat tartalmaz, amelyekre gyakran van szükség de ismételt megírásuk fárasztó vagy helyigényes. A hagyományos C++ program sok kisméretű, könnyen karbantartható függvényből épül fel.
[szerkesztés] Eljárás, függvény
A Pascal nyelvben megkülönböztettünk függvényeket (function) és eljárásokat (procedure), aszerint, hogy volt -e visszatérési érték vagy sem. A C++ -ban azonos módon definiálhatjuk a kettőt:
//Eljárás, nincs visszatérési érték void func_1(){ std::cout << "Hello World!" << std:endl; } //Függvény, string típusu visszatérési érték std::string func_2(){ return "Hello World!"; }
A "void" a C++ általános típusa, a függvény neve elé írt típus a visszatérési értéket jelöli. A fenti pélában tájékoztattuk a fordítót, hogy nem lesz visszatérési érték. A függvény neve után írt zárójelekben lehetnek a függvény paraméterei (ha vannak neki) :
//Kiírja a megadott karaktersorozatot void func(std::string msg){ std::cout << msg << std::endl; }
[szerkesztés] Definíció, deklaráció
A függvényhívás előtt meg kell adnunk a függvény deklarációját, ami jelzi a fordítónak, hogy egyáltalán létezik a függvény. A definíció nem feltétlenül esik egybe a deklarációval, a programon belül bárhol elhelyezkedhet.
A deklarációban szerepelnie kell a visszatérési értéknek, a függvény nevének és paraméterlistájának. Ezeket együttesen(név és paraméterlista) aláírásnak (szignatúra) vagy a függvény prototípusának nevezzük.
#include <iostream> //deklaráció void func(std::string msg); std::string func_2(); int main(int argc, char *argv[]) { //hívás func("Hello"); std::cout << func_2(); return 0; } //definíció void func(std::string msg){ std::cout << msg << std::endl; } std::string func_2(){ return "Hello World!"; }
A második függvény a karaktersort, amivel visszatért átadta a cout << operátorának, így a szöveg megjelenik a szabványos kimeneten.
[szerkesztés] Paraméterátadás
A C++ -ban kétféle paraméterátadási mód van:
- Érték szerinti, az átadott típusból másolat készül a memóriában, az eredeti értéket nem módosítja a függvény.
- Cím szerinti, paraméterként az átadott típus referenciája szerepel, a függvény módosíthatja a paramétereket.
//a és b összegével tér vissza int sum_1(int a, int b){ return a + b; } //az eredményt c -ben tárolja void sum_2(int a, int b, int &c){ c = a + b; }
A második változat harmadik paramétere nem "c" értéke, hanem a memóriában elfoglalt címe.
A C programozási nyelvben minden érték szerint adódik át, a C++ a kompatibilitás érdekében megtartotta ezt a módszert és az érték szerinti átadást (pass-by-value) alapértelmezettnek veszi.
Kis objektumok esetén (pl. egy int) az érték szerinti átadás hatékonyabb lehet, míg összetettebb típusok esetében a cím szeriti átadás (pass-by-reference) jobb.
class A { public: A(){...} A(const A & a){...} ~A(){...} ... private: std::string s, k; }; A func_val(A a){ return a; } A func_ref(A & a){ return a; } A x; func_val(x); func_ref(x);
A példában az első függvényhíváskor meghívódik az A osztály másoló konstruktora, hogy inicializálja a -t x -el majd a függvény által visszaadott objektumot is inicializálja, végül meghívódik a destruktor a -ra és a visszaadott objektumra. Eközben az A osztály tagjainak is meghívódik a konstruktora és a destruktora.
A második függvényhívás az objektum címét adja át, így csak a viszaadott példányt kell inicializálni.
[szerkesztés] Alapértelmezett paraméterek
A függvénydefinícióban bizonyos paraméterekhez alapértelmezett értéket rendelhetünk. Ezt az értéket a program akkor használja ha az adott argumentum nem szerepel a lisában. Alapértelmezett értékkel rendelkező paraméter után csak ugyanilyen paraméterek szerepelhetnek a formális listában.
void sayHello(std::string msg = "Hello"){ std::cout << "Hello" << std::endl; } //A függvény hívása sayHello(); sayHello("Hello"); //A kettő ugyanazt jelenti
[szerkesztés] Inline függvények
Inline függvény esetén a függvény hívás helyére a függvény kódja helyettesítődik be, még fordítási időben. Ezáltal megspórolható a függvényhívás költsége, viszont növekszik a tárgykód. Általában kis méretű, nem bonyolult függvények esetén használható hatékonyan.
inline void sayHello(){ std::cout << "Hello" << std::endl; }
Az inline definíció csak javaslat a fordítónak, amelyet nem muszáj figyelembe vennie. A legtöbb fordító a ciklust vagy rekurzív függvényhívást tartalmazó függvény esetén elutasítja az inline direktívát.
[szerkesztés] Lokális változók
A függvények belsejében (illetve a programban lévő blokkokon belül) deklarált változókat lokális változóknak nevezzük. Ez a gyakorlatban azt jelenti, hogy a láthatóságuk és élettartalmuk a függvényen (blokkon) belülre korlátozódik. A lokális változók a függvényhívás végén automatikusan megsemmisülnek és kívülről nem hivatkozhatóak.
//Két változó értékének cseréje void swap(int &a, int &b) { int tmp = a; //nem dinamikusan (statikusan) lefoglalt változó a = b; b = tmp; } tmp = 10; //Hiba, tmp nem hivatkozható a hatókörén (a függvény blokkján) kívül
A dinamikus objektumokra mutató pointerek szintén megsemmisülnek a hatókörükből kikerülve, de az objektum maga nem.
int * createArray(int length) { int * v = new int [n]; return v; //A függvény egy n elemű tömbre mutató pointerrel tér vissza } int * t = createArray(10); t[0] = 12; //Működik, most t mutat a tömbre v[1] = 2; //Hiba, v már nem létezik
Ha nem gondoskodunk a blokkon belül létrehozott dinamikus objektum külső elérhetőségéről, az érvényes hivatkozás nélkül a memóriában marad, azaz memóriaszivárgás keletkezik.
void func() { int * v = new int [10]; } v[0] = 12; /*Hiba, a tömbre mutató pointer már nem létezik, és más sem mutat rá -> memóriaszivárgás*/
[szerkesztés] Túlterhelés
A túlterhelés leveszi a programozó válláról a sokféle név megjegyzésének terhét, mivel azonos névvel létezhet több függvény is.
Akkor beszélünk túlterhelésről, ha azonos látókörben(scope) több azonos nevű függvény van deklarálva, különböző szignatúrával. egyébként elfedésről(ha egy külső látókörben van a másik név).
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 bonyolultak, így óvatosan kell túlterhelt nevet bevezetni.
//példakód: struct Point { Point(int X, int Y): x(X), y(Y){} int x,y; }; double VectorLength(int, int); double VectorLength(Point); int main() { cout << VectorLength(3, 4) << endl; cout << VectorLength(Point(3, 4)) << endl; //feltehetőleg mindkét függvény 5.0-t fog visszaadni }
[szerkesztés] Kivételkezelés
Kivételnek (exception) azt a hibás állapotot, vagy váratlan eseményt nevezzük, amely megszakítja a program rendes futását. A kivételkezelés lehetővé teszi, hogy a vezérlés ahhoz a programrészhez kerüljön, amely képes az adott kivétel kezelésére. A throw utasítás segítségével kivételt "dobhatunk" amelyet a program "elkaphat" (catch). A C++ megszakításos modell alapján kezeli a kivételeket, azaz a kivételt kiváltó programrész futása megszakad. Három elem szükséges a kivételkezelés megvalósításához:
- A kivételt kiváltható programrész kijelölése (try)
- A kivétel dobása (throw)
- A kivétel elkapása (catch)
A C++ -ban kevés beépített kivétel található, de bármely típus dobható, így magunknak is definiálhatunk ha szükséges. A kezelőig(a megfelelő típust elkapó catch a hívási sorban valahol feljebb) tartó minden automatikus változó megsemmisül destruktorhívással.
Ha kivételkezelés közben újabb kivétel keletkezik(például egy felszámolt változó destrukora dob(ez nagyon veszélyes)) a futás eredménye definiálatlan, de általában katasztrófális. Érdekesség, hogy a C++ a statikus területen mindig fenntart akkora helyet, hogy a memóriafoglaló bad_alloc kivételét ki tudja váltani.
void f(){ throw 1; } //ez a függvény mindenképpen dob kivételt try{ f(); //kivétel dobódik }catch(1){ //elkapjuk std::cerr << "Exception!\n"; }
Függvények esetében megadhatjuk, hogy milyen kivételeket továbbíthat:
void f() throw(); //nem dobhat kivételt void g() throw(int); //int típusú kivételek try{ g(); }catch(int){ //int típusú kivételek } ... //más kivételek elkapása ... }catch(...){ //minden kivétel elkapása, ez mindig az utolsó helyen áll }
[szerkesztés] Dinamikus memóriakezelés
A dinamikus memóriahasználat alapgondolata, hogy adataink számára akkor foglalunk helyet amikor szükség van rá, ha pedig feleslegessé válnak azonnal felszabadítjuk a memóriaterületet. A C++ -ban különösen fontos szerepe van, mivel nincs "szemétgyüjtő" mint a JAVA -ban vagy C# -ban. A felszabadítatlan memória "memóriaszivárgáshoz" (memory leak) vezethet.
[szerkesztés] A new és delete operátorok
A C++ -ban lehetőség van használni a C nyelv malloc (és egyéb változatai) és free utasítását, de ajánlott az újakat alkalmazni. A new operátor az operandusban megadott típusnak megfelelő méretű területet foglal a memóriában, és arra mutató pointert ad vissza. A delete felszabadítja a new által lefoglalt területet:
int *v = new int; //helyfoglalás egy int -nek delete v; //felszabadítjuk
Nemcsak egy elemnyi terület elfoglalására van lehetőség, hanem több egymás után elhelyezkedő elem számára is foglalhatunk területet. Ezt az adatstrultúrát dinamikus tömbnek nevezzük:
int *v = new int [10]; //helyfoglalás 10 db int -nek delete[] v; //felszabadítjuk
Többdimenziós tömböt is foglalhatunk dinamikusan, ekkor a tömb elemei egy-egy pointerre mutatnak:
int **v = new int * [10]; //10 db int -re mutató pointer for(int i = 0;i < 10;++i) { v[i] = new int [10]; // 10x10 -es mátrix, v minden eleme 10 db int -re mutat for(int j = 0;j < 10;++j) { v[i][j] = i + j; } } //felszabadítjuk a mátrixot, minden egyes mutatót külön for(int i = 0;i < 10;++i){ delete[] v[i]; } delete[] v; //végül magát v -t is
Fontos, hogy minden new -hoz a megfelelő delete -et használjuk:
int *t = new int; delete[] t; //definiálatlan, a legjobb esetben lefoglalva marad a memória int *v = new int[10]; delete v; //szintén definiálatlan.
Az malloc -al szemben a new nemcsak területet foglal, de automatikusan meghívja a megfelelő konstruktorokat is.
Főleg nagyméretű adatok esetén előfordulhat, hogy nincs elég memória. Ekkor a C++ "std::bad_alloc" kivételt dob.
try{ int *t = new int[10000]; //40000 byte memóriát foglalunk }catch(std::bad_alloc){ //Hibakezelés }
[szerkesztés] Objektumorientált C++
[szerkesztés] Adattagok és tagfüggvények
Az adattagokat a változókhoz hasonlóan deklaráljuk, de az adattagok nem tartalmazhatnak inicializációs listát. Ha egy osztályon belül egy másik osztályt akarunk adattagként használni, akkor előzőleg szerepelnie kell a másik osztály teljes deklarációjának.
Az adattagokon műveleteket végző tagfüggvényeket az osztály törzsében deklaráljuk. Ezen a függvény szignatúráját, vagy a helyben kifejtett implicit inline definicióját értjük (vagyis a függvény prototípusa előtt nem szerepel az inline kulcsszó). Az inline módosító megadásával az osztálydefiníción kívűl definiált függvényeket is inline -á tehetjük.
class MyClass { public: MyClass(int x) { value = x; } void printValue() { std::cout << value << std::endl; } //inline definíció private: int value; };
Amennyiben csak a deklarációt tartalmazza az osztály, a függvény törzsét azon kívűl kell definiálni. A definíció sorrendben a visszatérési értékből, az osztály nevéből, a hatókör operátorból, a függvény szignatúrájából és törzséből áll:
class MyClass { public: MyClass(int x) { value = x; } void doSomething(); private: int value; }; void MyClass::doSomething(){ /*do something*/ } //külső definíció
Egy osztály bármely tagfüggvénye hozzáfér az adattagokhoz, függetlenül annak elérésétől.
[szerkesztés] Statikus tagok
A static kulcsszóval bevezetett adattagokból és tagfüggvényekből osztályszinten egy darab létezik.
[szerkesztés] Konstruktorok
Az objektumok kezdeti értékadásaiért (inicializálás) speciális tagfüggvények a konstruktorok felelnek. A konstruktor olyan tagfüggvény amelynek neve megegyezik az osztályéval és nem rendelkezik típussal.
class MyClass { public: MyClass(const int & data) { x = data; } //Konstruktor private: int x; }; MyClass* mc = new MyClass(10); //mc.x egyenlő 10 -el
A fordító minden olyan esetben mikor egy objektum létrejön meghívja a konstruktorát. Egy osztálynak bármennyi konstruktora lehet a szignatúrától függően. Alapértelmezés szerint minden osztály két konstruktorral rendelkezik, a paraméter nélküli (default) és a másoló (copy) konstruktorral. Ha saját konstruktort készítünk, attól fogva az alapértelmezett nem lesz elérhető. A konstruktorok egyaránt lehetnek public, private vagy protected elérésüek. A csak private konstruktorokat tartalmazó osztályt rejtett osztálynak nevezzük.
Ha az egyparaméteres konstruktorokkal rendelkező osztályok példányainak nem teljesen illeszkedő típust adunk kezdőértékül akkor a fordító implicit típuskonverziót hajt végre. Ezt megtilthatjuk az explicit kulcsszó használatával:
class MyClass { public: explicit MyClass(const int & data){ x = data; } //csak int -et fogad el private: int x; }; void f() { MyClass x; x = 3; //hiba! explicit kulcsszó miatt nincs konverzió x = MyClass(3); // jó }
Ha az objektum egy másik osztály pédányát is tartalmazza, akkor a belső osztály konstruktorát a külső osztály konstruktorában hívjuk. Ezt a taginicializációs lista használatával oldhatjuk meg, amelyet a konstruktor szignatúrája után kettősponttal elválasztva adhatunk meg:
class MyClass { public: MyClass(const int & data) : x(data) { }; private: int x; };
Taginicializációs listát csak a konstruktorban adhatunk meg. Használata kötelező, ha az osztály referencia típusú vagy paraméterezett típussal rendelkező adattagot tartalmaz. A konstans tagok beállítására is ezt használjuk:
class MyClass { public: MyClass(const double & d_data, const int & i_data) : y(d_data), x(i_data) { }; private: const double y; int x; };
[szerkesztés] Destruktorok
Az objektumok által felhasznált memória mindaddig lefoglalt marad, míg fel nem szabadítjuk. Erre a célra a C++ biztosít számunkra egy speciális tagfüggvényt, a destruktort. Hasonlóan a konstruktorhoz, a destruktornak sincs visszatérési értéke. A destruktornak nem lehetnek paraméterei. A destruktor nevét az osztály nevéből és a hullám karakterből (tilde: ~) képezzük:
class MyClass { public: MyClass(int val){ t = new int(val); } //konstruktor ~MyClass(){ delete t; } //destruktor private: int * t; };
Ha nem definiálunk destruktort az osztályunkban, a fordító automatikusan létrehozza. A destruktor minden olyan esetben meghívódik amikor az objektum érvényessége megszünik. Kivételt képeznek a dinamikusan (a new operátorral) létrehozott példányok, amelyeknek csak a megfelelő delete operátor hívhatja meg a destruktorát. A destruktor közvetlenül is hívható.
Dinamikus tömbök esetén a konstruktorok az indexek növekvő sorrendjében hívódnak meg, a destruktorok éppen fordítva, de csak a delete[] operátor alkalmazásával. A statikus tömbök ugyanígy törlődnek, de automatikusan (tehát nem kell delete), amint kikerülnek a hatókörükből. A nem megfelelő delete használatával a legjobb esetben is csak a tömb első eleme semmisül meg.
[szerkesztés] Példányosítás
Egy osztály egy memóriában létrehozott példányát objektumnak nevezzük. Minden objektum rendelkezik a neki megfelelő osztály minden egyes adattagjával(természetesen az egyes példányok külön másolatokat birtokolnak (kivéve a statikus tagokat)) és tagfüggvényével. Egy objektumot létrehozhatunk dinamikusan és statikusan is.
class MyClass{ ... }; MyClass my_static_object; //statikus definíció MyClass * my_dynamic_object = new MyClass(); //dinamikus definíció
A két esetben a tagok elérése különbözik. Statikus definíció esetén a pont(.) operátort, míg dinamikus esetben a (->) operátort használjuk.
my_static_object.member_func(); my_dynamic_object->member_func();
[szerkesztés] Öröklõdés
Az öröklõdés az objektumorientált stílus meghatározó eleme, amely a kódújrafelhasználást és a dinamikus típuskezelést teszi támogatottá. C++-ban az eszköztára az absztrakt metódusok, a (többszörös) öröklõdés és a virtual kulcsszó.
Absztraktnak nevezünk egy osztályt, ha van legalább egy absztrakt tagfüggvénye(ezt a függvénydeklaráció után tett "= 0"-val jelezzük), ekkor az osztály nem példányosítható.
Az öröklés úgy hoz létre új objektumot, hogy az megtartja az õse minden adattagját és (az õsosztályban nem private) tagfüggvényét.
Segítségével nem kell ismernünk egy objektum pontos típusát, mutatókon és referenciákon keresztül dinamikus kötésû mûveleteket hívhatunk meg(polimorfizmus - többalakúság).
Hatékonysági okok miatt C++-ban minden függvény statikus, ha nincs explicit megmondva, hogy virtual ott, vagy a bázisosztályban.
[szerkesztés] Öröklés láthatósága
Az öröklõdés lehet public, ez a bázisosztály osztály specializációja, protected illetve private, ekkor a származás nem lesz kívülrõl látható, azaz le kell mondanunk a dinamikus típusazonosításról, de egyébiránt teljesértékû öröklõdés.
[szerkesztés] Névütközések
Ha a bázis és a származtatott osztályban szerepel ugyanolyan néven függvény akkor nincs túlterhelés, a származtatott elfedi azokat, a névtér-szabályok miatt. A nem-virtuális függvények ellenben fordítási idõben kötõdnek, így bázisra mutató pointeren keresztül minden nem-virtuális függvényhívás a bázisosztálybelit fogja végrehajtani.
struct A { virtual void print1() { cout << "A"; } void print2() { cout << "A"; } virtual ~A() //ökölszabály: bázisosztályban _mindig_ legyen virtuális a destruktor, //a delete operátor így tudja megfelelően megsemmisíteni az objektumot {} }; struct B: public A //public alapértelmezett: nem kötelezõ { virtual void print1() { cout << "B"; } void print2() { cout << "B"; } }; int main() { A* p = new B; B.print1(); //"B" B.print2(); //"A" }
[szerkesztés] Generikus C++
[szerkesztés] A generikus programozásról
A generikus programozás az alapvetõen típusfüggetlen algoritmusok(pl. rendezések) és általános célú tároló szerkezetek(pl. listák) létrehozásán alapuló programozási stílus, a generikus programozási nyelv ami ezt nyelvi eszközökkel támogatja.
[szerkesztés] A template kulcsszó
A C++ közvetlenül támogatja ezt a programozási stílust, a template kulcsszóval, mely osztályok és függvények elé egyaránt beszúrható. A formális paraméterek a template után <>-ben sorolandók fel, típusuk lehet konkrét típus(pl. int) vagy típusparaméter, ezt a typename kulcsszóval jelöljük.
//sablon-osztály template <typename T, int size> class MyBuff { public: MyBuff(){} T GetItem(int); private: T buf[size]; }; template<typename T, int size> T MyBuff<T, i>::GetItem(int num) { return buf[num]; }
A tagfüggvények és az osztályon belül deklarált osztályok maguk is sablonok, kifejthetõk az osztályon kívül is, ekkor jelezni kell a teljes template szignatúrát(pl. GetItem). Az osztálysablon nem implicit inline tagfüggvényeit minden olyan forrásállományba be kell építenünk, amelyből azokat hívjuk (bevett szokás az osztályt és tagfüggvényeit egyetlen fejállományban elhelyezni).
A sablondeklarációban typename helyett írható class is, a kettő között nincs különbség. Amikor a fordító számára nem egyértelmű, hogy típussal van dolga, akkor a typename/struct/class szóval jelezhetjük ezt:
template <class T> void func() { typename T::iterator ti; }
A fenti példában a T típus még nem jött létre, ezért tudatnunk kell a fordítóval a létezését.
Az osztály példányosításakor ki kell írni a paramétereket az osztály neve után.
void f() { //... MyBuff<int, 10> IntBuff; //... }
Ekkor a fordító a paraméterek típusából megállapítja T aktuális értékét, nem kell explicit kiírni.
//sablon-függvény template<typename T> void sort(vector<T>& v) { //valamely rendezõ algoritmus } //... void func(vector<int>& vekt) { sort(v); }
[szerkesztés] Példányosulás
A sablonok fordítási idõben példányosulnak, így a fordítónak ismernie kell a típusparaméterek típusát, a konkrét típusú paramétereknek konstansnak kell lennie. A hibát legkésõbb szerkesztéskor jeleznie kell a fordítónak. Mivel csak a használt sablonok példányosulnak, ezért kódtakarékos megoldás lehet a generikus programozás, de akár összetettebb megoldások is elképzelhetõk(jellemzõ példa a < operátor, melyet nem lehet minden típushoz értelmesen biztosítani, ezért a list::sort fordítási hibát okoz ilyenekbõl épített listára, de listát magát létre lehet hozni). Rossz tervezés esetén azonban nagyon elszaporodhatnak az egymástól alig különbözõ függvények kódjai. A fordító felismeri a MyBuff<int, 10> és MyBuff<int, 10+2-2> közötti azonosságot.
[szerkesztés] Típusazonosságok
Két sablon pontosan akkor azonos típusú, ha a sablon-paramétereik azonosak, egyéb esetekben teljesen különálló típusok. Ez magával vonja, hogy a sablonok teljesen függetlenek az osztályhierachiától. A sablon nem terhelhetõ túl a paramétereire, de specializációt lehet adni, konkrét típusokra/értékekre.
[szerkesztés] Template metaprogramok
A sablonokkal fordítási idejű programokat lehet írni, és ez a nyelv Turing-teljes, azaz minden számítógéppel megoldható problémára alkalmazható. Faktorszámítás:
template <int N> struct Faktor { enum { value = N * Faktor<N - 1>::value }; }; template <> struct Faktor<0> { enum { value = 1 }; }; // Faktor<4>::value == 24 // Faktor<0>::value == 1 int main() { int x = Faktor<4>::value; // == 24 int y = Faktor<0>::value; // == 1 }
[szerkesztés] Irodalom
- Bjarne Stroustrup: A C++ programozási nyelv
- Scott Meyers: Hatékony C++
- Stephen C. Dewhurst: C++ hibaelhárító
- Tóth Bertalan, Lapteva Natalia: Programozzunk C++ nyelven