[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - az első program

[2016. szeptember 06.] [ christo161 ]

A legelső program, ami általában a programozás tanfolyamokon szóba kerül mindössze annyit csinál, hogy kiír a képernyőre (pontosabban a parancssorba) egy szöveget. Ebben a fejezetben néhány kiíratással kapcsolatos alapvető tudnivalóról lesz szó.

Előző rész: első lépések
Következő rész: változók, konstansok, literálok

Nézzük tehát annak a programnak a C++ forráskódját, ami kiírja a parancssorba, hogy Hello World!

//1. peldaprogram forraskodja

#include <iostream>
using namespace std;

int main() {
	cout << "Hello World!" << endl;
	return 0;
}

A program futtatása CodeBlocks-ban:

codeblocks_hello_world.png

Könnyű megtalálni a kiíratásért felelős utasítást, mivel abban szerepel a Hello World! szöveg:

cout << "Hello World!" << endl;

Habár ez egy nagyon egyszerű program, nem elég ezt az egyetlen utasítást tartalmaznia, mert a kiíratáshoz (és adatbekéréshez, melyről a következő fejezetben lesz szó) használható dolgok (például cout, cin, printf, scanf, getline, endl, satöbbi) deklarációit/definícióit a programunk alapból nem tartalmazza (hiszen nincs minden programban szükség parancssorba való kiíratásra, vagy onnan történő adatbekérésre).
Az #include <iostream> utasítás helyére az úgynevezett preprocesszor (előfordító) beilleszti az iostream nevű header fájl tartalmát, még a fordítás megkísérlése előtt, melyben többek között a parancssorba történő kiíratás, és az onnan történő beolvasáshoz használt dolgok (objektumok és függvények mint például cout, cin, printf) deklarációi találhatóak.
Ha ez az utasítás nem szerepelne a forráskódban, akkor a program nem fordulna le, egy arra vonatkozó hibaüzenettel, hogy a fordító nem ismeri a cout-ot.

Ügyeljünk arra, hogy a tananyagban található példák nagy része csak programkód részlet, mint ahogy az imént kiemelt utasítás is. Ezek csak akkor fognak működni, ha egy (jól) működő programkódban beillesztjük őket a megfelelő helyre, ami a legelső programok esetén jellemzően a main függvény kapcsoszárójelei közötti részt jelenti (ezt nevezzük a main függvény blokkjának).
Minden C++ nyelven írt program kódjában szerepelnie kell main függvénynek, mivel a main függvény a program belépési pontja. A C++ programok érdemi tartalma a main függvény blokkjában lévő első utasítással kezdődik.
Az első néhány fejezet példaprogramjainak az összes érdemi utasítása a main függvényben fog szerepelni, vagyis ez azt jelenti, hogy az első néhány fejezet példaprogramjainak a main függvény lesz az egyetlen függvénye, ezekben a programokban nem hozunk létre új függvényeket.

int main() {
	/*ez itt a main függvény blokkja
	a nagyon egyszerű programok érdemi utasításait ide írhatjuk*/
}

Nem könnyű pontosan meghatározni, hogy mit értek érdemi utasítások alatt. Lényegében azt, amit a programunk valójában csinál (pl. a fentebbi példában kiírja a parancssorba a Hello World! szöveget). Noha a fentebbi példában a main függvényen kívüli utasítások sem lényegtelenek, de azok csak az érdemi utasítások használatának előfeltételeit teremtik meg.
Lesznek majd olyan példaprogramok is, amikben új függvényeket írunk, és azokban már a main függvényen kívül is lesznek érdemi utasítások, de az első néhány fejezetben ezzel még nem foglalkozunk. Egyelőre térjünk vissza a kiíratásra vonatkozó utasításokra.

A cout után << operátorral (műveleti jellel) választhatjuk el egymástól a paramétereket (minden paraméter előtt szerepelnie kell a << operátornak). (A << műveleti jelet angolul insertion operatornak nevezik, Herbert Schildt referenciakönyvében láttam inzerternek magyarosírva, de még senkitől nem hallottam így mondani).
Egyetlen utasításban nyugodtan megadhatunk sok paramétert is, de szétbonthatjuk őket több utasításra is:

cout << "Hello World!";
cout << endl;

Fontos, hogy ha mi gépeljük be a kiírandó szöveget (nem pedig a felhasználótól kérjük be, amiről majd a következő fejezetben lesz szó), akkor azt tegyük idézőjelek közé, beleértve a szóközöket is, abban az esetben is, ha azok a kiírandó szöveg végén, vagy elején helyezkednek el. A fenti példában is láthattuk, hogy a Hello World! idézőjelek közé van téve.
Az idézőjelekből fogja tudni a fordító, hogy mettől meddig tart a kiírandó szöveg. Ha elfelejtjük a kiírandó szöveget idézőjelek közé tenni, akkor azt (pontosabban annak a szóközökkel elválasztott részeit) a fordító megpróbálja változóként, esetleg valamilyen kulcsszóként vagy operátorként értelmezni, ami az esetek túlnyomó részében fordítási hibához vezet, a program nem fog lefordulni.
Megjegyzés: az idézőjelek közé írt szövegeket ebben a fejezetben kiírandó szövegnek nevezzük/tekintjük. Természetesen ezeket a szövegeket nem csak kiíratni lehet, hanem el is lehet tárolni (és az eltárolt értéket többször is felhasználni), és különböző műveleteket lehet velük végezni, de erről csak a következő fejezetben lesz szó.

A kimeneten a nem idézőjelek közé tett matematikai kifejezések helyére a kifejezések eredménye illesztődik be. Például:

cout << "A 15 / 4 kifejezes (maradekos osztas) eredmenye: " << 15/4
<< ", a maradek: " << 15%4 << endl
<< "A 15.0 / 4 kifejezes (valos osztas) eredmenye: " << 15.0/4 << endl;

codeblocks_aritmetikai_muveletek.png

Ha tehát azt írjuk, hogy cout << "15/4" , akkor 15/4 lesz kiírva a parancssorba, ha viszont elhagyjuk az idézőjeleket, és azt írjuk, hogy cout << 15/4 , akkor 3 lesz kiírva a parancssorba (mivel 15-ben 3-szor van meg a 4).
A maradékos osztás (más néven egész osztás) és a valós osztás közötti különbséget majd a következő fejezetben tárgyaljuk.

Észrevehettük, hogy a cout paraméterei közt szerepel egy endl nevű elem is. Ez majdnem teljesen egyenértékű az újsor karakterrel, a kettő közti különbséget kezdőként nem lényeges tudni (az endl üríti az output buffert), az egyszerű programok szempontjából egyenértékűnek tekinthetjük az újsor karakterrel. Az újsor karaktert a \n-nel jelöljük. Ha idézőjelek közé írjuk, a kiírandó szöveg bármely részén elhelyezhetjük, nem kell egy újabb << operátorral elválasztani a szövegtől.

cout << "Hello\nWorld!\n";

Illetve mint ahogy más karaktereket is, így az újsor karaktert is írhatjuk aposztrófok közé, de csak akkor, ha önálló karakterként szerepel a kódban (nem pedig egy több karakterből álló szöveg részeként):

cout << '\n';

Esetleg előfordulhat, hogy a programkód szövegét egy honlapról, vagy egy formázott szöveges fájlból (pl. Word dokumentum, PDF dokumentum) másoljuk át a fejlesztői környezetbe, vagy a forráskódot tartalmazó fájlba, és a szövegszerkesztő, amivel amivel a dokumentum készítője eredetileg dolgozott, díszített idézőjeleket használt. Ekkor ahhoz, hogy a program működjön, ezeket ki kell cserélnünk sima idézőjel karakterre.
Ez a sima idézőjel: " "
Ez díszített: “ ”
Ez is díszített: „ ”
Ez is: „ “
Sajnos ez a probléma nem csak idézőjelek és aposztrófok esetén áll fenn, hanem egyéb karakterek (például szóközök, tabulátorok) esetén is, és igencsak macerás például minden szóközt kicserélni egy hosszú kódban.
Ha valaki ilyen jellegű hibába fut bele, jellemzően először nem fogja érteni, hogy mivel lehet a baj, hiszen amelyik sorban a fordító a hibát jelzi a programkódban, ott látszólag minden rendben van.
A lényeg, hogy ne felejtsük el, hogy ilyen esetekkel is találkozhatunk, illetve ha programkódot szeretnénk például doc, docx, odt vagy pdf formátumú fájlokba illeszteni, akkor először plain text (egyszerű szöveg) formátumban írjuk meg, és onnan másoljuk át az előbb említett fájlokba.
Programkódok írására tehát lehetőség szerint valamilyen fejlesztői környezetet használjunk (pl. CodeBlocks, Visual Studio, Qt Creator), esetleg valamilyen online fordítót (pl. rextester.com), vagy valamilyen plain text szövegszerkesztőt (Windowson például notepad, notepad++, Linuxon például gedit, kate, illetve több platformon használható például a Visual Studio Code vagy Sublime Text szövegszerkesztő).
Esetleg segíthet a pastebin.com oldal is, ami szintén plain text formátumban tárolja a kódokat.

Megjegyzések (kommentek) a programkódban

A programkód szövegében bárhol elhelyezhetünk olyan sorokat, vagy szövegrészeket, amiknek nem lesz hatása a program működésére. Ezeket hívjuk kommenteknek. Általában a program működésével, esetleg készítőjével kapcsolatos információt tartalmaznak, illetve az is lehet, hogy a programkód egy részének régebbi verziója vagy a programkód egy még be nem fejezett (még nem működő) része szerepel a kommentekben. Nagyobb projektekben jó eséllyel csak magyarázó kommentekkel találkozhatunk, hiszen a különböző verziójú kódok elkülönítésére verziókezelő rendszereket (pl. git, svn, cvs) használnak.
Az egyik kommentelésre használható jelölés a //, ami egy adott sor végéig tart (nem tekintendő sor végének, ha például egy szövegszerkesztő csak azért tördeli a szöveget, mert az nem fér ki egy sorba, például azért mert túl kicsi a szövegszerkesztő ablaka, vagy túl nagy a betűméret), a másik pedig a /*, ami a következő */-ig  tart, legyen az akár több sorral később is (a */ hiánya esetén a programkód végéig tart a /* hatása). Egyiket sem kötelező sor elején kezdeni. Példák:

/*Az első programom.
Nevem: Kovács Ádám*/

cout << "Hello World!\n"; //<< endl;

Ügyeljünk viszont arra, ha a /* */ jeleket használjuk kommenteléshez, akkor egy kommenten belül ne helyezzünk el újabb /* */ jeleket, de legalábbis */ jelet ne, mert akkor azt fogja a fordító a komment végének tekinteni, és így az újabb hátralévő */ fordítási hibát okoz.

// Hibás kód:
/*cout << "Hello World!" << endl;
/*cout << "Hello\nWorld!\n";*/
return 0;*/

Ha csak ennyit írunk a programkódunk érvényes részére (pl. nem egy // kommentbe), megbizonyosodhatunk a hibáról:

// Hibás kód:
/*/**/*/

Angolul a //-t single-line commentnek, a /**/-t pedig inline commentnek nevezik. Magyarul egysoros és többsoros kommentnek szokták fordítani, ami természetesen nem egészen pontos, mivel a /**/ jelöléssel egy adott soron belüli részt is kikommentelhetünk, és a // jelölést sem kötelező a sor elején kezdeni.

Ha egy kódrészletet #if 0 és #endif preprocesszor direktívák közé helyezünk el, akkor az ugyanúgy fog hatni, mintha kikommenteltük volna. Fontos viszont, hogy ez esetben az #if 0 és az #endif sorában semmi egyéb ne szerepeljen.

#if 0
cout << "ezek az utasitasok nem hajtodnak vegre" << endl;
return 0;
#endif
//Hibás kód:
#if 0 cout << "ez igy forditasi hibat eredmenyez, a preprocesszor direktivaknak kulon sorban kell szerepelnie"; #endif

Vezérlőkarakterek

A kiírandó szövegekben többféle vezérlőkaraktert is használhatunk. Ilyen például a \n (újsor karakter), illetve a \t (vízszintes tabulátor).
Az idézőjelek között a \ jelből tudja eldönteni a fordító, hogy az azt követő karaktert máshogy kell értelmezni. A \ jelet C++ programkódokban escape karakternek szoktuk nevezni. (Más programozási nyelvben persze lehet, hogy nem pont a \ jel az escape karakter). Fontos, hogy ne tegyünk szóközt a \ jel és az utána következő karakter közé.

Vannak olyan vezérlőkarakterek, melyek a parancssorba kiírt szövegben nem kerülnek értelmezésre (pl. függőleges tabulátor: \v, vagy mondjuk a lapdobás: \f). Ezek például akkor nyernek értelmet, ha például a program kimenetét a nyomtatóra küldjük, vagy egy fájlba írjuk (pl. unixos/linuxos parancssorban: ./programnev > lp0 vagy ./programnev > fajlnev.txt ). Amikor a parancssorba kiíródik a program kimenete, legfeljebb csak egy speciális karaktert fogunk látni azokon a helyeken, ahol például \v és \f vezérlőkaraktereket helyeztünk el a kiírandó szövegben.

Ha olyan szöveget szeretnénk kiíratni, ami tartalmaz idézőjelet, annak az egyik módja, ha a szövegen belüli idézőjel elé \ jelet írunk, annak érdekében, hogy a fordító el tudja dönteni, hogy az az idézőjel még nem a kiírandó szöveg végét jelző idézőjel.

cout << "cout << \"Hello World\" << endl;" << endl;

Ehhez hasonlóan, ha szeretnénk, hogy a kiírandó szövegben \ jel szerepeljen (a szöveg részeként), akkor tegyünk elé egy másik \ jelet. Másképp fogalmazva ha a \ jel a kiírandó szöveg része, és nem escape karakter szerepét tölti be, akkor \\-t írjunk helyette.

Sorok tördelése a forráskódban

Ha túl hosszú a kiírandó szöveg, és azt szeretnénk, hogy ne legyenek a képernyőről kilógó részek (pl. hogy egy kivetítőn, vagy egy screenshoton (képernyőképen) is olvasható legyen a teljes szöveg), de a program működése mégis ugyanazt eredményezze, akkor azt például így tehetjük meg.

cout << "ha ez a szoveg kifer a kimeneten egy sorba, \
akkor a program egy sorba fogja irni" << endl;

Tehát a kiírandó szövegen belül tegyünk egy \ jelet a sortörés elé, mely hatástalanítja a (nem látható) sorvége jelet. Nem muszáj két szó közé tenni a sortörést, akár egy szavon belül is elhelyezhető. Ekkor a kiírt szöveg ugyanaz lesz, mintha a forráskódban nem szerepelne sortörés. (Persze ettől függetlenül lehet sortörés a kiírt szövegben, ha a szöveg eléri a sor végét az őt megjelenítő parancssorban, de azért kevés az esélye, hogy ez pont ott lesz, ahová a  \ jelet tettük.)

Másik példa a kiírandó szövegek tördelésére ha részekre bontjuk, és egymás mögé írjuk a részeit, úgy hogy szóközt, tabulátort, újsort leszámítva semmi egyebet nem írunk közéjük. Ez esetben a program ezeket a kiírandó szövegeket automatikusan összefűzi (konkatenálja), az eredmény ugyanaz lesz, mintha két idézőjel közé írtuk volna az egész szöveget. Persze figyeljünk az idézőjeleken belül elhelyezett szóközökre a szóhatárokon.

cout << "ha ez a szoveg " "kifer a kimeneten egy sorba, " "akkor a program egy sorba fogja irni" << endl;

Fontos megjegyezni, hogy ezek a példák csak a forráskód kiírandó szövegen belüli (tehát az idézőjelek közötti) tördelésre vonatkoznak.

A forráskódban a kiírandó szövegek idézőjelein kívül bárhol elhelyezhetünk szóközt, tabulátort vagy sortörést, de értelemszerűen csak az azonosítók, kulcsszavak és operátorok között, azokat nem tördelhetjük, különben fordítási hibát eredményezünk.
Fordítási hibát okoz például, ha cout helyett c out-ot írunk, vagy << helyett < <-t írunk, de a cout és a << operátor közé nyugodtan tehetünk szóközt, tabulátort, vagy akár sortörést is.

A fentebbi maradékos osztást és valós osztást tartalmazó példában láthatóan máshogy van tördelve a kód, mint ahogy a szöveg kiírásra kerül.

Az is előfordul, hogy a forráskód egy blokkon belüli részét beljebb tabuláljuk (ezt indentálásnak nevezzük). Például a main függvény kapcsos zárójelei közötti utasítások jellemzően egy tabulátorral beljebb kezdődnek, mint a main függvényt körülvevőek. Persze ezen fejezet példaprogramjainál ez majdhogynem mindegy, de majd ha több egymásba ágyazott blokk fog szerepelni valahol a kódban, akkor az indentálás sokkal átláthatóbbá teheti a kódot.

Ha a kódot vagy annak egy részét facebookra vagy valamilyen chatre másoljuk be, akkor jellemzően nem őrződik meg az indentálás, ami egy kicsit hosszabb/összetettebb kódban nagyon zavaró lehet.
Ha valami nem működik a kódban, és segítséget szeretnénk kérni, akkor a kódot vagy forrásfájl formájában, vagy például pastebin.com-os link formájában küldjük el, mert úgy megmarad az átláthatósága.

Sajnos ha egy program forráskódjának szövegét egy honlapon vagy például egy pdf vagy Word dokumentumban olvassuk, akkor a tördelés megtévesztő lehet. Egy egyszerű példa, hogy több sorban lehet írva egy // jel után következő komment, ha nem fér ki egy sorba (pedig a // jel hatása csak egy adott sor végéig tart).
A fejlesztői környezetekben általában nincs beállítva a hosszú sorok tördelése, hanem ha egy sor nem fér ki, a görgetősáv segítségével tekinthetjük meg a további részeit. Ha mégis beállítanánk a hosszú sorok tördelését, akkor a fejlesztői környezet természetesen csak azt fogja sorvégének tekinteni a forráskódban, ahová ténylegesen entert ütöttünk (vagy olyan szöveget másoltunk be valahonnan, ami sortörést tartalmaz).

Raw sztringliterálok

Ha olyan szöveget szeretnénk kiíratni, amiben sok a speciális karakter (például idézőjel, vagy \ jel), és nem szeretnénk bajlódni az escape karakterekkel, akkor használhatjuk az úgynevezett raw sztringliterálokat.

cout << R"(\\ \n \t"")" << endl;

A zárójelek közti szöveget a program olyan formában fogja kiírni, ahogyan azt a forráskódban is látjuk (WYSIWYG = what you see is what you get). Persze ha az ékezetek amúgy nem működnek jól, akkor itt sem fognak, illetve a tördelés különbözhet a forráskódban és a kimeneten.

Mivel az újsor karakter (\n) is a szöveg részeként lesz értelmezve, ezért ha sortörést szeretnénk elhelyezni a szövegben, azt raw sztringliterálok segítségével így tehetjük meg:

cout << R"(elso sor
masodik sor)" << endl;

Azonban hogyha a zárójelen belül szerepelne egymás mellett egy zárójel és egy idézőjel, így: )" akkor azt a fordító a raw sztringliterál végeként értelmezné (az azt követő szövegrészben értelmezve lennének a speciális karakterek, ami akár fordítási hibát is eredményezhet).
Nyilván valószínűtlen, hogy a kiírandó szövegben szerepeljen a )" kifejezés, de ha mégis biztosra akarunk menni, arra is van megoldás. Az idézőjel és a zárójel közé írhatunk egy tetszőleges karakterekből álló, szóközt nem tartalmazó szöveget, ami a raw sztringliterál elejét és a végét jelöli. Nyilván olyan szöveget érdemes megadni, amiről biztosan tudjuk, hogy nem fog szerepelni a zárójelek közt.

cout << R"raw(\\n\t()"")raw" << endl;

Ezen példa esetén, ha a zárójelek közti szövegben valahol előfordulna a )raw" kifejezés, akkor azt követően értelmezve lennének a speciális karakterek. Nyilvánvalóan olyan kifejezést érdemes használni a raw sztringliterál végeként amit nem kívánunk a kiírandó szövegben (vagyis a két zárójel között) használni.

Szintaktikai tanácsok

Ne felejtsük el, hogy az utasítások végére pontosvesszőt kell írni, hiszen a fordító ebből tudja eldönteni, hogy hol van egy utasítás vége.

Nem kell pontosvessző a preprocesszor direktívák végére. Ilyen például az #include <iostream>. Hiszen a preprocesszálás még a fordítás előtti lépés. A preprocesszor először beilleszti az iostream header fájl tartalmát az #include <iostream> sor helyére, a fordító pedig már a beillesztett tartalommal dolgozza fel a kódot.
Ügyeljünk arra is, hogy minden include direktíva egy külön sorban legyen, mely sorban az include direktíván (vagy az esetleges kommenteken) kívül más egyéb ne szerepeljen.

A main függvény visszatérési típusa a C++ szabvány szerint int, nem pedig void. Ezt csak azért írom le, mert néha látok olyan kódot az interneten, ahol int main() { /*utasítások*/ } helyett void main() { /*utasítások*/ } szerepel. Előfordulhat, hogy van olyan fordító, ami elfogad nem szabványos kódot, de biztos, hogy olyan fordító is van, ami ezt nem fogadja el, és inkább törekedjünk arra, hogy ez ebből adódó esetleges problémákat megelőzzük.
forrás: https://www.geeksforgeeks.org/fine-write-void-main-cc/

A main függvény utolsó utasításának (a return 0;-nak) az elhagyása nem befolyásolja a program működését.

KIEGÉSZÍTŐ INFORMÁCIÓK

Arra bíztatok mindenkit, hogy olvassa el ezt a részt is, de az itt lévő információk nem szükségesek a következő fejezetek megértéséhez. Ennek a résznek csupán az a célja, hogy aki most hall ezekről a fogalmakról (függvény, osztály, objektum) először, annak egy nagyjábóli képet nyújtson róluk.

Mit csinál az #include <iostream> utasítás?

Erről már volt szó korábban. A preprocesszor beilleszti az iostream header fájl tartalmát oda, ahol az #include <iostream> szerepel.
Általában több ehhez hasonló sor is szerepelni fog egy forrásfájl elején, ilyesmik mint például: #include <fstream>, #include <cstdlib>, #include <string>, #include <vector>. Persze a forráskódban külön sorban kell szerepelniük, nem így vesszővel elválasztva, mint itt.

Ezeket a fájlokat együttesen C++ Standard Librarynak hívják (magyarul szabványos függvénykönyvtárnak vagy osztálykönyvtárnak szokták fordítani). Az algoritmusokat és adatszerkezeteket tartalmazó részhalmazát C++ Standard Template Library-nek nevezik (STL-nek szokták rövidíteni, magyarul szabványos sablonkönyvtárnak szokták fordítani). Előfordul, hogy összekeverik a kettőt, és hibásan az STL részének nevezik például az iostream header fájlt is.

Ezekben a fájlokban függvények és osztályok/objektumok deklarációja szerepel. Ha include-olunk egy ilyen fájlt a forráskódunkban (az include-olás importálást vagy hozzáfűzést jelent), akkor a tartalmának felhasználásával valamilyen, a programozásban gyakran felmerülő feladatot, problémát könnyebben oldhatunk meg.
Ez a feladat lehet alapvető, mint például fájlokban lévő értékek beolvasása (ezt az fstream header fájl nélkül igencsak nehezen tudnánk megoldani), de lehet kényelmesítő is, például sok azonos típusú értéket tömbökben is lehet tárolni, de az std::vector adatszerkezet elemszáma könnyebben módosítható (amit a vector header fájl include-olását követően használhatunk).

Fontos: csak azt include-oljuk, amit használni is fogunk (include what you use), különben a program indokolatlanul több helyet fog foglalni, és lassabban fog futni.

Itt láthatjuk a C++ Standard Library fájljainak tartalmát és a használatukkal kapcsolatos példaprogramokat:
http://www.cplusplus.com/reference/

Aki most látja ezt a listát először, annak a tartalma nyilván nem sokat mond, de minél több példaprogramot nézünk majd át, annál jobban megismerjük őket, és egy idő után rá fogunk érezni, hogy mi az, amire szükségünk van a programjainkban.

Magukat a header fájlokat is megtalálhatjuk a háttértárolónkon. Érdekességképpen belenézhetünk a tartalmukba, de semmiképp ne keseredjünk el, ha kezdőként nem értünk belőle semmit :)
Linuxon (Ubuntuban) build-essential csomag telepítése esetén nálam itt találhatóak:
/usr/include/
Windowson CodeBlocks mingw fordítót tartalmazó verzió telepítése esetén pedig itt:
C:\Program Files (x86)\CodeBlocks\MingW\include

A C++ Standard Library nem a C++ programozási nyelv része. Például tömböket használhatunk a C++ Standard Library fájljai nélkül (mert a tömb a C++ nyelv része), de az STL vector adatszerkezetének használatához szükség van az #include <vector> utasításra.

Túl egyszerű lenne a C++ programozás, ha csak a C++ Standard Library létezne. Számtalan egyéb függvénykönyvtár/osztálykönyvtár (angolul library, class library, esetleg object-oriented library) létezik.
Előfordulhat, hogy egy fejlesztői környezet automatikusan telepít egyéb library-ket is, esetleg a fejlesztői környezet telepítőjében kiválaszthatjuk, hogy milyen egyéb library-ket szeretnénk telepíteni (jó példa erre a Visual Studio), de olyan libraryk is léteznek, melyeket sajátkezűleg kell letöltenünk.

Egy lista C++-hoz használható libraryk-ról
Ezek között jó eséllyel szerepelnek olyanok, amikről már hallottunk. Például: OpenGL, Vulkan, SDL, Qt, ImGUI, GTK+, FLTK, Boost.
A Microsoftnak is van saját C++ libraryje, az úgynevezett MFC.

Természetesen nem csak a C++ nyelvben lett így megoldva, hogy nem minden része a nyelvnek, hanem a nyelvet egyéb (mások által már elkészített) építőkövekkel is kiegészíthetjük, a programunkban használni kívánt funkcióktól függően.
A C++ Standard Libraryhoz hasonló más programnyelvekben például: C Standard Library, Java Class Library, C# Base Class Library.

Mit jelent a programozásban egy függvény?

Egy olyan elkülönített programrész (alprogram), amely egy vagy több utasítást tartalmaz, amelyekkel egy részfeladatot old meg a programban. Akár a main függvényben is szerepelhetne a programunk összes utasítása, de egy bonyolult program kódja átláthatóbb, ha részekre (például több függvényre) bontjuk azt.

Az első példaprogramjainkban ugyan a main függvényen kívül nem hozunk létre további függvényeket, de használni fogunk olyanokat, amik a C++ Standard Library valamelyik header fájljának include-olásával érhetünk el.

Egy függvénynek lehet (akár több) bemenete (aktuális paramétere) és lehet kimenete (visszatérési értéke), mindenesetre egy neve (azonosítója) mindenképp kell, hogy legyen. A visszatérési érték nélküli függvényeket egyes programozási nyelvekben eljárásnak, procedúrának nevezik (C++-ban ezek a void visszatérési értékű függvények).

A függvény meghívásának nevezzük, amikor futtatjuk/alkalmazzuk az adott függvényt. Ekkor jellemzően átadjuk neki azokat az adatokat, amelyekkel szeretnénk, hogy dolgozzon, illetve esetlegesen azt is, hogy az eredményt milyen nevű (azonosítójú) változóba várjuk.
Egy függvényt többször is meghívhatunk, amivel utasítások ismétlődését küszöbölhetjük ki, de legalábbis rövidíthetjük le.
Függvények meghívása nagyjából így néz majd ki:
adat3 = függvénynév1(adat1, adat2);
függvénynév2(adat1);
függvénynév3();

Ezek csak személtető példák, így ebben a formában nyilvánvalóan nem működnek, de majd idővel megtanuljuk a pontos hasztnálatukat.

A procedurális programozási paradigma szerint a programjainkat részfeladatokra (függvényekre) bontjuk, és a függvények egymásnak adogatják át azokat az adatokat, amelyekkel dolgoznak, ezzel átláthatóbbá tesszük a kódot.
Például a Pascal vagy C nyelvekben a függvényeké a főszerep. A C++-ban függvények mellett már osztályokat/objektumokat is használhatunk.

Mit jelent a programozásban egy osztály/objektum?

Aki programozásra adja a fejét, jó eséllyel találkozott már az objektumorientált programozás kifejezéssel. Eszerint a paradigma szerint szintén részekre bontjuk a programot, de nem a részfeladatok szerint, hanem az emberi gondolkodás fogalmai szerint, vagy adatfeldolgozási szempontok szerint (itt jönnek be az olyan fogalmak, mint a verem, sor, fa, satöbbi).
Az objektumorientált programozáshoz kapcsolódó fogalom az enkapszuláció (egységbe zárás), az adatok és a rajtuk végzett műveletek egységbe zárása.
Egy osztály programozási fogalmakkal leírva változókból (ezekben tároljuk az adatokat) és függvényekből (ezekkel valósítjuk meg a műveleteket) áll. Az osztály változóit az osztály adattagjainak, az osztály függvényeit pedig az osztály tagfüggvényeinek vagy metódusainak nevezzük.

Az osztály és az objektum közti különbséget szerintem egyszerűbb inkább példákkal elmagyarázni.
Legyen mondjuk egy autó nevű osztály (mint személygépkocsi). A programozó dönti el, hogy milyen adatok tarozhatnak ehhez az autó osztályhoz (például típus, szín, futott kilometer, évjárat, rendszám, tulajdonos), illetve azt is, hogy milyen műveleteket lehessen velük végezni (például tulajdonos vagy rendszám módosítása, futott kilometer lekérdezése).
Amikor egy objektumot létrehozunk ez alapján az autó osztály alapján (erre mondjuk azt, hogy példányosítjuk az osztályt), akkor adunk neki egy egyedi nevet (azonosítót) és konkrét adatokkal töltjük fel (megadjuk a típust, színt, satöbbit).

Egy osztály adattagjait elképzelhetjük úgy is, mint egy adatbázis egy adattáblájának mezőneveit, egy objektum adattagjait pedig úgy, mint egy adatbázis egy adattáblájának egy rekordját.

A tagfüggvények meghívása nagyjából így néz ki:
objektumnév1.tagfüggvénynév1(adat1,adat2,adat3);
objektumnév2->tagfüggvénynév1(adat1);
osztálynév1::tagfüggvénynév2();

Ezek szintén csak szemléltető példák.

Bonyolítja a helyzetet, hogy az osztályokból hierarchiát építhetünk fel. Ezt nevezzük öröklődésnek, miszerint egy osztály alapján létrehozunk egy másik osztályt, melyet némileg kibővítünk (például: jármű -> kötöttpályás jármű -> villamos). Egy osztályból persze több osztály is származhat, sőt, C++-ban még tovább bonyolítja a helyzetet, hogy egy osztály több osztályból is származhat (többszörös öröklődés).
Az öröklődés kapcsán tipikus példa szokott lenni a matematikai alakzatok (síkidomok, testek) kerületének, területének, felszínének, térfogatának megadása, mert ugyebár a matematikai alakzatok egy jól megadható hierarchiát alkotnak.

A helyzetet megintcsak tovább bonyolítja, hogy osztályok/objektumok nem csak ilyen magától értetődő fogalmak lehetnek, mint a személygépkocsi, hanem elvont programozásbeli adatszerkezetek is, mint például a verem (stack), a sor (que), vagy az adatfolyam (stream).
A cout (amit a parancssorba való kiíratáshoz használtunk) például az ostream osztály objektuma, melynek a megvalósítása bizony elég bonyolult.

Természetesen még tovább lehet bonyolítani az osztályok/objektumok felhasználását, de abba itt most inkább már ne menjünk bele :)

A függvények és az osztályok pontos működésébe nyilván nem kell most még belelátni, csak azért írtam róluk, hogy amikor azt halljuk, hogy ez a függvény, meg az az osztály, akkor ne arra gondoljuk, hogy valami bonyolult matematikai fogalomról van szó, hanem csak a programunkban végrehajtandó részfeladatokra vagy műveletekre.

Esetleg még hasznos információ lehet, hogy amikor azt halljuk, hogy struktúra (struct), akkor az C++-ban majdnem ugyanazt jelenti, mint az osztály (class), de nem ugyan az, mint a C nyelvben használható struct. Illetve az is előfordulhat, hogy a struktúrákat (és így az osztályokat is) felhasználói típusoknak, vagy összetett típusoknak nevezik bizonyos tananyagokban.

Végül, de nem utolsó sorban, egy idézet a C++ atyjától az objektum orientáltsággal kapcsolatban: "Without classes, a reader of the code would have to guess about the relationships among data items and functions..."

Mit csinál a using namespace std; utasítás?

A programunkban létrehozott függvényeket, osztályokat úgynevezett névterekbe csoportosíthatjuk, egyrészt a bonyolultabb programok jobb átláthatósága miatt, másrészt a névütközések elkerülése miatt.
A tananyag elején nyilvánvalóan nem fogunk ilyesmit csinálni, hiszen még saját függvényeket sem hoztunk létre, de azt viszont érdemes tudni, hogy a C++ Standard Library függvényei és osztályai az std névtérben vannak létrehozva (definiálva).

Ha használjuk a using namespace std; utasítást, akkor nem kell megjelölni egy std névtérben definiált dolog (pl. a cout) esetén, hogy azt az std névtérben keresse a fordító.

Ha elhagyjuk ezt az utasítást, akkor viszont jelölni kell az std névteret, minden esetben, amikor olyan dolgot használunk, ami az std névtérben lett definiálva.

Például ha az első példaprogramból kihagyjuk ezt az utasítást, akkor a cout és az endl helyett std::cout-ot, és std::endl-t kell írnunk, különben programunk nem fog lefordulni, egy olyan hibaüzenettel, hogy a fordító nem ismeri a cout-ot.

//1. peldaprogram forraskodja using direktiva nelkul

#include <iostream>

int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}

Azért lett ez így kitalálva, hogyha esetleg a C++ Standard Libraryn kívül más függvénykönyvtárat is használunk, akkor ne okozzanak névütközést a C++ Standard Libraryben lévő és az egyéb függvénykönyvtárban lévő, azonos nevű dolgok.

using direktívákat nem csak teljes névterekre, hanem egy adott névtér adott elemére is alkalmazhatjuk. Például:

using std::cout;

Ezt követően a cout esetén nem kell jelölni az std névteret, viszont az endl, cin, satöbbi esetén igen.

Előfordulhat, hogy vizsgán, munkahelyen megtiltják a using namespace std; utasítás használatát.

Mit csinál a return 0; utasítás?

Lényegesen egyszerűbb, mint az eddig leírtak :)

Ez a main függvény visszatérési értéke. Ha a program futása eljut eddig a pontig, akkor a main függvény visszaad egy 0 értéket az őt meghívó dolognak (konkrétan az operációs rendszernek). És mivel a 0 itt egy egész szám típusú érték, ezért van az, hogy a main függvény visszatérési típusa int (integer, azaz egész szám).

A 0 érték konvenció szerinti jelentése itt az, hogy a program futása sikeresen véget ért.

Sajnos hozzá kell tenni, hogy itt a sikeres szó nem feltétlenül azt takarja, hogy azt is csinálja a program, amit szeretnénk, hanem csak azt, hogy nem hibával állt le a program.

Bitenkénti eltolás (shiftelés)

Bár kezdőként nem túl valószínű, hogy ilyennel találkozzunk, de azért talán nem árthat tudni,hogy a << operátor kontextustól függően jelenthet bitenkénti eltolást is, azaz kettővel való szorzást/osztást.

Ekkor 11 lesz kiírva:

cout << 1 << 1;

Ekkor viszont 2, azaz 1*2;

cout << (1 << 1);

Ekkor 4, azaz 1*2*2;

cout << (1 << 2);

Ekkor pedig 16, azaz 256/(2*2*2*2):

cout << (256 >> 4);

Ebben a tananyagban nem ezt fogjuk használni kettővel való szorzáshoz, vagy osztáshoz, de előfordulhat, hogy valamilyen forráskódban találkozunk vele, mivel a számítógép processzora gyorsabban elvégzi, mint a kettővel való szorzást vagy az osztást.

Mindenesetre más (nem csak matek) példák esetén is lesz olyan, hogy ha nem zárójelezünk egy kifejezést, akkor eltérő lesz az eredménye. Ez azért van, mert definiálva van (meg van szabva) egy fontossági sorrend (úgynevezett precedencia), hogyha egy kifejezés többféleképp értelmezhető, akkor alapból hogyan legyen értelmezve.
Tehát a << operátor a cout-ot követően elsősorban a különböző paraméterek megadását szolgálja, és ott (a cout-ot követően) csak akkor lesz belőle shiftelő operátor, ha zárójelezünk egy olyan (egész számokat tartalmazó) kifejezést, amiben szerepel (a << operátor).

Az operátorok precedenciájáról majd a következő fejezetekben lesz szó bővebben.

Következő rész: változók, konstansok, literálok

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr678815940

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.