Vezérlési szerkezetek
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ás
ahol 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

Az
if (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ás
szerkezet 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. A
while (kifejezés)
   utasítás
szerkezetben 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)
utasítás
ami teljesen egyenértékű a while utasítással megvalósított
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áld
Minden 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ése
szerkezetben 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