C Programozás 3. FEJEZET

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 ifelse 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 ifelse szerkezet else ága opcionális egymásba ágyazott ifelse 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 elseif 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 ifelse ifelse 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 dowhile 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 dowhile utasítás egyenértékű a Pascal repeat-until utasításával.A tapasztalatok azt mutatják, hogy a dowhile 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 dowhile 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 dowhile 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



megosztom