Programozás I. segédlet
Tartalomjegyzék Előszó... 2 1. Bevezetés... 3 2. Adattípusok, értékadás, egyenlőségek... 4 3. Kiíratás... 5 4. Elágazás... 6 5. For ciklus... 7 6. While, do while ciklusok... 8 7. Logikai kapcsolatok... 9 8. Adatbekérés, ASCII tábla...10 9. Tömbök...11 10. Függvények, eljárások...12 11. Sztring...14 12. Típuskonvertálás...16 13. Sorba rendezés, szélsőérték kiválasztás...17 14. Inputellenőrzés...18 1
Előszó Ebben a segédletben csak a legszükségesebb információkat fogjátok megtalálni, ami a korrepetálással kiegészítve elegendő a Programozás I. vizsga teljesítéséhez. Bízom benne, hogy a segédletben található anyagok a tanulásban a segítségetekre lesznek. A segédletben megtalálható szubrutinokat és a feladatok megoldásait eléritek a macsodev.hu oldalon. Changelog: V1 - Initial release - Bool logika kiegészítés V1.2 - For ciklus kiegészítés - Feladatok - While, do while ciklusok - Tömbök V1.3 - Műveletek kiegészítés - Adatbekérés, ASCII tábla - Typo - Anyagrész sorrend csere V1.4 - Stílus - Kiegészítések - Hivatkozások - Függvények - Tömbök kiegészítés V1.5 - Bevezetés style - Typo - Sztring - Sztringkezelés V1.6 - Előszó link kiegészítés - Typo - Table style - strlen javítás V1.6.2 - Inputellenőrzés - Typo V1.7 - Képfeliratok - Logikai kapcsolatok - Inputellenőrzés kiegészítés V1.8 - Sorba rendezés, szélsőérték kiválasztás - Typo V1.8.1 - Stílus javítás - Inputellenőrzés feladat javítás V1.8.2 - Néhány rész átfogalmazása, érthetőbbé tétele V1.8.3 - Oldalszámok - Tartalomjegyzék - Table color fix - Átfogalmazás - Adattípusok, értékadás, egyenlőségek táblázat kiegészítés - Egységes világos téma V2 (Első kiadás) - Bekezdés térközök - Feladatok és kérdések képhez igazítása - Képaláírások törlése - Képek javítása, új képek hozzáadása - Feladatok javítása Ez a Mű a Creative Commons Nevezd meg! - Ne add el! 4.0 Nemzetközi Licenc feltételeinek megfelelően felhasználható. 2
1. Bevezetés Aki már tanult programozni valamilyen szinten, az már az alapokat tudni fogja, csak a szintaktikát kell összerakni. Aki viszont most kezdi, annak az alapoktól kell megtanulnia a programozást. Ahhoz, hogy nekiálljunk, kell egy fejlesztői környezet, amiben megírjuk a programunkat, és le is tudjuk fordítani azt. Ilyen fejlesztői környezet például a Microsoft Visual Studio vagy a CodeBlocks. Ha ezeket beszereztük, akkor bele is kezdhetünk az első programunk megírásába. Hogyan néz ki egy C program? A C program (jobb esetben és általában) fejfájlok bekapcsolásával kezdődik, amit a következőképpen tehetünk meg: #include <fejfajlneve.h> A fejfajlneve.h helyett természetesen mást kell írni, még pedig azon fejfájlok neveit, amelyekre szükségünk lesz a programhoz. Például ha ki akarunk írni valamit a képernyőre, akkor szükségünk lesz az stdio.h fejfájlra, ami nagyon sok előre megírt feladatot el tud végezni, nekünk már csak használnunk kell (STDIO=Standard Input Output/szabvány ki- és bemenet). A fejfájlok után következik a main függvény. Ez lesz a programunk fő része, egy keret, amiben majd a programunkat fogjuk írni. A program lefutásakor a main függvényben található utasítások fognak végrehajtódni. A függvényeket (így a main függvényt is) a következőképpen kell használni: int main() vagy float osztas(float szam1, float szam2) vagy char atvalt(int szam) A függvény (vagy eljárás) azonosítója annak neve lesz, ezzel fogjuk majd meghívni őket (példában szereplő azonosítók: main, osztas, atvalt). Az azonosítók előtt szereplő kifejezés az adott függvény/eljárás visszatérési értéke lesz. Függvények és eljárások írásáról, visszatérési értékről a Függvények témakörben lesz szó. Az azonosító (main, osztas, atvalt) után következik két kerek zárójel, amely paraméterlistának hívunk. Itt fogjuk megadni, hogy pontosan milyen adatok kellenek ahhoz, hogy a függvényünk megoldja az általunk megoldani kívánt feladatot (erről is később lesz szó). A függvények és eljárások után egy {} jelpár kell, ami közé azt fogjuk majd írni, hogy a szubrutinnak pontosan mit is kell majd csinálnia. A program megírása közben használtuk a printf() utasítást, ami nem más, mint az stdio.h fejfájl egy része, egy beépített függvény, ami a képernyőre írja nekünk azt, amit megadunk neki paraméterben. A printf() használata csak akkor lehetséges, ha a programunk legelején bekapcsoltuk az stdio.h fejfájlt. Hasonlóképpen fogunk dolgozni a többi beépített fejfájlokkal és azok függvényeivel, eljárásaival. 3
2. Adattípusok, értékadás, egyenlőségek Az előző részben megírtuk első programunkat. A továbbiakban függvények és eljárások hívásával, ismétlődések szervezésével, változók beállításával fogunk foglalkozni, melynek célja, hogy az adott adatokat a (vizsga)feladatnak megfelelően fel tudjuk dolgozni. A következő lépés tehát a változók használatának megtanulása. A változót tekintsük egy kis fióknak, amibe tetszőlegesen rakhatunk egy adott értéket. Azonban nem rakhatunk mindenféle értéket ugyanabba a fiókba. Egy adott típusú fiókba csak ugyanolyan típusú értéket rakhatunk. A változóknak több fajtája van, az elemi (primitív) változók a következőek: - int: integer, vagyis egész számokat tudunk benne tárolni, 2 32 nagyságú tartomány - char: karaktereket tudunk benne tárolni. Valójában ez is csak egész számokat tárol, az ASCII táblában szereplő betű kódját tárolja csak (használatáról: Adatbekérés, ASCII tábla) - float (vagy double): lebegőpontos számokat tudunk benne tárolni, vagyis olyan számokat, amik nem egészek. Ezekkel az elemi változókban fogjuk tárolni és onnan kiolvasni az adatokat. Hogyan is használjuk ezeket a változókat? A main függvény után a {} jelpár legelső soraiban van az a hely a kódban, ahol lehetőségünk van változókat felvenni, deklarálni (nem a main függvény kiváltsága ez, minden szubrutinnál is a legelején kell felvenni a változókat). A változókat mindig úgy vesszük fel, hogy először a típusát írjuk be, majd annak adunk egy nevet (azonosító). A fenti felsorolás mintája alapján vegyünk fel egy int típusú szam azonosítójú változót! Ez a szam változó nekünk most egy számot fog tárolni, amit vagy megadunk neki most a felvételnél, vagy majd később adunk neki értéket. Ha deklarálásnál adunk neki értéket, akkor azt a példán keresztül nézve így tehetjük meg: int szam=12;. Figyeljük meg, hogy mit is csináltunk: felvettünk egy int típusú változót, aminek a szam nevet adtuk, majd ebbe a szam nevű változóba beleraktuk a 12-es számot. A parancs(ok) végét pontosvesszővel zárjuk a C-ben. Láthattuk, hogy egy darab egyenlőségjel használatával értéket tudunk belerakni egy változóba. De akkor hogyan hasonlíthatunk össze két változót, ha az egyenlőség jel már foglalt? Hogyan adunk össze két értéket, osztunk, szorzunk stb, egyszóval: mik a használható jelek és szimbólumok az egyes műveletekre? = értékadás / és * osztás és szorzás == egyenlőségvizsgálat! logikai tagadás + és - összeadás és kivonás!= egyenlőtlenség ++ és -- inkrementálás és dekrementálás, vagyis a változó értékének növelése && és logikai ÉS és logikai VAGY vagy csökkentése 1-gyel % maradékos osztás (a maradékot adja vissza) < és <= kisebb/kisebb vagy egyenlő 4
3. Kiíratás Még egy-két dolgot tisztába kell tenni, mielőtt látható eredményt érünk el. Elsőként nézzük meg, hogy hogyan is tudjuk rávenni a programot, hogy írja ki a változót (számot, karaktert), amit látni akarunk. A képernyőre való kiíratásra a printf() beépített függvényt használjuk. A függvény meghívása után meg kell adnunk paraméterben, hogy mit is szeretnénk kiíratni. Persze ez nem ilyen egyszerű, nézzük meg egy példán keresztül kicsit részletesebben, hogy hogyan is zajlik ez. Az első sor az egy tipikus meghívás a C-ben. Ez jelen esetben azt jelenti, hogy meghívjuk a printf() függvényt, hogy végezze el a dolgát azokkal az adatokkal, amit mi megadtunk. Az első sorban nyílván nem adtunk meg semmit, így picit finomítanunk kell rajta. A második sorban kiegészítettük két idézőjellel, így már érvényes a parancs, de lefutáskor nem fog nekünk semmit a képernyőre írni, mivel még nem írtunk bele semmit. Amit ki akarunk íratni, azt a két idézőjel közé kell írnunk; ezt mutatja a harmadik sor. A harmadik sort lefuttatva már látnánk a beírt szöveget, viszont ahhoz, hogy a mi szam változónkat meg tudjuk jeleníteni, valamilyen módon meg kell jelölnünk azt a szövegben, erre szolgálnak a formátumspecifikátorok, formátumsztringek. Lényegében ezek használatával jelöljük azt, hogy a szöveg egy bizonyos helyén mi valami mást akarunk kiíratni. Azonban magában ez még nem elég, a program nem fogja kitalálni, hogy mit akarunk odaírni. Éppen ezért (ahogyan az 5. sorban látható) még a printf() függvény zárójelein belül, de az idézőjelek mögé rakunk egy vesszőt, és sorban felsoroljuk azokat a változókat, amiket meg kívánunk jeleníteni. Az egyes változókhoz a következő formátumjelölők használhatóak: %i vagy %d int változó %f float, double változó %c char változó %s sztring Ezeken a jelöléseken kívül van pár karakter, amik láthatatlanok, ezek az úgynevezett fehér karakterek. Ilyenek például az új sor (\n), a tabulátor (\t), de a szóköz is annak számít. Ezeket használjuk ugyanúgy, mint egy normális karaktert. Például ha két sorban akarjuk kiírni azt a két szót hogy Hello és világ, akkor a parancs a következőképpen néz ki: printf( Hello\nvilág );. Ekkor a Hello után egy sortörést szúr be a program, így a világ szó már új sorban kezdődik. 5
4. Elágazás Ha egy műveletet csak bizonyos feltétel(ek) teljesülésekor szeretnénk hogy lefusson, akkor elágazást használunk. A következő kulcsszavak használatával tudunk elágazást létrehozni: if (FELTÉTEL){ } else if (FELTÉTEL) { } else { } Elágazás kezdete (FELTÉTEL az aktuális összehasonlítandó érték) (HA (FELTÉTEL) AKKOR ) Ha (általában) kettőnél több ágat szeretnénk, akkor az else if -et kell használni (KÜLÖNBEN HA (FELTÉTEL) AKKOR ) Ha egyik ág feltétele sem teljesül, akkor az itt megadott utasítások hajtódnak végre (KÜLÖNBEN ) Ezeket tökéletesen le lehet fordítani angolról magyar nyelvre, és vica versa. Vegyük például a következő feladatot: ha a szam változó egyenlő 12-vel, akkor írjuk ki a szam változót, különben írjuk ki hogy a szam változó nem egyenlő 12-vel: Természetesen ebben az esetben a kimenet az első ágban szereplő mondat lesz, tehát a program ki fogja írni a szam értékét. Ha az egyik ág (ebben a példában az első) feltétele teljesül, akkor a program már nem fogja vizsgálni a többi ágat. Ezért van az, hogy csak az első sort írta ki, és nem mindkettőt. Természetesen nem csak egyenlőséget vizsgálhatunk, használhatjuk az összes hasonlító műveletet, ami az Adattípusok, értékadás, egyenlőségek fejezetben szerepel. Az említett példánál maradva meg kell jegyezni, hogy a feltétel után nincs kapcsos zárójelpár írva. Ez azért van, mert ha csak egy művelet van, amit végre kívánunk hajtani az elágazásban, akkor ez a kapcsos zárójelpár elhagyható. A feltételek vizsgálata a Logikai kapcsolatok című fejezetben van részletezve. 6
5. For ciklus Ahhoz, hogy ismételten elvégzett műveleteket tudjunk leprogramozni, ismernünk kell ciklusokat. A ciklusok olyan folyamatok, amelyben a meghatározott feladatok ismételten futnak le egy megadott feltétel és lépték szerint. Tehát ha van valami művelet, vagy olyan műveletek összessége, melyek ismétlődően visszatérnek, akkor azokat legcélszerűbb egy ciklusba szervezni. A for ciklust a következők szerint tudjuk használni: for(értékadás;feltétel;léptetés) { } Figyeljük meg, hogy a ciklus zárójelén belül három részt alakítottunk ki, ezeket pontosvesszővel választottuk el. Értékadásnál ha több változónak szeretnénk értéket adni, akkor azokat vesszővel választjuk el. Ide veszünk fel egy külön int változót (általában i, j, k névvel látják el őket), ez lesz a ciklusváltozónk amit majd léptetünk, ezzel biztosítva a folyamatosságot. A feltétel nem változott, ugyanaz, mint az elágazásoknál, vagy akárhol máshol. A léptetés az Adattípusok, értékadás, egyenlőségek című fejezetben lévő táblázatban az inkrementáló operátorokkal is lehetséges. Nézzünk rá egy konkrét példát: írassuk ki a számokat 1-től egészen 10-ig! Természetesen a ciklus belsejében (úgynevezett ciklusmagban) bármilyen műveletet beírhatunk, így lehetnek ott matematikai műveletek, elágazások, akár újabb ciklusok is. Ahogyan értékadásnál több változónak is adhatunk egyszerre értéket, úgy több feltétel és több léptetés is megadható egyidőben. A ciklus értékadás és léptetés része bármikor elhagyható, ha az indokolt. Feladatok: - Írjunk olyan programot, amely 2 hatványait kiírja egészen addig, amíg az érték kisebb vagy egyenlő, mint 1024! Ha az érték éppen 512, akkor a program az aktuális érték helyett írja ki azt, hogy Ketto a kilencediken. - Írjunk olyan programot, amelynek ciklusváltozója 10-től egészen -10-ig megy! o Módosítsuk a feladatot: csak akkor írassuk ki az aktuális értéket, ha az negatív! o Az alapfeladatot írjuk át úgy, hogy az aktuális érték mindig ellenkező előjellel kerüljön kiíratásra (anélkül, hogy a léptetést módosítanánk)! - Írjunk olyan programot, amely egy általunk előre meghatározott szam azonosítójú int számot X-szer eloszt 2-vel egymás után. X és szam változó értékét deklaráláskor adjuk meg. Minden osztás eredményét írjuk ki 2 tizedesjegy pontossággal! 7
6. While, do while ciklusok A for cikluson kívül még két másik ciklusunk is van, amiket választhatunk. Természetesen a feladat logikájától függ, hogy éppen melyik lenne a legcélravezetőbb választás. A while ciklust előltesztelő ciklusnak, a do while ciklust hátultesztelő ciklusnak is nevezik, nézzük is meg mindjárt, hogy miért. Előljáróban érdemes annyit tudni róluk, hogy a for ciklussal ellentétben itt csak egy feltétel kell a ciklusunk lefutásához. Éppen ezért a változók kezdeti értékeiről és az esetleges léptetésekről nekünk kell gondoskodnunk. while(feltétel) { } előltesztelő ciklus //AMÍG (FELTÉTEL) ADDIG { } do { }while(feltétel); hátultesztelő ciklus //CSINÁLD { } AMÍG (FELTÉTEL) Nézzünk rá egy egyszerű példát: Gyakorlatilag a két ciklus szerkezete hasonló egymáséhoz, egymás megfordítottjainak tekinthetjük őket, és nagyban hasonlítanak a for ciklusra is, kivéve az előbb tárgyalt léptetést és értékadást. Az értékadást el tudjuk végezni ciklus elején/ciklusban (ha nincs használva feltételben, feladatfüggő), a léptetésről (általában) a ciklus végén szoktunk gondoskodni. Kérdések: - Milyen számok kerülnek kiíratásra a while ciklusban? - Mi lehet az előnye a hátultesztelő ciklusnak, ha a cél egy adat bekérése? - Meddig kérünk be karaktereket a hátultesztelő ciklusban? 8
7. Logikai kapcsolatok Az előző pár fejezetben megismerkedhettünk a logikai műveletekkel (Adattípusok, értékadás, egyenlőségek), amiket különféle ciklusszervezésekkor, elágazások írásakor használtunk. A logikai vizsgálatok arra jók, hogy egy vagy több érték vagy kifejezés megvizsgálásával eldönthessük, hogy kell-e az általunk megadott műveletet végrehajtanunk vagy sem. A feltételek vagy igazak, vagy hamisak. Hamis érték a nulla, igaz érték minden, ami nem nulla. Így tehát ha a feltételünkben egy függvény bármilyen nem nulla számmal tér vissza, a feltétel igaznak bizonyul. Akkor is igaz lesz a feltételünk, ha az általunk leírt egyenlőség (vagy bármilyen más összehasonlítási mód) igaznak bizonyul. Ilyen esetben a feltétel értéke 1. Abban az esetben, ha a feltételben visszatérő függvény visszatérési értéke nulla, vagy ha az általunk írt feltétel hamisnak bizonyul, a feltétel értéke 0 lesz, ennek megfelelően nem kerül lefutásra az adott ág tartalma, vagy a ciklus megáll, attól függően, hogy hol használtuk a feltételt. A logikai tagadás az adott feltétel értékét változtatja az ellentettjére. Ha a feltételünk igaz volt, de ezt negáljuk (tagadjuk), akkor hamissá válik a feltételünk, annak ellenére, hogy a benne szereplő kifejezés igaz. Fordítva: ha egy hamis feltételt megtagadunk, igazzá válik. Bármilyen nem nulla szám tagadása a feltételben hamis eredményt mutat. Hogy tudunk erre példát előállítani? Egyszerűen beírunk számokat feltételként és meglátjuk, hogy mi lesz a lefutás eredménye. A kimeneten látszik, hogy csak az első és a harmadik sor fut le, hiszen ezekben az elágazásokban a feltétel egy nem nulla szám. A!12 a tizenkettes szám tagadása, bármely (nem nulla) szám negáltja pedig hamis eredményt ad, ezért nem futott le az adott sor. Az utolsó sor feltétele nulla, ami már önmagában hamis. Ha tehát egy változó értéke vagy egy függvény visszatérési értéke 0, akkor az adott elágazás vagy ciklus nem fut le. De mi van akkor, ha nekünk pont akkor kell, hogy lefusson, ha az érték nulla? Nagyon egyszerű a válasz: megvizsgáljuk, hogy az adott kifejezés nulla-e, azaz egy egyenlőség vizsgálattal így már a feltétel igaz lesz, hiszen nulla egyenlő nullával. 9
8. Adatbekérés, ASCII tábla Ahhoz, hogy általunk megadott adatokat tudjunk tárolni, meg kell teremtenünk a kapcsolatot köztünk és a programunk között. Erre szolgál az stdio.h fejfájl, mely tele van előre megírt függvényekkel és eljárásokkal, ezeket csak meg kell hívnunk. Ilyen függvény például a getchar() is. A getchar() függvény visszatérési értéke karakter. Így tehát ha be szeretnénk kérni egy karaktert, akkor ezt a függvényt meghívhatjuk. Ahhoz hogy bele is rakja az általunk begépelt karaktert az általunk felvett karakter változóba, a változónak értékként a függvényt adjuk meg, mivel a visszatérési érték (az, amit a függvény eredményül ad nekünk) lesz az az érték, amit mi beírtunk, és amit a változóban tárolnunk kell. Egy rövid példa egy karakter bekérésére: Kérdések: - Mi történik, ha felveszünk egy i nevű egész változót, és oda kérjük be a karaktert, valamint azt íratjuk ki karakterként? - Mi történik, ha kicseréljük a %ct %d-re? Mint már említve volt, a char típus lényegében egy korlátolt int típus, egymás értékeit oda vissza lehet tárolni. A karaktereket egy táblázat szerint kódolták. Ez az ASCII tábla, ahol minden számhoz tartozik egy hozzárendelt karakter. Ha a bekért A karakterünket egészértékként írajuk ki (%d), akkor 65-öt fog kiírni a program (ez az ASCII kódja). Ha viszont karakterként (%c), akkor az A betűt kapjuk. Feladatok: - Szervezzünk do-while ciklust, mely addig kér be tőlünk egy karaktert, amíg az nem az X! Ha a megadott karakter X, akkor a ciklusból lépjünk ki, a program pedig írja ki az ASCII kódját! - Szervezzünk do-while ciklust, mely a 0 karakter megadásáig kér be tőlünk betűket. Elágazással próbáljuk meg a nagybetűket kicsivé, a kicsiket nagybetűvé alakítani. Az így kapott átalakított betűt írjuk ki, és azt is, hogy miből mibe alakítottuk! 10
9. Tömbök Az Adattípusok, értékadás, egyenlőségek részben volt szó arról a hasonlatról, hogy egy változó olyan, mint egy fiók. Abban az esetben, ha több változót szeretnénk használni, akkor célszerűbb egy tömböt felvenni. Ha egy változó egy fiók volt, akkor egy tömb egy egész szekrény, és mi határozhatjuk meg, hogy hány kis fiókból álljon. Tömböket tudunk készíteni a már felsorolt típusokból és következőképpen kell őket deklarálni: int tomb[10]; vagy char mondat[10]; Tehát ugyanúgy van egy típusa, egy azonosítója, és ez van kiegészítve egy szögletes zárójelbe tett számmal, ami számunkra a szekrényben a fiókok számát reprezentálja (hány külön változót tud tárolni). A tömbre hivatkozákor a szögletes zárójelben lévő szám az indexszám. Innentől kezdve a használata megegyezik az egyszerű változókéval, annyi különbséggel, hogy hivatkoznunk kell arra az elemre, amelyet módosítani szeretnénk, amivel dolgozni akarunk. Például: A tömbök kis fiókjait deklaráláskor is feltölthetjük, adhatunk nekik értéket: Sőt, egyetlen érték megadásával az egész tömböt ki tudjuk nullázni: Így most a tomb nevű int típusú tömbünk összes kis fiókjába betettük a 0 értéket. A C nyelvben a tömbök indexelése 0-tól kezdődik. Éppen ezért, ha felvettünk egy N elemű tömböt, akkor a tömb utolsó elemét az N-1 indexszámmal érhetjük el. int szamok[4] 23 65 122 1 0.elem 1.elem 2.elem 3.elem Feladatok: - Vegyünk fel egy 10 elemű egész típusú tömböt. Szervezzünk egy ciklust, melyben az i ciklusváltozó 1-től egészen 50-ig mehet. A tömbünkbe gyűjtsük ki az összes páratlan számot (maradékos osztás). A ciklus érjen véget, ha a 10 elemű tömbünk megtelt. (Segítség: a tömbünk indexeléséhez egy külön változó kell, amit 0-ról indítunk egészen 9-ig, és csak akkor léptetjük, ha páratlan számnál tartunk). A ciklus lefutása után írassuk ki a letárolt számokat! - Írjunk programot, mely addig kér be tőlünk karaktereket, amíg nem üres sort adunk meg neki, vagy az összes megadott karakterek száma nem éri el a 10-et. A bekért karaktereket egy tömbben tároljuk. A bekérések végeztével a program írja ki egy sorban az összes megadott karaktert! 11
10. Függvények, eljárások A függvények és eljárások a programozás szerves részét képezik. Így ebben a fejezetben belenézünk, hogy hogyan kell függvényeket írni, mi az a visszatérési érték, mi az a paraméter, és egyáltalán hogyan is működik mindez. A segédletben már többször is előfordultak függvények és eljárások (összefoglaló nevükön szubrutinok vagy metódusok), sőt, már használtunk is belőlük párat (például: getchar(), printf(), main()). Olyan műveletekre érdemes szubrutint szervezni, amikről tudjuk, hogy többször is elő fog fordulni, illetve ha szebbé szeretnénk tenni a programunkat, akkor szintén érdemes külön részegységet létrehozni erre a célra. A függvény és eljárás készítésének három lépése: 1- Szubrutin szervezése a. Első lépés a visszatérési érték meghatározása, ez lehet valamilyen adattípus (Adattípusok, értékadás, egyenlőségek). Ha nem lesz mit visszaadni, akkor void (ez esetben nem függvény, hanem eljárás). b. Második lépés a függvény (vagy eljárás) nevének, azonosítójának a megadása. Ez tetszőleges, de célszerű utalni vele a funkciójára. c. Harmadik lépés a paraméter meghatározása. Itt kerek zárójelben meg kell adni mindent, amire a függvénynek (vagy eljárásnak) szüksége van a műveletek elvégzéséhez. Ha nem kér adatot, akkor void, ellenkező esetben mindenhol adattípussal együtt egy tetszőleges nevű változólistát írunk bele. d. Negyedik lépés: írjuk meg a függvényünket (vagy eljárásunkat) tetszőlegesen. A példában csak egy kiíratás történik, természetesen lehet bármi mást is elvégezni (változókat felvenni, ciklusokat szervezni, műveleteket végezni stb.). 2- Prototípus main() függvény elé másolása. A függvényünk (vagy eljárásunk) első sorát kimásoljuk a main függvény elé, és lezárjuk. Ezzel biztosítottuk, hogy a fordító megtalálja majd a függvényünket. 3- Függvényhívás. Itt programunkban a már ismert módon meghívjuk a szubrutint, ami elvégzi a feladatát (így volt hívva a getchar(), printf() stb függvény is). Néhány szabály: - Nem void függvényeknél (eljárásoknál) a visszatérés (return) kötelező - Függvényhíváskor nem kell leírnunk se a visszatérés típusát, se a paraméterek típusát - A függvény visszatérési értéke és a változó amiben azt tároljuk meg kell, hogy egyezzen (de legalább kompatibilis legyen vele, például: int <-> double) - Ha több paraméterünk van, akkor vesszővel választjuk el őket (lásd: printf(), pow()) 12
Nézzünk egy élethűbb példát a függvények használatára. Szervezzünk függvényt, amely egy általunk megadott oldalhosszúságú négyzet területét adja vissza (azaz a visszatérési érték típusa biztosan nem void lesz) Ebben a programban az a nevű változóban tároltuk a négyzet egy oldalának hosszát. Ezt paraméterként átadtuk a negyzetterulet() nevű függvényünknek. A függvény átveszi, az ő saját terulet nevű változójába kiszámolja a területet a beépített pow() függvény segítségével, majd ezt visszaadja nekünk (return). A visszatérési értéket az eredmeny változónk fogja megkapni, hiszen egyenlővé tettük a függvényünkkel. Így már csak a kiíratás maradt hátra. Kérdések: - Nézzük meg, hogyan ugrál a programunkban a vezérlés egyik részről a másikra. Mi hajtódik végre, mielőtt az eredmeny változónkba érték kerülne? - Mi az összefüggés a negyzetterulet() függvény visszatérési típusa és a return terulet között? - Milyen paramétereket várhat a pow() függvény? Mi kell ahhoz, hogy ezt a beépített függvényt használhassuk? Feladatok: - Vegyünk fel egy 5 elemű egész számokat tároló tömböt, majd töltsük is fel azt! Írjunk saját függvényt, mely a paraméterben megadott értéket köbre emeli, majd visszaadja azt! For ciklus segítségével menjünk végig a tömb elemein és írassuk ki egyenként a régi és az új értéket is! - Írjunk eljárást, mely paraméterként egy karaktert és egy egész számot vár! Az eljárás a paraméterek felhasználásával annyiszor írja ki a megadott karaktert, ahányszor azt átadtuk neki a második paraméterben! - Írjunk függvényt, mely egy karaktert vár bemenő paraméterként! Abban az esetben ha az átadott karakter betű, a visszatérési érték legyen 1, ha szám akkor 2, minden más esetben 0! A visszatérési értéket tároljuk, majd értékének megfelelően írjuk ki a képernyőre, hogy mi volt a megadott karakter! - Szervezzünk függvényt, melyben egy paraméterben megadott karaktert kell átalakítani! Ha a megadott karakter kisbetű, alakítsa át nagybetűre, ha a karakter nagybetű, alakítsa át kicsire! Egyéb karakter esetén a függvény adja vissza az eredeti karaktert! Az így kapott visszatérési értéket tároljuk megfelelő típusú változóban, és írassuk ki azt! 13
11. Sztring Ha megértettük a tömbök és a függvények használatát, akkor itt az ideje, hogy rátérjünk a vizsgák alappillérét jelentő sztringekre. A sztring egy karakterekből álló tömb. Ez azt jelenti, hogy több betűt, számot és írásjelet (karaktereket) egymás után tudunk rakni, amiből következik, hogy egész szavakat, mondatokat tudunk vele tárolni. Karaktereket a getchar() függvénnyel kértünk be, ennél azonban van jobb megoldás tömbökre. Írhatnánk saját függvényt is erre a célra, de erre már van egy jól bevált, a vizsgán is használható előre elkészített függvényünk, a getline(). A getline() függvény visszatérési értéke a megadott karakterlánc hossza, éppen ezért a paraméterben megadott tömbbe fogja beletölteni azt, amit bekéréskor beírtunk. Mivel nem beépített függvényről van szó, így a prototípust a main elé kell másolni. A bekérés után kezelhetjük karaktertömbként is a beolvasott adatot, de van rá sok előre elkészített fügvvény, ezeket a string.h fejfájlban találjuk meg. A kiíratás a már említett módon, %s formátumjelölővel lehetséges. A sztringek végét egy lezáró nulla (\0) karakter jelzi, hogy a sztringkezelő függvények tudják, hol kell megállni. A lezáró nulla egy láthatatlan (fehér) karakter. Sajátkezű feldolgozáskor mindig a lezáró nullánál találjuk meg a sztring végét. A lezárás 1 karakternyi helyet el fog foglalni a sztringünkben, emiatt a ténylegesen bekérhető karaktermennyiség N-1. Kérdések: char szo[5] szo[0] szo[1] szo[2] szo[3] szo[4] A L M A \0 - Miért int a getline() visszatérési értéke? Mi történne, ha a visszatérési értéket nem tárolnánk le? - Mit jelent a getline() függvény int n paramétere? - Hány elemű tömböt kell felvennünk egy 20 karakterből álló mondathoz és miért? - Hogyan tudhatjuk meg egy bevitt karakterlánc hosszát? - Mi az a define? Miért van mindenhová MAX írva? Miért hasznos ez? 14
A sztringek kezelésére rengeteg függvény és eljárás meg van írva a string.h fejfájlban. Ezekkel a szubrutinokkal a bemenő (input) adatokat tudjuk ellenőrizni, illetve azokat a karakterláncokat, amelyek a feladat során eredményként előállnak. Lássuk, miket kell tudnunk használni: strlen strcmp strncmp strcpy strncpy A sztring hosszát adja vissza (int érték). Két sztringet hasonlít össze. Ha nincs eltérés, vagyis a két sztring megegyezik, akkor a visszatérési érték 0 (int). Két sztringet hasonlít össze, ám ezúttal csak az első n karaktert vizsgálja (paraméterként kell megadni) Sztringet másol Sztringet másol egy megadott karakterhatárig Ezen felül a sztringek bekérésére szolgáló getline() függvényünk visszatérési értéke is használható a megadott karakterlánc hosszának ellenőrzésére. Fontos tudni, hogy a lezáró nulla nélküli karakterláncokat ezekkel a függvényekkel nem tudjuk ellenőrizni, hiszen mindegyik a lezárást keresi a karakterek között. Ebből következik, hogy ha a lezáró nulla után állnak még karakterek, akkor ezeket nem veszik figyelembe a fent említett függvények. Az strcmp() függvény használható sorbarendezéshez. A paraméterekben szereplő sztringek közül az elsőhöz van hasonlítva a második sztring, így a visszatérési érték 1 abban az esetben ha a második sztring az első után következik betűrendben. Ellenben mikor a második sztring az első előtt következne akkor a visszatérési érték -1. Sztringek megadhatóak direkt módon is, külön karaktertömb definiálása nélkül. Az idézőjelek közé írt szöveg sztringként értelmezhető. Kérdések: - Határozzuk meg az egyes függvények paramétereit. Határozzuk meg melyik paraméter mire szolgál és hogy a visszatérési érték az egyes esetekben mit jelent! - Mit ír ki a program, ha a következő karakterláncot íratjuk ki vele: abc\0def? - Melyik függvényt válasszuk, ha azt szeretnénk igazolni, hogy az általunk megadott karakterlánc első három karaktere az 123 karakterlánc? Feladatok: - getline() függvénnyel kérjünk be egy maximum 20 karakter hosszú karakterláncot. Ezt a láncot egy 21 elemű tömbbe kérjük be, amit define segítségével rögzítsünk! Írassuk ki a sztring hosszát, valamint hasonlítsuk össze az 123 karaktersorral! - Vegyünk fel két karaktertömböt, majd az egyikbe kérjünk be egy sor karaktert. Az így keletkezett sztringet másoljuk át a másik tömbbe, majd írassuk ki azt! - Az előző feladaton módosítsunk úgy, hogy csak az első 5 karaktert másolja át a programunk! Hogyan lehet máshogy megoldani, ha nem függvényt használunk rá? - Készítsünk saját függvényt, ami hasonló módon működik, mint az strlen, azaz számolja össze a karakterlánc karaktereinek számát! 15
12. Típuskonvertálás Ha belegondolunk, hogy be kell kérnünk egy float vagy double számot, hogyan tennénk? Vagy ha csak egy sima int számot kellene bekérnünk? Persze a getchar() függvénnyel meg tudunk adni ilyen adatokat, de azzal csak egy számjegyet tudnánk bekérni, mivel 1 karaktert vár inputként. getline() segítségével pedig lehet több karaktert is megadni. Nézzük meg, hogyan kérhetünk be egy tetszőleges számot. Az átalakítás sztringből készít int illetve float típust. Az átalakításokért felelős függvényeket az stdlib.h fejfájl tartalmazza, tehát ennek bekapcsolásáról gondoskodnunk kell, mielőtt a függvényeket használatba vennék. A két átalakító függvény a következő: i = atoi(tomb) f = atof(tomb) Egy sztringet alakít át int típusúvá. A képletben az i egy int típusú változó, tomb a sztringünk. Egy sztringet alakít át float típusúvá. A képletben az f egy float típusú változó, tomb a sztringünk. Ezeket a függvényeket kizárólag ideális vagy ellenőrzött környezetben használhatjuk. Az ideális alatt azt értjük, hogy a felhasználó biztosan nem téveszti el az inputot (tehát szám bekérésénél nem más karaktereket fog megadni). Az ellenőrzött környezetben mi magunk ellenőrizzük le, hogy a megadott lánc csak szám karaktereket, előjelet, illetve float esetén legfeljebb 1 pontot (és csak is pont karaktert) tartalmaz. A vizsgákon csak a második eset fog előfordulni, azaz alapesetben a feltételezés az, hogy rossz adatot adnak meg, hibás az input. Nézzünk két egyszerű meghívást a két függvényre: A két példaprogram kizárólag akkor ad helyes átalakított értéket, ha a számok között nincs semmilyen egyéb karakter float esetén pedig csak a pontot fogadja el tizedesvesszőként. A helytelen adatokra a visszatérési érték a sztring elején álló helyes karakterek átalakítottja, legrosszabb esetben pedig nulla. Ebből láthatjuk, hogy szükség van ellenőrzésre, és egyes esetenként átalakításokra is. Feladatok: - Írjunk programot, mely bekér 5 darab 10-nél nagyobb vagy egyenlő és 1000-nél kisebb egész számot! A program a bekéréshez a getline() függvényt használja! A bekért adatot tároljuk el egy karaktertömbben, majd ezt alakítsuk át int értékké! Az egyes értékeket tároljuk el egy egész tömbben majd a bekérés végeztével írassuk ki a tömb elemeit! - Írjunk olyan programot, mely bekér 3 darab valós számot, és ezekből átlagot von! A megadott valós szám működjön vesszős és pontos tagolással is! 16
13. Sorba rendezés, szélsőérték kiválasztás A későbbiek során sokszor lesz szükségünk egy sorba rendezett tömbre. Egy tömböt sorba rendezni igen egyszerű. Nem kell más hozzá, mint két ciklus, egy feltétel, és az indexelésekkel kapcsolatos alapfogalmak. Sokféle rendező algoritmus létezik, ami most bemutatásra kerül az talán az egyik legegyszerűbb. A képen szereplő példafeladatban egy 5 elemű egésztömbünk volt, amit előre feltöltöttünk véletlenszerű számokkal. A rendezés a következők szerint zajlott: az első ciklus 0-tól indul, és a tömb vége előtt 1-gyel áll meg, míg a második ciklus változója (j) mindig az i aktuális értékéhez képest 1-gyel nagyobb értéket vesz fel kezdetben, és a tömb végéig megy. Így az i viszonylag lassan növekszik, míg j mindig újra és újra növekedni kezd. Ezáltal ez a két ciklusváltozó remekül használható a tömbünk indexelésére, a tömbünkben lévő két érték összehasonlítására, és ezáltal a sorrend kialakítására. A két érték vizsgálatánál mindig az adott sorrend kialakításához szükséges feltételt nézzük (kisebb vagy nagyobb). Ha (a példán keresztül nézve) a tomb[j] kisebb, mint a tomb[i] (azaz egy, a tömbben később szereplő érték kisebb mint az előbb szereplő), Buborékrendezés akkor a kettőt kicserélve megoldottuk a problémát. A kicseréléshez szükségünk van egy átmeneti tárolóra, hiszen ha egyből felülírnánk valamelyik tömbelemet, akkor annak tartalma elveszne. Így előtte mindenképpen mentsük el az egyik tömbelemet, hogy az értékadás után az visszaállítható legyen. Ezt az eljárást alkalmazva a tömbünk elejére kerülnek a kisebb számok, míg a hátsó részbe a nagyobbak. Természetesen innen már könnyű megtalálni a minimum vagy maximum értéket, hiszen biztos, hogy az a sorba rendezett tömbünk egyik végében lesz. Van azonban olyan helyzet, amikor a tömbünk sorrendjén nem változtathatunk, de mégis meg kell tudnunk, melyik volt a maximum, vagy a minimum érték, és hogy mi volt az. Szélsőérték kiválasztás Erre egy még egyszerűbb algoritmus áll rendelkezésünkre, nem kell más hozzá, csak egy ciklus és egy feltétel. Tegyük fel, hogy a tömb első (index szerint nulladik) eleme a legnagyobb. Ha tényleg az volt, akkor eltaláltuk, ha nem, akkor az algoritmus majd talál nagyobbat nála. A ciklus végigfut a tömbön, és ha a jelenlegi maximumnál nagyobbat talál, akkor a max változóba belerakja az aktuális maximumot. A maxi változó pedig a maximumunk helyét (indexét) fogja jelezni a tömbünkben, így bármikor hivatkozhatunk rá (például olyan kérdésnél, hogy hányadik elem volt a legkisebb a tömbben). 17
14. Inputellenőrzés Ha elsajátítottuk az eddigi fejezetek anyagát, akkor rátérhetünk a tárgy lelkét jelentő inputellenőrzésekre. Inputok ellenőrzésére azért van szükség, hogy a megadott adatok biztosan helyesek legyenek formailag és tartalmilag is. A feladatok nagy többsége ugyanazon sablonra épül, hasonlóképpen járunk el mindegyik esetben. Próbáljuk meg a következő feladatot megoldani: Feladat: - Kérjünk be legfeljebb öt darab személy vezetéknevét, keresztnevét és életkorát! Ha a vezetéknévnek üres sort adunk meg, a program lépjen ki! Egy név a következőképpen néz ki: minimum 3 és maximum 10 karakterből állhat, csak az angol ABC betűi szerepelhetnek benne és a nevek kezdőbetűi nagybetűk. A nevek bekérése és ellenőrzése után kérjünk be egy életkort is! Ezt is ellenőrizzük le az alábbiak alapján: minimum 18 és maximum 101 lehet az érték, nem lehet benne írásjel és számon kívül semmilyen karakter sem! Az esetleges hibákról tájékoztassuk a felhasználót, hiba esetén kérjük be újra az adatot! Ha helyes input adat került megadásra, a program írja ki a megadott nevet és a hozzá tartozó életkort. Első ránézésre nehéznek tűnhet a feladat, azonban egy egyszerű szerkezet alapján és az elsajátított ismereteinkre támaszkodva könnyedén megoldhatóak az ilyen és ehhez hasonló feladatok. Alapvetően az ilyen feladatok vázát a do while ciklus adja. Nem csak azért használjuk ezt, mert adatbekérésre ez a legkényelmesebb, hanem mert logikai kapcsolókat használva könnyedén kezelhetünk bármit a cikluson belül, hiszen az egyes bekérésektől a vezérlés csak akkor jut ki, ha a kapcsolót átváltjuk. Ezt a kapcsolót pedig csak akkor váltjuk át, ha a megadott input megfelel nekünk, vagy ki szeretnénk lépni a bekérésből. Ilyen kapcsolóval látjuk el a három do while ciklusunk feltételét (két név bekérés plusz az életkor bekérése). Az első ciklusban a nevet kérjük be, és elágazásokkal ellenőrizzük annak megfelelőségét. Először mindig azt vizsgáljuk, hogy mik lehetnek a hibák. Ebből következik, hogy az utolsó, else ágban lesz az az eset, amikor minden követelménynek megfelel az input, és a kapcsolót átválthatjuk. Hasonlóképpen járunk el a másik névnél és az évszámnál is. Ezt a három ciklust bele kell foglalnunk egy nagy do while ciklusba, hiszen öt jó bekérés után vagy üres sor esetén ki kell tudnunk lépni. Ez esetben érdemes ide is egy kapcsolót felvenni, amivel azt jelezzük, hogy ki kívánunk lépni az egész nagy ciklusból. Az ellenőrzések szimpla tömbműveletek, az egyes tömbök elemeit kell vizsgálni a megadott kritériumok alapján. Fontos, hogy bekérés után először mindíg a sztring hosszát vizsgáljuk meg, hiszen az üres sorra kilépés vagy a nem megfelelő számú karakterek hibája az első dolog, amit a hossz alapján le tudunk ellenőrizni. Ezek után jöhetnek a különböző ellenőrző függvények. Ha mindegyiken átjut az adat, akkor egy else ágba kerülünk, ahol sztringek esetén általában már csak a kapcsoló átállítása marad (hiszen jó volt az adat), számoknál viszont először az átalakítás, majd az értékek ellenőrzése következik (tartalmi ellenőrzés). Az adatok bekérése és ellenőrzése után már csak ki kell írni azokat, illetve növelnünk kell a ciklusváltozót, ezzel jelezve, hogy készen állunk a következő adat fogadására. Mivel ilyenkor a vezérlés visszaugrik a ciklus elejére, ezért itt remek lehetőség nyílik a kapcsolók alaphelyzetbe állítására, ezzel feltételezve, hogy az adatok rosszak lesznek. Persze ez csak annyit jelent, hogy rossz adat megadása esetén ne ugorjuk át az adott adat bekérését, hanem újra kérje be, amíg helyes adat nem érkezik. A külső do while ciklus feltételében meghatározzuk, hogy üres sorra vagy a maximum elemszám elérésekor lépjen ki a ciklus, így az adatbekérés ezzel véget ér. 18