Ciklus (programozás)

A Wikipédiából, a szabad enciklopédiából

A ciklus, vagy iteráció a számítógép-programozás és az algoritmusok egyik alapvető eszköze, amely az ismétlődő (azonos vagy hasonló) tevékenységek megvalósítására szolgál. A ciklus beépítését a programba ciklusszervezésnek is nevezik. A ciklust az egyes programozási nyelvek különböző kulcsszavakkal valósítják meg.

A ciklus részei[szerkesztés]

Alább egy egyszerű BASIC nyelvű ciklust láthatunk, amelyik 2-től 5-ig egyesével haladva kiírja a számokat és a négyzetüket:

FOR i=2 TO 5 STEP 1
    PRINT i, " négyzete: ", i*i
NEXT i

Ebben a ciklusban az i a ciklusváltozó, amely a ciklus kezdetén értéket kap. A szerepe kettős: egyrészt vezérli a ciklust, mivel a ciklusváltozó értékétől függ, hogy folytatódik-e a futás, másrészt a pillanatnyi értéket a program felhasználja. A ciklus futásának kezdetén tehát az i értéke 2 lesz; mivel ez kisebb, mint 5, ezért a ciklusmag lefut, vagyis a program kiírja a képernyőre a

2 négyzete: 4

sort. Ezután i értéke 3-ra változik, és ismét lefut. Ez addig ismétlődik, amíg az i értéke 5 lesz. Ekkor még utoljára lefut a ciklus, tehát a képernyőn még megjelenik az 5 négyzete is, majd az i értéke 6 lesz, de ez már nagyobb, mint 5, ezért a program kilép a ciklusból, és a futás a következő sorral folytatódik. Végeredményben a következőt írja ki a program:

2 négyzete: 4
3 négyzete: 9
4 négyzete: 16
5 négyzete: 25

Ebben a ciklusban a ciklusváltozónak van kezdőértéke (2), végértéke (5) és lépésköze (1). Nem minden ciklusban van ciklusváltozó. Egyes programnyelvekben több is lehet belőle. A ciklus első sorát ciklusfejnek nevezik. Általában ez vezérli a ciklust; itt kap helyet a ciklusváltozó(k) definíciója, valamint a futási feltétel is. A középső sor a ciklusmag – ismert még a ciklus törzse néven is –, vagyis a ténylegesen ismétlődő utasítások meghatározása. A fenti példában ciklus magja mindössze egyetlen utasításból áll. Végül az utolsó sor a ciklusvég; az elöltesztelő és a számláló ciklus esetében ez puszta lezáró utasítás, amely a ciklusmag végét jelzi (egyes programnyelvekben esetenként el is maradhat), a hátultesztelő ciklus esetén pedig ez a rész tartalmazza a futási feltételt.

Ciklus nélkül a program:

PRINT 2, " négyzete: ", 2*2
PRINT 3, " négyzete: ", 3*3
PRINT 4, " négyzete: ", 4*4
PRINT 5, " négyzete: ", 5*5

Bár ez a programocska ciklus nélkül is megírható, azonban eltérő adatok esetén teljesen át kellene írni, míg a ciklust tartalmazó változatban csupán a kiindulási adatokat kell módosítani. A példában mindössze 4 számot és a négyzeteiket kellett kiíratni. Nagyobb intervallum esetén a ciklus tömörsége nyilvánvaló, szemben a ciklus nélküli program terjengősségével.

Típusai[szerkesztés]

Feltételes ciklus (while)[szerkesztés]

A feltételes ciklus olyankor használatos, amikor nem feltétlenül ismert előre, hogy futásidőben hányszor kell a ciklusnak lefutnia (például egy számítógép lemezkatalógusában lévő fájlok listázása). Az viszont ismert, hogy milyen feltétel teljesülése esetén kell a programnak tovább futnia vagy leállnia. (Ez a kettő egymással ekvivalens, hiszen a további futás, illetve a leállás feltétele egymás tagadása.) A feltétel teljesülését vizsgálhatjuk a ciklusfejben vagy a ciklusvégben is; a különbség abban fog állni, hogy legalább egyszer lefut-e a ciklus.

Elöltesztelő ciklus[szerkesztés]

Az elöltesztelő ciklus sémája:

Ismételd, amíg a feltétel igaz
    utasítások
ciklus vége

Az elöltesztelő ciklus tehát először megvizsgálja, hogy a feltétel fennáll-e. Ha igen, akkor lefuttatja a ciklusmagot, és újból kezdődik; ha nem, akkor a program a ciklus utáni ponton folytatódik, azaz a ciklusmag kimarad. Lehetséges tehát, hogy az elöltesztelő ciklus egyszer sem fog lefutni.

Az elöltesztelő ciklus tipikus példája az adatállományok beolvasása; előfordulhat ugyanis, hogy az állomány üres, és ilyenkor nincs mit beolvasni. Hasonló a helyzet a könyvtárak listázásakor is, hiszen a könyvtárban nem biztos, hogy van állomány.

Az elöltesztelő ciklus tipikus kulcsszava a while. Általános alakja a következő:

while (feltétel) {
    utasítás(ok);
}

Példa (awk):

i = 0;
while (i < 10) {
    print i;
    i++;
}

Hátultesztelő ciklus[szerkesztés]

A hátultesztelő ciklus sémája:

Ismételd
  utasítások
amíg a feltétel igaz

Mivel a feltételvizsgálat a ciklusmag után áll, ezért a hátultesztelő ciklus legalább egyszer mindenképpen lefut.

A konkrét programnyelvi megvalósítástól függ, hogy a hátultesztelő ciklusban a folytatás vagy a kilépés feltételét kell-e megadni. A ciklus az első esetben addig fut, amíg a ciklusvégben megadott feltétel igaz (ennek tipikus kulcsszava a while), a másik esetben pedig addig, amíg igazzá nem válik (tipikus kulcsszava az until).

Általános alakja a következő:

do {
    utasítás(ok);
} while (feltétel);

Jellemző példa a hátultesztelő ciklusra az adatbevitel ellenőrzése. Tegyük fel, hogy regisztrálni szeretnénk egy ingyenes postafiókot az interneten. A szerveren futó programnak először ellenőriznie kell, hogy szabad-e még a választott azonosító, majd azt is, hogy kitöltöttük-e az összes kötelező adatmezőt, és addig kell ismételnie ezt a lépést, amíg az összes adat helyessé nem válik. Sok szolgáltató ezt egy lépésben végzi el, vagyis először ki kell töltenünk az űrlapot, aztán tudjuk meg, hogy szabad-e még az azonosító. Ebben az esetben a ciklus a következőképpen nézhet ki:

Ismételd
    Olvasd be az adatokat
    Ha az azonosító foglalt, írd ki, és add vissza az űrlapot
    Ha egy kötelező mező hiányzik, írd ki, és add vissza az űrlapot
amíg hibás az űrlap

Mivel a feltételt csak az adatbeolvasás után ellenőrzi a program, ezért az adatok beolvasása legalább egyszer mindenképpen megtörténik. Ha sikerült elsőre jól kitölteni az adatlapot, akkor tovább lehet lépni.

Zavart okozhat a szöveges leírásokban a magyar amíg szó két, egymással lényegében ellentétes jelentése. Az algoritmusok leírásában az amíg szót mindig a 'mialatt', 'miközben' értelmében használjuk, ami az angol while-nak felel meg. A fenti eljárást úgy is fogalmazhatnánk, hogy „olvasd be az adatokat, ahányszor kell, amíg jók lesznek” – ebben az esetben az amíg szót az angol until értelmében használtuk, ami szintén lehet a hátultesztelő ciklus kulcsszava. Ezért előfordulhat, hogy a programkódban épp a szöveges algoritmusban látható feltétel tagadását kell megfogalmaznunk.

Számláló ciklus (for)[szerkesztés]

A számláló ciklus általánosságban olyan elöltesztelő ciklust jelent, amely egy felsorolható típus adott intervallumán léptet végig, speciálisan egész számokon. Üres intervallumra nem fut le.

C-ben a for ciklus majdnem teljesen ekvivalens az elöltesztelő ciklussal, de más nyelvekben nem feltétlenül van ez így: a for ciklus átírható más ciklussá, de visszafelé általában nem. A for ciklusnak kisebb lehet a kifejezőereje, de könnyebb olvasni: ezért a használata javasolt minden olyan esetben, ahol csak lehetséges.

Számok összegzése forciklussal C-ben:

int sum = 0; // Kezdetben a szumma 0
int i;
for (i = 0; i < 200; ++i) // Kezdőérték; feltétel; növelés
    sum += i; // Az i hozzáadása sum-hoz

Számok összegzése whileciklussal C-ben:

int sum = 0;
int i = 0;
while (i < 200) {
    sum += i;
    ++i;
}

Iteráló ciklus (for in, foreach)[szerkesztés]

Egyes nyelvekben a foreach kulcsszót kell használni. Arra való, hogy egy egy tároló (pl. tömb, lista, bináris fa stb.) elemeit bejárják és minden egyes elemre végrehajtják a ciklus törzsében található utasítást illetve utasításokat. Erre egy egyszerű példa PHP-ban:[1]

$arr = array(1, 2, 3, 4);   // Tömb az 1, 2, 3, 4 számokból
foreach ($arr as &$value) { // A tömb bejárása
    $value = $value * 2;    // Minden elem megszorzása 2-vel
}
// A $arr elemei itt rendre 1, 2, 3 és 4

Egy 'lista' bejárása (AutoLISP):

(foreach n '(a b c) (print n))

Egyenértékű a

(print a)
(print b)
(print c)

megadásával. Mivel egy lista elemszáma nem kötött, az előny nyilvánvaló. Ráadásul a végrehajtandó utasítások száma sincs korlátozva (a fenti példában csak egy print szerepel).

Néhány programozási nyelv a for inutasítással valósítja meg a tömbök, listák bejárását, pl. az awk: (már létezik a szavak nevű tömb)

for (x in szavak) {
    if (length(x) > 12) { # ha a szó hosszabb, mint 12 karakter
        print x           # akkor kiírjuk
    }
}

Ez a példa egy MS-DOS kötegelt állományában szerepel, és törli az aktuális könyvtárból az ideiglenes állományokat néhány tipikus kiterjesztés alapján:

for %%c in (*.bak *.tmp *.$$$ *.wbk) do del %%c

Beavatkozás a ciklus menetébe[szerkesztés]

Egy ciklus menetébe beavatkozni többféleképpen lehet:

  • a ciklusváltozó növelésével, ill. csökkentésével;
  • a break utasítással;
  • a continue utasítással.

Mindhárom módszer használható a for és a while segítségével létrehozott ciklusokban. Az egyetlen követelmény, hogy a ciklus törzsén belül kell elhelyezni,különben hibajelzést kapunk.

Ciklusváltozó módosítása[szerkesztés]

Gyakran előfordul, hogy n darab különböző számot kell generálnunk vagy bekérnünk. Ilyenkor kézenfekvő a for ciklus használata, ugyanakkor ismétlődés vagy hibás adat esetén szükség lehet a visszalépésre.

using namespace std;
set<int> lottoSzamok;
for (int i = 0; i < 5; ++i) {
    int aktualis;
    cout << (i + 1) << ". lottó szám: ";
    cin >> aktualis;
    if (aktualis >= 1 && aktualis <= 90 && lottoSzamok.find(aktualis) == lottoSzamok.end()) {
        lottoSzamok.insert(aktualis);
    } else {
        cerr << "A szám nem megfelelő vagy már volt." << endl;
        --i;  // visszalépés
    }
}

Az ADA nyelvben ez nem áll fent, a for ciklus itt egy szigorúan kötött, ellenőrzött konstrukció, például nem lehet a ciklusváltozót változtatni:

for I in 1..200 loop
    SUM := SUM + I;        -- első 200 szám összege, ADA for ciklussal
end loop;

A break utasítás[szerkesztés]

Ez a speciális, ciklusok esetén használható utasítás azonnal megszakítja a ciklus futását, és függetlenül a ciklus vezérlő feltétel értékétől, függetlenül minden mástól azonnal (erőszakosan) befejezi a ciklus futását. A program futása ettől nm szakad meg, folytatódik a ciklus után következő utasítás végrehajtásával. A break és a ciklus vége közötti utasítások nem lesznek végrehajtva.

A continue utasítás[szerkesztés]

A break-hez hasonló utasítás a continue. Ennek használata esetén a ciklus futása nem szakad meg (fejeződik be) mint a break, sőt, ellenkezőleg. A continue hatására a ciklusmag maradék utasításai ebben a menetben már nem hajtódnak végre, de a ciklus futása folytatódik, mintha elértük volna a ciklusmag végét, vagyis újra a vezérlő feltétel kiértékelése következik. Például szeretnénk kihagyni az 1234-es irányítószámot:

kimarad = 1234;
start = 1230;
end = 1240;

for (i = start; i <= end; i++){
    if (i == kimarad) {continue} else {
        print i, "\n";
    }
}

A continue utasítás pl. menük „kiszürkítésére” is jól használható.

Néhány nevezetes alkalmazás[szerkesztés]

Egymásba ágyazott ciklusok[szerkesztés]

Egymásba ágyazott ciklusokat tipikusan akkor szokás használni, amikor egy területen (például egy kép képpontjain, vagy egy mátrixon) kell műveleteket végezni. Bizonyos rendező algoritmusok (pl. buborékrendezés) is egymásba ágyazott ciklusokat használnak. Egy számtáblázat celláinak az összegzése pl. így néz ki:

int **t, n, m;
// <-- Beolvasás n-be, m-be (méretek) és t-be
int sum = 0;
for (int i = 0; i < n; ++i)
    for (int j = 0; j < m; ++j)
        sum = sum + t[i][j];
std::cout << "A cellák összege: " << sum << std::endl;

A ciklusokat használó programok rendszerint korlátozzák a ciklusok egymásba ágyazhatóságának számát. Egyrészt a ciklusok egymásba ágyazásával a kiértékelések száma hatványozódik, ami drámaian növeli a program futásidejét (kibírhatatlanul lassúvá válik). Másrészt pedig a kiértékeléshez használt veremtár betelhet a sok átmeneti adattól, ami pedig a program működését akadályozza meg (elszáll a program).

A végtelen ciklus[szerkesztés]

A végtelen ciklus olyan ciklus, amelynek futása külső esemény bekövetkezte nélkül sohasem zárulna le.Végtelen ciklus történhet egy programban előre eltervezetten vagy pedig a program hibájából is: a programozónak más volt a szándéka, de mégis olyan ciklust sikerült írnia, amely néhány helyzetben (vagy soha) nem tud kilépni a ciklusból. Általában vagy a hibás feltétel megadása vagy a feltétel vizsgálatának hibája okozza az ilyen végtelen ciklust.

Példa:

int x = 1; /* számláló kezdőértéke */
while (x != 10) { /* csináld míg x nem lesz 10 */
    /*
        A ciklus törzse:
        itt helyezkedne el a többször futtatandó kódunk
    */
    x = x + 2; /* x növelése kettesével */
}

A program futása során az x a következő értékeket veszi fel: 1, 3, 5, 7, 9, 11, … így soha nem lesz 10, ezért a ciklus örökké fut.

Előre tervezett végtelen ciklus[szerkesztés]

Végtelen ciklust akkor terveznek be egy programba, ha azt szeretnék elérni, hogy a program bizonyos része örökké fusson. Ilyen eset például a menük kirajzolása a képernyőre, adatbevitel a parancsértelmezők készenléti jelére stb. Az előre tervezett végtelen ciklusból is ki lehet lépni a break utasítással.

// elöltesztelő végtelen ciklus
while (1) {
    // itt helyezkedne el a többször futtatandó kódunk
    // ami a végtelenségig ismétlődik
}
 
// hátultesztelő ciklus
do {
    // itt helyezkedne el a többször futtatandó kódunk
    // ami a végtelenségig ismétlődik
} while (true);

// "amíg-nem" végtelen ciklus
until (false) {
    // itt helyezkedne el a többször futtatandó kódunk
    // ami a végtelenségig ismétlődik
}

// hátultesztelő "amíg-nem" ciklus
do {
    // itt helyezkedne el a többször futtatandó kódunk
    // ami a végtelenségig ismétlődik
} until (1);

// végtelen for ciklus
for ( ; ; ) {
    // itt helyezkedne el a többször futtatandó kódunk
    // ami a végtelenségig ismétlődik
}

Vakciklus[szerkesztés]

Vak- vagy várakozó ciklusnak az olyan ciklust nevezik, amelynek a magja üres. Az ilyen ciklusokat a program futásának lassítására vagy várakozáshoz szokták használni.

Például az ABC 80 BASIC nyelve nem tartalmazott várakozó utasítást. Időnként szükség lehet egy program futásának szándékos szüneteltetésére. Ilyen lehet például a képernyőre írás utáni várakozás vagy egy játékprogram, ahol különböző nehézségi szintek vannak megadva. Az alábbi számláló ciklus elszámol tízezerig és ezzel lassítja a futást (időt adva a felhasználónak az üzenet elolvasására.):

FOR I=10 TO 10000
NEXT I

Hasonló példa a Pascalban írt alábbi hátultesztelő végtelen ciklus is, amely egy billentyű lenyomására várakozik (addig ismétli a semmit, amíg egy billentyűt meg nem nyomnak).

repeat until keypressed;

A többfeladatos operációs rendszerek általános elterjedése óta a vakciklusok nem igazán jó megoldások időzítésre vagy várakozásra, mert pazarolják a processzort mint erőforrást. Azonban manapság is szükség lehet erre a módszerre, amikor a feladatváltás időköltségénél kevesebbet szeretnénk várakozni.

Üres ciklus[szerkesztés]

Üresnek neveznek egy ciklust akkor, ha a magja egyszer sem fut le.

Ciklus ugró utasítással (goto)[szerkesztés]

Ugró utasítással akkor lehetséges ciklust szervezni, ha az adott nyelv támogatja a címkék elhelyezését, továbbá van olyan utasítás, amellyel egy már megadott címkére lehet ugrani. Ez azt jelenti, hogy a program az adott címkétől folytatódik. Használata kerülendő.

Az alábbi program egy ciklust valósít meg BASIC-ben a GOTO ugró utasítás segítségével.

10 PRINT "Meddig irjam ki a szamokat? ";
20 INPUT N
30 I = 1
40 IF I > N THEN GOTO 80
50 PRINT I
60 I = I + 1
70 GOTO 40
80 REM "PROGRAM VEGE"

Kapcsolódó szócikkek[szerkesztés]

Források[szerkesztés]

  1. Archivált másolat. [2009. február 13-i dátummal az eredetiből archiválva]. (Hozzáférés: 2010. január 19.)