Programozás tankönyv VI. Fejezet Elıírt lépésszámú ciklusok Ismétlés a tudás anyja. Hernyák Zoltán 61/312
Az eddig megírt programok szekvenciális mőködésőek voltak. A program végrehajtása elkezdıdött a Main függvény elsı utasításán, majd haladtunk a másodikra, harmadikra, stb amíg el nem értük az utolsó utasítást, amikor is a program mőködése leállt. Nem minden probléma oldható meg ilyen egyszerően. Sıt, leggyakrabban nem írható fel a megoldás ilyen egyszerő lépésekbıl. Vegyük az alábbi programozási feladatot: írjuk ki az elsı 5 négyzetszámot a képernyıre (négyzetszámnak nevezzük azokat a számokat, amelyek valamely más szám négyzeteként elıállíthatóak - ilyen pl. a 25). public void Main() Console.WriteLine( 1. négyzetszám = 1 ); Console.WriteLine( 2. négyzetszám = 4 ); Console.WriteLine( 3. négyzetszám = 9 ); Console.WriteLine( 4. négyzetszám = 16 ); Console.WriteLine( 5. négyzetszám = 25 ); A fenti program még a hagyományos szekvenciális mőködés betartásával íródott. De képzeljük el ugyanezt a programot, ha nem az elsı 5, de elsı 50 négyzetszámot kell kiírni a képernyıre! Vegyük észre, hogy a program sorai nagyjából egyformák, két ponton különböznek egymástól: hogy hányadik négyzetszámról van szó, és annak mennyi az értéke. Az hogy hányadik négyzetszámról van szó az mindig 1-gyel nagyobb az elızı értéktıl. Nézzük az alábbi pszeudó-utasítássorozatot: 1. I := 1 // az I változó értéke legyen 1 2. Írd ki: I,. négyzetszám =, I*I 3. I := I+1 // az I változó értéke növelve 1-el 4. ugorj újra a 2. sorra Ennek során kiíródik a képernyıre az 1. négyzetszám= 1, majd a 2. négyzetszám = 4, 3. négyzetszám = 9, stb A fenti kódot nevezhetjük kezdetleges ciklusnak is. A ciklusok olyan programvezérlési szerkezetek, ahol a program bizonyos szakaszát (sorait) többször is végrehajthatjuk. Ezt a szakaszt ciklus magnak nevezzük. A fenti példaprogramban ez a szakasz (a ciklusmag) a 2. és a 3. sorból áll. A 4. sor lényege, hogy visszaugrik a ciklusmag elsı utasítására, ezzel kényszerítve a számítógépet a ciklusmag újbóli végrehajtására. 62/312
Vegyük észre, hogy ez a ciklus végtelen ciklus lesz, hiszen minden egyes alkalommal a 4. sor elérésekor visszaugrunk a 2. sorra, így a program a végtelenségig fut. Ez jellemzıen helytelen viselkedés, az algoritmusok, és a programok egyik fontos viselkedési jellemzıje, hogy véges sok lépés végrehajtása után leállnak. Módosítsunk a kódon: 1. I := 1 // az I változó legyen 1 2. Ha az I értéke nagyobb, mint 5, akkor ugorj a 6. sorra 3. Írd ki: I,. négyzetszám =, I*I 4. I := I+1 // az I változó értéke növelve 1-el 5. ugorj újra a 2. sorra 6.. A 2. sorba egy feltétel került, mely ha teljesül, akkor befejezzük a ciklusmag végrehajtását, mivel kilépünk a ciklusból, a ciklus utáni elsı utasításra ugorva. Hogy ezen feltétel legelsı alkalommal is kiértékelhetı legyen, az 1. sorban beállítjuk az I változó értékét (értékadás). Valamint szintén figyeljük meg a 4. sort. Ezen sor is nagyon fontos, hiszen az I értéke e nélkül nem érhetné el a kilépéshez szükséges 6 értéket. A ciklusoknak tehát az alábbi fontos jellemzıjük van: Tartalmaznak egy utasításblokkot, amelyet akár többször is végrehajtanak (ciklusmag). Tartalmaznak egy feltételt (vezérlı feltétel), amely meghatározza, hogy kell-e még ismételni a ciklusmagot. A ciklusmag tartalmaz olyan utasítást, amelynek segítségével elıbb-utóbb elérjük azt az állapotot, hogy kiléphessünk a ciklusból. Tartalmaznak egy kezdıértékadást (a ciklus elıtt), amely biztosítja, hogy a ciklus feltétele már legelsı alkalommal is egyértelmően kiértékelhetı legyen. A C#-ban a legnépszerőbb ciklus a FOR ciklus: public void Main() int i; for (i=1;i<=5;i=i+1) Console.WriteLine( 0. négyzetszám = 1,i,i*i); A fenti példában a for kulcsszóval jelöljük, hogy ciklus szeretnénk írni. A for kulcsszó után zárójelben három dolgot kell megadni: kezdıérték beállítása ( i=1 ), ismétlési feltétel ( i<=5 ), léptetı utasítás ( i=i+1 ). 63/312
Formailag a három részt pontosvesszıvel kell elválasztani. A ciklusmagot pedig kapcsos zárójelek közé kell tenni (utasításblokk). A C# az alábbi módon értelmezi a for ciklust: 1. végrehajtja a kezdıértékadást, 2. kiértékeli a vezérlı feltételt, és ha HAMIS, akkor kilép a ciklusból, 3. végrehajtja a ciklusmagot, 4. végrehajtja a léptetı utasítást, 5. visszaugrik a 2. lépésre. Ha a vezérlı feltétel igaz, akkor végrehajtja a ciklusmag utasításait, ezért ebben az esetben a vezérlı feltételt a ciklusban maradás feltételének is nevezhetjük. Másik dolog, amit megfigyelhetünk: a vezérlı feltétel kiértékelése már legelsı alkalommal is a ciklusmag elıtt hajtódik végre. Ennek megfelelıen egy hibás kezdıértékadás eredményezheti azt is, hogy a ciklusmag egyetlen egyszer sem hajtódik végre. Például: for (i=10;i<=5;i=i+1) Console.WriteLine(i); A for ciklusra jellemzı, hogy tartozik hozzá egy ciklusváltozó, melyet az esetek többségében i -vel jelölünk, elsısorban hagyománytiszteletbıl. Ezen i változó felveszi a kezdıértékét, majd szintén az esetek többségében ezt az értéket minden ciklusmag lefutása után 1-el növeli, amíg túl nem lépjük a végértéket. A for ciklus egy nagyon gyakori alakja tehát felírható az alábbi módon ( cv jelöli a ciklusváltozót): for (cv = kezdıérték, cv <= végérték, cv = cv+1)... 64/312
Ennek megfelelıen elıre kiszámítható, hogy a ciklusmag hányszor hajtódik végre: for (i=1;i<=10;i++) Console.WriteLine(i); Ezen ciklus magja legelsı alkalommal az i=1, majd i=2,, i=10 értékekre hajtódik végre, összesen 10 alkalommal. Vegyük az alábbi programozási feladatot: kérjünk be 3 számot, és írjuk ki a képernyıre a 3 szám összegét. Az elsı megoldás még nem tartalmaz ciklust: int a,b,c,ossz; b = Int32.Parse( Console.ReadLine() ); c = Int32.Parse( Console.ReadLine() ); ossz = a+b+c; Console.WriteLine( A számok összege =0,ossz); Ugyanakkor, ha nem 3, hanem 30 számról lenne szó, akkor már célszerőtlen lenne a program ezen formája. Kicsit alakítsuk át a fenti programot: int a,ossz; ossz = 0; ossz = ossz+a; ossz = ossz+a; ossz = ossz+a; Console.WriteLine( A számok összege =0,ossz); A fenti program ugyanazt teszi, mint az elızı, de már látszik, melyik az utasításcsoport, amelyet 3-szor meg kell ismételni. Ebbıl már könnyő ciklust írni: int i,a,ossz=0; for(i=1;i<=3;i=i+1) ossz = ossz+a; Console.WriteLine( A számok összege =0,ossz); Sokszor ez a legnehezebb lépés, hogy egy már meglévı szekvenciális szerkezető programban találjuk meg az ismételhetı lépéseket, és alakítsuk át ciklusos szerkezetővé. 65/312
Programozási feladat: Készítsük el az elsı 100 egész szám összegét. =0,ossz); int i,ossz=0; for (i=1; i<=100 ; i=i+1) ossz = ossz + i; Console.WriteLine( Az elsı 100 egész szám összege Jegyezzük meg, hogy a ciklusokban az i=i+1 utasítás nagyon gyakori, mint léptetı utasítás. Ezt gyakran i++ alakban írják a programozók. A ++ egy operátor, jelentése növeld meg 1-gyel az értékét. A párja a -- operátor, amelynek jelentése: csökkentsd 1-gyel az értékét. Másik észrevételünk, hogy aránylag gyakori, hogy a ciklusmag egyetlen utasításból áll csak. Ekkor a C# megengedi, hogy ne tegyük ki a blokk kezdet és blokk vége jeleket. Ezért a fenti kis program az alábbi módon is felírható: =0,ossz); int i,ossz=0; for (i=1; i<=100 ; i++ ) ossz = ossz + i; Console.WriteLine( Az elsı 100 egész szám összege Magyarázat: a program minden lépésben hozzáad egy számot az ossz változóhoz, amelynek induláskor 0 a kezdıértéke. Elsı lépésben 1-et, majd 2-t, majd 3-t,, majd 100-t ad az ossz változó aktuális értékéhez, így állítván elı az elsı 100 szám összegét. Megjegyzés: a fenti feladat ezen megoldása, és az alkalmazott módszer érdekes tanulságokat tartalmaz. Ugyanakkor a probléma megoldása az elsı N négyzetszám összegképletének ismeretében egyetlen értékadással is megoldható: ossz= 100*101 / 2; Programozási feladat: Határozzuk meg egy szám faktoriálisának értékét. A számot kérjük be billentyőzetrıl. szam, fakt ); int szam,i,fakt=1; for (i=1; i<=szam ; i++ ) fakt = fakt * i; Console.WriteLine( A(z) 0 szám faktoriálisa = 1, Magyarázat: pl. a 6 faktoriális úgy számolható ki, hogy 1*2*3*4*5*6. A program a fakt változó értékét megszorozza elıször 1-el, majd 2-vel, majd 3-al,, végül magával a számmal, így számolva ki lépésrıl lépésre a végeredményt. 66/312
Megjegyzés: a szam növelésével a faktoriális értéke gyorsan nı, ezért csak kis számokra (kb. 15-ig) tesztelhetjük csak a fenti programot. Programozási feladat: Határozzuk meg egy szám pozitív egész kitevıjő hatványát! A számot, és a kitevıt kérjük be billentyőzetrıl! int szam,kitevo,i,ertek=1; kitevo = Int32.Parse( Console.ReadLine() ); for (i=1; i<=kitevo ; i++ ) ertek = ertek * szam; Console.WriteLine( A(z) 0 szám 1. hatványa = 2, szam,kitevo,ertek); Magyarázat: pl. 4 harmadik hatványa kiszámolható úgy, hogy 4*4*4. A program a ertek változó értékét megszorozza annyiszor a szam -al, ahányszor azt a kitevı elıírja. Programozási feladat: Határozzuk meg egy szám osztóinak számát! A számot kérjük be billentyőzetrıl! 1.,szam,db); int szam,i,db=0; for (i=1; i<=szam ; i++ ) if (szam%i==0) db++; Console.WriteLine( A(z) 0 szám osztóinak száma: Magyarázat: a 10 lehetséges osztói az 1..10 tartományba esnek. A módszer lényege, hogy sorba próbálkozunk a szóba jöhetı számokkal, mindegyiket ellenırizve, hogy az osztója-e a számnak, vagy sem. Az oszthatóságot a % operátorral ellenırizzük. A % operátor meghatározza a két operandusának osztási maradékát (pl. a 14%4 értéke 2 lesz, mert 14 osztva 4-gyel 2 osztási maradékot ad). Amennyiben az osztási maradék 0, úgy az i maradék nélkül osztja a szam változó értékét, ezért az i változóban lévı érték egy osztó. Minden osztó-találat esetén növeljük 1-gyel a db változó értékét. Programozási feladat: Írassuk ki a képernyıre egy szám összes többszörösét, amelyek nem nagyobbak, mint 100, csökkenı sorrendben! A számot kérjük be billentyőzetrıl! int szam,i; for (i=100; i>=szam ; i-- ) if (i % szam == 0) Console.WriteLine( 0,i); Magyarázat: elindulunk 100-tól, visszafele haladunk, minden lépésben csökkentve a ciklusváltozónk értékét (i--). Minden lépésben megvizsgáljuk, hogy a szóban forgó i többszöröse-e az eredeti számnak. Ez akkor teljesül, ha a szám maradék nélkül 67/312
osztja az i -t. Valahányszor találunk ilyen i értéket, mindannyiszor kiírjuk azt a képernyıre. Ugyanezen feladat megoldható hatékonyabban, ha figyelembe vesszük, hogy ezen számok egymástól szam távolságra vannak: int szam,i,max; max = (100/szam)*szam; for (i=max; i>=szam ; i=i-szam ) if (i % szam == 0) Console.WriteLine( 0,i); Magyarázat: a 100/szam osztási mővelet, mivel két egész szám típusú érték között kerül végrehajtásra automatikusan egész osztásnak minısül. Ha a szam értéke pl. 7, akkor a 100/szam eredménye 14. Ha ezt visszaszorozzuk a szam -al, akkor megkapjuk a legnagyobb olyan értéket, amely biztosan többszöröse a szam -nak, és nem nagyobb mint 100, és a legnagyobb ilyen többszöröse a szam -nak, amely nem nagyobb mint 100 ( max ). Ez lesz a kiinduló értéke a ciklusváltozónak. A további többszörösöket úgy kapjuk, hogy a kiinduló értéket minden cikluslépésben csökkentjük szam -al. 68/312