KURZUS: Programozás alapjai

MODUL: II. modul

6. lecke: Utasítások

Ebben a leckében elmélyedünk a korábban már megismert utasítások (pl. feltételes szerkezet, elöltesztelő ciklus) részleteiben, és további, eddig kiaknázatlan lehetőségeket is megismerünk (pl. hátultesztelő ciklus, többirányú elágazás switch-csel). A példaprogramok nem csak az új ismeretek elmélyítését segítik, de néhány egyszerű algoritmust is bemutatnak, melyeknek a gyakorlatban később hasznát vehetjük (pl. rendezés, keresés).

A lecke végére a hallgatónak tisztában kell lennie:

  • Az alkalmazható utasításokkal, azok szintaktikai szabályaival, működésével.
  • A rendezett tömbökön használható bináris keresés algoritmusával, a minimum-kiválasztásos rendezés működésével, képesnek kell lennie ezek alkalmazására.
  • Egész számok formai ellenőrzésének módjával.
  • Számok minimumának, maximumának, átlagának meghatározásával.

A lecke kulcsfogalmai: blokk, láthatóság, címkézett utasítás, üres utasítás.

Az utasítások fajtái

Az utasításokat - ha mást nem mondanak - megadásuk sorrendjében hajtja végre a processzor. Az utasításoknak nincs értéke. Végrehajtásuk hatással van bizonyos adatokra, vagy programelágazást valósítanak meg stb. Definíciójuk a következő:

utasítás:
 összetett-utasítás
  címkézett-utasítás
  kifejezés-utasítás
  szelekciós-utasítás
  iterációs-utasítás
  ugró-utasítás

Összetett utasítás

összetett-utasítás:
  { <deklarációlista><utasításlista> }

deklarációlista:
  deklaráció
  deklarációlista deklaráció

utasításlista:
  utasítás
  utasításlista utasítás

Az összetett utasítás utasítások (lehet, hogy üres) listája { }-be téve. Az összetett utasítást blokknak is nevezik, mely szintaktikailag egy utasításnak minősül, és szerepet játszik az azonosítók hatáskörében és láthatóságában.

Ha a deklarációlistában előforduló azonosítót már korábban az összetett utasításon kívül is deklarálták, akkor a blokkra lokális azonosító elfedi a blokkon kívülit a blokk teljes hatáskörében. Tehát az ilyen blokkon kívüli azonosító a blokkban nem látható.

C blokkban előbb a deklarációs, s csak aztán a végrehajtható utasítások következnek.

Az összetett utasítások akármilyen mély szinten egymásba ágyazhatók, s az előbbi szerkezet a beágyazott blokkra is vonatkozik.

Tudjuk, hogy az auto tárolási osztályú objektumok inicializálása mindannyiszor megtörténik, valahányszor a vezérlés a "fejen át" kerül be az összetett utasításba. Ez az inicializálás azonban elmarad, ha a vezérlés ugró utasítással érkezik a blokk "közepére". Már említettük, de újra elmondjuk, hogy tilos ;-t írni az összetett utasítás záró }-e után!

Címkézett utasítás

címkézett-utasítás:
  azonosító : utasítás
  case konstans-kifejezés : utasítás
  default : utasítás

A case és a default formák csak switch utasításban használatosak. Ezek ismertetésével majd ott foglalkozunk! Az

azonosító : utasítás

alakban az azonosító (címke) célja lehet például egy feltétlen elágaztató

goto azonosító;

utasításnak. A címkék hatásköre mindig az őket tartalmazó függvény. Nem deklarálhatók újra, de külön névterületük van, és önmagukban nem módosítják az utasítások végrehajtási sorrendjét.

C-ben csak végrehajtható utasítás címkézhető meg, azaz

{
  /* . . . */
  cimke: /* HIBÁS */
}

A megoldás helyessé válik, ha legalább egy üres utasítást teszünk a cimke után:

{
  /* . . . */
  cimke: ; /* OK */
}
Kifejezés utasítás

kifejezés-utasítás:
  <kifejezés>;

Ha a kifejezést pontosvessző követi, kifejezés utasításnak minősül. A fordító kiértékeli a kifejezést, s eközben minden mellékhatás érvényre jut, mielőtt a következő utasítás végrehajtása elkezdődne. Például az

x = 0, i++, printf("Hahó!\n")

kifejezések, s így válnak kifejezés-utasításokká:

x = 0; i++; printf("Hahó!\n");

A legtöbb kifejezés utasítás hozzárendelés vagy függvényhívás.

Üres utasítást (null statement) úgy kapunk, hogy kifejezés nélkül pontosvesszőt teszünk. Hatására természetesen nem történik semmi, de ez is utasításnak minősül, azaz szintaktikailag állhat ott, ahol egyébként utasítás állhat.

Szelekciós utasítások

szelekciós-utasítás:
  if(kifejezés) utasítás
  if(kifejezés) utasítás else utasítás
  switch(kifejezés) utasítás

Látszik, hogy két szelekciós utasítás van: az if és a switch. A feltételesen elágaztató

if(kifejezés) utasítás1 else utasítás2

utasításban a kifejezésnek aritmetikai, vagy mutató típusúnak kell lennie. Ha értéke zérus, akkor a logikai kifejezés hamis, máskülönben viszont igaz. Ha a kifejezés igaz, utasítás1 következik. Ha hamis, és az else ág létezik, akkor utasítás2 jön. Mind utasítás1, mind utasítás2 lehet összetett utasítás is!

A nyelvben nincs logikai adattípus, de cserében minden egész és mutató típus annak minősül. Például:

if(ptr == 0) /* Rövidíthető "if(!ptr)"-nek. */
if(ptr != 0) /* Rövidíthető "if(ptr)"-nek. */

Az else ág elhagyhatósága néha problémákhoz vezethet. Például a

if(x == 1)
  if(y == 1) puts("x = 1 és y = 1\n");
else
  puts("x != 1\n");

forrásszövegből a programozó "elképzelése" teljesen világos. Sajnos azonban egyet elfelejtett. Az else mindig az ugyanazon blokk szinten levő, forrásszövegben megelőző, else ág nélküli if-hez tartozik. A megoldás helyesen:

if(x == 1){
  if(y == 1) puts("x = 1 és y = 1\n"); }
else
  puts("x != 1\n");

Az if utasítások tetszőleges mélységben egymásba ágyazhatók, azaz mind az if, mind az else utasítása lehet újabb if utasítás is. Például:

if(x == 1)
  { if(y == 1) puts("x = 1 és y = 1\n");
    else puts("x = 1 és y != 1\n"); }
else
  { if(y == 1) puts("x != 1 és y = 1\n");
    else puts("x != 1 és y != 1\n"); }

Többirányú elágazást (szelekciót) valósít meg a következő konstrukció:

if( kifejezés1 ) utasítás1
else if( kifejezés2 ) utasítás2
else if ( kifejezés3 ) utasítás3
/* . . . */
else utasításN

Ha valamelyik if kifejezése igaz, akkor az azonos sorszámú utasítást hajtja végre a processzor, majd a konstrukciót követő utasítás jön. Ha egyik if kifejezése sem igaz, akkor viszont utasításN következik.

Ha egy n elemű, növekvőleg rendezett t tömbben keresünk egy x értéket, akkor ezt bináris keresési algoritmust alkalmazva így interpretálhatjuk:

int binker(int x, int t[], int n){
  int also=0, felso=n-1, kozep;
  while(also<=felso){
    kozep=(also+felso)/2;
    if(x<t[kozep]) felso=kozep-1;
    else if(x>t[kozep]) also=kozep+1;
    else return kozep; } /* Megvan. */
  return (-1); }        /* Nincs meg. */

A binker háromirányú elágazást realizál. x t tömbbeli előfordulásának indexét szolgáltatja, ill. -1-et, ha nincs is x értékű elem a tömbben.

A bináris keresés növekvőleg rendezett tömbben úgy történik, hogy először megállapítjuk, hogy a keresett érték a tömb alsó, vagy felső felébe esik. A módszert aztán újraalkalmazzuk az aktuális félre, s így tovább. A dolognak akkor van vége, ha a kereső tartomány semmivé szűkül (ilyenkor nincs meg a keresett érték), vagy a felező tömbelem egyezik a keresett értékkel.

Rendezett sorozatban egy érték keresésének ez a megfelelő módja, és nem a minden elemhez történő hasonlítgatás (soros vagy szekvenciális keresés)!

A többirányú programelágaztatás másik eszköze a

switch(kifejezés) utasítás

melyben a kifejezésnek egész típusúnak kell lennie. Az utasítás egy olyan speciális összetett utasítás, mely több case címkét

case konstans-kifejezés : utasítás

és egy elhagyható default címkét

default : utasítás

tartalmazhat. A vezérlést azon case címkét követő utasítás kapja meg, mely konstans-kifejezésének értéke egyezik a switch-beli kifejezés értékével. A végrehajtás aztán itt addig folytatódik, míg break utasítás nem következik, vagy vége nincs a switch blokkjának.

A case címkebeli konstans-kifejezésnek is egész típusúnak kell lennie. Ez a Típusok és konstansok lecke Deklaráció fejezetében írottakon túl további korlátozásokat ró a konstans kifejezésre. Operandusai csak egész, felsorolás, karakteres és lebegőpontos állandók lehetnek, de a lebegőpontos konstanst explicit típuskonverzióval egésszé kell alakítani. Operandus lehet még a sizeof operátor is, aminek operandusára természetesen nincsenek ilyen korlátozások.

A végrehajtás során a kifejezés és a case konstans-kifejezések értékén is végbemegy az egész-előléptetés. A kifejezés az összes esetleges mellékhatásával egyetemben valósul meg, mielőtt az értékhasonlítás megkezdődne.

Ha nincs a switch kifejezés értékével egyező case címke, akkor a vezérlést a default címke utasítása kapja meg. Ha nincs default címke sem, akkor vége van a switch-nek.

Az utasítás használatához még két megjegyzést kell fűzni:

  • Több case címke is címkézhet egy utasítást.
  • Egyazon switch utasításban viszont nem fordulhat elő két azonos értékű case konstans-kifejezés.

A switch utasítások egymásba is ágyazhatók, s ilyenkor a case és a default címkék mindig az őket közvetlenül tartalmazó switch-hez tartoznak.

A switch utasítást szemléltetendő írjuk át a szabvány bemenet karaktereit kategóriánként leszámláló pelda7.c-t!

/* PELDA17.C: A bemenet karaktereinek
  leszamlalasa kategoriankent */
#include <stdio.h>
void main(void){
  short k, num=0, feher=0, egyeb=0;
  printf("Bemeneti karakterek leszamlalasa\n"
        "kategoriankent EOF-ig, vagy Ctrl+Z-ig.\n");
  while((k=getchar())!=EOF) switch(k){
    case '0': case '1': case '2':
    case '3': case '4': case '5':
    case '6': case '7': case '8':
    case '9':
      ++num;
      break;
    case ' ': case '\n': case '\t':
      ++feher;
      break;
    default:
      ++egyeb; }
  printf("Karakter szamok:\n----------------\n"
        "numerikus: %5hd\nfeher:    %5hd\n"
        "egyeb:    %5hd\n----------------\n"
        "ossz: %10ld\n", num, feher, egyeb,
        (long)num+feher+egyeb); }

Belátható, hogy a switch utasítás használhatóságát szegényíti, hogy a kifejezés csak egész típusú lehet és, hogy a case címkék csak egészértékű konstans-kifejezések lehetnek. Az if-nél említett többirányú elágaztatási konstrukció így jóval általánosabban használható.

Iterációs utasítások

iterációs-utasítás:
  while(kifejezés) utasítás
  do utasítás while(kifejezés);
  for(<kifejezés>; <kifejezés>; <kifejezés>) utasítás

Szemmel láthatóan háromféle iterációs utasítás van, amiből kettő elöltesztelő ciklusutasítás. A

while(kifejezés) utasítás

kifejezéséről ugyanaz mondható el, mint az if utasítás kifejezéséről. Lépésenként a következő történik:

1.Kiértékeli a fordító a kifejezést, melynek során minden esetleges mellékhatás is megvalósul. Ha hamis (zérus) az értéke, akkor vége a ciklusnak, s a while-t követő utasítás jön a programban.
2.Ha a kifejezés igaz (nem zérus), akkor az utasítás végrehajtása, és újból az 1. pont következik.

Világos, hogy a kifejezés értékének valahogyan változnia kell az utasításban, különben a ciklusnak soha sincs vége. Az utasítás állhat több utasításból is, azaz összetett utasítás is lehet. Ha az utasítások közt ugró utasítás is van, akkor ezen mód is kiléphetünk a ciklusból.

A

for(<init-kifejezés>; <kifejezés>; <léptető-kifejezés>) utasítás

elöltesztelő, iteratív ciklusutasítás, melynek megvalósulása a következő lépésekből áll:

1.A fordító végrehajtja az init-kifejezést, ha van. Többnyire egy vagy több változó értékadásáról lehet itt szó.
2.Kiértékeli a kifejezést. Ha hamis (zérus), akkor vége a ciklusnak, s a for-t követő utasítás jön a programban. Látható, hogy a szintaktika szerint ez a kifejezés is elhagyható. Ilyenkor 1 (igaz) kerül a helyére, azaz a végtelen ciklus könnyedén felírható:

for( ; ; ); -> for( ; 1; );
3.Ha a kifejezés igaz (nem zérus), akkor az utasítás jön,
4.aztán a léptető-kifejezés, majd újból a 2. pont következik.

Ha a for utasítást while-lal szeretnénk felírni, akkor azt így tehetjük meg, ha a ciklusmagban nincs continue:

<init-kifejezés>;
while(<kifejezés>) { utasítás; <léptető-kifejezés>; }

A szintaktikai szabályt összefoglalva: a for-ból akár mindegyik kifejezés is elhagyható, de az első kettőt záró pontosvesszők nem! A vessző operátor alkalmazásával mind az init-, mind a léptető-kifejezés több kifejezés is lehet. Ezt szemlélteti az előző szakaszban a pelda16.c-ban megírt strrv függvény.

Írjunk void rendez(int a[], int n) függvényt, mely növekvőleg rendezi saját helyén az n elemű a tömböt!

  • Indulva az első elemtől megkeressük a tömb minimális elemét, és kicseréljük az első elemmel.
  • A 2. elemtől kezdve a hátra levő tömbrészben keressük meg a legkisebb elemet, s ezt kicseréljük a 2. elemmel, s így tovább.
  • Látszik, hogy minimumkeresést utoljára a tömb utolsó előtti és utolsó elemén kell elvégezni, s az itteni esetleges csere után rendezett is az egész tömb.
void rendez(int a[],int n){
  int i, j, m, cs;
  for(i=0; i<n-1; ++i){
    for(j=i+1, m=i; j<n; ++j) if(a[j]<a[m]) m=j;
    if(i!=m){ cs=a[i]; a[i]=a[m]; a[m]=cs;} } }

Ez ugyebár példa az egymásba ágyazott ciklusokra!

Készítsük el az int egesze(char s[]) rutint, mely ellenőrzi a decimális egész konstans írásszabályát a paraméter karaktertömbön. 1-et szolgáltat, ha a dolog rendben van, s zérust, ha nem!

/* A decimális számjegyek száma maximálisan: */
#define HSZ sizeof(int)/sizeof(short)*5
int egesze(char s[]){
  int i = 0, kezd;
  /* A karakterlánc eleji fehér karakterek átlépése: */
  while(s[i]==' ' || s[i]=='\n' || s[i]=='\t') ++i;
  /* Az előjel átlépése: */
  if(s[i]=='+' || s[i]=='-') ++i;
  kezd=i; /* A számjegyek itt kezdődnek. */
  /* Előre a következő nem numerikus karakterre: */
  while(s[i]>='0' && s[i]<='9' && i-kezd<HSZ) ++i;
  /* Döntés: */
  if(kezd==i || s[i]!='\n' && s[i]!='\t' &&
    s[i]!=' ' && s[i]!=0) return 0;
  else return 1; }

Látszik, hogy HSZ 16 bites int esetén 5 (32767), és 32 bitesnél 10 (2147483647). Arra való, hogy ennél több decimális számjegyet ne fogadjon el a rutin.

Vegyük észre, hogy a függvény visszautasítja, ha egyetlen számjegy karaktert sem tartalmaz a numerikus karakterlánc! Elveti azt is, ha a numerikus lánc rész nem fehér karakterrel, vagy lánczáró zérussal végződik. Az ellenőrzés persze nem tökéletes, hisz a numerikus karakterlánc ábrázolási határok közé férését nem biztosítja. Például 16 bites int-nél minden ötjegyű számot érvényesnek tekint, holott a -99999 és -32769, valamint a 32768 és 99999 közötti számok ábrázolhatatlanok. Ez az ellenőrzés azonban csak akkor lenne könnyen megvalósítható, ha tesztelés közben konvertálnánk is a számot. Ekkor azonban már "többe kerülne a leves, mint a hús".

Készítsük programot, mely egész számokat kér be. Meghatározza az átlagukat, a minimumukat, maximumukat, és növekvőleg rendezi is őket! Az egészek darabszáma csak futás közben dől el.

/* PELDA18.C: Egesz szamok atlaga, minimuma, maximuma és
              rendezese */
#include <stdio.h>
#include <stdlib.h> /* Az atoi miatt! */
#include <limits.h> /* INT_MIN és INT_MAX vegett! */
#define MAX 100    /* A tomb max. elemszama. */
#define INP 20      /* Az input puffer merete. */
int getline(char s[],int lim){
  int c,i;
  for(i=0;i<lim&&(c=getchar())!=EOF&&c!='\n';++i)s[i]=c;
  s[i]='\0';
  while(c!=EOF && c!='\n') c=getchar();
  return(i); }
/* A decimalis szamjegyek szama maximalisan: */
#define HSZ sizeof(int)/sizeof(short)*5
int egesze(char s[]){
  int i = 0, kezd;
  /* A karakterlanc eleji feher karakterek atlepese: */
  while(s[i]==' ' || s[i]=='\n' || s[i]=='\t') ++i;
  /* Az elojel atlepese: */
  if(s[i]=='+' || s[i]=='-') ++i;
  kezd=i;  /* A szamjegyek itt kezdodnek. */
  /* Elore a kovetkezo nem numerikus karakterre: */
  while(s[i]>='0' && s[i]<='9' && i-kezd<HSZ) ++i;
  /* Dontes: */
  if(kezd==i || s[i]!='\n' && s[i]!='\t' &&
    s[i]!=' ' && s[i]!=0) return 0;
  else return 1; }
void rendez(int a[],int n){
  int i, j, m, cs;
  for(i=0; i<n-1; ++i){
    for(j=i+1, m=i; j<n; ++j) if(a[j]<a[m]) m=j;
    if(i!=m){ cs=a[i]; a[i]=a[m]; a[m]=cs;} } }
void main(void){
  int n=0;          /* A rendezendo elemek szama. */
  char sor[INP+1];  /* Az input puffer. */
  int a[MAX];      /* A egeszeket tarolo tomb. */
  int min, max;    /* Minimum, maximum. */
  double osszeg=0.; /* Az osszeg */
  int i;
  while(n<1||n>MAX){
    printf("\nHany egesz szamot rendezunk(1-%d)? ",MAX);
    getline(sor,INP);
    if(egesze(sor)) n=atoi(sor);}
  printf("\n\nKerem a rendezendo szamokat %d és +%d"
    " kozott!\n", INT_MIN, INT_MAX);
  for(i=0;i<n;++i){
    printf("%3d: ",i+1);
    if(getline(sor,INP)>0 && egesze(sor))
      a[i]=atoi(sor);
    else --i; }
  min=max=a[0];
  for(i=0;i<n;osszeg+=a[i],++i)
    if(a[i]<min) min=a[i];
    else if(a[i]>max) max=a[i];
  printf("\nA szamsorozat\tminimuma:%14d.\n"
        "\t\tmaximuma:%14d.\n"
        "\t\tatlaga:  %17.2f\n",
        min, max, osszeg/n);
  printf("\nA rendezett szamok:\n");
  rendez(a, n);
  for(i=0; i<n; i++){
    printf("%13d",a[i]);
    if(!((i+1)%6)) putchar('\n'); }
  putchar('\n'); }

Nem rendezett sorozatban a minimum, vagy maximum megkeresésének az a módja, hogy vesszük a sorozat egy létező elemét (többnyire az elsőt), és aztán hasonlítgatjuk a többiekhez, hogy vajon van-e nálánál kisebb, ill. nagyobb. Ha van, akkor attól kezdve azzal folytatjuk a hasonlítást.

El kell ismerni természetesen, hogy a minimum és maximum megkeresése teljesen felesleges volt, mert a rendezés után ezeket az értékeket a[0] és a[n-1] amúgy is tartalmazza.

Vegyük még észre, hogy az egészek összegét a for ciklus léptető-kifejezésében számítjuk ki!

A

do utasítás while(kifejezés);

hátultesztelő ciklusutasítás. A kifejezésre ugyanazon megkötések érvényesek, mint a while-nál. A dolog lényege az, hogy a fordító a kifejezés értékétől függetlenül egyszer biztosan

1.végrehajtja az utasítást.
2.Kiértékeli a kifejezést. Ha hamis (zérus), akkor vége a ciklusnak, s a while-t követő utasítás jön a programban. Ha viszont igaz, akkor az 1. pont következik.

Összesítve: Az utasítást egyszer mindenképp végrehajtja a processzor, s ezt követően mindaddig ismétli, míg a kifejezés hamis nem lesz.

A szemléltető példában az itoa függvény az int n paraméterét karakterlánccá konvertálja, és elhelyezi a paraméter s karaktertömbben:

#include <stdio.h>
#include <string.h>
#include <limits.h>
void itoa(int n, char s[]){
  int i=0, elojel, j=0;
  if(n==INT_MIN) { ++n; ++j; }
  if((elojel=n)<0) n=-n;
  do s[i++]=n%10+'0'; while(n/=10);
  if(elojel<0) s[i++]='-';
  s[i]=0;
  s[0]+=j;
  strrev(s); }

A belsőábrázolási formából karakterlánccá alakító itoa rutinban az i ciklusváltozó. A j logikai változó, s induló értéke 0. Egyetlen egy esetben egyértékű, ha n éppen INT_MIN. A probléma az ugye, hogy mindenképp pozitív értéket kívánunk konvertálni az esetleges negatív előjelet megjegyezve, de az INT_MIN -1-szerese nem ábrázolható int-ként, hisz éppen eggyel nagyobb INT_MAX-nál. Ha ilyenkor megnöveljük eggyel n értékét, akkor -1-szerese épp a felső ábrázolási korlát lesz, amivel már nincs probléma. A j logikai változó tehát akkor 1, ha volt növelés. Az elojel változóra n eredeti előjelének megtartása végett van szükség, és azért, hogy negatív n esetén a keletkező karakterlánc végére ki lehessen tenni a mínuszjelet.

Az algoritmus éppen megfordítva állítja elő a karakterláncot. Nézzük csak asztali teszttel a 16 bites int esetét! Tegyük fel, hogy n értéke az ominózus -32768!

Amig a do-while utasításig nem érünk, j 1 lesz, n 32767 és i zérus marad. Lejátszva a ciklust a következő történik:

j:1
n:32767327632732300
i:0123456
s:'7''6''7''2''3''-''\0'

A táblázatban az az állapot látszik, amikor a do-while-t követő két utasítás is lezajlott. Az s[0]-ból, vagyis '7'-ből, '8' lesz, és aztán a szabvány könyvtári strrev megfordítja a saját helyén az eredmény karakterláncot.

Ugró utasítások

ugró-utasítás:
  break;
  continue;
  goto azonosító;
  return <kifejezés>;

Csak iterációs (while, do-while és for) vagy switch utasításon belül használható a

break;

mely befejezi ezeket az utasításokat, azaz hatására a vezérlés feltétel nélkül kilép belőlük. Több egymásba ágyazott iterációs utasítás esetén a break csak abból a ciklusból léptet ki, amelyben elhelyezték, azaz a break csak egyszintű kiléptetésre képes.

Készítendő egy int trim(char s[]) függvény, mely a paraméter karaktertömb elejéről és végéről eltávolítja a fehér karaktereket a saját helyén, s visszatér az így letisztított karakterlánc hosszával!

#include <string.h>
int trim(char s[]){
  int i=0, n;
  /* A fehér karakterek eltávolítása a lánc végéről: */
  for(n=strlen(s)-1; n>=0; --n)
    if(s[n]!=' '&&s[n]!='\n'&&s[n]!='\t') break;
  s[++n]='\0';
  /* A fehér karakterek eltávolítása a lánc elejéről: */
  while(s[i]==' '||s[i]=='\n'||s[i]=='\t') ++i;
  if(i) { n=0; while(s[n++]=s[i++]); --n; }
  return(n); }

Csak iterációs utasításokban (while, do-while és for) alkalmazható a

continue;

amely a vezérlést a kifejezés kiértékelésére viszi while és do-while estén, ill. hatására a léptető-kifejezés kiértékelése következik for utasításnál. Egymásba ágyazott iterációs utasítások esetén ez is mindig csak az őt magába foglaló iterációs utasításra vonatkozik. (switch-ben csak iterációs utasításon belül használható a continue!)

Feltétlen vezérlésátadást hajt végre az azonosítóval címkézett utasításra a

goto azonosító;

Az utasítást bármilyen mély blokk szintről is végrehajtja a processzor, de a cél címkézett utasításnak ugyanabban a függvényben kell lennie, ahol a goto is van.

Megjegyezzük, hogy a goto utasítás használata szintaktikai értelemben nem hiba, viszont ettől függetlenül kerülendő új programok létrehozásakor! Viszonylag könnyű belátni, hogy egy megfelelően felépített blokkszerkezettel rendelkező programban a goto utasításra valójában semmi szükség nincs, és a címkék is csak switch utasításban indokolhatók. A goto használata sokkal nehezebben karbantartható, több potenciális hibát tartalmazó, nehezebben ellenőrizhető működésű programokhoz vezet.

A void visszatérésűektől eltekintve a függvény testében lennie kell legalább egy

return <kifejezés>;

utasításnak. Ha a rutin visszatérési típusa típus, akkor a kifejezés típusának is ennek kell lennie, vagy hozzárendelési konverzióval ilyen típusúvá alakítja a kifejezés értékét a fordító. A return hatására visszakapja a vezérlést a hívó függvény, s átveszi a visszatérési értéket is.

A típus visszatérésű függvényt hívó kifejezés

fv(aktuális-paraméterlista)

típus típusú jobbérték, s nem balérték:

típus t;
t=fv(aktuális-paraméterlista);  /* OK */
t=++fv(aktuális-paraméterlista); /* Hiba */

A függvényhívás hatására beindult végrehajtás a return utasítás bekövetkeztekor befejeződik. Ha nincs return, akkor a függvény testét záró }-ig megy a végrehajtás.

Ha a függvény által visszaadott érték típusa void, és a függvényt nem a testét záró }-nél szeretnénk befejezni, akkor a függvény belsejébe a kívánt helyre return utasítást kell írni.

Feladatok

1. Olyan int int binkerf(int x, int t[], int n) függvényt szerettünk volna írni, ami a csökkenőleg rendezett, n elemű t tömbben megkeresi x előfordulását, és ennek indexével tér vissza, vagy -1-gyel, ha a keresett érték nem található. Sajnos nem sikerült.

/*1*/int binkerf(int x, int t[], int n){
/*2*/  int also=0, felso=n-1, kozep;
/*3*/  while(also<=felso){
/*4*/    kozep=(also+felso)/2;
/*5*/    if(x<t[kozep]) felso=kozep-1;
/*6*/    else if(x<t[kozep]) also=kozep+1;
/*7*/    else return t[kozep]; } /* Megvan. */
/*8*/  return (-1); } /* Nincs meg. */
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
/*3*/  while(also>=felso){
/*3*/  while(also<felso){
/*3*/  while(also>felso){
/*5*/    if(x>t[kozep]) felso=kozep-1;
/*6*/    else if(x>t[kozep]) also=kozep+1;
/*7*/    else return kozep; } /* Megvan. */

2. Alakítsa át a pelda18.c-ben adott programot úgy, hogy az ne kérje be előre a rendezendő számok darabszámát, hanem az érték bekérésekor megadott üres sor jelentse az adatbevitel végét!

/* PELDA18X.C: Egesz szamok atlaga, minimuma, maximuma és
rendezese. */
#include <stdio.h>
#include <stdlib.h> /* Az atoi miatt! */
#include <limits.h> /* INT_MIN és INT_MAX vegett! */
#define MAX 100 /* A tomb max. elemszama. */
#define INP 20 /* Az input puffer merete. */
int getline(char s[],int lim) {
  int c,i;
  for(i=0; i<lim&&(c=getchar())!=EOF&&c!='\n'; ++i)s[i]=c;
  s[i]='\0';
  while(c!=EOF && c!='\n') c=getchar();
  return(i);
}
/* A decimalis szamjegyek szama maximalisan: */
#define HSZ sizeof(int)/sizeof(short)*5
int egesze(char s[]) {
  int i = 0, kezd;
  /* A karakterlanc eleji feher karakterek atlepese: */
  while(s[i]==' ' || s[i]=='\n' || s[i]=='\t') ++i;
  /* Az elojel atlepese: */
  if(s[i]=='+' || s[i]=='-') ++i;
  kezd=i; /* A szamjegyek itt kezdodnek. */
  /* Elore a kovetkezo nem numerikus karakterre: */
  while(s[i]>='0' && s[i]<='9' && i-kezd<HSZ) ++i;
  /* Dontes: */
  if(kezd==i || s[i]!='\n' && s[i]!='\t' &&
      s[i]!=' ' && s[i]!=0) return 0;
  else return 1;
}
void rendez(int a[],int n) {
  int i, j, m, cs;
  for(i=0; i<n-1; ++i) {
    for(j=i+1, m=i; j<n; ++j) if(a[j]<a[m]) m=j;
    if(i!=m) {
      cs=a[i];
      a[i]=a[m];
      a[m]=cs;
    }
  }
}
void main(void) {
  int n=0; /* A rendezendo elemek szama 0-rol indul. */
  char sor[INP+1]; /* Az input puffer. */
  int a[MAX]; /* A egeszeket tarolo tomb. */
  int min, max; /* Minimum, maximum. */
  double osszeg=0.; /* Az osszeg */
  int i;
  printf("Kerem a rendezendo szamokat %d és +%d"
        " kozott!\n", INT_MIN, INT_MAX);
  printf("A befejezes ures sorral tortenik!\n");
  while(n<MAX) { /* Bekeres a tomb utolso elemeig tarthat. */
    printf("%3d: ",n+1);
    /* Ha megadnak sort, es az egesz szam, akkor ez lesz
    a tomb kovetkezo eleme, s noveljuk az elemszamot. */
    if(getline(sor,INP)>0) {
      if(egesze(sor)) a[n++]=atoi(sor);
    }
    /* Ha ures sort adnak meg, es van mar rendezendo elem,
    akkor befejezodott a bekeres. */
    else if(n) break;
  }
  min=max=a[0];
  for(i=0; i<n; osszeg+=a[i],++i)
    if(a[i]<min) min=a[i];
    else if(a[i]>max) max=a[i];
  printf("\nA szamsorozat\tminimuma:%14d.\n"
        "\t\tmaximuma:%14d.\n"
        "\t\tatlaga: %17.2f\n",
        min, max, osszeg/n);
  printf("\nA rendezett szamok:\n");
  rendez(a, n);
  for(i=0; i<n; i++) {
    printf("%13d",a[i]);
    if(!((i+1)%6)) putchar('\n');
  }
  putchar('\n');
}

3. Mely sorokat kellene átalakítani a void rendez(int a[],int n)függvényben és mire, hogy az valós számok rendezésére alkalmassá váljon?

/*1*/void rendez(int a[],int n) {
/*2*/  int i;
/*3*/  int j;
/*4*/  int m;
/*5*/  int cs;
/*6*/  for(i=0; i<n-1; ++i) {
/*7*/    for(j=i+1, m=i; j<n; ++j) if(a[j]<a[m]) m=j;
/*8*/    if(i!=m) { cs=a[i]; a[i]=a[m]; a[m]=cs; } } }
Jelölje meg azokat a sorokat, amikkel a függvény a kívánt működésre bírható!
/*1*/void rendez(double a[],int n) {
/*1*/void rendez(int a[],double n) {
/*1*/void rendez(double a[],double n) {
/*2*/  double i;
/*3*/  double j;
/*4*/  double m;
/*5*/  double cs;