View
2
Download
1
Category
Preview:
Citation preview
Programozás alapjai segédanyag 2018
Kóbor jegyzet
Kóbor Ervin
Utolsó módosítás: 2018.09.10.
Tartalomjegyzék
Tartalomjegyzék 2
1. Bevezetés 4
1.1. Miért programozunk és miért C-ben? . . . . . . . . . . . . . . . . . . . . . . . 4
2. Hello World! 6
2.1. Egy C forráskód alapvetései . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2. Változók ki és be . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3. Aritmetika és függvények 15
3.1. Precedenciasorrend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2. Változók értékeinek módosítása . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3. Függvények . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4. Feltételes vezérlési szerkezetek 23
4.1. Else if feltételek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.2. Switch-case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3. Feltétel a feltételben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5. Ciklusok 30
5.1. For-ciklus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2. Ciklus a ciklusban . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.3. While-ciklus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.4. Do-while ciklus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.5. Break, continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2
6. Tömbök és stringek 40
6.1. Egydimenziós tömbök . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.2. Tömbök és ciklusok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.3. Többdimenziós tömbök . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7. Fájlkezelés 48
8. Struktúrák 50
8.1. Struktúrák alapveto tulajdonságai . . . . . . . . . . . . . . . . . . . . . . . . . 50
8.2. Tömbök struktúrában és struktúrapéldányok paraméterként . . . . . . . . . . . 52
8.3. Struktúratömbök . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
9. Pointerek 55
9.1. Dinamikus tömbök . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
9.2. Kétdimenziós dinamikus tömbök . . . . . . . . . . . . . . . . . . . . . . . . . 59
3
1. fejezet
Bevezetés
Ez a jegyzet abból a célból jött létre, hogy segítsen a hallgatóknak megérteni a Programozás
alapjai kurzus fobb mérföldköveit. A dokumentum írása során törekedtem az egyszeru és ért-
heto fogalmazásmódra, hogy a teljesen kezdo hallgatók számára is minden egyértelmu legyen.
Túlnyomó részt azokkal a témakörökkel fogunk foglalkozni, amelyek a ZH-kon fordulhatnak
elo.
Tartsuk észben, egy jegyzet soha nem lesz elég a megfelelo rutin elsajátítására, ehhez
hosszú-hosszú órákon keresztül kell rendszeresen gyakorolni. Sem az eloadásra, sem a gya-
korlatra való bejárást nem érdemes mellozni, s ez az iromány kizárólag ismétlo jelleggel jött
létre. Elképzelheto, hogy olvasás során találkoztok hibákkal, ezeket legyetek szíves jelezni az
e-mail címemre: Kobor.Ervin@stud.u-szeged.hu
További rutin elsajátítására ajánlom az alábbi oldalakat:
• www.hackerrank.com
• www.codeabbey.com
• www.codewars.com
• www.coderbyte.com
1.1. Miért programozunk és miért C-ben?
Nos, a kérdés elso felére azért nem bonyolult a válasz. A programozás semmi egyéb, mint
problémamegoldás, problémák pedig mindenhol vannak. Szinte lehetetlen olyan tudomány-
ágat mondani, aminek a problémakörén ne segített volna már az informatikai alkalmazás, meg
amúgy is, ki ne akarna problémamegoldásban jó lenni? Szorosan együttmuködik a természettu-
4
dományokkal, rengeteg új dolog született ennek eredményeképpen. Sot, ha késobb úgy dönte-
nénk, hogy nem informatikával vagy valamilyen tudománnyal akarunk foglalkozni, bármilyen
további szakterületet is választunk, a programozás a legkülönbözobb esetekben is a hasznunkra
lehet, ha máshol nem, legalább a gondolkodásmódban.
Arra hogy miért C-vel kezdünk, röviden annyi a válasz, hogy egy gyors, megbízható nyelv,
módfelett jól dokumentált és szinte az összes vele kapcsolatos kérdés másodpercek alatt ki-
keresheto. Utóbbi sok újabb nyelvrol nem garantálható. Tehát a C várhatóan öt év múlva is
ugyanilyen szerepet fog betölteni a piacon ugyanilyen formai sajátosságokkal.
Itt felmerülhet a kérdés, hogy mivel egy 70-es években született nyelvrol beszélünk, mennyit
is ér ez a tudás? Nos, bár sokan elsore nem gondolnak bele, de a C a mai napig ott húzódik
rendszerek tömkelege mögött. Mivel egy erosen gépközeli nyelv, ezért rendkívül gyors. Sebes-
sége miatt operációs rendszerek fejlesztéséhez a mai napig használják, ugyanezért az alapból is
idoigényes funkciók kiszolgálására is optimális választás. Beágyazott rendszerekhez, grafikai
motorokhoz, mikroprocesszorokhoz és vezérléi rendszerekhez is használják.
Szintaktikája, vagyis a C nyelvben értelmezett utasítás/parancssorozatok sok nyelv szintak-
tikai alapjául szolgálnak, szóval ha megfeleloen elsajátítjuk, a többi nyelvvel már mérföldekkel
egyszerubb dolgunk lesz(Java, C#, C++). Bonyolultságát tekintve nem tartozik a könnyu nyel-
vek közé, számos olyan dolgot kell nekünk kézzel megírni, amit más nyelvekben már jelezni/-
tudni sem kell. Ennek azonban elonye is van, méghozzá az, hogy késobb nemcsak elfogadni
fogjuk felsobb szintu nyelvek esetében a dolgokat, hanem érteni és tudni fogjuk a mögöttes
folyamatokat is. Tehát egészen nyugodtan kijelenthetjük bárkinek, hogy a C egy kezdo progra-
mozónak egy stabil alapot ad a késobbiekre nézve.
5
2. fejezet
Hello World!
A C egy általános célú programozási nyelv, ami annyit tesz, hogy rendkívül sokféle alkalmazás
fejlesztheto benne, hiszen alapvetoen semmilyen irányba sem specifikált. Használható maga-
sabb szintu nyelvekkel együtt, mivel könnyedén beágyazható, valamint kompatibilitási prob-
lémák miatt sem kell aggódnunk, ugyanis minden platformra létezik C-fordító, s programun-
kat mindenhol tudjuk futtatni. Ha egy muködo programot akarunk létrehozni, elso lépésben
szükségünk lesz egy forráskódra. Ez egy egyszeru szöveges fájl, kiterjesztése mindig az adott
programnyelvnek megfelelo, ez most nekünk a .c kiterjesztés lesz.
A forráskódunkban fogjuk az adott nyelv szabályai szerint leírni azt, hogy programunk fu-
tása alatt mit is csináljon a gép. Ez ugye teljesen gépi szinten a bináris nyelv, efelett helyez-
kedik el az assembly, amiben már szerepelnek parancsok, és a programozó által is olvasható,
azonban elég nehézkes robosztusabb programok esetében. Errol a szintrol fogunk eljutni a C
nyelvre, ahol már a gépi nyelv egy viszonylag barátságosabb absztrakciójával dolgozhatunk.
Mivel szöveges fájlról beszélünk, a forráskód szerkesztheto bármely szövegszerkesztovel, de
kényelmesebb ezt olyan programmal megtenni amit kifejezetten kódolás céljából fejlesztettek
ki. Ilyen szövegszerkesztok pl.: Notepad++, Atom, Sublime-text, Visual Studio Code.
A forráskód megírását követoen egy C fordítóval kell lefordítani ezt a forraskod.c fájlt,
ez általában a gcc. A fordítási parancs linux-terminálból a következoképpen néz ki:
gcc -o futtathatoallomany forraskod.c.
Ha a megfelelo módon lefordítottuk (compile) a forráskódot, megtörténtek az ábrán látható
folyamatok, és a szöveges állomány szintaktikailag1 helyes, generálódik nekünk egy futtatható
1Mint minden beszélt nyelvnek, úgy a programozásban használt nyelveknek is vannak szabályai, amelyeketbe kell tartani. Ezeket a szabályokat összefoglaló néven szintaxisnak (syntax) nevezünk. Minden programozásinyelvben mások a szabályok, amelyeket meg kell tanulni az adott nyelv használata érdekében.
6
2.1. ábra. A futtatható állomány keletkezésének folyamata
állomány. A futtatható állomány windowson .exe, linuxon egy kiterjesztés nélküli fájl. Ha
forráskódunkban szintaktikai hiba van, akkor nem generálódik futtatható állomány, hiszen olyan
parancs szerepel a kódunkban, amit a fordító nem tud értelmezni.
Amennyiben a kész állományt futtatjuk (run), és a kódunk a célunknak megfelelo, a program
az általunk tervezett lépéseket fogja végrehajtani. Futás közben is akadhatnak azonban prob-
lémák, ugyanis az, hogy a fordító számára értelmes parancsokat írtunk, nem jelenti azt, hogy
azok helyesek is vagy éppen azt csinálja a program, amit szeretnénk. Ilyenkor szemantikai2
hibáról beszélünk.
Vannak olyan integrált fejlesztoi környezetek (IDE3), ahol már telepítés után készen kapjuk
a szövegszerkesztot és a fordítót is. Itt csak meg kell írnunk a kódot, és egy gombnyomással
nem csak lefordíthatjuk, de egybol futtathatjuk is a kódunkból létrehozott fájlt, ilyen IDE-k C-
hez például a CodeBlocks, DevC++, Eclipse C/C++, Visual Studio.
Megjegyzés: ha linuxos környezetben dolgozunk órán, és az otthoni gépünkön windows van,
akkor hatékony megoldás a CygWin terminál használata. Generál egy home könyvtárat, ahol
érvényesek a linux parancsok, gcc-t is lehet hozzá telepíteni.
2A kódrészlet által végrehajtott feladatot nevezzük így.3Integrated Development Environment.
7
2.1. Egy C forráskód alapvetései
Kezdjünk is bele a jegyzet elso forráskódjának megírásába. Az alábbi képen egy kód található,
ami gyakorlatilag semmit sem csinál, pusztán szerepelnek rajta a program futásához alapveto
dolgok.
#include <stdio .h>
int main ( ) {
/ / i t t van a program b e l e p e s i p o n t j a
/ *i t t van a program b e l e p e s i p o n t j a
* /
return 0 ;}
2.2. ábra. Egy kód, ami nem csinál semmit
Tehát ha a fentebbi kódot az elobbiekben említett módon futó programmá varázsoljuk, akkor
a programunk rendben lefut, de nem történik semmi látványos. Tekintsük át, mi is történik eme
semmi hatására, hogyan értelmezzük a 2.2-es példakódban olvasottakat.
Az elso sorban betöltjük talán az egyetlen úgynevezett header fájlt, amit használni fogunk a
kurzuson. Az stdio.h egy külso fájl és a késobbiekben majd biztosítja nekünk, hogy tudjunk
adott helyekrol beolvasni illetve adott helyekre kiírni. Ugyanolyan kódot tartalmaz, amihez
hasonlót mi is meg tudunk/fogunk tudni késobb írni, de gondosan, elore leírt és ledokumen-
tált4 C szabványokban. Más header fájlok természetesen más beépített muveleteket foglalnak
magukban. Tehát a header fájlok függvényeikkel a lehetoségeinket szaporítják kódolás során.
Hamarosan azt is látni fogjuk, jelen esetben mik ezek.
Az int main() rész a program belépési pontja, az ún. main függvény. Innen kezdo-
dik a programunk végrehajtása, az itt kapcsos zárójelek között elhelyezkedo utasításokat a gép
sorrendben fogja végrehajtani. Ez a belépési pont a futtatható programoknál adott kell, hogy
legyen, csak az itt elhelyezkedo utasítások hajtódnak végre automatikusan! Ha majd a
késobbiekben beírogattuk az utasításainkat a main függvénybe, akkor amint sorról sorra vég-
rehajtotta azokat a gép, elérkezünk a return 0-ig. A program a 0-val való jelzéssel kommu-
nikálja a gép felé, hogy rendben lefutott a program.
Egy forráskódban lehetoségünk van kommenteket írni. Ezeket látható módon a // paranccsal
4http://devdocs.io/c/
8
tehetjük meg, ez egy egysoros komment, vagyis amit utána írunk az adott sorban, azt a fordító
nem fogja értelmezni. Ennek másik verziója a többsoros komment /* szoveg [ENTER] szoveg
*/ alakban. Ezeket sok esetben használhatjuk, üzeneteket hagyhatunk a kódunkat olvasóknak,
vagy éppen magunknak (terjedelmesebb kódok esetén kötelezo használni!). Ugyanakkor bi-
zonytalan kódrészeket is kikommentezhetünk, annak érdekében, hogy azok nélkül futtassuk a
programot.
Ahhoz, hogy tovább tudjunk lépni, mondjuk írjunk a képernyore egy szöveget, az stdio.h
header egy függvényével, a printf-fel. A jelenlegi felhasználásánál ez a függvény jóval sok-
oldalúbb, de mi most még csak egy általunk választott tetszoleges szöveg kiíratására használjuk!
#include <stdio .h>
int main ( ) {
printf ("Hello World!\n" ) ;
return 0 ;}
2.3. ábra. Egy kód, ami kiírja a kimenetre: Hello World!
A szöveget programozásban más néven stringnek hívjuk, és ilyen formában "" jelek között
írjuk a tartalmát. A \n azt jelenti, hogy a kiírt szöveg után még írtunk sortörést is, hogy ha
ezután a köszönés után még mást is írunk ki, az már új sorba kerüljön. Tehát ha ezt a \n-t nem
tesszük ki, akkor egy esetleges további kiíratás az elozo szövegünk mellé kerülne sorfolytono-
san. Ugyanígy tabulálhatunk is a \t-vel, arra viszont figyeljünk, hogy a "" jelek közé írjuk az
ilyen szeparátorokat. A printf és amúgy a return 0 után azért van pontosvesszo(;) mert a
fordító így tudja elkülöníteni egymástól az utasításokat. Minden utasítás után rakni kell, ha csak
az utasítás nem rendelkezik másik szeparációs szimbólummal, mint például a jelek, de ezt majd
látni fogjuk. Fontos, hogy bár olybá tunik, hogy sorvégeken helyezkedik el a pontosvesszo, ne
higyjünk a szemünknek, ugyanis nem sor, hanem utasításszeparátor. Ebbol következik, hogy
egy többsoros C kódot akár egy sorba is írhatnánk.
9
2.2. Változók ki és be
Említettük már, hogy a programozó legtöbbször valós életbol vett problémákat old meg. Ha
végiggondoljuk, akkor ha valós életbeli problémákat akarunk leírni, logikus, hogy milyen tí-
pusú adatokat kell tárolnunk: szám és karakter. Egy programban különbözo típusú változókat
vehetünk fel, s ezek egyenként képviselik egy-egy elemét a megoldandó problémának. Tehát
ahogy középiskolában matematika órán létrehozhattunk egy x változót, ami egy számot vehetett
fel értéknek, itt ez ugyan így igaz, csak éppenséggel jeleznünk kell, hogy az adott x vagy y vagy
z vagy a, b ... változó milyen típusú lesz. Ha ez megvan, akkor értékeket is rendelhetünk a
típusnak megfeleloen hozzájuk.
Az adattípusoknak C-ben az alábbi verzióit különítjük el:
• int - egész szám - 4 bájt
• float - valós szám (7 tizedesjegy) - 4 bájt
• double - valós szám (15 tizedesjegy) - 8 bájt
• char - karakter - 1 bájt
A felsorolt adattípusokat primitív adattípusoknak hívjuk. Mindegyik más-más méretet fog-
lal el a számítógép memóriájában. A felsorolásban feltüntetett kulcsszavakkal fogjuk majd
deklarálni az adott típusú és adott nevu változókat tipus valtozonev; módon. A dekla-
ráció annyit tesz, mint létrehozni a változót az adott kódrészletben. Egy kódrészletben (késobb
látni fogjuk, ez mire értendo) nem lehet változónév ismétlodés, mert a program nem tudná be-
azonosítani, hogy mikor mire hivatkozunk. Ha már létrehoztunk egy változót, akkor nem árt,
ha értéket is adunk neki, ezt inicializálásnak hívjuk, ezt megtehetjük rögtön a deklarációval
egyetemben a forráskódban, vagy késobb a felhasználóra bízhatjuk az értékadást. Egy változót
többször is inicializálhatunk különbözo értékekkel, azonban csak egyszer deklarálhatunk!
float valt1 ; / / d e k l a r a l t u k a v a l t 1 nevu f l o a t t i p u s u v a l t o z o tvalt1 = 3 . 1 4 ; / / i n i c i a l i z a l t u k a v a l 1 nevu v a l t o z o tvalt1 = 9 8 . 1 3 1 2 1 ; / / m o d o s i t o t t u k az i n i c i a l i z a l t e r t e k e t
char valt2 ; / / d e k l a r a l j u k a v a l t 2 nevu c h a r t i p u s u v a l t o z o tvalt2 = 'j' ; / / i n i c i a l i z a l t u k a v a l t 2 v a l t o z o tvalt2 = 'z' ; / / m o d o s i t o t t u k az i n i c i a l i z a l t e r t e k e t
2.4. ábra. Az ábrán egy kis kódrészlet látható, amin a deklaráció és az inicializáció kap szerepet.
Különösen figyelni kell rá, hogy valós számok (float és double) esetén itt tizedes pontot
használunk a már megszokott tizedesvesszo helyett!
10
A char változótípussal kapcsolatban annyit érdemes megjegyezni, hogy ha 0-127 között
adunk neki értéket, ezekhez a számokhoz tartozik egy ún. ASCII-táblabeli karakter(kis/nagy
betuk, számok és néhány karakter). Ha ezen az intervallumon kívül adunk meg értéket, akkor
az ún. extended ASCII karakterekhez fogunk hozzáférni(egyéb szimbólumok). Amennyiben
pozitív irányból lépjük túl az intervallumot, úgy ez [128;255]-ig tart, ha pedig a negatív irány
felé, akkor [-1;-127]-ig, azonban itt fordított sorrendben, és ugyanígy továbbhaladva a hagyo-
mányos ASCII-beli karaktereket is fordítva bár, de megközelíthetjük. Ezeket a karaktereket és a
számokat is értékül lehet adni egy char változónak, ha karakterként kezeljük a változót, akkor
mindkét esetben karaktert fogunk visszakapni. Fontos még megjegyezni, hogy az ASCII-beli
karaktereket a char változóknak ’ jelek között (pl.: ’r’ vagy ’q’) adjuk értékül.
Említésre került az imént, hogy a printf valamivel komolyabb feladatokat is el tud lát-
ni, mint egy tetszoleges szöveg kiírása, ez a feladat pedig a változók értékeinek kiírása(2.5-ös
ábra). Ehhez azonban a változóknak eloször értékkel kell rendelkezniük! Mint azt láthatjuk,
a kódban való értékadáson kívül erre kínál alternatívát a scanf. A változók deklaráció után
még nem rendelkeznek értékkel, azonban miután a felhasználó megadta a változók értékeit, már
igen. Egy adott programba bármilyen módon beolvasott adat/adathalmaz forrását inputnak, míg
a program által generált adatot/adathalmazt outputnak hívjuk. Innen is ered az elnevezés: STan-
darD Input/Output, az stdio rövidítés is ezt jelenti.
A "" jelek között szereplo kifejezéseket és/vagy karaktereket formátumstringnek hívjuk, erre
egyaránt találunk példát a printf-ben és a scanf-ben is. Mostmár talán jobban értelmét nye-
ri ez a kifejezés, mintha ez a tetszoleges szöveg kiíratásnál hangzott volna el, ugyanis láthatjuk,
hogy a tetszoleges szövegeken kívül a %betu alakú jelölések felelnek a kimenet formátumáért.
Ezek a jelölések képviselik az I/O függvényeknek átadott változók típusait, innen fogja tudni
a programunk kiírás/beolvasás esetén, hogyha adtunk át változót a formátumstring után, akkor
hol szerepeljen a szövegben, és milyen típusként kell kiíratni.
• %d - int - egész szám
• %f - float - valós szám (7 tizedesjegy)
• %lf - longfloat(double) - valós szám (15 tizedesjegy)
• %c - char - karakter
Így már világos, hogy scanf és printf formai használata nagyjából azonos, ha változók
kiíratásáról vagy beolvasásáról beszélünk, azzal a különbséggel, hogy a scanf-nél a válto-
zónevek elé egy & jel kerül. Olyan sorrendben adjuk meg a formátumstringben a típusokat,
11
#include <stdio .h>
int main ( ) {
int a ; / / d e k l a r a c i ofloat b ; / / d e k l a r a c i odouble c ; / / d e k l a r a c i ochar d ; / / d e k l a r a c i o
scanf ("%d %f %lf %c" , &a , &b , &c , &d ) ; / / i n i c i a l i z a c i o b i l l e n t y u z e t r o l v a l o b e o l v a s a s s a l
/ *i n i c i a l i z a l a s k e z z e l
a = 1 4 ;b = 3 . 4 5 6 ;c = 9 . 3 2 1 4 1 2 2 1 4 ;d = ' r ' ;
* /printf ("a valtozo erteke: %d\nb valtozo erteke: %f\nc valtozo erteke: %lf\nd valtozo ←↩
erteke: %c\n" , a , b , c , d ) ;
return 0 ;}
2.5. ábra. Az ábrán található kódban deklarálunk 4 változót, majd inicializáljuk oket, ezutánkiírjuk a képernyore
a valtozo erteke : 33b valtozo erteke : 44 .320000c valtozo erteke : 98 .323232d valtozo erteke : c
2.6. ábra. A 2.5-es ábra kimenete 33, 44.32, 98.323232 és c input esetén
amilyen sorrendben azok a "" jel utáni, vesszovel elválasztott részekben következnek. Mindkét
függvény változó paraméterlistájú, ami annyit tesz, hogy akárhány változót és szöveget kiírat-
hatunk velük.
Ha a 2.5-ös ábrán található többsoros kommentbeli = jellel való értékadásokat hajtanánk
végre a scanf helyett, akkor persze a billentyuzetrol bevitt értékek helyett az elore deklarált
fix értékek íródnának a képernyore. Azt, hogy a változókat inicializáljuk-e, vagy beolvassuk
valahonnan (esetleg más, beviteltol függoen generálódó eredményeket adunk értékül nekik),
mindig a probléma szerkezete választja meg. Értelemszeruen a fix dolgok kiszámolására, fix
értékeket adunk meg, de ha azt akarjuk, hogy programunk sok-sok lehetséges adattal tudjon
számolni, akkor a változók értékeit a felhasználóra bízni.
A 2.7-es ábrán látható forráskódban egy char típusú változó értékét a felhasználó adja
meg billentyuzetrol. Ezután kiírjuk képernyore a változó értékét, ami ugye bármilyen ASCII-
12
#include <stdio .h>
int main ( ) {
char letter ;scanf ("%c" , &letter ) ;printf ("Az eredeti karakter:\n%c\n" ,letter ) ;letter = letter + 1 ;printf ("Az eredeti karakter utan kovetkezo karakter:\n%c\n" ,letter ) ;
return 0 ;}
2.7. ábra. Az ábrán egy karakter típusú változó értékének növelése és kiíratása látható
beli karakter lehet. Jön a csavar, ugyanis ha a változó értékét megnöveljük egyel (a következo
fejezet taglalja az aritmetikai muveleteket részletesen), ennek hatására a következo kiíratásban
az eredeti karakterre rákövetkezo ASCII-beli karakter íródik ki. Egyszeru tesztelni ezt (kis
vagy nagy) betukkel, ugyanis ezek az ASCII-ban abc sorrendben helyezkednek el, tehát betu
megadása esetén ugyanez a program az eredeti beture következo abc-beli karaktert írja ki má-
sodjára. Tehát J -> K, a->b, viszont Z és z után, valamint A és a elott nem betuk tartózkodnak
az ASCII-ban, hanem más karakterek!
Létezik egy másik változótípus, amit saját magunk definiálhatunk, ez az ún. enum. Olyan
esetekben alkalmazzuk ezt a típust, amikor egy változó értéke nem lehet túl sokféle, viszont
szeretnénk, ha nem kellene megjegyezni, hogy a feladat esetében mit jelent az 1, 2 vagy a 3.
Ekkor lehetoségünk van nevet adni ezeknek az értékeknek, s kvázi képeztünk egy nyelvi hidat
a feladat és annak absztrakciója között.
#include<stdio .h>
enum week{Hetfo = 1 , Kedd , Szerda , Csutortok , Pentek , Szombat , Vasarnap
} ;
int main ( ){
enum week day ;day = Szerda ;printf ("%d" ,day ) ;return 0 ;
}
2.8. ábra. Az ábrán az enum típus lényegére láthatunk egy példát
Az enum definiálása a main függvényen kívül történik enum név formátumban. A kulcs-
szavakhoz integer értékek tartoznak, a definiálatlan kulcsszavak az utolsó definiált kulcsszótól
13
számozódnak. Ha nem definiáltuk az elso tag értékét, akkor 0-tól számozódik egészen a legkö-
zelebbi definícióig. Amennyiben nem adunk a tagoknak értéket úgy 0-tól n-ig értékelodnek fel.
Definiálás után enum név változónév paranccsal hozhatjuk létre a változónkat, aminek
értékként a definícióban szereplo kulcsszavakat adhatjuk meg. Ezeknek integer értéke egyenlo
a definícióban megfeleltetett számokkal.
14
3. fejezet
Aritmetika és függvények
Ebben a fejezetben kicsit közelebb kerülünk a tényleges problémamegoldási módszerekhez. Az
elozo fejezetben megnéztük, hogyan tudunk változókat felvenni, amikkel majd egy adott prob-
léma elemeit fogjuk képviseltetni, tárgyaltuk ezek változatait, illetve azt, hogyan kell beolvasni
billentyuzetrol és kiíratni adatokat a képernyore.
Ha vannak változóink, akkor azokkal muveleteket is akarunk végezni. Az alapmuveleteket
az alábbiak szerint definiáljuk C-ben:
• szorzás - itt: *
• osztás - itt: /
• összeadás - itt: +
• kivonás - itt: -
• maradékos osztás - itt: %
Amint azt a 3.1-es példakódban látjuk, ha ilyen viszonylag egyszerubb muveleteket vég-
zünk, két lehetoségünk van. Az egyik, hogy a változókon elvégzett muveletsorozatot csak a
kiíratásban hajtom végre, így az adott eredmény csak kiíródni fog a képernyore, viszont nem
tárolódik el. Ez látható a nem kikommentezett printf-es sorokban. Ennek hátránya, hogy
komplexebb feladatok esetén szükségünk lehet egy muvelet eredményére a továbbiakban, tehát
ha a feladat megkívánja, akkor a 3.1-es ábrán látható kikommentezett részekben látható módon
eloször felveszünk egy új változót, aminek értékadásában végezzük el a muveletet, majd ezt a
változót íratjuk ki.
Fontos dolog az is, hogy mivel a C egy erosen típusos nyelv, ezért ha egy int változóba
felvesszük a 101/5 értéket, akkor nem valós számot kapunk, hanem a tizedesjegy eltunik s ilyen
15
#include <stdio .h>
int main ( ) {
int a = 9 8 ;int b = 2 ;
/ / i n t c = a+b ;printf ("%d\n" ,a+b ) ; / / p r i n t f ("%d " , c ) ;
/ / i n t c = a − b ;printf ("%d\n" ,a−b ) ; / / p r i n t f ("%d " , c ) ;
/ / i n t c = a * b ;printf ("%d\n" ,a*b ) ; / / p r i n t f ("%d " , c ) ;
/ / i n t c = a / b ;printf ("%d\n" ,a /b ) ; / / p r i n t f ("%d " , c ) ;
/ / i n t c = a % b ;printf ("%d\n" ,a%b ) ; / / p r i n t f ("%d " , c ) ;
return 0 ;}
3.1. ábra. Alapmuveletek elvégzése csak kiíratásban és egy új változóban is(kommentezett rész)
#include <stdio .h>
int main ( ) {
double num = 1 . 0 / 5 . 0 * 5 . 0 ;
printf ("%lf" , num ) ;
return 0 ;}
3.2. ábra. Könnyen elrontható kód
veszteség árán a muvelet integerré alakult. Ez komplexebb feladatoknál sok gondot okozhat,
mivel ha nem figyelmesen veszünk fel egy változót, és egy pontatlan értékkel dolgozunk to-
vább, akkor rossz eredményt fogunk kapni. Ezt kétféle módon tudjuk orvosolni, vagy minden
double változóval érintkezo változót double-ként veszünk fel, vagy típuskonverziót hasz-
nálunk.
Az elso pontra példa: próbáljuk ki, hogy a 3.2-es példakódban a 2.0-t 2-re, az num változó
típusát pedig int-re cseréljük, garantáltan nem fogunk helyes eredményt kapni, mivel elvésznek
a tizedesjegyek.
A típuskonverzió azt jelenti, hogy egy adott típusú változó konvertálható egy másik típusra
úgy, hogy a konvertálni kívánt típust a változó neve elé írjuk zárójelben. Például adott az int
var = 5, akkor az egész szám a float var2 = (float)var paranccsal konvertálható
16
valós számmá, azaz 5.0-vá. Az alábbi konverziók léteznek:
• char -> int
• int -> char
• int -> float/double
• float/double -> int (a tizedesjegyek eltunnek)
3.1. Precedenciasorrend
Az ún. precedenciasorrendet már középiskolában is ismertük, ami ugye a muveletek pl.: szor-
zás, osztás, összeadás, kivonás sorrendiségét jelenti. Ehhez tartja magát a C is, szóval csakúgy,
mint matematikában, oda kell figyelnünk a tetszoleges sorrendu muveleteknél a zárójelezésre.
Létezik egy precedenciatáblázat1, ami leírja, hogy zárójelezés nélkül milyen sorrendben érté-
kelodnek ki a muveletek. Ennek ismerete nem árt, de a zárójelezéssel mindig biztosra tudunk
menni.
#include <stdio .h>
int main ( ) {
float a = 1 . 0 / 4 . 2 5 * 3 . 1 1 ;float b = 1 . 0 / ( 4 . 2 5 * 3 . 1 1 ) ;printf ("%f\n" ,a ) ;printf ("%f\n" ,b ) ;
return 0 ;}
3.3. ábra. Példakód, ami bemutatja a zárójelezés fontosságát
Ha el akarjuk végezni az 1/(4.25*3.11) muveletsorozatot, akkor a példakódban az a változó
kiíratása esetében nem megfelelo eredményt kapunk, hiszen elsonek az 1.0/4.25 fog kiértéke-
lodni. A b változó eredménye viszont már a kívánt eredményt adja. Ha tehetjük, mindent
zárójelezzünk be, aminél fennállhat a gyanú egy kis tévedésre. Egy ilyen muvelethibára már
egy közepesen hosszú kódban sem kényelmes dolog ráakadni.
1http://www.onewayelectronics.hu/delete/c_precedencia.pdf
17
3.2. Változók értékeinek módosítása
Késobb nagyon sok olyan feladatunk lesz, ahol egy változó meglévo értékébol kiindulva kell
muveleteket végeznünk. Ha a feladat az, hogy növeljük egy változó értékét a háromszorosára,
akkor azt vajon hogyan csinálnánk? Nos, ilyenkor azzal a logikával állunk hozzá a muvelethez,
hogy a változó értéke legyen egyenlo a változóval és a rajta elvégzett muvelettel.
Tehát, ha a feladat az volna, hogy olvassunk be a felhasználótól egy tetszoleges integer
változóba értéket, s ennek nyomán növeljük meg a változó értékét 101-el, akkor miután beol-
vastuk tf az a változó értékét, azt mondjuk, hogy: a = a + 101. Tehát az új érték legyen
egyenlo a régivel + amennyivel megnövelem. Ugyanezt a hatást tudjuk elérni akkor, ha azt ír-
juk, hogy a += 101. A 3.4-es példakódban láthatjuk, hogyha növelni szeretnénk egy változó
értékén, akkor a változó + szám alak semmit nem ér, csupán behelyettesül a kódunkba
egy szám, de a változó értéke nem változik. Ellenben ha a fentebb leírt módszert alkalmazzuk,
megkapjuk a kívánt eredményt.
#include <stdio .h>
int main ( ) {
int var = 5 ;
var − 3 3 ; / / a v a r v a l t o z o e r t e k e nem modosul−27; / / e zze e r t e k e l o d i k k i az e l o z o s o r
printf ("%d\n" ,var ) ; / / k i i r
var −= 3 3 ; / / v a r e r t e k e n e k c s o k k e n t e s e 33−mal
printf ("%d\n" ,var ) ; / / k i i r
return 0 ;}
3.4. ábra. Példakód, ami bemutatja a változók értékeinek direkt változtatását
Természetesen ugyanezt megtehetjük az összes alapmuvelettel, amirol eddig már szó volt,
de azért tekintsük át még egyszer.
• a = a * b ugyanaz mint: a *= b
• a = a / b ugyanaz mint: a /= b
• a = a + b ugyanaz mint: a += b
• a = a - b ugyanaz mint: a -= b
• a = a % b ugyanaz mint: a %= b
18
Teljesen opcionális, hogy melyik formát alkalmazzuk, célszeru spórolni a kód terjedelmével
és ott alkalmazni rövidebb formákat, ahol csak tehetjük. Hasonló trükk létezik a változók 1-el
történo növelésére(inkrementálás) és 1-el történo csökkentésére (dekrementálás). Alapvetoen
ez ugye i = i + 1 illetve i = i - 1 ként nézne ki. Az elobb felsorolt rövidebb formák
mellett alkalmazhatjuk a i++ és i-- alakú postfix2 muveleteket is. Ezekkel ugyancsak ekvi-
valensek a ++i és a --i prefix3 kifejezések, amik abban különböznek a postfixtol, hogy ha
ilyen inkrementálást/dekrementálást adunk értékül egy változónak, akkor prefix esetén a válto-
zó értéke egybol a növelt/csökkentett érték lesz, postfix esetében pedig csak a következo sortól
kezdve.
#include <stdio .h>
int main ( ) {
double a = 107 ;/ / o s szu k e l az ' a ' v a l t o z o t 3−mala /= 3 . 0 ;printf ("%lf\n" ,a ) ;/ / s z o r o z z u k meg az ' a ' v a l t o z o t 5− t e la *= 5 . 0 ;printf ("%lf\n" ,a ) ;/ / von junk k i az ' a ' v a l t o z o b o l 200− a ta −= 2 0 0 . 0 ;printf ("%lf\n" ,a ) ;
int i = 5 ;int j ;j = i−−; / / j e r t e k e 5 i e r t e k e 5printf ("%d\n" ,j ) ; / / i g y h i a b a c s o k k e n t az i , j e r t e k e : 5j = −−i ; / / j e r t e k e 3 i e r t e k e 3printf ("%d\n" ,j ) ; / / j e r t e k e 3 i e r t e k e 3 , most c s o k k e n t !
return 0 ;}
3.5. ábra. Példakód, ami bemutatja a muveleteket és az inkrementálást, dekrementálást
A példakód elso felében a legeloször tárgyalt szimpla értékváltoztatásokat szemléltetem,
láthatjuk, hogy nagyon veszélyes dolog összekeverni a pre- és postfix operátorokat. A második
felében pedig nyomon követhetjük a pre- és postfix értékváltoztatás közötti különbséget.
2változónév után jelölt inkrementáció/dekrementáció3változónév elott jelölt inkrementáció/dekrementáció
19
3.3. Függvények
Ha már eleget gyakoroltunk, és írtunk sok muveletet végrehajtó programokat, akkor felmerül-
hetett a kérdés, hogy ha ezt a tendenciát folytatjuk, bonyolultabb kódok esetén hogyan lesz
átlátható a kód. Erre kínálnak nekünk megoldást a függvények. Alapvetoen a main függvény
- ami ugye mint azt tudjuk, a programunk belépési pontja - nem arra hivatott, hogy közvetlen
problémamegoldó kódrészeket tegyünk bele. Egy jól struktúrált program main függvénye to-
vábbi függvényeket hív, s csak azon fontos deklarációk, inicializációk szerepelnek a hívások
mellett, amik a feladat kiszolgálásához feltétlen szükséges, hogy ide kerüljenek.
Ha adott egy probléma, azt jellemzoen részproblémákra bontjuk, s ahelyett, hogy egymást
követoen a main függvénybe pakolnánk oket, minden részproblémára definiálunk egy függ-
vényt.
#include <stdio .h>
int sum (int var1 , int var2 ) { / / a fuggveny f e j l e c eint result = var1+var2 ; / / muve le treturn result ; / / v i s s z a t e r e s
}
int main ( ) {
int a , b ;scanf ("%d %d" , &a , &b ) ;printf ("%d\n" ,sum (a ,b ) ) ; / / a sum ( a , b )−v e l meghiv juk a f u g g v e n y t a−r a e s b−r e
return 0 ;}
3.6. ábra. Példakód, ami egy összegkiírató programot szemléltet függvény használatával
A példakódban jól látszik, hogy a függvénynek van fejléce, amiben megtalálható a neve:
sum, a paraméterlistája: int var1, int var2 és végül a visszatérési típusa: int. Ezek után
lezajlik valamiféle muvelet, számítás, s végül a return kulcsszóval átadunk egy, a visszatérési
típusnak megfelelo típusú változót. Jelen esetben láthatjuk, hogy int változóval térünk vissza
és int a függvény visszatérési típusa.
Na de hova térünk vissza? Láthatjuk, hogy a main függvényben az a és b változók beol-
vasása után integerként íratjuk ki a sum(a,b) kifejezést. Ezt azért tehetjük meg, mert amikor
a kiíratáshoz érünk, a beolvasott a és b változóval meghívódik a sum függvény. Ekkor var1
és var2 helyére behelyettesül a két beolvasott változó értéke, majd a függvénytörzsben levo
kifejezések elvégzik az általunk definiált muveletsort. Ezek után a return result visszaté-
résnek köszönhetoen végül a printf függvénybe a két változó összege (vagyis a result változó
20
értéke) helyettesül be. Amint a függvény visszatért oda, ahol meghívtuk, a kód végrehajtása on-
nan folytatódik tovább sorról sorra.
A függvénynév tetszoleges, célszeru úgy választani, hogy tükrözze a függvény által végzett
feladatot.
A paraméterlistában kiemelten fontos, hogy a függvény definiálásakor megadott paramé-
ternevek nem kell, hogy azonosak legyenek az átadott változók neveivel. Tehát attól, hogy a
main függvényben a és b változót olvastunk be billentyuzetrol és erre hívtuk meg a függvényt,
attól még a függvénydefinícióban var1 és var2-re kell hivatkozni. Ez azért van, mert egy függ-
vény újra meghívható akár másik ketto c és d változóra is a késobbiekben, és pont ezért jó,
hogy a var1,var2 általános leírást ad minden további változókra végzett muveletre, amikkel ezt
a függvényt késobb meghívjuk.
A main függvényben deklarált változókat úgy adjuk át paraméterként, hogy csak a válto-
zónevek kerülnek a függvény meghívásakor a zárójeles részébe vesszovel elválasztva, a típusuk
semmiképpen! Ugyanígy a függvény visszatérési típusa sem szerepel meghíváskor, csak a függ-
vénydefinícióban. Fontos, hogy jelen esetben a és b változó nem létezik a sum függvényben,
hiszen azok a main függvényben lettek deklarálva. Ez a függvények scope-tulajdonsága mi-
att van, deklarálhatunk azonos nevu változókat a sum függvényben és a main-ben, nem lesz
névütközés, mivel azok külön függvény scope-ban foglalnak helyet.
Természetesen a függvények bármilyen eddig és késobb tanult muveletsorozatot végrehajt-
hatnak, egy függvény hívhat több másik függvényt is. Elofordulhat azonban, hogy nincs szük-
ség arra, hogy értékkel térjünk vissza valahová, csupán nyom nélkül végre szeretnénk hajtani
egy utasítássorozatot, vagy éppen csak kiírni a képernyore annak eredményét. Erre használjuk
az ún. void visszatérési típust, ilyenkor ugyanis nincs visszatérési érték, így értelemszeruen a
return kulcsszóra sincs szükség.
A 3.7-es ábrán láthatjuk, hogy itt elég pusztán meghívni a függvényt, így is megtörténik
a kiíratás, hiszen a mulThree függvényben megírtuk. Természetesen a 3.6-os példakód is
kivitelezheto lett volna így, nem kellett volna a printf-be visszatérni, teljesen szabad moz-
gásterünk van a megvalósításban, egészen addig, amíg a programunk a feladatot hajtja végre.
Ami még nagyon fontos a függvények esetében, hogy a paraméterben átadott változók ér-
tékein hiába módosítunk a függvényben, azok nem fognak a visszatérést követoen megváltozni.
Mi még ugyanis csak érték szerinti paraméterátadást hajtunk végre, amikor is a paraméterben
átadott változóknak lemásolódnak az értékei, így a függvényünk tudja, milyen értékkel kap-
21
#include <stdio .h>
void mulThree (int x ) { / / fuggveny f e j l e c e , n i n c s v i s s z a t e r e s i e r t e k a vo id m i a t tprintf ("%d\n" ,x*3) ; / / k i i r o m a p a r a m e t e r u l k a p o t t v a l t o z o 3x o s a t
}
int main ( ) {
int value ;scanf ("%d" , &value ) ;mulThree (value ) ; / / meghivom a f u g g v e n y t a b e o l v a s o t t v a l t o z o m r a/ / k i fog e z u t a n i r o d n i a 3x osa
return 0 ;}
3.7. ábra. Példakód, egy programot szemléltet, ami egy tetszolegesen beolvasott integer számháromszorosát írja ki a képernyore
ta meg a változókat, de azokon úgy, hogy a visszatérést követoen megváltozzon az értékük,
módosítani nem tud.
Szóval ha egy függvényben végrehajtott számítás eredményét tovább szeretnénk vinni a
programban, akkor vissza kell az eredménnyel térnünk a program egy adott pontjára, és onnan-
tól már tovább is lehet vele dolgozni.
A továbbiakban célszeru úgy felépíteni a programjainkat, hogy függvényekkel részfelada-
tokra bontjuk oket, így sokkal tagoltabb, átlátható kódot kapunk. Bezárólag itt egy példaprog-
ram, ami lebegopontos visszatérést hajt végre, egy négyzet területét számolja ki. Ha a kikom-
mentezett utasításokat végrehajtaná a program, akkor láthatnánk az elozoekben tagolt állítások
igazságát, miszerint ha érték szerint adunk át egy változót, a függvényben végrehajtott muvele-
tek nem változtatnak a változó visszatérés utáni értékén.
#include <stdio .h>
double terulet (double a , double b / * p a r a m e t e r l i s t a * / ) {/ / a = a + 1 ;return a*b ; / / v i s s z a t e r e s az e redmennye l
}
int main ( ) {
double a , b ;scanf ("%lf %lf" , &a , &b ) ; / / a d a t o k b e o l v a s a s aprintf ("%lf" , terulet (a ,b ) ) ;/ / p r i n t f ("%d " , a ) ;return 0 ;
}
3.8. ábra. Példakód, amin szemléltetjük egy bevitt adatokból területetet számoló program for-ráskódját!
22
4. fejezet
Feltételes vezérlési szerkezetek
Mostmár értjük az adatok beolvasását, kiíratását, valamint a rajtuk elvégzett muveleteket is.
Azonban a bevitt adatok sokfélék lehetnek, ezért meg kell oldanunk, hogy programunk bizonyos
bevitelek esetén máshogy reagáljon, mint más esetekben.
Például, ha beolvassuk egy szoba homérsékletét és 19 fok alatt kell bekapcsoljon egy fu-
torendszer, akkor ha a homérsékletet képviselo változónk éppen 30 AKKOR semmit sem kell
tennie a programnak. Azonban HA 15 fok van, AKKOR be kell, hogy kapcsoljon a futés.
Természetesen az is megeshet, hogy egy változótól nem csak ilyen igen/nem tevékenységek
függnek, hanem konkrétan teljesen mást csinál a program egy adott utasításra, mint egy má-
sikra, például tételezzük fel, hogy az 1-es érték esetén pénzt akarunk betenni a bankba, a 2-es
értéknél pedig pénzt akarunk kivenni. Az egyszeruség kedvéért nézzünk egy egyszeru példa-
programot, ahol (mi vagyunk a szenzor) billentyuzetrol visszük be az adott homérsékletet, és a
programunk megmondja nekünk, hogy a futés be kell-e hogy kapcsoljon.
#include <stdio .h>
int main ( ) {
double degree ;scanf ("%lf" , °ree ) ;
if (degree < 19) { / / ha a b e o l v a s o t t v a l t o z o e r t e k k i s ebb , min t 19printf ("Futes bekapcs!\n" ) ;
}else{ / / e s ha nem ki sebb , hanem nagyobb v egyen lo , akkor :printf ("Eleg meleg van!\n" ) ;
}
return 0 ;}
4.1. ábra. Példakód, ami futésszabályzó muködését szemlélteti
23
Két primitív adattípus közötti reláció a következo lehet:
• == egyenlo
• != nem egyenlo
• < kisebb
• > nagyobb
• <= kisebb egyenlo
• >= nagyobb egyenlo
Ha az if zárójeles részében szereplo feltétel igaz, akkor történik meg a futes bekapcs ki-
írás, viszont ha nem, akkor az eleg meleg van feliratot kapjuk. Ezzel konkrétan két halmazra
osztottuk a változó lehetséges értékeitol függo muveleteket: 19-nél kisebb(if ág) és 19-nél na-
gyobb egyenlo (else ág). Ha az if feltételében szereplo kifejezés igaz, akkor végrehajtódik az
if blokkja, különben az else-é, de kizárólag az egyik! Fontos, hogy ezért itt is beszélhetünk
scope-okról, mivel ha egy if-ágban változót deklarálunk egy adott értékkel, akkor ugyanazt a
változót deklarálhatjuk az else-ágban is, nem lesz névütközés, hiszen egyszerre csak az egyik
ág teljesülhet, így a kétértelmuség nem merülhet fel(4.3-mas példakód).
Ebbol következik, hogy az elágazásokban deklarált változókat nem használhatjuk az elága-
záson kívül. Ha belegondolunk ez logikus, hiszen csak bizonyos feltétel hatására hajtódik végre
az adott blokk, amíg nem térünk rá az elágazásra, addig végre sem hajtódnak az abban szereplo
utasítások.
A fordított eset azonban már érdekesebb, ugyanis egy elágazás látja és használhatja a rajta
kívül létrehozott változókat.
Ha van egy var változóm az elágazáson kívül, és létrehozok egy szintén var nevu, a
külsovel azonos típusú változót, akkor ugye az elobb leírtak alapján nem lehet névütközés.
Ilyenkor az elágazásban deklarált változó overloadolja az elágazáson kívüli változót.
Amíg a 4.2-es példakódban benthagyjuk a kikommentezett részt, addig a külso (5) értéket
fogjuk a képernyore írni, viszont ha létrehozzuk a változót az elágazáson belül, akkor már az új
(20) értéket látjuk. Ha nem történik overload, akkor a külso változó értékét változtathatjuk
a feltételben!
Az else használata nem kötelezo, tehát ha azt szeretnénk, hogy egy bizonyos esetben tör-
ténjen valami, de amennyiben az nem teljesül, ne történjen semmi, akkor az else ágat egysze-
ruen nem írjuk ki. Viszonylag sok esetben lehet ilyen.
24
#include <stdio .h>
int main ( ) {
int a = 5 ;int b = 2 2 ;if (b == 22) {
/ / i n t a = 2 0 ;printf ("%d\n" ,a ) ;
}return 0 ;
}
4.2. ábra. Példakód, ami az elágazásban történo overloadot szemlélteti
#include <stdio .h>
int main ( ) {
int decide ;scanf ("%d" , &decide ) ;
if (decide > 0) { / / ha a b e o l v a s o t t v a l t o z o e r t e k nagyobb , min t 0int number = 4213413421;printf ("%d" , number ) ;
}else{ / / ha a b e o l v a s o t t v a l t o z o e r t e k k i s e b b v e g y e n l o min t 0int number = 53515123421;printf ("%d" , number ) ;
}
/ / p r i n t f ("%d " , number ) ; n i n c s i l y e n v a l t o z o ebben a scope−ban
return 0 ;}
4.3. ábra. Példakód, ami az elágazás scope-ot szemlélteti
A 4.3-as ábrán láthatjuk viszont, hogy a main scope-jában nem tudjuk kiírni a number
változó értékét, hiszen az ott nem létezik.
Nagyon fontos, hogy ha olyan feltételt akarunk szabni egy változóra, hogy az egy interval-
lumba essen(pl.: a változó értéke 18 és 20 között van), akkor a következo kifejezés helytelen:
if(18 <= degree <= 20). Tudni kell ugyanis, hogy ha egy feltétel, (mondjuk degree = 20 esetén
a 18 <= degree) igazra értékelodik ki, akkor a kifejezés helyére egy 1-es (vagyis igaz) kerül, te-
hát if(1) lesz, ekkor hajtódik végre a feltételhez tartozó utasítássorozat. Ellenkezo esetben if(0)
lesz, ilyenkor ugye az else ágra ugrunk. Azonban ha ott lábatlankodik egy <=20 is, akkor azt
fogjuk kapni, hogy 1 <= 20 tehát minden esetben végre fog hajtódni az if ág, hiszen ha hamis
a feltétel, a 0 akkor is kisebb, mint 20. Akkor mi is lenne a megoldás? A feltételeket egyenként
kell kikötni, azaz a fenti rossz példa helyett két feltételt kell létrehozni és ezeket szükség szerint
éselni vagy vagyolni:
25
• && - és
• || - vagy
Így a if(deree >= 18 && degree <= 20) feltétel jelen esetben a 18 19 20 in-
tervallumot adja igaznak, tehát készen vagyunk. Érdemes elgondolkodni a logikai operátorok
szerepein. Ha az elozo ést vagyra cseréljük, akkor a if(degree >= 18 || degree <=
20) feltétel minden számra igazat ad. Nézzünk példát most ne számokra, hanem mondjuk
karakterekre. Írjunk egy olyan programot, ami felismer egy nagybetut, ha azt bevisszük a bil-
lentyuzetrol.
#include <stdio .h>
int main ( ) {
char letter ;scanf ("%c" , &letter ) ;
if (letter >= 'A' && letter <= 'Z' ) { / / ha a b e o l v a s o t t v a l t o z o e r t e k A es Z k o z o t t van az ←↩ASCII−ban :
printf ("Nagybetu!\n" ) ;}else{ / / e g y e b k e n t :printf ("Nem nagybetu!\n" ) ;
}
return 0 ;}
4.4. ábra. Példakód, ami billentyuzetrol feliseri a nagybetuket
Mint azt látjuk a 4.3-as ábrán, az if-else elágazással most is két halmazra szedtük a lehetsé-
ges értékeket: ’A’ és ’Z’ közötti és bármi más.
4.1. Else if feltételek
Vannak esetek, amikor nem elég, ha csak kétfelé ágaztatjuk a program áramlását, sokszor nem
ilyen fekete-fehér egy probléma. Olyan feladat esetében, ahol egy adott homérsékletu vízrol kell
megállapítani, hogy milyen halmazállapotú, például 3 lehetséges eset van (jég, cseppfolyós,
goz), szóval jelenlegi tudásunkkal ez nem vagy csak körülményesen kivitelezheto. Azonban
most tekintsük a 4.4-es példakódot, amiben ez a probléma egy csapásra megoldódik!
Fontos, hogy amíg egy if-else feltételtípusban if-bol és else-bol is egyaránt csak 1 lehet,
addig else if(...)-bol bármennyi lehet egy elágazásban, így bármennyi esetet lefedhe-
tünk. Természetesen a scope-ok itt is érvényben vannak, és a sok feltétel közül itt is csak egy
26
#include <stdio .h>
int main ( ) {
double degree ;scanf ("%lf" , °ree ) ;if (degree >= 100) { / / ha a b e o l v a s o t t e r t e k nagyobb vagy e g y e n l o min t 100
printf ("Goz!\n" ) ;}else if (degree > 0 && degree < 100) { / / ha a b e o l v a s o t t e r t e k 0 es 100 k o z o t t vanprintf ("Viz!\n" ) ;
}else{ / / e g y e b k e n t : a zaz ha a b e o l v a s o t t e r t e k k i s ebb , min t 0 :printf ("Jeg!\n" ) ;
}
return 0 ;}
4.5. ábra. Példakód, amivel megállapítjuk a vízrol, hogy milyen halmazállapotú
fog teljesülni. Amennyiben egy esetre több feltétel is igaz lesz a definiáltak közül, úgy az elso
elágazásra térünk rá, ami igaz. Ha else if-et alkalmazunk, az else akkor is elhanyagolha-
tó, amennyiben a program helyessége szempontjából is az.
4.2. Switch-case
Olyan helyzetek is lehetnek, amikor sok feltételt kell lekezelni, amik viszonylag egyszeruek.
Ha mondjuk az ABC elso 5 betujét kellene felismerni, akkor kellene 1 if, 4 else if és 1
else. Ez túlságosan sok sort használna fel a forráskódunkból, kevésbé lenne olvasható és át-
látható. Az ilyen problémák megoldására létezik az ún. switch feltételes vezérlési szerkezet.
Fontos azonban, hogy lebegopontos feltételeket és összetett feltételeket nem tudunk velük ke-
zelni, szóval tényleg csak egyszeru és sok feltétel esetében használatosak. A fentebb említett
ABC-s feladatot a 4.5-ös példakód szemlélteti.
Amint látjuk, itt a változót, amit vizsgálunk, elég egyszer megadni, így már egyszerubb,
mintha sok else if-et írnánk. A case utasítások után írjuk azokat az eseteket, amelyek
teljesülésénél akarjuk, hogy bizonyos utasítások végrehajtódjanak. Tehát ha a switch(..)-
ben szerepel egy változó, és adott egy case ’A’, akkor ez ekvivalens egy if(valtozo == ’A’)
kifejezéssel. Ezek után rendszerint kettospontot rakunk, majd megírjuk az utasítást/utasításso-
rozatot és rakunk egy breaket. A break használata nélkül ugyanis továbbhaladnánk a többi
caseen is, amíg nem lesz az egyikben egy break, ez a switch sajátosságaihoz tartozik. Csak-
úgy, mint az else if feltételes vezérlési szerkezetnél, itt is az az igaz ág fog végrehajtódni,
amelyik sorrendben eloször lesz igaz.
27
#include <stdio .h>
int main ( ) {
char grade ;scanf ("%c" , &grade ) ; / / b e o l v a s s u k k a r a k t e r k e n t a g r a d e v a l t o z o t
switch (grade ) { / / ha a g r a d e b e o l v a s o t t e r t e k ecase 'A' : / / A akkor :printf ("A betu!\n" ) ;break ;
case 'B' : / / B akkor :printf ("B betu!\n" ) ;break ;
case 'C' : / / C akkor :printf ("C betu!\n" ) ;break ;
case 'D' : / / D akkor :printf ("D betu!\n" ) ;break ;
case 'F' : / / F akkor :printf ("F betu!\n" ) ;break ;
default : / / Egyebkent :printf ("A betu nem tartozik az ABC elso 5 betuje koze!\n" ) ;
}
return 0 ;}
4.6. ábra. Példakód, amin egy egyszeru switch-case szerkezet található
Fontos, hogy felismerjük a feladatunk elvégzése során, melyik formában kell az elágazáso-
kat alkalmazni, ez csak és kizárólag a mi megítélésünktol függ. Programozásban nem feltétlenül
létezik rossz választás ilyen téren, maximum kevésbé hatékony.
4.3. Feltétel a feltételben
Spinoff: Bár ez magától értetodik, de nem csak a kiíratások, és egyéb kisebb muveletek rej-
tozhetnek külön-külön elágazásokban, hanem függvényhívások is (sot, nagyobb programoknál
foleg azok). A függvények, mint tudjuk, pedig meghívhatnak más függvényeket is, amikbol
az következik, hogy teljesen más területeken halad a program egyik esetben, mint a másikban.
A programok eszerinti haladási irányát áramlásnak hívhatjuk, hiszen ezeken a függvényeken
közlekedik a programunk futás során.
Bár innentol már ez is magától értetodik, de egy elágazásban bármi lehet, ha pedig bármi
lehet, akkor mégtöbb elágazás is. Mikor van erre szükség? Ezt megint csak feladata válogat-
ja. Ha absztraktan kellene leírni, akkor azt mondanám, hogy egyfajta filterezés (szurés) szeru
esetekben kell, hogy feltétel szerepeljen egy feltételben.
28
Gondoljunk bele, ha három int változóról kell eldönteni, hogy melyikojük a legnagyobb
(egyenloség esetén nem kell variálni, bármelyiket írhatjuk).
#include <stdio .h>
int main ( ) {
int a , b , c ;scanf ("%d %d %d" , &a , &b , &c ) ;if (a < b ) { / / ha az a k i sebb , min t b , akkor l e h e t hogy b a legnagyobb
if (b > c ) { / / ha b nagyobb , min t c akkor b a legnagyobbprintf ("%d a legnagyobb\n" ,b ) ;
}else{ / / ha c nagyobb , akkor c a l egnagyobbprintf ("%d a legnagyobb\n" ,c ) ;
}}else{ / / ha az a nagyobb , min t b , akkor l e h e t h a a l egnagyobbif (c > a ) { / / ha c nagyobb , min t a , akkor c a l egnagyobbprintf ("%d a legnagyobb\n" ,c ) ;
}else{ / / amugy meg aprintf ("%d a legnagyobb\n" ,a ) ;
}}
return 0 ;}
4.7. ábra. Példakód, amin megállapítjuk, hogy három szám közül melyik a legnagyobb
Az a < b feltételben megszurtük, hogy a kisebb-e, mint b, amennyiben nem, akkor tovább
vizsgáljuk b és c viszonyát. Ellenkezo esetben a és c viszonyát kell vizsgálnunk tovább. Az
eddigi példákat szánt szándékkal írom a main függvénybe, hogy ne okozzak kavarodást, de
lassan szokjuk meg, hogy függvényeket használunk feladatok végrehajtására.
#include <stdio .h>
void parosE (int var ) {if (var % 2 == 0) { / / ha a p a r a m e t e r u l a t a d o t t v a l t o z o 0 maradeko t ad 2−v e l o s z t v a , azaz ha←↩
p a r o s :printf ("Paros!\n" ) ;
}else{ / / ha p a r a t l a n , akkor :printf ("Paratlan!\n" ) ;
}}
int main ( ) {
int a ;scanf ("%d" , &a ) ;parosE (a ) ; / / a t a d j u k a fgv−nek az a v a l t o z o t
return 0 ;}
4.8. ábra. Példakód, amin függvény használatával látjuk be egy billentyuzetrol bekért számról,hogy páros-e
29
5. fejezet
Ciklusok
Az elozo fejezetekben megtanultunk különbözo adattípusokat kezelni, beolvasni, kiírni, kötni
hozzájuk feltételesen bizonyos eseményeket a program futását tekintve, azonban ez így ko-
rántsem teljes. Jelenlegi tudásunkkal számos problémát nem tudunk még megoldani, hiszen
hiányzik a muveletek ismétlodése, egymás utáni lépések elkövetése sorozatszeruen. A ciklusok
pontosan ezt a célt szolgálják, ahogy a nevük is mutatja, ismételnek egy muveletet, amíg vala-
mi be nem következik. Róluk fog szólni ez a fejezet. Három alapveto ciklusfajta bemutatásra
fog kerülni. Elöljáróban annyit, hogy csak úgy, mint a különbözo feltételes vezérlési szerkeze-
teknél, itt is meg van, hogy milyen célra melyik ciklusfajtát "illik" használni, azonban kódolás
során a mi feladatunk ezek megválasztása, s ha nem jól választunk majd esetleg, közvetlen
probléma nem mindig következik belole.
De pontosan mire is gondolhatok ha azt mondom: ciklus? Nos, ahogy már leírtam, muvele-
tek ismétlésére egy adott pontig. Ez lehet például olyan muvelet, mint hogy: Írd ki a számokat
1-100-ig. Ez ugye jelenlegi tudásunkkal nettó 100 sor lenne. Ciklust igénylo feladat még: Ad-
dig olvass be billentyuzetrol egy számot, amíg az nem osztható 11-gyel. De éppen egy elozo
feladattal élve ez is egy ciklussal megoldható feladat: Olvasd be mindig, hány fok van, és ha
kell, kapcsold be a futést, ezt csináld 1 napig. Megfigyelhetjük, hogy mindegyik feladatnál van
egy pont, ameddig ismételni kell egy adott muveletsort.
5.1. For-ciklus
Mint minden további ciklusfajtánál, szükségünk van egy ciklusváltozóra. Ennek kell, hogy
legyen kezdoértéke, ehhez a változóhoz(vagy esetenként változókhoz) kötjük a bennmaradási
30
feltételt, s kell egy muvelet, ami minden ciklusba végrehajtódik, majd késobb a bennmaradási
feltétel elromlásához vezeti egyszer a ciklust, amikor is az véget ér. Ezt úgy képzeljük el, hogy
ha 0 a ciklusváltozónk kezdoértéke, a bennmaradási feltétel az, hogy ez legyen kisebb egyenlo,
mint 100, és a ciklusváltozót minden lépésben növelem 1-el, akkor 0, 1, ...., 100 számsorozat
keletkezik. Ha 100-tól kellene visszaszámolni 0-ig, akkor -1 lépésközzel 100, 99, 98, .., 0
számtani sorozatot kapom. De nézzünk is egy példakódot erre.
#include <stdio .h>
int main ( ) {
int i ;for (i = 0 ; i < 101 ; i++) { / / 0− t o l i ndu lunk , e g y e s e v e l l epkedunk 100− i g
printf ("%d\n" ,i ) ;}for (i = 100 ; i >= 0 ; i−−){ / / 100− t o l i ndu lunk , e g y e s e v e l l epkedunk v i s s z a 0− i g
printf ("%d\n" ,i ) ;}
return 0 ;}
5.1. ábra. Példakód, amin for-ciklussal írjuk ki oda-vissza a [0;100] intervallumot
A ciklusváltozó deklarációja után a fejlécben levo kifejezések a következo sorrendben kell,
hogy kövessék egymást: (ciklusváltozó kezdoértéke; bennmaradási feltétel; lépésköz). Láthat-
juk, hogyha 1-el akarjuk a ciklusváltozó értékét negatív vagy pozitív irányba változtatni, akkor
i++ vagy i-- használatához folyamodtunk. Nagyobb lépésközök esetén, például 5, i +=
5 vagy i -= 5 lenne a megfelelo muvelet. A bennmaradási feltételt úgy olvassuk ki, hogy
addig hajtsd végre a ciklusmagot, AMÍG IGAZ, hogy i < 101. Fontos, hogy a ciklusok
bennmaradási feltételeiben levo relációk azonosak az if-nél említettekkel! A { } jelek kö-
zött helyezkedik el az ún. ciklusmag, ami egy tetszoleges utasítássorozatot tartalmaz, szerkezete
ismeros lehet a feltételes vezérlési szerkezetekbol.
A ciklusokon belülre is kerülhet függvényhívás és feltétel is, szóval bármi, amit eddig tanul-
tunk. Fontos, hogy scope téren itt is igaz, ami a feltételes vezérlési szerkezeteknél, a cikluson
belül deklarált változók cikluson kívül nem látszanak, viszont a kívül deklarált változók látsza-
nak a ciklusban.
A késobbi feladatokban látni fogjuk, hogy a ciklusok kulcsfontosságú szerepet töltenek be
az algoritmusokban1, ezért csak úgy mint minden eddigi témát, fontos, hogy hibátlanul elsajá-
títsuk muködésüket és szerepüket. Most, hogy már ismerjük a for-ciklust, például meg tudjuk1Egy probléma megoldásához szükséges lépések sorozata
31
mondani, hogy hány 11-gyel osztható szám létezik [1 és 1000] között.
#include <stdio .h>
int main ( ) {
int i ;int sum = 0 ; / / v a l t o z o l e t r e h o z a s a s z a m l a l a s r a
for (i = 1 ; i < 1001 ; i++) { / / e g y e s e v e l l epkedunk 1− t o l 1000− i gif (i % 11 == 0) { / / ha t a l a l u n k o l y a n szamot , ami maradek n e l k u l o s z t h a t o 11−g y e l :sum++; / / a s z a m l a l o v a l t o z o n k a t e g y e l n o v e l j u k
}}printf ("%d" ,sum ) ;
return 0 ;}
5.2. ábra. Példakód, amivel megszámláljuk, hány 11-el osztható szám van [1;1000] között
Az 5.2-es példakódban létrehoztunk egy sum változót, aminek kezdoértéke 0, ebben a vál-
tozóban fogjuk számlálni a 11-gyel osztható számokat. Mivel a cikluson kívül deklaráltuk,
ezért a cikluson belül látható. Tehát nincs más dolgunk, mint futtatni a ciklusunkkal egy 1-tol
1000-ig tartó számsort, és egy olyan feltételt tenni, hogyha a szám 11-gyel osztható, akkor a
sum változónk értéke növekedjen 1-el. Végül pedig kiírattuk képernyore a változónkat, tehát
így megkaptuk az eredményt. Az i értéke a for-ciklus után 1001 lesz, mert akkor lépünk ki a
for-ciklusból, ha az i már nem kisebb, mint 1001, tehát a feltétel nem teljesült.
Gondolom mostanra világos, hogy egy for-ciklus bizonyos paraméterek mellett hányszor
fog lefutni, azonban még utoljára gondoljuk végig, lépésrol lépésre.
• Az i változónk kezdoértéke 1, ami kisebb, mint 1001 így végrehajtódik a ciklusmag (a
kezdeteknél történo feltételvizsgálat miatt hívjuk a for-ciklust elöl tesztelo ciklusnak)
• Amikor elértünk a for } jeléhez, akkor következik be az i betu növelése
• Majd ezután visszatérünk a for(...;...;...) fejléchez.
• Itt ellenorizzük, hogy igaz-e még az i-re tett bennmaradási feltétel
• Ha igen, akkor elölrol kezdodik a ciklusmag
• Ha nem, akkor ugrunk a for-ciklus } jele után következo kódrészhez.
Tehát a ciklusmagok újra és újra végrehajtódnak. A ciklusmag egy adott végrehajtódást
iterációnak nevezzük, tehát amikor legeloször hajtódik végre a ciklusban levo kód, akkor az az
elso iteráció és így tovább...
32
Most már meg tudunk oldani olyan feladatokat is, mint a maximum érték megállapítása bil-
lentyuzetrol bevitt számsorozat tagjai közül. Ennek kivitelezését az 5.3-as ábrán olvashatjuk.
Ez talán az eddig végrehajtott legalgoritmikusabb feladat, tehát ilyenkor nem árt elvonatkoz-
tatni attól, hogy egy számunkra új dologról (programozás) van szó, és csak magát a problémát
szemlélni. Elso lépésnek tegyük meg az elso bevitt értéket maximumnak. Errol gondoskodik
nekünk az if(i == 0) feltétel, amiben is ha az i = 0-s iterációban járunk, megtesszük
maximumnak az elso értéket. Ez egyszer fog lefutni és utána soha, mivel az i többször már
nem lesz egyenlo 0-val. Ezek után már csak azt kell figyelni, hogy a 10 beolvasott szám közül
lesz-e ennél nagyobb. Ezt követoen minden iterációban csak a második feltételnek van esélye
igazra kiértékelodni, ami annyit tesz, hogyha nagyobb az általunk bevitt szám, mint az eloször
beolvasott/az eddigi maximum akkor megtesszük azt a max változónk új értékének.
#include <stdio .h>
int main ( ) {int i ;int max , value ;for (i = 0 ; i < 1 0 ; i++) {
scanf ("%d" , &value ) ; / / minden i t e r a c i o e j e l e n b e o l v a s u n k egy e r t e k e t a va lue−baif (i == 0) { / / ha az e l s o i t e r a c i o b a n vagyunk , a max e r t e k e az e l s o b e o l v a s o t t v a l t o z omax = value ;
}if (value > max ) { / / ha a b e o l v a s o t t e r t e k nagyobb , min t az e l o z o maxmax = value ; / / akkor az a max
}}printf ("%d\n" , max ) ;
return 0 ;}
5.3. ábra. Példakód, a maximum értéket határozzuk meg 10 db beolvasott szám közül
5.2. Ciklus a ciklusban
Mivel egy ciklusban bármilyen kódrészlet szerepelhet, így egy másik ciklus is. Ilyenkor a külso
ciklus egy iterációjában le fog futni a belso ciklus összes iterációja. Példának okáért, ha a külso
ciklus egy 4 iterációs for-ciklus, a belso pedig 5, akkor 4*5 iterációról beszélhetünk összesen.
Láthatjuk, hogy az 5.4-es ábrán egy for-ciklust ágyaztunk a másikba. A külso for-ciklus
elso iterációjában az i végig 1, viszont kiíródik az 1 * 1, 1 * 2, 1 * 3, ..., 1 *
10 tabulátorral elválasztva, mivel a belso for-ciklus j változója elmegy egészen 10-ig. Ezután
persze véget ér a belso ciklus, kiíródik egy sortörés, hogy majd ha a következo iterációban (i =
33
#include <stdio .h>
int main ( ) {
int i , j ;
for (i = 1 ; i <= 1 0 ; i++) {for (j = 1 ; j <= 1 0 ; j++) {printf ("%d\t" , i * j ) ;
}printf ("\n" ) ;
}
return 0 ;}
5.4. ábra. Példakód, ami kiírja a képernyore 10-ig a szorzótáblát
1 2 3 4 5 6 7 8 9 102 4 6 8 10 12 14 16 18 203 6 9 12 15 18 21 24 27 304 8 12 16 20 24 28 32 36 405 10 15 20 25 30 35 40 45 506 12 18 24 30 36 42 48 54 607 14 21 28 35 42 49 56 63 708 16 24 32 40 48 56 64 72 809 18 27 36 45 54 63 72 81 9010 20 30 40 50 60 70 80 90 100
5.5. ábra. Az 5.4-es ábrán található forráskód által generált kimenet!
2) végigszorozzuk j-vel az i-t, akkor a 2 * 1, 2 * 2, 2 * 3, .. 2 * 10 új sorba
kerüljön és így tovább
Bármelyik fajta késobb következo ciklusba is lehet ágyazni bármilyen másik ciklust bár-
mennyiszer, ezeknek semmi trükkje nincsen, a továbbiakban felesleges volna leírni egyenként.
Természetesen csak akkor tegyünk ilyeneket, ha a feladat végrehajtása szempontjából szüksé-
ges.
5.3. While-ciklus
Ha kelloen megbarátkoztunk a for-ciklussal, akkor ez sem fog nehézséget okozni. A while-
ciklus is a kezdoérték - bennmaradási feltétel - lépésköz elven alapszik, csupán a szintaktikája
más, s ebbol kifolyólag vannak helyzetek, ahol logikusabb a használata. Például ha tízes szám-
rendszerrol váltunk át egy számot kettes számrendszerbe, akkor nem tudjuk pontosan, hány
lépésbol fogjuk megkapni a kívánt eredményt (hiszen ez a bemenettol függ), s amikor ezt el-
mondhatjuk egy feladatról, általában while-t használunk.
34
#include <stdio .h>
void decToBin (int num ) {
while (num != 0 ) { / / a d d i g megyunk , amig a num 0 nem l e s zprintf ("%d" , num % 2) ; / / k i i r a t j u k a num 2−v e l a d o t t maradeka t : 1 v 0num /= 2 ; / / majd a num e r t e k e t m e g v a l t o z t a t o m num/2− re , i g y e l o b b vagy u tobb 0− t kapok
}}
int main ( ) {
int num ;scanf ("%d" , &num ) ;decToBin (num ) ;
return 0 ;}
5.6. ábra. Példakód, amivel megvalósítjuk a kettes számrendszerbe való átváltást tízes szám-rendszerbol
Bemenet : 12Kimenet : 0011Olvasd : 1100
Bemenet : 18Kimenet : 01001Olvasd : 10010
Mivel még nem tudjuk a módját, hogyan kellene eltárolni a bináris számjegyeket, és vissza-
felé kiíratni, így most csak olvassuk a számjegyeket visszafelé (mintha papíron végeztük volna
el a muveletet) és megkapjuk az eredményt.
Láthatjuk, hogy itt a while kulcsszó zárójeles részébe kerül a bennmaradási feltétel, vi-
szont a ciklusváltozónak már értékkel kell rendelkeznie, mire elérünk a while soráig. Ez azért
van, mert a kezdoérték megadása a ciklusváltozónak itt nincs a fejlécbe építve szintaktikai-
lag. Jelen esetben ez az érték a billentyuzetrol való beolvasással van biztosítva. Egy másik
különbség, hogy míg for-ciklus esetén a fejlécben változtattuk a ciklusváltozó értékét, addig itt
ténylegesen bele kell írni a ciklusba. Ha csak nem szükséges máshogy csinálni, ezt a ciklusmag
utolsó sorában tegyük meg. Természetesen minden feladatban, ahol for-ciklust használunk, azt
felcserélhetnénk while-lal, és fordítva.
A for- és while-ciklust is elöl tesztelo ciklusoknak hívjuk, ugyanis a ciklusváltozó kezdoér-
tékére adott feltétel már az elso iteráció elott kiértékelodik.
Ebbol az következik, hogyha ciklusunk feltétele while(i > 2) és az i már a ciklus elott
a 1 értéket vette fel, akkor a ciklusunknak egy iterációja sem fog lefutni.
Egy másik eset lehet, amikor olyan feltételt adunk meg, ami a kezdoérték és a ciklus ál-
35
tal elvégzett muveletek tekintetében már sohasem teljesülhet. Ekkor végtelen ciklusba esünk.
Például, ha a kezdoértékünk 2, a feltételünk pedig while(i < 3), a ciklusmag pedig csökkenti a
ciklusváltozót mindig 1-el, akkor egyre csak távolodni fogunk a céltól, és a programunk sosem
fog véget érni. Ilyenkor kézzel kell bezárni a programot, hiszen az magától nem zárul be.
Az 5.7-es példakódban látható egy-egy példa arra, amikor nem kerülünk be a ciklusba,
valamint arra, amikor sohasem kerülünk ki belole.
#include <stdio .h>
int main ( ) {
int i = 5 ;int j = 3 ;
while (i < 4) {printf ("Ez sosem irodik ki!\n" ) ;i++;
}
while (j != 4 ) {printf ("Addig irodik, amig be nem zarod az ablakot!\n" ) ;j−−;
}
return 0 ;}
5.7. ábra. Példakód, mikor nem lépünk be a ciklusba, és mikor nem lépünk ki soha
Természetesen ezek for-ciklus esetén is elofordulhatnak, sot a most következo do-while cik-
lus esetén is. Az 5.7-es példakódnál egyszerubb eset, ha csak simán elhagyjuk a ciklusváltozó
változtatását garantáló kifejezést a kódból :). Az i változó már alapvetoen nagyobb, mint 4,
szóval a ciklus el sem fog kezdodni.
A j változó már sosem lehet 4, hiszen a változó alapértéke 3, a ciklus pedig ezt csökkenti.
5.4. Do-while ciklus
Azért volt fontos az elozo 2 ciklus esetén megjegyezni, hogy elöl teszteloek, mert a do-while
ciklus hátul tesztelo. Ez azt jelenti, hogy egyszer mindenképpen lefut a ciklusmag, s a benn-
maradási feltétel az elso iteráció után hajtódik végre. Ez például olyankor jöhet jól, ha bevitt
eredményhez akarjuk kötni a program futását. Ilyen eset, ha a felhasználótól K és Z közé eso
betuket várunk, és le kell ellenoriznünk, hogy valóban azt adja-e meg.
Láthatjuk, hogy szintaktikáját tekintve talán ez a kakukktojás a három eddig tanult ciklus-
fajta közül. Felül csak egy do található, s ahogy már említettem, a feltétel a ciklusmag után
36
#include <stdio .h>
int main ( ) {
char letter ;do{
scanf ("%c" , &letter ) ;
}while (letter < 'K' | | letter > 'Z' ) ;
return 0 ;}
5.8. ábra. Példakód feltételes beolvasásra
értékelodik ki, ezért van, hogy szerkezetileg is a kódrészlet végén helyezkedik el a fejléc, ami
után fontos utána a ; használata.
Az 5.8-as példakódban a feltétel annyit jelent, hogy addig kérjünk be adatot amíg az rossz,
tehát kisebb, mint K vagy nagyobb, mint Z. Ha az adat helyes, a feltétel nem teljesül, vagyis
nem ismétlodik meg még egyszer a ciklusmag.
#include <stdio .h>
void fibo (int num ) {int first = 0 ; / / e l s o v a l t o z oint second = 1 ; / / masodik v a l t o z oint i = first + second ; / / ebbe g e n e r a l j u k a k o v e t k e z o tdo{
first = second ; / / e l s o b o l masodik l e s zsecond = i ; / / masod ikbo l az e l o z o harmadiki = first + second ; / / a h a r m a d i k b o l p e d i g az e l o z o 2 o s s z e s e n
}while (i < num ) ; / / ha a g e n e r a l t szamaink mar nem ki sebbek , min t ami t b e i r t u n k v i z s g a l a t r a ←↩, akkor :
/ / ha nagyobb : nem f i b o/ / ha pon t a n n y i : f i b o
if (i == num | | num == 1 | | num == 0) {printf ("A bevitt szam fibonacci szam!\n" ) ;
}else{printf ("A bevitt szam NEM fibonacci szam!\n" ) ;
}
}
int main ( ) {
int num ;scanf ("%d" , &num ) ;fibo (num ) ; / / f i b o fuggveny meghivasa a b e o l v a s o t t num v a l t o z o r a
return 0 ;}
5.9. ábra. Példakód, amin egy Fibonacci-számokat felismero program forráskódja látható
Az 5.9-es ábrán Fibonacci-számokat ismer fel a programunk. A Fibonacci-sorozat elso két
37
eleme a 0 és az 1, majd a sorozat további számai mindig az adott elemet megelozo 2 tagból
képzodnek. Tehát: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ... . Programunkban létrehoztunk egy first
és egy second változót 0 és 1 alapértelmezett értékkel, az aktuális harmadikat, pedig mindig
az i változónkba fogjuk kiszámolni. Egészen addig fogjuk a számokat ilyen módon számolni,
amíg azok kisebbek, mint a felhasználó által beírt szám. Ha nem kisebb, akkor nagyobb, vagy
egyenlo, ha egyenlo, akkor szerencsénk volt, mert az általunk beírt szám Fibonacci-szám, ha
pedig nagyobb, akkor nem az. A fibo függvény végén azért kell a feltételben külön tesztelni
az 1-et és a 0-t, mivel a ciklus alapvetoen már a 4. Fibonacci-számra kezdi el nézni a sorozatot,
így a 0, 1, 1 nem esik bele a vizsgált tartományba.
5.5. Break, continue
Amikor lépéseket teszünk egymás után ciklusokkal, lehetnek olyan esetek, amiket meg szeret-
nénk kerülni, vagy éppen olyan kritikus tulajdonságokkal bírnak, hogy elofordulásuk esetén le
kell, hogy álljon egy ciklus.
A break (ez a Switch-bol kell, hogy ismeros legyen) utasítás véget vet a ciklusnak és a
program a ciklus utántól folytatódik.
A continue utasítás semmissé teszi az adott iterációban utána következo utasításokat, tehát
azok nem történnek meg.
#include <stdio .h>
int main ( ) {
int i = 0 ;double val = 9 9 . 5 ;double var ;while (i < 5) {
scanf ("%lf" , &var ) ;if (var == 0 . 0 ) {continue ;
}printf ("%lf\n" , val /var ) ;i++;
}
return 0 ;}
5.10. ábra. Példakód arra, hogyan ússzuk meg a 0-val való osztást
Az 5.10-es ábrán az 99.5 számot osztjuk el 5 beolvasott számmal, majd ezeket kiírjuk kép-
ernyore. 0-val azonban nem osztunk, így ha a felhasználó esetleg 0-t adna meg, akkor nem kell
38
kiíratni egy olyan számot, ami nem létezik. Fontos, hogy így az i ciklusváltozó sem növekedik,
tehát ugyanúgy 5 érvényes számot olvasunk be, hiába adunk meg akár tízszer is 0-t.
A break utasítással ugyan ciklusokból tudunk kilépni, amikor szeretnénk, de mivel a felté-
telek pontosan erre valók, ezért használatuk nem túl elegáns.
39
6. fejezet
Tömbök és stringek
Ha valós életbeli reprezentációkat akarunk a programjainkban felállítani, akkor célszeru lehet,
hogy tudjunk például karakterekbol karaktersorozatot csinálni vagy éppen bármely más válto-
zókból egy adatszerkezetben többet eltárolni. Eddig ha adatokat akartunk tárolni, külön-külön
kellett oket kezelni, azonban ha ezek egy helyen tárolódnak, jelentosen egyszerubb a kezelésük.
Nos, a tömbök erre kínálnak megoldást.
6.1. Egydimenziós tömbök
Az olyan adatszerkezeteket, amik azonos adattípusú változókat tárolnak, tömböknek nevezzük.
Például, ha egy osztály tanulóinak átlagait kell eltárolnunk, akkor float/double típusú változók
egy tömbjét kell felvennünk. A karakterek sorozatai is így muködnek, ha van egy tömbünk, ami
karaktereket tárol, akkor a sok karakter együtt egy szöveget alkot.
Felmerülhet, hogy nem lehet elég belerakosgatni változókat egy ilyen tömbbe anélkül, hogy
utána ne tudnánk egy-egy bekerült értéket pontosan elérni. Errol az indexek gondoskodnak. A
tömb elso eleme a 0. index, második az 1. stb... Ebbol következik tehát, hogy egy 5 elemu
tömb esetén 0-4-ig, egy 10 elemu tömb esetén 0-9-ig tudjuk indexelni a tömböt. Ha egy, a
40
(tömbünk_mérete-1)-nél nagyobb indexet adunk meg, (mondjuk 5 elemu tömbnél 6, 7 vagy
8) akkor túlindexelésrol, ha 0-nál kisebb index-szel akarjuk elérni a tömb egy elemét, akkor
alulindexelésrol beszélünk.
#include <stdio .h>
int main ( ) {
int numbers [ 4 ] = {1 , 3 , 3 , 7 } ;
printf ("A tomb elso eleme: %d\n" , numbers [ 0 ] ) ;
numbers [ 2 ] = 4 ;
double letters [ 1 0 ] ;
int i ;
for (i = 0 ; i < 1 0 ; i++) {printf ("Add meg a %d. elemet:\n" ,i ) ;scanf ("%lf" , &letters [i ] ) ;printf ("%lf" ,letters [i ] ) ;
}
return 0 ;}
6.1. ábra. Példakód a tömbök alapveto kezelésére
A 6.1-es példakódon eloször szemléltetjük, hogyan lehet úgymond kézzel inicializálni
egy tömböt. Itt megadtuk az 1, 3, 3 és 7 számokat egy integer tömb elemeinek, majd a
numbers[szám] (ahol a szám [0;4] intervallumon helyezkedik el) paranccsal tudjuk elér-
ni a tömb egy elemét, akár kiíratásra, akár más muveletre (mert ez a kifejezés egy egyszeru
változóval ér fel). Egyenként is változtathatjuk a tömb elemeinek értékeit, ezt a numbers[2]
= 4 képviseli a példakódban.
A letters nevu tömb 10 elemu, itt már fáradságos megadni kézzel az elemeket, így egy
ciklusba ágyazzuk az értékek beolvasását. Fontos a tömbindexek és a ciklusok összehangolása.
0-tól indítjuk a for-ciklust, mivel 0 a tömbünk elso indexe is, s kisebb, mint 10-ig megy, hiszen a
tömb utolsó indexelheto eleme: 9. Így iterációról iterációra a tömb 0,1,2,3..., 9 elemeit olvassuk
be és íratjuk ki. Természetesen a printf-fel elvégzett kiíratás is ismétlodik, és minden amit a
ciklusmagba írunk.
Nézzünk egy példát string beolvasására a 6.2-es kódban.
Ez a példakód beolvas és kiír egy stringet. Ami eloször feltunhet az az, hogy hiányzik egy
& jel a scanf-bol. Ez csak és kizárólag string beolvasásánál érvényes, és ezzel együtt ez az
egyetlen tömb, amit egyetlen scanffel be tudunk olvasni, tehát más tömbök esetén ilyet nem
41
#include <stdio .h>
int main ( ) {
char str [ 1 0 ] ;scanf ("%s" , str ) ;printf ("%s" , str ) ;
return 0 ;}
6.2. ábra. Példakód egy string beolvasására és kiíratására
csinálhatunk. Mint azt látjuk, létrehoztunk egy 10 elemu karaktertömböt, de ha kipróbáljuk,
ennél rövidebb szöveg bevitele esetén is hibátlanul visszakapjuk azt a kiíratáskor. Fontos, hogy
amíg egy hagyományos n elemu tömb esetén az n-edik indexen véletlenszeru memóriainfor-
máció található, addig karaktertömböknél az n-edik elemen az ún. nullkarakter van, amit
így jelölünk: ’\0’. Amennyiben itt nem 10-hosszú stringet adunk meg beolvasáskor, hanem
mondjuk 3-at, abban az esetben a 3. indexre fog kerülni egy ’\0’, a 0, 1 és 2. indexre pedig
a 3 karakter. Ha 10-nél hosszabb szöveget olvasunk be, a beolvasott szó akkor is eltárolódik,
ezzel csak annyi probléma van, hogyha mi 10 férohelyet biztosítottunk a tömbünknek, akkor az
ezen túlcsorduló férohelyek már olyan memóriaterületbe íródhatnak, ahol más változók kapnak
helyet, s ha ezt a memóriaterületet mi felülírjuk, az végzetes hiba lehet a programunk futására
nézve. Ezt buffer overflow/overrun-nak nevezzük. Ebbol következik, hogy érdemes hosszabb
karaktertömböt deklarálni, ha pontosan nem tudjuk, mekkora szöveget fog beírni a felhasználó.
A nullkarakter a tömb egy indexére tételével ha már adott is egy bevitel, elvághatjuk a szót.
Rendkívül fontos az is, hogy ha késobb egy paraméterül adott tömb indexein változtatásokat
eszközölünk a függvényben, akkor az a változók értékeivel ellentétben a függvény-scope-on
kívül is meg fog változni!
6.2. Tömbök és ciklusok
Tömbök és ciklusok használatával rengeteg feladatot meg tudunk már oldani. Nézzünk egy pél-
dát. Ismerjük fel, hogy a beolvasott szó palindroma-e! A palindromák olyan szavak, amelyeket
visszafelé olvasva is az eredeti szót kapjuk. Pl.: gorog, kek, gezakekazeg, indulagorogaludni.
A 6.3-as ábrán a length függvény az elozoekben tárgyaltakon alapul. Kihasználjuk, hogy
a stringek végét a nullkarakter jelzi, így könnyedén megszámlálhatjuk egy ciklussal a betuket,
majd számukkal visszatérünk. A függvény eredményét az l változóban tároljuk. Láthatjuk,
42
#include <stdio .h>
int length (char word [ ] ) {int i = 0 ;while (word [i ] != '\0' ) {
i++;}return i ;
}
int isPalindrome (char word [ ] , int length ) {int i ;for (i = 0 ; i < length / 2 ; i++) {
if (word [i ] != word [length−1−i ] ) {return 0 ;
}}return 1 ;
}
int main ( ) {
char word [ 1 0 0 ] ;scanf ("%s" , word ) ;int l = length (word ) ;if (isPalindrome (word ,l ) ) {
printf ("Palindroma!\n" ) ;}else{printf ("Nem palindroma!\n" ) ;
}
return 0 ;}
6.3. ábra. Példa egy palindrómákat felismero program kódjára
hogy jó nagy méretet adtunk a word tömbünknek, hogy nagyon hosszú stringeket is beleírhas-
sunk. Ne hagyjuk figyelmen kívül a függvény fejlécét, itt ugyanis láthatjuk, hogy a hagyomá-
nyos tömböket (késobb lesz másmilyen is) üres zárójellel kell átadni, mint paraméter.
Az isPalindrome függvényben egy for-ciklust találunk, ami a szó hosszának a feléig
megy. Azért megy csak a feléig, mert ahhoz, hogy megállapítsuk, hogy a szó eleje ugyanaz-e,
mint a vége, elég ha csak a szó feléig tart az i változó, és egy feltételben mindig kivonjuk
a szó hosszából azt. Tehát az elso iterációban az word[i] a tömb 0. elemét vizsgálja, míg
a word[length-1-i] pedig az utolsót. A második iterációban a word[i] a tömb 1. elemét
vizsgálja, a word[length-1-i] pedig az utolsó elottit, és így tovább... Felmerülhet a kérdés, hogy
páratlan hossz esetén hogyan muködik a dolog. Nos, egy 5 hosszú szónál a bennmaradási
feltétel (5/2 muvelet) 2-re értékelodik ki (hiszen a length integer), tehát ha vesszük a gorog
szót, akkor az elso g o lesz visszafelé összehasonlítva a o g-vel, ami a szó vége. Így egy betu
kimarad az összehasonlításból ám ilyenkor nekünk teljesen mindegy, mi a középso betu, hiszen
a szó palindroma lesz bármely középso betu esetén.
43
Ha csak egy betu nem egyezik a tükörképével, akkor a szó nem palindroma, tehát biztos,
hogy rossz: return 0-val jelezzük majd a main függvényben levo feltételnek, hogy mehet
az else ágra, mert a szó nem palindroma. Ellenkezo esetben, ha return 0 nélkül kijutunk a
for-ciklusból, tiszta lelkiismerettel visszatérhetünk 1-el. Emlékezzünk, hogy a main függvény-
ben lévo feltételben azért nincs ott az == 1, mivel ha 1-el térünk vissza, akkor if(1) esetet
kapunk, tehát az if ágára megyünk tovább így is, 0 esetén pedig az else-re.
Számtalan dolgot végezhetünk még tömbök és ciklusok felhasználásával, bevitt/adott adat-
halmazban kereshetünk minimumot/maximumot, ahogyan az elozo fejezetben csináltuk vagy
éppen megszámolhatjuk egy szóban a magánhangzókat, stb, gyakoroljuk ezeket be alaposan!
#include <stdio .h>
int vowelCount (char szo [ ] ) {
int i = 0 , count = 0 ; / / i n i c i a l i z a l o k egy c i k l u s es egy s z a m l a l o v a l t o z o twhile (szo [i ] != '\0' ) { / / a d d i g megy a c i k l u s u n k , amig a v i z s g a l t szo i−e d i k k a r a k t e r e nem ←↩
n u l l k a r a k t e rif (szo [i ] == 'a' | | szo [i ] == 'e' | | szo [i ] == 'i' | | szo [i ] == 'o' | | szo [i ] == 'u' ) { ←↩
/ / ha maganhangzo akkor :count++; / / novelem a s z a m l a l o v a l t o z o t
}i++; / / minden i t e r a c i o b a n novelem a c i k l u s v a l t o z o t
}return count ; / / v i s s z a t e r e k a s z a m l a l o v a l t o z o m m a l
}
int main ( ) {
char szo [ 1 0 0 ] ;scanf ("%s" , szo ) ;printf ("%d\n" , vowelCount (szo ) ) ;
return 0 ;}
6.4. ábra. Egy példaprogram, ami egy bevitt szóban számlálja meg a magánhangzókat
44
6.3. Többdimenziós tömbök
Sok feladat megoldásához az egydimenziós tömb már nem elegendo, vagy túl bonyolult lenne.
Például, ha adott 4 ember, akinek el kellene tárolni 3 havi költségeit, akkor ehhez kézzel külön
létre kellene hozni 4 tömböt, amiknek külön kezelése fáradalmas, és akkor még csak 4 emberrol
beszéltünk. Ennek megoldása képpen tudunk kvázi mátrixot vagy ún. 2-dimenziós tömböt,
vagy akár 3, 4, .., n dimenziósat is használni.
Ezek elemeit, akár csak az egyszeru tömbökét, indexekkel tudjuk elérni, csak éppenséggel
itt meg kell adni a sor és az oszlopindexet is. Nézzünk egy példát akkor a már fentebb említett
4 ember 3 havi költségeinek tárolását elvégzo programra a 6.4-es példakódban!
Természetesen a tömbök itt is 0-tól n-1-ig indexelhetoek. Megfigyelhetjük, hogy más sza-
bály vonatkozik a függvények fejlécére, mint a hagyományos tömböknél, ugyanis az oszlopszá-
mot mindenképpen fel kell tüntetni. Ha megértettük az egymásba ágyazott ciklusokat az elozo
fejezetben, akkor ez sem fog problémát okozni. Itt az történik, hogy a külso ciklus felel egy
adott sorért, a belso ciklus pedig a sorban fellelheto oszlopokért. Tehát, ha a külso for-ciklus i
ciklusváltozója 0, akkor a tömb 0. sorában járunk. Ekkor a külso for-ciklusban találkozunk a
belso for-ciklussal, aminek ciklusváltozója j, ez pedig növekedni fog egészen az bennmaradási
feltételéig (tehát 1 ember 3 havi költségeiig), majd csak ezután kezdodik a külso for-ciklus i =
1-es iterációja (azaz a második ember költségei).
A kétdimenziós kiíratásokkal már szemléltetni is tudunk bizonyos ábrákat, például egy
négyzet foátlóját vagy mondjuk amoba játékot, de furcsa alakokat is kirajzolhatunk velük (AS-
CII art).
Ha például egy négyzet foátlóját kell kirajzolni, akkor ott szükség lesz a képzeloeronkre.
Úgy fogjuk a foátlót szemléltetni, hogy a foátló helyére írunk egy adott dolgot, és mindenhová
45
#include <stdio .h>
void koltsegBevisz (int arr [ ] [ 3 ] ) { / / l a t h a t j u k hogy 2d−s tombok a t a d a s a n a l az o s z l o p s z a m o t ←↩meg k e l l a d n i
int i , j ; / / c i k l u s v a l t o z o kfor (i = 0 ; i < 4 ; i++) { / / 4 s o r t k e z e l u n k
for (j = 0 ; j < 3 ; j++) { / / e s 3 o s z l o p o tprintf ("Adja meg a %d ember %d. havi koltseget:\n" , i , j ) ;scanf ("%d" , &arr [i ] [j ] ) ; / / b e o l v a s s u k az i−e d i k s o r j−e d i k o s z l o p a n a k e r t e k e t
}}
}
void koltsegKiir (int arr [ ] [ 3 ] ) {int i , j ; / / c i k l u s v a l t o z o kfor (i = 0 ; i < 4 ; i++) { / / 4 s o r t k e z e l u n k
printf ("A %d. ember koltsegei:\t" , i+1) ; / / minden s o r egy ember , t e h a t az a d a t o k e l o t t ←↩k i i r j u k e z t
for (j = 0 ; j < 3 ; j++) { / / e s 3 o s z l o p o tprintf ("%d\t" , arr [i ] [j ] ) ; / / egy embernek 3 a d a t a van , k i i r j u k
}printf ("\n" ) ;
}}
int main ( ) {
int koltsegek [ 4 ] [ 3 ] ;koltsegBevisz (koltsegek ) ;koltsegKiir (koltsegek ) ;
return 0 ;}
6.5. ábra. Példakód egy 4 ember 3 havi költségeit eltároló programra
A 1 . ember koltsegei : 1000 2000 3000A 2 . ember koltsegei : 3000 3121 5321A 3 . ember koltsegei : 9856 4533 3213A 4 . ember koltsegei : 2134 3213 7457
pedig mást, így látható lesz, mit szeretnénk csinálni. A main függvényben tehát létrehozunk
egy csupa 0 2d-s tömböt. Ahol egyenlo az i és a j koordináta, "vonalat húzunk", azaz az
ottani értéket 1-re állítjuk, ugyanis ezzel definiálható a foátló (ha nem hisszük, rajzoljuk le). A
többi helyen hagyjunk 0-kat. Ezt a kódot a 6.6, míg a kimenetet az ot követo ábrán láthatjuk. A
kiir() függvény ugyanúgy meg van, mint az elozo példakódban, csupán a muvelet függvénye
más.
A kódban 6.6-os kód main függvényében megfigyelhetjük a direkt 2-dimenziós inicializá-
ciót, itt a már megszokott {} jelek közé újabb ilyen jelekben kerül be több egydimenziós tömb
(így hoztuk létre a feladat elején a csupa 0 2d-s tömbünket). Ahogy növekszik a dimenziószám,
úgy szaporodik a {} jelek száma, valamint a bejáráshoz szükséges ciklusoké is.
46
#include <stdio .h>
void foatlo (int array [ ] [ 5 ] ) {int i , j ; / / c i k l u s v a l t o z o kfor (i = 0 ; i < 5 ; i++) { / / 5 s o r
for (j = 0 ; j < 5 ; j++) { / / 5 o s z l o pif (i == j ) { / / akkor r a j z o l u n k a t l o t , ha a 2 k o o r d i n a t a e g y e n l o
array [i ] [j ] = 1 ; / / a r a j z o l a s t ugy v a l o s i t j u k meg , hogy 1−e t i r u n k 0 h e l y e t t}
}}
}
void kiir (int array [ ] [ 5 ] ) {int i , j ;for (i = 0 ; i < 5 ; i++) {
for (j = 0 ; j < 5 ; j++) {printf ("%d " , array [i ] [j ] ) ; / / k i r a j z o l o m az i e d i k s o r j e d i k e l e m e t
}printf ("\n" ) ;
}}
int main ( ) {
int array [ 5 ] [ 5 ] = { { 0 , 0 , 0 , 0 , 0 } , { 0 , 0 , 0 , 0 , 0 } ,{ 0 , 0 , 0 , 0 , 0 } , { 0 , 0 , 0 , 0 , 0 } , { 0 , 0 , 0 , 0 , 0 } } ; / / i n i c i a l i z a l o m f u l l 0− r a az 5x5−os tombomfoatlo (array ) ; / / meghivom az a r r a y r a a f o a t l o k e s z i t o fgv−tkiir (array ) ; / / k i i r a t o m az a r r a y−t
return 0 ;}
6.6. ábra. Példakód, ami egy 2-dimenziós tömb foátlóját rajzolja ki
1 0 0 0 00 1 0 0 00 0 1 0 00 0 0 1 00 0 0 0 1
6.7. ábra. 6.6 példakód kimenete
47
7. fejezet
Fájlkezelés
Eddig olyan programokkal foglalkoztunk, amik billentyuzetrol olvastak be különbözo változók-
ba adatokat, és a konzolra írták ki az eredményeiket. Ez volt tehát a standard input és output.
Azonban ha belegondolunk, sok olyan program létezik, ami vagy a beolvasott értékeket, vagy a
kimenetet fájlokkal közvetíti.
A bemeneti/kimeneti csatornák tehát állíthatóak. Az átállítást tetszoleges könyvtárakkal is
megtehetjük a legváltozatosabb felületekre, de mi most a fájlokra irányítással fogunk foglalkoz-
ni, ami történetesen megtalálható az stdio könyvtárban.
Vágjunk is a dolgok közepébe, nézzünk példát egy programra, ami beolvas egy fájlból egy
sort, majd kiírja a képernyore!
#include <stdio .h>
int main ( ) {
char sor [ 1 0 0 ] ; / / char tomb d e k l a r a c i o
FILE * fp ; / / f i l e p o i n t e r l e t r e h o z a s afp = fopen ("input.txt" ,"r+" ) ; / / f i l e p o i n t e r n e k a t a d j u k az fopen v i s s z a t e r e s i e r t e k e t/ / az fopenbe e l s o argumentumkent b e i r j u k a f a j l u n k neve t , masodiknak p e d i g hogy mi t ←↩
s z e r e t n e n k a f a j l l a l c s i n a l n i
fscanf (fp , "%s" , sor ) ; / / f a j l b o l v a l o b e o l v a s a sprintf ("%s" , sor ) ; / / s ima p r i n t f
fclose (fp ) ; / / f a j l p o i n t e r f e l s z a b a d i t a s a
return 0 ;}
7.1. ábra. Egy program forráskódja, ami beolvas egy fájlból 1 sort, majd kiírja a képernyore
A FILE * fp-vel létrehoztunk egy fájl típusú fp változót, ebbe nyitjuk meg a következo
sorban az fopen(...)-nel a fájlt, ennek elso paramétere a fájl neve, a második paramétere
48
az írási/olvasási jog.
Fájlkezelési jogok:
• "r" - csak olvasás (ha a fájl nem létezik, nem történik semmi)
• "r+" - írás beolvasás (ha a fájl nem létezik, nem történik semmi)
• "w" - csak írás (ha a fájl nem létezik, nem történik semmi)
• "w+" - írás, beolvasás (ha a fájl nem létezik, kreál egyet, ha létezik, felülírja)
• "a" - hozzáfuzés (ha a fájl nem létezik, kreál egyet)
• "a+" - hozzáfuzés + beolvasás (ha a fájl nem létezik, kreál egyet)
Kézenfekvo az fscanf(...) használata is, megegyezik a scanf(...)-ével, csupán
elso paraméternek a fájlmutató nevét kell megadni. Az fclose() függvényt illik meghívni
a feladat végeztével, ez ugyanis felszabadítja a FILE típusú változó által lefoglalt memória-
területet. Erre alapvetoen csak folyamatosan futó programok esetén van szükség, de nem árt
megszokni a használatát.
Véleményem szerint nem igényel sok taglalást ez a fejezet, a fájlmutató létrehozásán és
bezárásán kívül a fájlcsatorna muveleteinek használata ekvivalens az eddig megszokottakkal.
Használható ciklusokban, függvényekben, mindenhol, ahol eddig a printf és scanf-et hasz-
nálhattuk.
#include <stdio .h>
int main ( ) {
char sor [ 1 0 0 ] ; / / k a r a k t e r t o m b d e k l a r a c i o j a
scanf ("%s" , sor ) ;
FILE * fp ; / / f i l e p o i n t e r l e t r e h o z a s afp = fopen ("output.txt" ,"w" ) ; / / f a j l n e v + j o g o s u l t s a g kombo
fprintf (fp , "%s" , sor ) ; / / f p r i n t f −f e l v a l o f a j l b a i r a s
fclose (fp ) ; / / f i l e p o i n t e r f e l s z a b a d i t a s a
return 0 ;}
7.2. ábra. Példa az fprintf() függvény használatára és a fájlbaírásra
49
8. fejezet
Struktúrák
A struktúrák témaköre megint csak kiszélesíti problémamegoldó eszközeink készletét. Ha
összefutottunk valaha olyan problémával, ahol egy adott személyrol kellett több adatot tárolni,
mondjuk egy iskola diákjainak neveit és címeit, akkor a diákok adatainak eltárolásához való-
színuleg 2 tömbhöz nyúltunk, aminek kezelése fáradtságos, és zavaróan nincsenek egységben
az adatok, holott azok egy dologhoz köthetoek. Amennyiben 2-dimenziós tömböt használtunk
az adattároláshoz, ott zavaró lehet, hogy csak azonos adattípusokat tudunk eltárolni. Erre kínál
megoldást a struktúra.
8.1. Struktúrák alapveto tulajdonságai
Egy struktúra lehetové teszi nekünk, hogy létrehozzunk egy olyan változót, ami egyszerre több,
elore megadott nevu változót képes eltárolni úgy, hogy azok lehetnek különbözo típusúak is.
Ennek megvalósítása során eloször létre kell hoznunk egy struktúra definíciót, majd valahol
létre kell hozni egy/több, a struktúrát reprezentáló változót.
Például, adatok Józsiról:
• Név: Gipsz József
• Születési év: 1995
• Anyja neve: Gipsz Jakabné
• Tanulmányi átlaga: 4.45
Láthatjuk, hogy Józsi adatai különbözo típusúak, ezért egy tömbökkel nemigen tudjuk rep-
rezentálni Ot. Nézzük hát, hogyan oldható ez meg(8.1-es ábra).
50
#include <stdio .h>
struct Diak{
char nev [ 1 0 0 ] ; / / s t r u k t u r a a d a t t a g j aint kor ; / / s t r u k t u r a a d a t t a g j achar anyjaneve [ 1 0 0 ] ; / / s t r u k t u r a a d a t t a g j adouble atlag ; / / s t r u k t u r a a d a t t a g j a
} ;
/ / t y p e d e f s t r u c t Diak d i a k ;
/ *
vagy
t y p e d e f s t r u c t {c h a r nev [ 1 0 0 ] ;i n t kor ;c h a r a n y j a n e v e [ 1 0 0 ] ;do ub l e a t l a g ;
} d i a k ;
* /
int main ( ) {
struct Diak jozsi ; / / s t r u k t u r a p e l d a n y o s i t a s a/ / t y p e d e f h a s z n a l a t a v a l : d i a k j o z s i ;scanf ("%s %d %s %lf" , jozsi .nev , &jozsi .kor , jozsi .anyjaneve , &jozsi .atlag ) ; / / p e l d a n y .←↩
a d a t t a g k i f e j e z e s s e l e r j u k e l j o z s i a d a t t a g j a i tprintf ("%s\n%d\n%s\n%lf\n" , jozsi .nev , jozsi .kor , jozsi .anyjaneve , jozsi .atlag ) ; / / ←↩
p e l d a n y . a d a t t a g k i f e j e z e s s e l e r j u k e l j o z s i a d a t t a g j a i t
return 0 ;}
8.1. ábra. Egy program forráskódja, szemlélteti a struktúrák alapveto használatát
A struktúra definiálása során hagyományos módon deklaráljuk az adattagokat, a struct
kulcsszó után megadjuk a struktúra nevét. A definíció { } jelek között történik, itt adjuk meg
az adattagokat, a } után ne felejdük el a pontosvesszot! Ez a definiálás mindig globálisan
történik, azaz nem esik bele egyik függvény-scope-ba sem. A struktúrát megvalósítani a main
függvényben látható módon kell, csak egy változónévre van szükségünk. Ha nagyon lusták
vagyunk, és ki szeretnénk spórolni a változó létrehozásánál a struct kulcsszót, akkor erre
kínál a typedef egy alternatívát. Ezzel kvázi aliasokat tudunk létrehozni, például a typedef
int EGESZSZAM; parancs után az EGESZSZAM x;-et a fordító int x;-ként értelmezi.
Így értelemszeruen a typedef struct Diak diak paranccsal a változónkat egyszeruen
a diak jozsi; paranccsal is létrehozhatjuk.
Egy diak struktúrából bármennyi embert tudunk példányosítani, persze egy változónév itt is
csak egyszer szerepelhet egy scope-ban. Ezután ennek adattagjait példánynév.adattag
51
kifejezéssel érhetjük el, ahogy az a példakódban történo beolvasásnál és kiíratásnál is látszik.
8.2. Tömbök struktúrában és struktúrapéldányok paraméter-
ként
Amint azt az elozo példakódon is láthattuk, az adattagok tömbök is lehetnek. Bár magától
értetodik, de ha a 8.1-es feladatban el akarnánk érni jozsi nevének elso betujét, akkor azt a
jozsi.nev[0] kifejezéssel tehetjük meg, és így tovább az összes elemet, tehát semmi sem
változott ilyen téren.
Struktúrákat is átadhatunk függvényeknek paraméterként, viszonylag egyértelmu módon,
ezt a 8.2-es ábra szemlélteti.
#include <stdio .h>
typedef struct{
char nev [ 1 0 0 ] ;int jegyek [ 1 0 ] ;double atlag ;
}diak ;
double calculateAvg (diak valaki ) { / / p a r a m e t e r u l egy s t r u k t u r a t va runkdouble sum ; / / t u d j u k , hogy a t l a g s z a m o l a s l e s z , t e h a t l e b e g o p o n t o s sum v a l t o z o t veszunk f e lint i ;for (i = 0 ; i < 1 0 ; i++) {
sum += valaki .jegyek [i ] ; / / j e g y e k o s s z e a d a s a}return sum / 1 0 . 0 ; / / a t l a g o l a s
}
int main ( ) {
diak jozsi ;/ / b e o l v a s s u k a j o z s i egyed n e v e tscanf ("%s" , jozsi .nev ) ;int i ;/ / b e o l v a s s u k mind a 10 j e g y e tfor (i = 0 ; i < 1 0 ; i++) {
scanf ("%d" , &jozsi .jegyek [i ] ) ;}/ / az egyed j e g y e i a l a p j a n k i s z a m i t j u k az a t l a g a t !jozsi .atlag = calculateAvg (jozsi ) ; / / v i s s z a t e r u n k a c a l c u l a t e A v g fgv−n y e l j o z s i a t l a g ←↩
a d a t t a g j a b aprintf ("%s\n%lf\n" , jozsi .nev , jozsi .atlag ) ;
return 0 ;}
8.2. ábra. Egy program forráskódja, szemlélteti a struktúrák alapveto használatát
A 8.2-es példakódban láthatjuk, hogy egy struktúrapéldányt átadni egy függvénynek, nem
52
kisebb feladat, mint ugyanezt egy változóval megtenni. Miután ez megtörtént, bármely adattag-
hoz hozzáférhetünk az eddig ismert példány.adattag módon. Csakúgy, mint egy vál-
tozón, a struktúrapéldány adattagjain is bármikor módosíthatunk, ezért tehettük meg, hogy
a calculateAvg() függvény eredményét értékül adtuk a jozsi.atlag adattagnak (a
calculateAvg átlagot számol Józsi jegyeibol). Fontos azonban, hogy itt is érték szerint ad-
juk át a struktúrát, tehát a függvényben hiába módosítjuk az adattagok értékét, azok nem fognak
a visszatérés után változni.
8.3. Struktúratömbök
A fejezetben már említettem egy olyan példát, hogy egy iskola diákjainak adatait kell eltárolni.
Nos, az eddigiek alapján azt már tudjuk, hogyan vegyünk fel egy struct változóban egy diá-
kot. Egy iskola azonban több diákot tárol, szóval ahhoz, hogy ezt megtehessük, struktúratömböt
kell alkalmaznunk, aminek minden indexén 1-1 diák foglal helyet. Ezek használata is ugyanúgy
muködik, mint a hagyományos tömböké, ugyanakkor figyeljünk arra, hogy egy struktúratömb
minden indexe egy struktúrapéldányt jelöl különbözo tulajdonságokkal! Egy struktúratömbben
levo példány tömb adattagjának egy elemének elérésére struktTomb[2].tomb[0] képpen
néz ki, ha eloször látjuk, kifejezetten furcsa lehet. Tekintsük meg, hogyan is nézne ki egy olyan
program, ami egy iskola diákjait tárolja névvel és átlaggal!
#include <stdio .h>
typedef struct{
char nev [ 1 0 0 ] ;double atlag ;
}diak ;
int main ( ) {
diak diakok [ 1 0 ] ; / / d i a k t i p u s u tomb
int i ;for (i = 0 ; i < 1 0 ; i++) { / / 10 i t e r a c i o , mive l 10 d i a k van
scanf ("%s %lf" , diakok [i ] . nev , &diakok [i ] . atlag ) ; / / minden i t e r a c i o b a n egy g ye r ek ←↩a d a t a i t k e r j u k be
printf ("%c\n" , diakok [i ] . nev [ 0 ] ) ; / / egy g y e r ek ugye a tomb i−e d i k eleme}
return 0 ;}
8.3. ábra. Egy program forráskódja, amin feltöltünk egy struktúratömböt billentyuzetrol, amidiákok adatait tárolja
53
A példakódban a teljes megértés érdekében a for-ciklusban szereplo printf()-fel kiírat-
juk minden bevitt nev 0. karakterét, hogy lássuk akcióban is a struktúratömb egy elemének
tömbadattagjának egy indexének elérését.
Struktúratömböket abban a speciális esetben használunk, amikor több adatot kell eltárolni
egy dologról, és abból a dologból több van. Mint azt már párszor említettem, szinte végtelen
formai és logikai variációja létezik egy-egy feladat megoldásának, ha programozásról beszé-
lünk, de érdemes mindig szem elott tartani a hatékonyságot és az egyszeruséget. Ezért csak
úgy, mint minden mást, a mi tisztünk eldönteni mikor használunk struktúrát, de figyelmesen és
megfontoltan használjuk.
54
9. fejezet
Pointerek
Eddigi tudásunk szerint egy változónak van típusa, neve és értéke. Pedig van bizony egy negye-
dik alkotó is, ez pedig a memóriacím. A mutató vagy pointer olyan változó, amely egy másik
változó címét tartalmazza. A C nyelvben gyakran használják a pointereket, egyrészt mert bizo-
nyos feladatokat célszeru velük megoldani, másrészt mert alkalmazásukkal sokkal tömörebb és
hatékonyabb program hozható létre.
A számítógépen a memóriacímek meg vannak számozva (az ábrán az 1462-es memóriací-
men egy int típusú b változó foglal helyet 17-es integer értékkel. Amikor pointereket haszná-
lunk a programunkban, tulajdonképp ezeket a címeket tároljuk.
Biztosan elofordult már kódírás közben, hogy a printf függvényben egy változó elott
ottfelejtettük a & jelet. Ilyenkor nem a várt értéket kaptuk, s ez azért volt, mert ha a változónév
elott egy & szerepel, akkor a változó memóriacím-értékét kapjuk meg. Ha egy változó dekla-
rációja során a változónév elé egy * jel kerül, akkor egy adott változótípusú pointert hoztunk
létre. Ennek deklarációja során, ahogy a 9.1-es példakód is szemlélteti, meg kell jegyezni, hogy
a nem kikommentezett rész ekvivalens a kikommentezettel. Ez azért fontos és azért jegyez-
zük meg alaposan, mert pointer esetén a *valtozo jelöli a memóriacímen található értéket,
a valtozo viszont már a memória címét tárolja, viszont a int *ptr = &a-ból könnyen
55
#include <stdio .h>
int main ( ) {
int a = 9 8 ;
int * ptr = &a ;/ / E z z e l e k v i v a l e n s :/ / i n t * p t r ;/ / p t r = &a ;
printf ("a valtozo erteke: %d\na valtozo memoriacime: %d\nptr valtozo erteke: %d\nptr ←↩valtozo memoriacime: %d\n" , a , &a , *ptr , ptr ) ;
return 0 ;}
9.1. ábra. Egy program forráskódja, ami szemlélteti a pointerek muködését
a valtozo erteke : 98a valtozo memoriacime : 6487620ptr valtozo erteke : 98ptr valtozo memoriacime : 6487620
9.2. ábra. A 9.1 ábrán látható kódból képzett program kimenete
arra lehetne következtetni, hogy a *ptr tárolja a &a memóriacím-értéket, holott ez csak egy
összevont jelölés és ilyenkor a memóriacímet valójában a ptr-nek adjuk át.
És hogy mire tudjuk ezt használni? Nos, rengeteg mindenre, de most nézzünk egy egysze-
ru példát, tételezzük fel, hogy szeretnénk két változó értékét kicserélni egy függvénnyel. Ez
jelenlegi tudásunk szerint aligha lehetséges, mivel csak érték szerint tudunk paramétereket át-
adni, tehát egy függvényben megtörténo muveletsor nem tud hatni azokra a változókra, amik
a függvényt hívó scope-ban lettek deklarálva. Cím szerinti paraméterátadással azonban már
kivitelezheto a muvelet.
Elsonek tekintsünk el a muvelettol, csak a paraméterátadást nézzük. Láthatjuk, hogy a
9.3-as ábrán a függvényünk fejléce a void swap(int *a, int *b). Most tekintsünk a
main függvényben szereplo függvényhívásra, láthatjuk, hogy két hagyományos változó memó-
riacímét adtunk át az *a és *b pointernek. A 9.1-es kódnál már leírtam, hogy ez teljesen valid
muvelet, mivel így a paraméterátadás int *a = &v1 és a int *b = &v2 muveleteknek
felel meg, azokkal pedig *a-ba és *b-be kerülnek rendre v1 és v2 értékei és a-ba és b-be pedig
&v1 és &v2, azaz a memóriacím-értékek. A swap függvény törzsében eloször is felvesszük a
t változót, ami egy ideiglenesen használt (temporary) változó, s ennek értékül adjuk a b pointer
értékét. Ezután a b pointernek értékül adjuk az a pointer értékét, majd a t változóban eltárolt
56
#include <stdio .h>
void swap (int *a , int *b ) {int t ;
t = *b ;*b = *a ;*a = t ;
}
int main ( ) {
int v1 = 5 ;int v2 = 9 9 ;swap(&v1,&v2 ) ;
printf ("%d es %d" , v1 , v2 ) ;
return 0 ;}
9.3. ábra. Egy program forráskódja, ami szemlélteti a cím szerinti paraméterátadást
eredeti b értéket adjuk át az a pointernek, így az értékcsere megtörtént. Mivel cím szerint adtuk
át a változókat a függvénynek, ezért ha a függvény annak a 2 változónak az értékén változtat,
amiket paraméterül megadtunk, akkor a változtatás a függvény visszatérte után is megmarad!
9.1. Dinamikus tömbök
Az eddig használt statikus tömbök is teljesen lefedték a feladatok megoldásához szükséges
eroforrásokat, azonban volt számos hátrányuk, noha elsore talán nem is feltunoek.
Amikor nem tudtuk elore, hogy a tömbünk pontosan hány adatot is fog tárolni, akkor álta-
lában jó nagy méretet adtunk meg neki, hogy egészen biztosan tárolni tudja a bevitt adatokat.
Azonban a tömbök által lefoglalt memóriaterületek a programunk bezártáig lefoglaltak marad-
tak. Kisebb programoknál, mint amiket mi írunk, ez abszolút nem feltuno, azonban folyamato-
san futó programoknál, amik sok memóriát használó funkcióval rendelkeznek, igen csak bajos,
ha a program futása végére beterítik a gép összes memóriáját.
Mivel kicsiben sem árt takarékosan és helyesen gondolkodni, ezért nem ártana egy olyan
megoldás, amivel tudatosan foglalnánk és szabadítanánk fel a memóriát. Nos, a dinamikusan
létrehozott tömbök pontosan ezt a célt szolgálják. Emellett számos más, bonyolultabb adatszer-
kezet létezik, amik pointerek használata nélkül nem hozhatóak létre (láncolt listák, komplikál-
tabb fák).
Amikor egy pointernek értéket adunk, annak lesz egy tárolt memóriacíme. Nos, biztosan
57
lesz olyan rész a memóriában, amit ha lefoglalunk, akkor lesz mellette még annyi szabad meg-
címezheto rész, ami elegendo egy tömb helyének lefoglalásához.
#include <stdio .h>#include <stdlib .h>
int main ( ) {
int n ;scanf ("%d" , &n ) ;int * arr = (int*)malloc (n*sizeof (int ) ) ; / / memoria a l l o k a l a s a n* t i p u s mere tben
int i ;for (i = 0 ; i < n ; i++) {
scanf ("%d" , (arr+i ) ) ; / / mive l az a r r a l a p b o l memoriacim , i g y nem k e l l a &/ / s c a n f ("%d " , &a r r [ i ] ) ;
}
printf ("KI:\n" ) ;for (i = 0 ; i < n ; i++) {
printf ("%d\n" , * (arr+i ) ) ;/ / p r i n t f ("%d \ n " , a r r [ i ] ) ;
}
free (arr ) ;
return 0 ;}
9.4. ábra. Egy program forráskódja, ami szemlélteti a dinamikus tömbök használatát
Amint azt a 9.4 ábrán látjuk, a tömbünket a (típus*)malloc(egésszám*sizeof(típus))
utasítással foglaljuk le. A tömbméret egy egész szám lehet, a típus pedig bármilyen primitív
adattípus illetve pointer(késobb). A malloc() utasítás memory allocationt jelent, tehát
memóriafoglalás. Paraméterül egy int típusú változót vár, ami a lefoglalt bájtok számát kép-
viseli. Tehát ha 5 db integer változót szeretnénk foglalni, akkor 5*4-et kell írni a malloc-ba,
tehát 20-at. Azonban ezt számolgatni elég fáradtságos, szóval a sizeof függvénybe csak be
kell írnunk paraméterül a változó típusát, és már készen is vagyunk.
Ha lefoglaltunk kello helyet a tömbünknek, akkor az arr azt a memóriacímet tárolja, amin
a tömb 0 indexu eleme helyet fog foglalni, a *arr pedig az ezen a memóriacímen levo értéket.
Itt úgy tudjuk hagyományos jelölésben mozgatni az indexmutatót, hogy hozzáadunk az adott
58
memóriacímhez egy egész számot, és az attól +egész számra levo indexet fogjuk tudni így el-
érni. Pl.: *(arr+5) a tömb 5. indexe, *(arr+0) a tömb 0. indexe. Ilyenkor kerüljük a *(arr)+5
kifejezést, mivel ez a tömb 0. értékéhez ad hozzá 5-öt, értelmes kifejezés, csak nem itt kell
használni. Láthatjuk, hogy a for-ciklussal ezt használjuk ki, ugyanis ha 0-tól indulunk, akkor
a i=0-s iteráció a tömb 0. eleme *(arr+0), i = 1 esetén a +1-es iteráció a tömb 1. eleme és így
tovább... A scanf-ben ilyenkor annyi változás van, hogy elég csak a (arr+i)-t beolvasni.
Amikor változókat olvastunk be, akkor emlékszünk, hogy a &valtozo utasítással tettük ezt
meg scanf-fel, ezzel a memóriacímet adtuk át. Azonban pointerek esetében az arr a me-
móriacímet tárolja, tehát itt már semmi trükk nem kell. A malloc kifejezés elotti castolás
(int*)-ra nem kötelezo, C++ fordítási kompatibilitás miatt van, de nincs rá feltétlen szükség,
mivel a malloc visszatérési értéke void*.
Mint azt látjuk a kikommentezett részben, a memória allokálása után kezelhetjük úgy is a
dinamikus tömböket, mintha hagyományos tömbök lennének. Erre én úgy tekintek, mint egy-
fajta engedményre, de alapvetoen, ha pointert használunk, megéri a hagyományos szintaktika
szerint leírni, mivel más erre épülo adatszerkezeteknél már a statikus tömbös jelölés értelmét
veszti.
9.2. Kétdimenziós dinamikus tömbök
Természetesen dinamikus tömbökbol is létezik kétdimenziós, sot, bármennyi. Ezek létrehozása
többféleképpen is megtörténhet, a példakódban most nézzük azt az esetet, amikor valójában
egy egydimenziós tömbünk van, viszont eltolással egy sort több, egymás alatt elhelyezkedo
sorra osztunk. Ha egy 3*3-es tömböt kell létrehozni, akkor megtehetem (amúgy hagyományos
tömböknél is), hogy az elso 3 érték: 3, 4, 5, második 3: 1, 2, 9, harmadik 3: 8, 7, 6. Ha tudom,
hogy 3 értékenként sort váltok, és egy tömbbe felveszem oket egymás mellé: 3, 4, 5, 1, 2, 9, 8,
7, 6, akkor tudni hogyha index szerint a 2. elemnél járok, akkor a 3. indexen található elem a
második sor 0. eleme és így tovább.
A 9.5-ös ábrán láthatjuk, hogy sor*oszlop nagyságú egydimenziós dinamikus tömb felvéte-
lével oldottuk meg a problémát, majd ezután *(tomb + sorindex * maxoszlopszám
+ j ciklusváltozó) megoldással bejárjuk és feltöltjük a dinamikus tömböt. Így az i =
0 iterációban a 0. elemtol kezdve olvassuk be az értékeket egészen a 2.-ig, majd ha az i-nk
1-re növekedett, akkor a 3. indextol olvasunk be az 5.-ig és így tovább. Természetesen itt is
59
#include <stdio .h>#include <stdlib .h>
int main ( ) {
int n , m ;scanf ("%d %d" , &n , &m ) ;int * arr = (int*)malloc (n*m*sizeof (int ) ) ; / / n*m* t i p u s m e r e t mere tu a l l o k a l a s
int i , j ;for (i = 0 ; i < n ; i++) {for (j = 0 ; j < m ; j++) {
scanf ("%d" , (arr + i*m + j ) ) ; / / s o r * o s z l o p + a k t u a l i s o s z l o p e l e mprintf ("A bevitt szam: %d\n" , * (arr + i*m + j ) ) ;
}
}
return 0 ;}
9.5. ábra. Egy program forráskódja, ami szemlélteti az 1-dimenziós tömbök használatát 2-dimenziósként
használhatunk [] jelek közötti tömbindexelést.
Azt hiszem ez elég egyértelmu, de most nézzünk egy példát, amikor valójában egy olyan
dinamikus tömböt hozunk létre, aminek minden eleme egy dinamikus tömbre mutat, tehát egy
valós kétdimenziós tömböt!
9.6. ábra. Így képzeljük el a valós 2-dimenziós dinamikus tömböket! Minden egyes allokáltslotból kiallokálunk még egy dinamikus tömböt.
A 9.7-es példakódon láthatjuk, hogy a dinamikus tömbre mutató pointer adatszerkezete már
**-al van jelölve. Ebbol csak n-nyit kell lefoglalni, hiszen ezekbol az n memóriaterületekbol
fognak majd kiágazni a dinamikus tömbök. Láthatjuk, hogy a foglalás után végigmegyünk az n
hosszú tömbön, majd a tömb minden elemére foglalunk még egy m hosszú tömböt.
Az így létrehozott 2dimenziós tömböket is lehet [] jelek közötti indexekkel kezelni.
60
#include <stdio .h>#include <stdlib .h>
int main ( ) {
int n , m ;scanf ("%d %d" , &n , &m ) ;
int **arr = (int **)malloc (n * sizeof (int *) ) ; / / egy p o i n t e r e k r e muta to p o i n t e r t o m b o t ←↩f o g l a l o k l e
int i , j ;
for (i = 0 ; i < n ; i++) {arr [i ] = (int *)malloc (m * sizeof (int ) ) ; / / az a l l o k a l t tomb minden e l e m e b o l a l l k o a l o k ←↩
megegy 1−d−s tombot}
for (i = 0 ; i < n ; i++) {for (j = 0 ; j < m ; j++) {
scanf ("%d" , ( * (arr+i ) +j ) ) ;printf ("%d\n" , ( * ( * (arr+i ) +j ) ) ) ;
}
}
return 0 ;}
9.7. ábra. Egy program forráskódja, ami szemlélteti a 2-dimenziós tömbök muködését
61
Recommended