2. FEJEZET | Tartalom | 4. FEJEZET |
3. FEJEZET:
Vezérlési szerkezetek
Egy nyelv vezérlésátadó utasításai az egyes műveletek végrehajtási sorrendjét határozzák meg. A korábbi példákban már találkoztunk a legfontosabb vezérlési szerkezetekkel. Ebben a fejezetben teljessé tesszük a képet és a korábbiaknál pontosabban írjuk le az egyes szerkezetek tulajdonságait.
3.1. Utasítások és blokkok
Egy olyan kifejezés, mint x = 0, i++ vagy printf(...) utasítássá válik, ha egy pontosvesszőt írunk utána. Pl:
x = 0;
i++;
printf(...);
A C nyelvben a pontosvessző az utasításlezáró jel (terminátor), szemben a Pascal nyelvvel, ahol elválasztó szerepe van.
A {} kapcsos zárójelekkel deklarációk és utasítások csoportját fogjuk össze egyetlen összetett utasításba vagy blokkba, ami szintaktikailag egyenértékű egyetlen utasítással. A kapcsos zárójelek használatára jó példa a függvények utasításait összefogó zárójelpár, vagy az if, else, while, for, ill. hasonló utasítások utáni utasításokat összefogó zárójelpár. A változók bármelyik blokk belsejében deklarálhatók, erről bővebben a 4. fejezetben írunk. A blokk végét jelző jobb kapcsos zárójel után soha nincs pontosvessző.
3.2. Az if-else utasítás
Az if-else utasítást döntés kifejezésére használjuk. Formálisan az utasítás szintaxisa a következő:if (kifejezés) 1. utasítás else 2. utasításahol az else rész opcionális. Az utasítás először kiértékeli a kifejezést, és ha ennek értéke igaz (azaz a kifejezés értéke nem nulla), akkor az 1. utasítást hajtja végre. Ha a kifejezés értéke hamis (azaz nulla) és van else rész, akkor a 2. utasítás hajtódik végre.
Mivel az if egyszerűen csak a kifejezés számértékét vizsgálja, ezért lehetőség van a program rövidítésére. A legnyilvánvalóbb ilyen lehetőség, ha az
if (kifejezés != 0)helyett az
if (kifejezés)utasítást írjuk. Egyes esetekben ez a forma természetes és nyilvánvaló, máskor viszont elég áttekinthetetlenné teszi a programot.
Mivel az if-else szerkezet else ága opcionális egymásba ágyazott if-else szerkezeteknél, hiányzó else ágak esetén nem világos, hogy a meglévő else ág melyik if utasításhoz tartozik. Például az
if (n > 0) if (a > b) z = a; else z = b;programrészletben az else a belső if utasításhoz tartozik, amit a programrész tagolása is mutat. Általános szabályként megfogalmazhatjuk, hogy az else mindig a hozzá legközelebb eső, else ág nélküli if utasításhoz tartozik. Ha nem így szeretnénk, akkor a kívánt összerendelés kapcsos zárójelekkel érhető el, mint pl. az
if (n > 0) { if (a > b) z = a; } else z = b;szerkezetben. A nem egyértelmű helyzet különösen zavaró az olyan szerkezetekben, mint az
if (n >= 0) for (i = 0; i < n; i++) if (s[i] > 0) { printf("..."); return i; } else /* ez így hibás */ printf("hiba: n értéke negatív\n") ;A tagolás egyértelműen mutatja a szándékot, de a fordítóprogram ezt nem veszi figyelembe, és minden figyelmeztető jelzés nélkül a belső if utasításhoz kapcsolja az else ágat. Az ilyen hibák nagyon nehezen deríthetők fel, és legegyszerűbben úgy kerülhetjük el azokat, ha a beágyazott if utasítást kapcsos zárójelekkel határoljuk.
Vegyük észre, hogy a z=a értékadás után az
if (a > b) z = a; else z = b;szerkezetben pontosvessző van. Ennek az az oka, hogy az if utasítást egy újabb utasítás követi (a z=a), amit pontosvesszővel kell zárni.
3.3. Az else-if utasítás
Azif (kifejezés) utasítás else if (kifejezés) utasítás else if (kifejezés) utasítás else if (kifejezés) utasítás . . . else utasításszerkezet olyan gyakran fordul elő, hogy mindenképpen megérdemli a részletesebb elemzést. Ez a szerkezet adja a többszörös döntések (elágazások) programozásának egyik legáltalánosabb lehetőségét. A szerkezet úgy működik, hogy a gép sorra kiértékeli a kifejezéseket és ha bármelyik ezek közül igaz, akkor végrehajtja a megfelelő utasítást, majd befejezi az egész vizsgáló láncot. Itt is, mint bárhol hasonló esetben, az utasítás helyén kapcsos zárójelek között elhelyezett blokk is állhat.
A szerkezet utolsó (if nélküli) else ága alapértelmezés szerint a „fentiek közül egyik sem” esetet kezeli. Néha ilyenkor semmit sem kell csinálni, ezért a szerkezetet záró
else utasításág hiányozhat, vagy valamilyen lehetetlen eset érzékelésével hibaellenőrzésre használható.
A következő példában egy háromirányú elágazás (döntés) látható. A feladat, hogy írjunk egy bináris keresést végző függvényt, amely egy rendezett v tömbben megkeresi az x értéket és megadja annak helyét (indexét). A függvény x előfordulási helyével (ami 0 és n-1 közé eshet) tér vissza, ha x megtalálható v elemei között és -1 értékkel, ha nem.
A bináris keresési algoritmus az x bemeneti értéket először összehasonlítja a rendezett v tömb középső elemével. Ha x kisebb a középső elemnél, akkor a keresés a táblázat alsó felében, ha nem, akkor a felső felében folytatódik. Mindkét esetben a következő lépés, hogy x-et összehasonlítjuk a táblázat megfelelő felében (alsó vagy felső) lévő középső elemmel. A két egyenlő részre osztás addig folytatódik, amíg a keresett elemet meg nem találtuk, vagy a tartomány mérete nulla nem lesz (üres tartomány). A binsearch kereső függvénynek három argumentuma lesz, a keresett x, a rendezett v tömb és a tömb elemeinek n száma. A program:
/* binsearch: megkeresi x értékét a növekvő irányba rendezett v[0]...v[n-1] tömbben */ int binsearch(int x, int v[ ], int n) { int also, felso, kozep; also = 0; felso = n - 1; while (also <= felso) { kozep = (also + felso) / 2; if (x < v[kozep]) felso = kozep - 1; else if (x > v[kozep]) also = kozep + 1; else /* megtalálta */ return kozep; } return -1; /* nem találta meg */ }A program minden lépésében az alapvető döntés az, hogy x kisebb, nagyobb vagy egyenlő a v[kozep] középső eleménél, és ezt a döntési sort egyszerűen valósíthatjuk meg az else-if szerkezettel.
3.1. gyakorlat. A bináris kereső program a ciklus belsejében két vizsgálatot végez, de egy is elegendő lenne (a futási idő csökkentése érdekében érdemes minden lehetséges műveletet a ciklusmagon kívül elvégezni). Írja meg a program olyan változatát, amelyben a cikluson belül csak egy vizsgálat van és hasonlítsa össze a kétféle változat futási idejét!
3.4. A switch utasítás
A switch utasítás is a többirányú programelágaztatás egyik eszköze. Az utasítás úgy működik, hogy összehasonlítja egy kifejezés értékét több egész értékű állandó kifejezés értékével, és az ennek megfelelő utasítást hajtja végre. A switch utasítás általános felépítése:switch (kifejezés) { case állandó kifejezés: utasítások case állandó kifejezés: utasítások . . . default: utasítások }Mindegyik case ágban egy egész állandó vagy állandó értékű kifejezés található, és ha ennek értéke megegyezik a switch utáni kifejezés értékével, akkor végrehajtódik a case ágban elhelyezett egy vagy több utasítás. Az utolsó, default ág akkor hajtódik végre, ha egyetlen case ághoz tartozó feltétel sem teljesült. A default ág opcionális, ha elhagyjuk és a case ágak egyike sem teljesül, akkor semmi sem történik. A case ágak és a default ág tetszőleges sorrendben követhetik egymást.
Az 1. fejezetben leírtunk egy olyan programot, amely megszámolta az egyes számjegyek, üres helyek és más karakterek előfordulását. A programban if-else if-else szerkezetet használtunk, most elkészítjük a switch utasítással felépített változatát.
#include <stdio.h> main( ) /* számok, üres helyek és mások számolása */ { int c, i, nures, nmas, nszam[10]; nures = nmas = 0; for (i = 0; i < 10; i++) nszam[i] = 0; while ((c = getchar( )) != EOF) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': nszam[c-'0']++; break; case ' ': case '\n': case '\t': nures++; break; default: nmas++; break; } } printf("számok ="); for (i = 0; i < 10; i++) printf(" %d", nszam[i]); printf(", üres hely = %d, más = %d\n", nures, nmas); return 0; }A break utasítás hatására a vezérlés azonnal abbahagyja a további vizsgálatokat és kilép a switch utasításból. Az egyes case esetek címkeként viselkednek, és miután valamelyik case ág utasításait a program végrehajtotta, a vezérlés azonnal a következő case ágra kerül, hacsak explicit módon nem gondoskodunk a kilépésről. A switch utasításból való kilépés legáltalánosabb módja a break és return utasítással való kilépés. A break utasítás a while, for vagy do utasításokkal szervezett ciklusokból való kilépésre is használható, amint erről a későbbiekben még szó lesz.
Az egyes case ágakon való folyamatos végighaladás nem egyértelműen előnyös. Pozitív oldala, hogy több esethez azonos tevékenység rendelhető (mint a példában, ahol az összes számjegyhez azonos tevékenységet rendeltünk). De ebből következik, hogy minden case ágat break utasítással kell lezárni, nehogy a vezérlés a következő case ágra kerüljön. Az egyik case ágról a másikra való lépkedés nem túl ésszerű, mert a program módosítása esetén a vezérlés széteshet. Azokat az eseteket leszámítva, amikor több case ághoz közös tevékenység tartozik, érdemes kerülni az egyes case ágak közötti átmenetet.
A program áttekinthetősége érdekében akkor is helyezzünk el break utasítást a programban, ha az utolsó ágban vagyunk (mint a példában, ahol a default ág is tartalmaz break utasítást), annak ellenére, hogy ez logikailag szükségtelen. Ha valamikor később a vizsgálati sort újabb case ágakkal egészítjük ki, ez a defenzív programozási stílus előnyös lesz.
3.2. gyakorlat. Írjunk escape(s, t) néven függvényt, amely a t karaktersorozatot az s karaktersorozat végéhez másolja és a másolás során a láthatatlan karaktereket (pl. új sor, tabulátor) látható escape sorozatokká (\n, \t) alakítja! A programot a switch utasítással írjuk meg! Készítsük el a függvény inverzét is, amely az escape sorozatokat a tényleges karakterekké alakítja!
3.5. Ciklusszervezés while és for utasítással
Korábban már találkoztunk a while és for utasításokkal szervezett ciklusokkal. Awhile (kifejezés) utasításszerkezetben a program először kiértékeli a kifejezést. Ha annak értéke nem nulla (igaz), akkor az utasítást végrehajtja, majd a kifejezés újra kiértékelődik. Ez a ciklus mindaddig folytatódik, amíg a kifejezés nullává (hamissá) nem válik, és ilyen esetben a program végrehajtása az utasítás utáni helyen folytatódik.
A for utasítás általános szerkezete:
for (1. kifejezés; 2. kifejezés; 3. kifejezés)ami teljesen egyenértékű a while utasítással megvalósított
utasítás
1. kifejezés while (2. kifejezés) { utasítás 3. kifejezés }szerkezettel, kivéve a continue utasítás viselkedését, amivel a 3.7. pontban foglalkozunk.
Szintaktikailag a for utasítás mindhárom komponense kifejezés. Leggyakrabban az 1. és 3. kifejezés értékadás vagy függvényhívás, és a 2. kifejezés egy relációs kifejezés. A három komponens bármelyike hiányozhat, de az őket lezáró pontosvessző kiírása ekkor is kötelező. Ha az 1. vagy 3. kifejezés hiányzik, akkor azokat egyszerűen elhagyjuk a for utasítást követő zárójelből. Ha a 2. (vizsgáló) kifejezés is hiányzik, akkor azt a gép úgy tekinti, hogy az állandóan igaz, és ezért a
for (;;) {
...
}
szerkezet egy végtelen ciklus, amiből feltehetőleg más módon (pl. break vagy return utasítással) kell kilépni.Teljesen a programozóra van bízva, hogy mikor használ while és mikor for utasítást a ciklusszervezéshez. Például a
while ((c = getchar( )) == ' ' || c == '\n' || c == '\t') ; /* ugorjon az üres karaktereknél */esetén nincs kezdeti értékadás vagy újbóli értékadás, ezért a while használata elég kézenfekvő.
A for utasítás egyszerű inicializálás és újrainicializálás esetén előnyös, mivel a ciklust vezérlő utasítások együtt, jól látható formában, a ciklusmag tetején helyezkednek el. Ez jól látszik a
for (i =0; i < n; i++)
...
szerkezetben, ami egyébként pl. egy tömb első n elemét feldolgozó programrész C nyelvű megfogalmazása. A programrész hasonló a FORTRAN DO utasításával vagy a Pascal for utasításával szervezett ciklushoz, annyi eltéréssel, hogy a C nyelvű for ciklusban a ciklusváltozó és a ciklus határa a ciklus belsejében változtatható, valamint hogy a ciklusváltozó a ciklusból való kilépés esetén is megtartja az értékét. Mivel a for ciklus komponensei tetszőleges kifejezések lehetnek, a for ciklus nem korlátozódik aritmetikai léptetésekre. Stiláris szempontból mégis helytelen, ha a for utasítás inicializáló és inkrementáló részébe a for-tól idegen számításokat helyezünk el. Célszerű a for utasítást kizárólag a ciklus vezérlésére fenntartani.
Nagyobb példaként bemutatjuk a számjegyekből álló karaktersorozatot számmá alakító atoi függvény egy másik változatát. Ez kissé általánosabb a 2. fejezetben bemutatott változatnál: képes a karaktersorozat bevezető üres helyeinek és a szám esetleg kiírt + vagy - előjelének kezelésére is. (A 4. fejezetben ismertetjük az atof függvényt, ami ugyanezt a feladatot lebegőpontos számokkal valósítja meg.)
A program szerkezete a bemeneti adatok struktúráját tükrözi:
ugord át az üres helyeket, ha vannak olvasd be az előjelet, ha van olvasd be az egészrészt és konvertáldMinden programrész elvégzi a maga feladatát és a dolgokat „tiszta” állapotban adja át a következő programrésznek. Az egész folyamat akkor ér véget, ha beolvasódik az első olyan karakter, ami nem lehet része egy egész számnak.
#include <ctype.h> /* atoi: az s karaktersorozat számmá alakítása */ int atoi(char s[ ]) { int i, n, sign; for (i = 0; isspace(s[i]); i++) ; /* átugorja az üres helyeket */ sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; /* átugorja az előjelet */ for (n = 0; isdigit(s[i]); i++) n = 10 * n + (s[i]-'0'); return sign*n; }A standard könyvtár a sokkal részletesebben kimunkált strtol függvényt tartalmazza, ami karaktersorozatok long típusú egésszé alakítására használható. Ennek leírását a B. Függelék 5. pontjában találjuk meg.
A ciklus vezérlésének egy helyen tartása főleg akkor előnyös, ha több, egymásba ágyazott ciklus van. Ezt jól példázza a következő program, amely az egész számokból álló tömb elemeit rendezi a Shell-algoritmussal. A rendezési algoritmus (amelyet D. L. Shell dolgozott ki 1959-ben) alapgondolata, hogy kezdetben az egymástól távoli elemek kerülnek összehasonlításra, szemben az egyszerűbb, cserélgetős rendezési algoritmusokkal, ahol a szomszédos elemeket hasonlítják össze. Ezáltal a kezdetben meglévő nagyfokú rendezetlenség gyorsan csökken és a későbbi lépésekben kevesebb munkát kell végezni. A programban az éppen összehasonlított elemek közti távolság fokozatosan egyre csökken, és végül a rendezés az egyszerű, szomszédos elemeket cserélgető rendezésbe megy át.
/* shellsort: a v[0]...v[n-1] tömb rendezése növekvő sorrendbe */ void shellsort(int v[ ], int n) { int tavolsag, i, j, atm; for (tavolsag = n/2; tavolsag > 0; tavolsag /= 2) for (i = tavolsag; i < n; i++) for (j = i-tavolsag; j >= 0 && v[j] > v[j + tavolsag]; j -= tavolsag){ atm = v[j]; v[j] = v[j+tavolsag]; v[j+tavolsag] = atm; } }A programban három egymásba ágyazott ciklus van. A legkülső az összehasonlítandó elemek közötti távolságot szabályozza úgy, hogy azt n/2 értékről indítva minden lépésben a felére csökkenti, egészen addig, amíg nulla nem lesz. A középső ciklus folyamatosan végigmegy az elemeken. A legbelső ciklus összehasonlítja az egymástól tavolsag értékre lévő elemeket és ha nincsenek megfelelő sorrendben, akkor megcseréli azokat. Mivel a tavolsag az utolsó lépésben 1-re csökken, ezért a tömb végül is helyes sorrendbe rendeződik. Vegyük észre, hogy a külső ciklus for utasítása ugyanolyan alakú, mint a többi, bár ez a for utasítás nem végez aritmetikai léptetést.
A C nyelv egyik eddig még nem említett operátora a , (vessző), amelyet legtöbbször a for utasításban használunk. A vesszővel elválasztott kifejezéspárok balról jobbra értékelődnek ki, és az eredmény típusa, ill. értéke a jobb oldalon álló operandus típusával, ill. értékével egyezik meg. Így egy for utasítás lehetőséget ad az egyes részekben több kifejezés elhelyezésére, pl. két index szerint párhuzamosan végzett feldolgozás érdekében. Ezt a megoldást példázza a reverse(s) függvény, amelynek feladata, hogy az s karaktersorozatot saját helyén megfordítsa.
#include <string.h> /* reverse: az s karaktersorozat megfordítása helyben */ void reverse(char s[ ]) { int c, i, j; for (i = 0, j = strlen(s)-1; i < j; i++, j--) { c = s[i]; s[i] = s[j]; s[j] = c; } }A függvények argumentumait, a deklarációban lévő változókat stb. elválasztó vessző nem vesszőoperátor, nem garantált a balról jobbra irányú feldolgozásuk.
A vesszőoperátort viszonylag ritkán használják, és a legtöbb alkalmazás szoros kapcsolatban van egymással. Ilyen pl. a reverse függvényben a for ciklus vagy a makróban a többlépéses számítások egyetlen kifejezéssel történő megadása. A vessző operátor jól használható a reverse függvényben az egyes tömbelemek cseréjénél is. A
for (i = 0, j = strlen(s)-1; i < j; i++, j--) c = s[i], s[i] = s[j], s[j] = c;szerkezetben a cserélés műveletei egyetlen utasításba foghatók össze.
3.3. gyakorlat. Írjunk expand(s1, s2) néven függvényt, amely az s1 karaktersorozatban lévő rövidítéseket s2 karaktersorozatban feloldja (pl. az a-z helyett kiírja az abc...xyz teljes listát)! A program tegye lehetővé a betűk és számjegyek kezelését, és gondoljunk olyan rövidítések feloldására is, mint a-b-c, a-z0-9 vagy -a-z is! Célszerű a kezdő vagy záró - jelet literálisként kezelni.
3.6. Ciklusszervezés do-while utasítással
Amint azt már az 1. fejezetben elmondtuk, a while és a for utasítással szervezett ciklusok közös tulajdonsága, hogy a ciklus leállításának feltételét a ciklus tetején (a ciklusmagba belépés előtt) vizsgálják. Ezzel ellentétesen működik a C nyelv harmadik ciklusszervező utasítása, a do-while utasítás. A do-while utasítás a ciklus leállításának feltételét a ciklusmag végrehajtása után ellenőrzi, így a ciklusmag egyszer garantáltan végrehajtódik. Az utasítás általános formája:do utasítás while (kifejezés);A gép először végrehajtja az utasítást és csak utána értékeli ki a kifejezést. Ha a kifejezés értéke igaz, az utasítás újból végrehajtódik. Ez így megy mindaddig, amíg a kifejezés értéke hamis nem lesz, ekkor a ciklus lezárul és a végrehajtás az utána következő utasítással folytatódik. Az ellenőrzés módjától eltekintve a do-while utasítás egyenértékű a Pascal repeat-until utasításával.
A tapasztalatok azt mutatják, hogy a do-while utasítást sokkal ritkábban használják, mint a while vagy for utasítást, bár hasznos tulajdonságai miatt időről időre célszerű elővenni, mint pl. a következőkben bemutatott itoa függvényben, amely egy számot karaktersorozattá alakít (az atoi függvény inverze). A feladat kicsit bonyolultabb, mint elsőre látszik, mivel a számjegyeket generáló egyszerű megoldások rossz sorrendet eredményeznek. Ezért úgy döntöttünk, hogy a karakterláncot fordított sorrendben generáljuk, majd a végén megfordítjuk.
/* itoa: az n számot s karaktersorozattá alakítja */ void itoa(int n, char s[]) { int i, sign; if ((sign = n) < 0) /* elteszi az előjelet */ n = -n; /* n-et pozitívvá teszi */ i = 0; do { /* generálja a számjegyeket, de fordított sorrendben */ s[i++] = n % 10 + '0'; /* a következő számjegy */ } while((n /= 10) > 0); /* törli azt */ if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); }A példában a do-while használata szükségszerű, vagy legalábbis kényelmes, mivel legalább egy karaktert akkor is el kell helyeznünk az s karaktersorozatban, ha n értéke nulla. A do-while ciklus magját kapcsos zárójellel kiemeltük (bár szükségtelen), mivel így a while rész nem téveszthető össze egy while utasítással szervezett ciklus kezdetével.
3.4. gyakorlat. Az itoa függvény itt ismertetett változata kettes komplemens kódú számábrázolás esetén nem kezeli a legnagyobb negatív számot; azaz az n = -2↑(szóhossz-1) értéket. Magyarázzuk meg, hogy miért! Módosítsuk úgy a programot, hogy ezt az értéket is helyesen írja ki, a használt számítógéptől függetlenül.
3.5. gyakorlat. Írjunk itob(n, s, b) néven függvényt, amely az n egész számot b alapú számrendszerben karaktersorozattá alakítja és az s karaktersorozatba helyezi! Speciális esetként írjuk meg az itob(n, s, 16) függvényt is, amely az n értékét hexadecimális formában írja az s karaktersorozatba.
3.6. gyakorlat. Írjuk meg az itoa függvénynek azt a változatát, amelynek kettő helyett három argumentuma van! Ez a harmadik argumentum legyen a minimális mezőszélesség, és az átalakított számot szükség esetén balról üres helyekkel töltse fel, hogy elegendően széles legyen!
3.7. A break és continue utasítások
Néha kényelmes lehet, ha egy ciklusból az elején vagy végén elhelyezett ellenőrzés kikerülésével is ki tudunk lépni. A break utasítás lehetővé teszi a for, while vagy do utasításokkal szervezett ciklusok idő előtti elhagyását, valamint a switch utasításból való kilépést. A break hatására a legbelső ciklus vagy a teljes switch utasítás fejeződik be.
A működés bemutatására írjuk meg a trim függvényt, amely egy karaktersorozat végéről eltávolítja a szóközöket, tabulátorokat és újsor-karaktereket. A program a break utasítást használja a ciklusból való idő előtti kilépésre, ha megtalálja a karaktersorozat legjobboldalibb (utolsó) nem szóköz-, tabulátor- vagy újsor-karakterét.
/* trim: eltávolítja a záró szóköz-, tabulátor- vagy újsor-karaktereket */ int trim(char s[ ]) { int n; for (n = strlen(s)-1; n >= 0; n --) if (s[n] !=' ' && s[n] != '\t' && s[n] != '\n') break; s[n + 1] = '\0'; return n; }Az strlen függvény visszatéréskor a karaktersorozat hosszát adja meg. A for ciklus a karaktersorozat végén kezdi a feldolgozást és addig vizsgálja a karaktereket, amíg megtalálja az első szóköztől, tabulátortól vagy új sortól különböző karaktert. A ciklus akkor áll le, ha az első ilyen karaktert megtaláltuk vagy n értéke negatívvá válik (azaz, amikor a teljes karaktersorozatot végignéztük). Az olvasóra bízzuk, hogy igazolja, a program akkor is helyesen működik, ha a karaktersorozat üres vagy csak üres helyet adó karaktereket tartalmaz.
A continue utasítás a break utasításhoz kapcsolódik, de annál ritkábban használjuk. A ciklusmagban található continue utasítás hatására azonnal (a ciklusmagból még hátralévő utasításokat figyelmen kívül hagyva) megkezdődik a következő iterációs lépés. A while és do utasítások esetén ez azt jelenti, hogy azonnal végbemegy a feltételvizsgálat, for esetén pedig a ciklusváltozó újrainicializálódik. A continue utasítás csak ciklusokban alkalmazható, a switch utasításban nem. Ha a switch utasításban ciklus volt, akkor a continue ezt a ciklust lépteti tovább.
A continue működését illusztráló egyszerű programrész az a tömb nem negatív elemeit dolgozza fel, a negatív elemeket átugorja.
for (i = 0; i < n; i++) { if (a[i] < 0) /* a negatív elemek átugrása */ continue; ... /* a pozitív elemek feldolgozása */ }A continue utasítást gyakran használjuk olyan esetekben, amikor a ciklus további része nagyon bonyolult, és ezért a vizsgálati feltétel megfordítása, ill. egy újabb programszint beágyazása a programot túlzottan mélyen tagolná.
3.8. A goto utasítás és a címkék
A C nyelvben is használható a gyakran szidott goto utasítás, amellyel megadott címkékre ugorhatunk. Alapvetően a goto utasításra nincs szükség és a gyakorlatban majdnem mindig egyszerűen írhatunk olyan programot, amelyben nincs goto. A könyvben közölt mintaprogramok a továbbiakban sem tartalmaznak goto utasítást.
Mindezek ellenére most bemutatunk néhány olyan esetet, amelyben a goto utasítás működése megfigyelhető. A goto használatának egyik legelterjedtebb esete, amikor több szinten egymásba ágyazott szerkezet belsejében kívánjuk abbahagyni a feldolgozást és egyszerre több, egymásba ágyazott ciklusból szeretnénk kilépni. Ilyenkor a break utasítás nem használható, mivel az csak a legbelső ciklusból lép ki.
Például a
for(...) for(...) { ... if (zavar) goto hiba; } ... hiba: a hiba kezeléseszerkezetben előnyös a hibakezelő eljárást egyszer megírni és a különböző hibaeseteknél a vezérlést a közös hibakezelő eljárásnak átadni, bárhol is tartott a feldolgozás.
A címke ugyanolyan szabályok szerint alakítható ki, mint a változók neve és mindig kettőspont zárja. A címke bármelyik utasítás előtt állhat és a goto utasítással bármelyik, a goto-val azonos függvényben lévő utasítás elérhető. A címke hatásköre arra a teljes függvényre kiterjed, amiben használják.
Második példaként tekintsük azt a feladatot, amikor meg szeretnénk határozni, hogy az a és b tömbnek vannak-e közös elemei. Egy lehetséges megoldás:
for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (a[i] == b[j]) goto talalt; /* nem talált közös elemet */ ... talalt: /* egy közös elem van, a[i] == b[j] */ ...Mint említettük, minden goto-t tartalmazó program megírható goto nélkül is, bár ez néha bonyolult a sok ismétlődő vizsgálat és segédváltozó miatt. Az előző példa goto nélküli változata:
talalt = 0; for (i = 0; i < n && !talalt; i++) for (j = 0; j < m && !talalt; j++) if (a[i] == b[j]) talalt = 1; if (talalt) /* egy közös elem van, a[i-1] == b[j-1] */ ... else /* nem talált közös elemet */ ...Néhány itt bemutatott kivételtől eltekintve a goto utasítást tartalmazó programok általában nehezebben érthetők és kezelhetők, mint a goto nélküli programok. Bár ez nem törvény, de jó ha betartjuk: a goto utasítást a lehető legritkábban használjuk.
2. FEJEZET | Tartalom | 4. FEJEZET |