9. gyakorlat 2014. november 12. (függvények) Függvények használata C++ nyelvben



Hasonló dokumentumok
Bevezetés a programozásba. 8. Előadás: Függvények 2.

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós március 3. Széchenyi István Egyetem, Gy r

8. gyakorlat Pointerek, dinamikus memóriakezelés

Bevezetés a programozásba I 10. gyakorlat. C++: alprogramok deklarációja és paraméterátadása

1. Alapok. Programozás II

Bevezetés a programozásba I.

C++ referencia. Izsó Tamás február 17. A C++ nyelvben nagyon sok félreértés van a referenciával kapcsolatban. A Legyakoribb hibák:

Bevezetés a programozásba. 9. Előadás: Rekordok

3. Osztályok II. Programozás II

C programozási nyelv Pointerek, tömbök, pointer aritmetika

Programozás alapjai. (GKxB_INTM023) Dr. Hatwágner F. Miklós október 11. Széchenyi István Egyetem, Gy r

Bevezetés a programozásba Előadás: Objektumszintű és osztályszintű elemek, hibakezelés

C memóriakezelés. Mutató típusú változót egy típus és a változó neve elé írt csillag karakterrel hozhatjuk létre.

Programozási nyelvek Java

Programozás alapjai gyakorlat. 4. gyakorlat Konstansok, tömbök, stringek

Bevezetés a programozásba I.

Programozás II. 4. Dr. Iványi Péter

Alprogramok, paraméterátadás

Programozás alapjai. 7. előadás

C programozás. 6 óra Függvények, függvényszerű makrók, globális és

Programozás II. 2. Dr. Iványi Péter

1. Jelölje meg az összes igaz állítást a következők közül!

Informatika terméktervezőknek

Programozási nyelvek (ADA)

Programozás C és C++ -ban

7. fejezet: Mutatók és tömbök

Bevezetés a programozásba Előadás: Tagfüggvények, osztály, objektum

A programozás alapjai 1 Rekurzió

Programozás C++ -ban 2007/7

Bevezetés a programozásba I.

Java programozási nyelv

Programozás II. 2. gyakorlat Áttérés C-ről C++-ra

A C programozási nyelv II. Utasítások. A függvény.

Maximum kiválasztás tömbben

Gregorics Tibor Modularizált programok C++ nyelvi elemei 1

Eljárások és függvények

Programozás I. 5. Előadás: Függvények

A verem (stack) A verem egy olyan struktúra, aminek a tetejéről kivehetünk egy (vagy sorban több) elemet. A verem felhasználása

Alkalmazott modul: Programozás 4. előadás. Procedurális programozás: iteratív és rekurzív alprogramok. Alprogramok. Alprogramok.

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

OOP #14 (referencia-elv)

OOP. Alapelvek Elek Tibor

Bevezetés a Python programozási nyelvbe

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós április 4. Széchenyi István Egyetem, Gy r

Statikus adattagok. Statikus adattag inicializálása. Speciális adattagok és tagfüggvények. Általános Informatikai Tanszék

5. Gyakorlat. struct diak {

Adatszerkezetek Tömb, sor, verem. Dr. Iványi Péter

HORVÁTH ZSÓFIA 1. Beadandó feladat (HOZSAAI.ELTE) ápr 7. 8-as csoport

Függvények. Programozás I. Hatwágner F. Miklós november 16. Széchenyi István Egyetem, Gy r

Rekurzió. Dr. Iványi Péter

Gregorics Tibor Tanácsok modularizált programok készítéséhez 1

Felvételi vizsga mintatételsor Informatika írásbeli vizsga

GENERIKUS PROGRAMOZÁS Osztálysablonok, Általános felépítésű függvények, Függvénynevek túlterhelése és. Függvénysablonok

Java II. I A Java programozási nyelv alapelemei

Programozás alapjai gyakorlat. 2. gyakorlat C alapok

Szövegek C++ -ban, a string osztály

Pénzügyi algoritmusok

A szemantikus elemzés helye. A szemantikus elemzés feladatai. A szemantikus elemzés feladatai. Deklarációk és láthatósági szabályok

Java II. I A Java programozási nyelv alapelemei

C++ programozási nyelv

Programozás C- és Matlab nyelven C programozás kurzus BMEKOKAM603 Függvények. Dr. Bécsi Tamás 6. Előadás

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Programozás C++ -ban 2007/1

Programozási nyelvek a közoktatásban alapfogalmak II. előadás

Programozási alapismeretek 3. előadás

Programozási nyelvek Java

C++ programozási nyelv Konstruktorok-destruktorok

Bevezetés a programozásba II. 5. Előadás: Másoló konstruktor, túlterhelés, operátorok

Visual C++ osztály készítése, adattagok, és metódusok, láthatóság, konstruktor, destruktor. Objektum létrehozása, használata, öröklés.

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós február 18. Széchenyi István Egyetem, Gy r

Programozás Minta programterv a 1. házi feladathoz 1.

Bevezetés a programozásba I.

Osztályok. 4. gyakorlat

Objektumorientált Programozás VI.

C programozási nyelv

A C programozási nyelv III. Pointerek és tömbök.

Mintavételes szabályozás mikrovezérlő segítségével

Programozási nyelvek Java

Programozás II gyakorlat. 6. Polimorfizmus

Bevezetés a programozásba Előadás: A const

és az instanceof operátor

Készítette: Nagy Tibor István

Bevezetés a programozásba. 11. Előadás: Esettanulmány

A C programozási nyelv III. Pointerek és tömbök.

Java VIII. Az interfacei. és az instanceof operátor. Az interfészről általában. Interfészek JAVA-ban. Krizsán Zoltán

Programozás alapjai. (GKxB_INTM023) Dr. Hatwágner F. Miklós augusztus 29. Széchenyi István Egyetem, Gy r

Programozás C++ -ban

Kezdő programozók hibái

A függvények névvel rendelkező utasításcsoportok, melyeknek információkat adhatunk át, és van egy visszatérési értékük.

Programozási nyelvek Java

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

Számítógép és programozás 2

Járműfedélzeti rendszerek II. 3. előadás Dr. Bécsi Tamás

Programozás C- és Matlab nyelven C programozás kurzus BMEKOKAM603 Előfeldolgozó rendszer Tömbök. Dr. Bécsi Tamás 4. Előadás

A C programozási nyelv V. Struktúra Dinamikus memóriakezelés

A programozás alapjai

Függvények. Programozás alapjai C nyelv 7. gyakorlat. LNKO függvény. Függvények(2) LNKO függvény (2) LNKO függvény (3)

Programozás alapjai C nyelv 7. gyakorlat. Függvények. Függvények(2)

Pénzügyi algoritmusok

Programozás I gyakorlat

Átírás:

9. gyakorlat 2014. november 12. (függvények) Függvények használata C++ nyelvben Eddigi gyakorlatunkban bizonyos esetekben nehezen tudtuk elkerülni a kódismétlés jelenségét. Jellemzően, akkor kényszerülünk sok kódmásolásra, mikor be szeretnénk olvasni változóink értékeit, hiszen mondjuk két tömbméretet reprezentáló int típusú változó beolvasása nem sokban tér el egymástól, csak épp a bekérést megelőző kiírás, valamint annak a változónak a neve, amibe beolvasunk jelentik a különbséget. Általánosabb esetben elmondható, hogy az egyes beolvasandó változók különböző típusúak lehetnek, rájuk más feltételek vonatkozhatnak és ezek megsértése esetén más-más reakciót követelhetünk meg a program részéről (pl más-más hibaüzenetet íratunk ki). Ilyenkor nem fogunk tudni egyszerűsíteni. Példa arra, amikor két kódrészlet csak kevéssé tér el egymástól: int n; cout<< Kerem irja be n erteket! <<endl; do cin>>n; hiba = cin.fail() n<=0; if(hiba) cout<< Pozitiv egeszet irj be! <<endl; cin.clear(); cin.ignore(1000, \n ); while(hiba); int m; cout<< Kerem irja be m erteket! <<endl; do cin>>m; hiba = cin.fail() m<=0; if(hiba) cout<< Pozitiv egeszet irj be! <<endl; cin.clear(); cin.ignore(1000, \n ); while(hiba); Mi a probléma a kódismétléssel? A kódunk hosszabb lesz, nehezebb lesz átlátni Ha változtatni szeretnénk a kódunkon, az összes ilyen előfordulásnál módosítanunk kell rajta. Hasonlóan, ha a sablon hibás, az összes helyen hibás lesz, átírandó Nehéz megállapítani, hogy mely pontokban kell változtatni a mintához képest. Ennek tipikus példája lehet az egymásba ágyazott for ciklus, ahol bár a belső ciklus változóját pl. j-nek hívjuk, mégis megszokásból i-t növeljük a ciklus végén, ezzel okozva nehezen felfedezhető hibát. Egy másik lehetőség lehet a fenti példa ignore utasítása. Aki nem ismeri a \n jelentését, hiheti azt, hogy a második verzióban \m lenne a helyes. Ez most egy elég szélsőséges példa volt, de az elv kétségkívül létezik. Bár a beolvasandó változóinknak megeshet, hogy semmi közük nincs egymáshoz, de ha így ömlesztve használjuk a fentihez hasonló kódrészleteket, akkor ezek a változók akár vétlenül is hathatnak egymásra (pl. a két beolvasás közös hiba változót használ. A mellékelt példában ugyan ilyesmi nem tud előfordulni, de ha óvatlanul használunk közös változókat, előfordulhat pl., ha az első beolvasás valahogyan igazzá állítja a hiba változót, akkor a második mondjuk már nem tud lefutni. Holott azt várnánk, hogy a két hiba változó teljesen független egymástól Sok olyan változónk lesz, amit csak ritkán, a kód egy részén használunk, de mégis a teljes program számára látható lesz, ez feleslegesen növeli a bonyolultságát, valamint

veszélyes lehet ha olyan helyen nyúlunk ezekbe bele, ahol nem kéne, vagy ahol nem számítunk rá Összességében tehát elmondhatjuk, hogy ilyenkor logikailag elkülönülő funkciókat fizikailag közösen kezelünk, ezzel hosszú távon megkeserítve saját és munkatársaink életét. A megoldás a függvények bevezetése. A függvény egy paraméterezhető kódrészlet, mely a paramétere behelyettesített értéke alapján más és más értéket tud kiszámolni vagy más és más viselkedést képes megvalósítani, de úgy, hogy azonos kód, azonos algoritmus van mögötte. Így, ha javítani akarunk ezen az algoritmuson, netán ki szeretnénk cserélni, akkor egyetlen (ráadásul könnyen beazonosítható) helyen kell csak ezt a változtatást megtennünk. Tegyük fel, hogy ki szeretnénk számolni a szinusz függvény értékét (mondjuk valami közelítő értékkel) 0 és pi/2 helyen. Ekkor ahelyett, hogy a potenciálisan bonyolult kódunkat kétszer egymás után megírjuk, ahol a második esetben valahány helyen át kell írnunk valamit 0-ról pi /2-re (aztán az se biztos, hogy olyan könnyen rájövünk hogy hol!) ehelyett inkább, mint ha valami matematikai kifejezést használnánk, csak leírjuk, hogy sin(0), illetve sin(pi/2). Persze, az algoritmust se ússzuk meg, meg kell írnunk, ha még senki se írta meg. Nézzünk egy egyszerű példát, pl az abszolútérték-függvényt: Szeretnénk két int típusú változónak értéket adni, az egyikbe kerüljön 5, a másikba pedig -4 abszolútértéke. int a = abs(5); int b = abs(-4); Írjuk meg az abszolútérték-függvényt: int abs(int a) return a<0? a : a; Mi történik a kód végrehajtása során? Elvégezzük az abs(5) kifejezés kiértékelését. A fordító talál egy abs nevű függvényt mely egy int típusú paramétert vár és inttel tér vissza tehát úgy veszi, hogy itt ennek a függvénynek a törzsét kell elvégeznie. Mivel a meghíváskori ún. aktuális paraméter értéke 5, emiatt a függvény törzsében található amúgy a-nak nevezett formális paraméter helyébe mindenhol 5-öt ír be 1. És az így kapott kifejezéssel returnol, tér vissza, azaz ott fogunk tartani, mintha ezt írta volna be a felső két sor helyett: Tehát: int a = 5<0? 5 : 5; int b = -4<0? 4 : -4; int a = 5; int b = 4; 1 Vigyázat, ez a két a nevű változó közel sem azonos. Az egyiket a hívó környezetben (mondjuk a main függvényben) hoztuk létre statikus változóként, a másik pedig egy függvény paraméterváltozója, mindegyik csak az őt tartalmazó blokk záró jeléig él és nevezhető nevén

Függvénydeklarációnak hívjuk az ilyen alakú felírást: vagy: double div(int, int); double div(int a, int b); Egy típusnévvel kezdődik, ez az amit a függvény értékként visszaad (ami kvázi bemásolódik a függvény hívási helyére annak végrehajtásával), aztán jön a függvény neve végül a formális paramétereinek a listája, azok típusaival és opcionálisan nevével megadva. Ez a deklaráció mindaz, amit muszáj tudnunk a függvényről, hogy használni tudjuk. Tudnunk kell mi a neve, hogy milyen típusú paramétereket adhatunk meg neki, és hogy milyen típusú változónak adhatjuk értékül (általánosabban:milyen típusú kifejezésbe írhatjuk bele 2 ). A deklarációban megadott adatokat hívjuk a függvény szignatúrájának. Egy helyes meghívása ennek: int a = 2; double d = div(4,a); A deklarációt folytatólagosan követheti a függvény definíciója, avagy a függvény törzsének megadása. Ez az a rész, ami a -ek között van, és amikor a függvényt meghívjuk ez a rész fog szekvenciálisan lefutni. Amikor egy return utasításhoz ér, akkor az az utáni értékkel visszatér a hívás helyére. Az esetleges returnt követő részeket már nem fogja lefuttatni. A definíció külön is megadható. Megtehetjük tehát azt, hogy valahol csak deklaráljuk a függvényt, valahol pedig kifejtjük, hogy mit csináljon, valahogy így: és int abs(int a); int abs(int a) return a<0? a : a; Ezt egyébként a változókkal is meg lehet tenni: int a; a = 5; Előrevetett deklarációnak (forward declaration) nevezik azt, amikor a forrásfájlon belül először felsoroljuk a függvények deklarációit, majd csak ezek után, a forrásfájl legvégén a definíciókat. Ezáltal valamivel olvashatóbb lesz a kód, amint megnyitjuk szövegszerkesztővel a forrásfájlt, látjuk, hogy ebben milyen szignatúrájú, milyen nevű függvények lesznek definiálva, és ha a függvények nevei értelmesek, ebből ránézésre ki tudjuk találni, ezek a függvények mit is csinálnak, anélkül, hogy átbogarásztuk volna az akár több 100 soros definícióikat. 2 ha van egy bool típusú függvényünk, akkor nem csak azt tehetjük meg, hogy bool típusú változónak értékül adjuk, hanem pl. if vagy while feltételébe és nyugodtan beírhatjuk a függvényhívást mert az bool típusú kifejezést vár!

Az előrevetett deklaráció másik haszna a következő furcsaság kikerülése: int a(int &i) if(i==0) b(i); return ++i; int b(int &i) if(i==1) a(i); return ++i; (Az elágazások meg bonyolítások csak azért kerültek bele, hogy ne okozzon egy végtelen ciklus jellegű dolgot a függvény meghívása, de a pirossal jelölt részek a fontosak) Az a() függvény meghívná b()-t, a b() függvény pedig meghívná a()-t. Ahhoz tehát, hogy a fordító elfogadja a() definícióját, már tudnia kell hogy van b(). És ahhoz, hogy elfogadja b()-ét, tudnia kell a()-ról. Tehát a()-t hamarabb kell definiálni mint b()-t, és tehát b()-t hamarabb kell definiálni, mint a()-t. Ez így nem megy. De ha előredeklaráljuk mindkettőt, akkor amikor a fordító a piros részekhez ér, megnyugszik, hogy lesz majd a forrásfájl későbbi szakaszában ilyen függvény is, hiszen deklarálva is van. Tehát a megoldás: int a(int &i); //ennek a kettonek a sorrendje int b(int &i); //egymashoz kepest mar mindegy int a(int &i) if(i==0) b(i); return ++i; //main fv. meg stb //es itt is mindegy a sorrend int b(int &i) if(i==1) a(i); return ++i; Mint láthattuk egy függvényben meghívhatunk más függvényeket. Egy speciális osztályát képezik a függvényeknek a rekurzív függvények. Ezek olyan függvények, amik saját magukat hívják meg, persze más paraméterezéssel, mert ha ugyanolyannal hívnák, akkor egy végtelen hívási lánc alakulna ki, és a program sose érne véget (azaz más szóval: sose terminálna) 3. 3 ez nem teljesen igaz, mert előbb bekövetkezne a stack overflow nevű hiba, és a program kilépne. A program futása során a meghívott függvények és a bennük deklarált változók egy verem (angolul stack) nevű adatszerkezetbe kerülnek. Amikor egy

Egy rekurzív függvényben mindig kell lennie egy a rekurzió végén esedékes sima visszatérésnek és egy rekurzív hívásnak, mondjuk az összeadásra vonatkozó Peano-axiómák alapján készíthető egy rekurzív függvény : int osszead(int a, int b) //feltelezve, hogy a es b is //termeszetes szamok!!! (EF) if(b == 0) return a; //rekurzio vege return osszead(a,b-1) + 1; //rekurziv hivas Nézzünk egy példát: osszead(3,2); //eredeti kifejezes osszead(3,1) + 1; //rekurziv hivas osszead(3,0) + 1 + 1; //meg egy rekurziv hivas 3 + 1 + 1; //rekurzio vege 5; //sima osszeadas eredmenye Persze ez nem egy túl hatékony megoldás, de nem is feltétlenül ilyen esetekben kell használni. Már a korábbi órákon is láthattunk függvényeket. A stringet C-típusú stringgé konvertáló c_str() egy függvény. Ennek nincs direktben paramétere, viszont egy stringen hívjuk meg, azaz egy string példány után egy ponttal elválasztva kell meghívni, onnan fogja tudni, hogy azt a stringet kell konvertálnia (amúgy ezt a fajta paraméterezést implicit paraméternek hívjuk). Deklarációja ilyen: const char* std::string::c_str() const; A const char* (bármi 4 is legyen az) jelenti a függvény visszatérési értéket, a C típusú sztringet. A kékkel jelölt rész adja meg azt a típust, amin a függvény megadható, tehát azt a bizonyos implicit paramétert. A :: jelet scope operátornak nevezik, két féle szituációban kerülhet elő, ez a példa speciel mindkettőre példa. std::akármi azt jelenti, hogy az ún. std névteren (namespace std) belüli valamiről van szó, string::akármi pedig hogy a string típus akármijéről van szó. Tehát itt az std névtér string típusának c_str() nevű paraméter nélküli konstans karaktertömbbel visszatérő értékű függvényéről van szó. Fuck yeah. Ami még szót érdemel az az utolsó const. Az azt jelenti, hogy azon a bizonyos implicit string paraméteren nem fog változtatni a függvény, ez a paraméter konstans. És valóban nem, hiszen a string megmarad ugyanannak, csak éppen a függvény visszatér egy char*-gal, ami ugyanezt a stringet másképp reprezentálja. Az int main() metódusunk is egy függvény! A végén a return 0; azt jelenti, hogy hiba nélkül lefutott. Ha a 0 helyett más számot írunk, akkor azzal az szokott lenni a szándékunk, hogy a hívó közegnek (operációs rendszer) jelezzük, valami hiba történt, és a nullától függvény véget ér, akkor kikerül a verem tetejéről, így már az őt hívó függvény fogja a veremtetőt képezni: mindig az aktuálisan aktív függvény adatai lesznek tehát itt. Ha a hívási lánc túl mély (vagy egy függvény túl nagy méretű tárat foglalt), akkor elfogyhat a stackre szánt memória, és bekövetkezik a fenti hiba 4 egy a dinamikus memóriában tárolt karakterekből álló fix értékű tömb, aminek az utolsó eleme egy \0 null karakter

különböző szám alapján azt is meg tudjuk mondani milyen fajta hiba volt ez (a main visszatérési értéke tehát szándék szerint egy hibakód). Egy programban csak egy main függvény lehet, és automatikusan annak végrehajtásával fog kezdődni a program végrehajtása. Az elméletben három fajta függvény-típust tudunk megkülönböztetni, a C++-ban bár nyelvi elemek szintjén nem tudunk direktben különbséget tenni, de mindhármat ki tudjuk fejezni valahogy: tiszta függvény mellékhatásos függvény eljárás (procedúra) Tiszta függvénynek nevezzük azt, ami az összes paraméterére nézve konstans, és végrehajtása után visszatér egy bizonyos értékkel. A matematikai értelemben vett függvények (cos(x) és társai) ilyenek, hiszen x-en nem változtatnak, de visszaadnak egy számot. Ilyen függvény pl. a const char* std::string::c_str() const; de mondjuk egy int abs(const int a); is az várhatóan. Persze nyilván ez a függvény törzsétől függ leginkább. Mellékhatásos függvény az, ami mindezeken felül még valami más látványos dolgot is csinál. Vagy módosítja a paramétereit, vagy a globális változókat 5, vagy mondjuk kiír konzolra 6 valami szívhez szólót. Az ilyenekkel nyilvánvalóan sok dolgot könnyebben ki tudunk fejezni, de mégis veszélyesek, ha nem ésszel használjuk ezeket. Hagyományos statikus tömbök beolvasásának esetében például használatuk megkerülhetetlen (már ha a beolvasást külön függvénybe akarjuk szedni 7 ). Bár tömbbel csak úgy direktben nem lehet visszatérni, de vannak rá eszközök hogy mégis, mindenesetre tömb beolvasáskor két adatnak is értéket adunk, a tömbnek és a méretét megadó számnak, és mivel csak egy dologgal térhetünk vissza, a másikat mellékhatásként kell alakítani. Általában így szoktunk tömböt beolvasni: int tombbe(int t[]); Ekkor a t tömb elemeit amikor megadjuk akkor igazából a tombbe függvény meghíváskori aktuális paraméterébe íródnak be, majd mondjuk érdemes visszatérni a tömbmérettel, amit mondjuk ebben a függvényben kértünk be (ált. n-nek szoktuk nevezni). Fordítva (n-nek adjunk értéket mellékhatásként és a tömbbel térjünk vissza) már kicsit neccesebb a dolog. Egyrészt nem lehet int[] egy függvény visszatérési értéke, másrészt ebben a félévben úgy is minden tömböt konstans MAXN mérettel foglaljuk le, tehát sok értelme sincs a tömb lefoglalását külön függvénybe rakni. Következő félévben megismerkedünk a vector típussal, majd 3. félévben a mutatókkal, ezekkel már sokkal kényelmesebben meg lehet csinálni ezt a fajta kódot, ráérünk akkor foglalkozni vele. 5 ha esetleg eddig nem lett volna teljesen világos, hogy miért veszélyes sok globális változót használni, hát ezért: mert boldogboldogtalan változtathat rajtuk és ez egy idő után követhetetlen, valamint az ilyen programoknál a részek egymástól való függősége nagyon nagy, ezért nehezen bővíthetők, nehezen átalakíthatók 6 ez az előző eset része, hiszen a cout is egy globális változó! 7 márpedig úgy menő

De mi van akkor, ha ki akarom számolni egy osztásnál az egész osztás eredményét és a maradékot IS? int div(const int osztando, const int oszto, int maradek) maradek = osztando % oszto; return osztando / oszto; Kb. ezt akarnánk csinálni, mégis azt vesszük észre, ha ezt a függvényt meghívjuk: int a = 10, b = 3, c,d; c = div(a,b,d); cout<<c; //3, mert 10-ben a 3 3-szor van meg cout<<d; //memoriaszemet (d nem kapott erteket a fuggvenyben?) A magyarázat a jelenségre az, hogy a div függvénynek a 3 paramétert egyaránt érték szerint adtuk át. Az érték szerinti paraméterátadás azt jelenti, hogy a függvény hívásakor az aktuális paraméter értéke (tehát: a=10, b=3, d=?) lemásolódik és beíródik a formális paraméterek helyére ez az érték (tehát: osztando=10, oszto=3, maradek=?). Ezek után elvégezzük a függvény törzsét, az egészosztás eredménye (3) visszatér a fv. hívásának helyén, azaz bekerül c-be, a formális maradek változóba bekerül a maradék, azaz az 1, de mivel az a függvény lokális változója, ezért a return pillanatában megszűnik, a memóriából törlődik. d- ben pedig marad az, ami eddig volt, azaz a kitudjami. Mert d-nek és maradeknak semmi köze egymáshoz, azon kívül, hogy kezdetben d felvette maradek értékét. A megoldás erre az ún. referencia szerinti paraméterátadás. Ilyenkor nem magát az értéket, hanem egy rá való hivatkozást adunk át. Ilyenkor ha d-t odaadjuk a maradek-nak, majd a maradek-ba írunk valamit, egyúttal d-be is írunk. A nyelvi elem ennek jelölésére a &. int div(const int osztando, const int oszto, int& maradek) maradek = osztando % oszto; return osztando / oszto; Ez már jól fog működni. Egyébként az & jel használata egy jó indikátora annak hogy ez a függvény mellékhatásos. Ami azt illeti egy mellékhatás nélküli függvénybe is nyugodtan írhatunk referencia szerinti átadást, de nem szoktunk, mert a referencia szerinti átadással arra utalunk, hogy azt a paramétert módosítani (vagy egyszerűen csak értékkel feltölteni) akarjuk. Viszont a referencia maga csak egy egész szám méretű (mondjuk 4 bájt) adat mindösszesen, tehát ezt átadni hatékony megoldás. Képzeljük el, hogy nem egyszerű adatokkal, hanem mondjuk fényképalbumokkal dolgozunk. Mondjuk meg kell számolnunk hány darab kép van az albumban: int darab(const Fenykepalbum f) return f.kepekszama(); vagy valami hasonló.

Az f objektum mérete több száz megabájt. Egyszerűbb lenne referencia szerint átadni, mert nem akarom egy darab függvény miatt ezt a sok MB-ot feleslegesen másolgatni majd felszabadítgatni. Erre való a konstans referencia. Ugye ha valamit referencia szerint adok át, akkor módosíthatom, cserébe nem kell másolni. Ha viszont konstans, akkor nem módosíthatom. Tehát ha valami konstans referencia, akkor nem kell másolni, de nem is módosíthatom! Pont ezt akarom ilyenkor. int darab(const Fenykepalbum& f) return f.kepekszama(); Maga a const szócska két dolgot csinál. Egyrészt minden függvénytörzsbeli f-re vonatkozó módosítási szándékomra fordítási hibát dob, azaz megvéd attól, hogy olyat csináljak a függvény törzsében, amit a függvény szignatúrájában még nem akartam. Másrészt tippeket ad a fordítónak, hogy hol lehet esetleg hatékonyabbá tenni a programot (optimalizálni). Ebbe mi nekünk már nem kell belelátnunk, a lényeg az, hogy ha egy függvényről tudjuk, hogy valamin nem változtat, mindig írjuk oda a const-ot, mert mind nekünk programozóknak, mind a program használóinak jól jön. De akkor mi volt az előbb a tömbbeolvasással? Ott miért nem kellett a &? Ez a tömb memóriabeli ábrázolása miatt van, a tömbök gyakorlatilag a tömb első elemének memóriabeli címét adják meg. Ezért is van, hogy a tömböt 0-val indexeljük, mert a tömbindex nem más, mint az első elem címétől való eltérés 8. Az első elem az első elemtől 0- val tér el, a második 1-gyel stb Tehát a tömb, mivel eleve cím, mindig referencia szerint adódik át, nem kell erről a tényről külön megemlékezni, viszont ha bemenő szemantikája van egy tömb paraméternek, akkor neki is kijár a const jelölés. Általános szabályok a const és a & használatára: paraméterlista elemei beépített típus saját típus bemenő paraméter fontos a kezdeti értéke, de nem módosítom be- és kimenő param. fontos a kezdeti értéke, de módosítom kimenő paraméter nem használom fel a kezdeti értékét, de módosítom const int i int& i int& i const T& t T& t T& t tömb const T t[] T t[] T t[] Tehát ha valami nem beépített típus, akkor MINDIG referencia szerint adjuk át. Ha valami bemenő, akkor MINDIG konstansként adjuk át. Tömböt MINDIG referencia szerint ad át, de nem jelöljük. Primitív típusú elemeket, ha módosítani akarok, akkor referencia szerint kell átadnom. A mellékhatásos függvényeket kivégeztük 8 na, ez se teljesen igaz, ha a tömbben mondjuk intek vannak, és mondjuk az intet 4 byte-on tároljuk, akkor igazából a 2. elem 4, a 3. elem 8 egység messze van az elsőtől. De ezt a fordító mind-mind tudja, mert ismeri a tömb elemtípusát. Ha én ehhez a címhez 1-et hozzáadok, akkor ő igazából sizeof(*t) (3. félévben lesz róla szó, mi ez)-vel beszorozza ezt az egyet, ha 2-t adok hozzá, azt szintén beszorozza vele, stb. Ezt hívjuk pointeraritmetikának.

Utolsó típusunk az eljárás, avagy procedúra. C++ programozási nyelvben erre egy elég fura nyelvi eszköz van, a void visszatérésű értékű függvények lesznek az eljárások. Eljárás az, ami bár mellékhatással bír, de nem tér vissza semmivel. Például egy tömbbeolvasást így is ki lehet fejezni: hívása: void tombbe(int& meret, int tomb[]); int n; int t[maxn]; tombbe(n, t); Vagy ha ki szeretnék többször is írni egy hosszú szöveget, nem érdemes a forráskód több pontjára is begépelni, inkább használjunk egy eljárást, amit meghívunk: void kiiras() cout<< ; Ilyen függvények nem térnek vissza semmivel, nem is szükséges return utasítást kiírni, bár lehetséges. De akkor nem szabad semmilyen értéket kiírni mögé, amivel visszatérjen, hanem egyszerűen csak return. Ez azt jelenti, hogy itt a függvény futása megszakad, véget ér, a hívás helyére visszaugrik, és ott folytatódik a program végrehajtása. Lehet egy függvénybe több returnt is írni, de csak az egyik fut majd le, amelyikre hamarabb ráfut. Egy rendes függvénynél viszont garantálni kell, hogy minden lehetséges végrehajtási ág vissza is tér valamivel 9. Még érdemes a függvények paraméterezésével kapcsolatban a következőket megjegyezni: Adhatunk a paramétereknek alapértelmezett értéket. Ekkor, ha híváskor nem töltjük azt ki, akkor ez az alapértelmezett érték fog a híváskor behelyettesítődni a formális paraméter helyébe: int rakovetkezo(int a = 5) return a+1; cout<<rakovetkezo(5); //6 cout<<rakovetkezo(8); //9 cout<<rakovetkezo(); //6 Mi van akkor ha több paraméter is van? Ekkor alapértelmezett értéket csak az utolsó néhány -nak adhatunk, azért mert különben a hívásból nem tudná kilogikázni a fordító, hogy most melyikről is van szó: int egy(int a = 1, int b = 2); //ok, hivhato igy is: egy(); 9 arra nem árt odafigyelni, hogy tapasztalatok szerint a Code::Blocks környezet ezt nem mindig várja el, nem mindig kapunk hibaüzenetet ha elfelejtjük kiírni a returnt, így nagyon csúnya futás idejű hibákat kaphatunk

int ketto(int a, int b = 2); //ok, hivhato igy is: ketto(1); int harom(int a, int b=2, int c=3); //ok, hivhato igy is: harom(1); int negy(int a=1, int b, int c=3); //forditasi hiba, mert ha negy(4,5)- ottel hivom meg, akkor most vajon arra gondoltam, hogy a opcionalis parameternek adtam a 4-es erteket es b kotelezo parameternek az 5-ost, avagy b-nek az 4-est es c-nek az 5-ost? Megtehetjük azt is, hogy a függvényeinket különböző típusokkal paraméterezzük, esetleg más-más mennyiségű paramétert használunk (erre az előző is egy példa volt!). Függvénytúlterhelésnek hívjuk azt, amikor több azonos nevű, de paraméterezésében eltérő függvényt adunk meg. Ez inkább csak a programozó számára hasznos, a fordító teljesen külön függvényként fogja értelmezni a másképp paraméterezett változatokat. int max(int a, int b); //ket szam maximuma int max(int a, int b, int c) //harom szam maximuma, //amit amugy visszavezethetunk return max(max(a,b),c); //ket szam maximumara double oszt(int a, int b); //ne csak egeszeket oszthassunk maaa el double oszt(double a, double b); int egy(int a, int b); int egy(int a) //ez vegso soron egy pelda return egy(a,0); //az alapertelmezett ertekre! Ez nem egy helyes túlterhelés, hiszen csak visszatérési értékében tér el: int a(int a, int b); bool a(int a, int b); Ez azért problémás, mert a függvény hívásánál nem biztos, hogy egyértelmű lesz, hogy melyiket hívtuk. Egyáltalán nem elvárás ugyanis az, hogy a visszatérési értékkel (is) rendelkező függvényeket olyan körülmények között hívjuk meg, hogy a visszatérési értéküket fel is használjuk. Mert ez első kettő még egyértelmű lenne, de a harmadik nem: int sz = a(1,2); //intes verziora gondolhattunk bool l = a(3,4); //boolosra gondolhattunk a(5,6); //??? Egy függvény, mint láttuk return értékként egy féle adatot tud csak visszaadni. Azt is láttuk, hogy tömböt csak úgy nem tud. Ha mégis tömböt, vagy több féle dolgot akarok visszaadni, akkor létre kell hoznom egy olyan típust, amibe bezárom ezeket a típusokat és ezzel térek vissza. Elég nehézkes megoldás. Így egyébként áldinamikus tömbbeolvasást is meg lehet valósítani (tehát amikor nem const MAXN méretű egy tömb tényleges mérete, hanem bekéréstől függő), de ezt inkább ne erőltessük, következő félévben jön az std::vector, amiben ez alapból benne van, 3. félévben pedig a mutatók, amivel normális, dinamikus memóriakezelést lehet megvalósítani nagyon egyszerűen. Kitartás.