programozás alapjai segédanyag 2018 kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf ·...

61
Programozás alapjai segédanyag 2018 Kóbor jegyzet Kóbor Ervin Utolsó módosítás: 2018.09.10.

Upload: others

Post on 10-Nov-2020

2 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

Programozás alapjai segédanyag 2018

Kóbor jegyzet

Kóbor Ervin

Utolsó módosítás: 2018.09.10.

Page 2: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 3: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 4: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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: [email protected]

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

Page 5: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 6: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 7: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 8: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 9: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 10: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 11: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 12: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 13: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 14: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 15: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 16: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 17: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 18: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 19: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 20: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 21: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

é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

Page 22: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 23: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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" , &degree ) ;

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

Page 24: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 25: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 26: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

• && - é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

Page 27: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#include <stdio .h>

int main ( ) {

double degree ;scanf ("%lf" , &degree ) ;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

Page 28: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 29: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 30: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 31: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 32: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 33: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 34: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 35: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 36: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 37: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 38: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 39: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 40: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 41: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

(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

Page 42: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 43: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 44: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 45: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 46: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 47: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 48: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 49: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 50: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 51: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 52: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 53: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 54: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 55: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 56: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 57: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 58: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 59: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

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

Page 60: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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

Page 61: Programozás alapjai segédanyag 2018 Kóbor jegyzetervink/stuffs/progalap-kobor-jegyzet.pdf · további szakterületet is választunk, a programozás a legkülönbözobb esetekben

#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