Kihagyás

Szelekciós vezérlés

Szelekciós vezérlések

Amikor véges sok rögzített művelet közül véges sok feltétel alapján választjuk ki, hogy melyik művelet kerüljön végrehajtásra, szelekciós vezérlésről beszélünk.

Attól függően, hogy a kiválasztás milyen módszerrel történik meg, különböző altípusai vannak ennek a vezérlésnek. Ezek az

  • egyszerű szelekciós vezérlés,
  • többszörös szelekciós vezérlés,
  • esetkiválasztásos szelekció,
  • illetve a föntiek kiegészítve egyébként ágakkal.

Egyszerű szelekciós vezérlés

Egyszerű szelekció esetén egyetlen feltétel és egyetlen művelet van (amely művelet persze lehet összetett).

Legyen F egy logikai kifejezés, A pedig tetszőleges művelet. Az F feltételből és az A műveletből képzett egyszerű szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Értékeljük ki az F feltételt és folytassuk a 2.) lépéssel.
  2. Ha F értéke igaz, akkor hajtsuk végre az A műveletet és fejezzük be az összetett művelet végrehajtását.
  3. Egyébként, vagyis ha F értéke hamis, akkor fejezzük be az összetett művelet végrehajtását.

A vezérlés bővíthető úgy, hogy a 3. pontban üres művelet helyett egy B műveletet hajtunk végre. Ekkor az alábbiak szerint módosíthatjuk a vezérlés megadását.

Legyen F egy logikai kifejezés, A és B pedig tetszőleges műveletek. Az F feltételből és az A és B műveletekből képzett egyszerű szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Értékeljük ki az F feltételt és folytassuk a 2.) lépéssel.
  2. Ha F értéke igaz, akkor hajtsuk végre az A műveletet és fejezzük be az összetett művelet végrehajtását.
  3. Egyébként, vagyis ha F értéke hamis, akkor hajtsuk végre a B műveletet és fejezzük be az összetett művelet végrehajtását.

A szerkezeti ábrán most is azt látjuk, hogy milyen részproblémák megoldása lesz a szelekció megoldása. A szerkezeti ábrákon a szelekciót egy téglalappal jelöljük, ami vízszintesen két részre van osztva. Ha tudjuk, a felső részbe kerül a feltétel, vagy az az elem, ami a feltételt meghatározza, a téglalaphoz csatolt műveletek pedig azok a műveletek lesznek, amelyek a feltétel(ek) bizonyos teljesülése mellett hajtódnak végre. Egyszerű szelekció esetében maga a feltétel egyértelműen beírható a téglalap felső részébe, az alsó résznél pedig jelölni tudjuk, hogy a feltétel igaz, vagy hamis értéke mellett mely alművelet végrehajtása jelenti a művelet befejezését.

kep kep

Az egyszerű szelekciót C nyelven az if utasítással valósítjuk meg.

A szelekció C megvalósítása:

1
2
3
if(F) {
    A;
}

Ha van egyébként ág is, akkor pedig:

1
2
3
4
5
if(F) {
    A;
} else {
    B;
}

Többszörös szelekciós vezérlés

Előfordulhat, hogy egyetlen feltétel nem elegendő ahhoz, hogy eldöntsük, mit is kellene csinálni, merre kellene haladni a vezérlésnek. Ilyenkor, ha több feltételünk van több művelettel, akkor többszörös szelekcióról beszélünk.

Legyenek Fi logikai kifejezések, Ai pedig tetszőleges műveletek 1 <= i <= n-re (azaz mind feltételből, mint A műveletből van n darab). Az Fi feltételekből és Ai műveletekből képzett többszörös szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Az Fi feltételek sorban történő kiértékelésével adjunk választ a következő kérdésre: van-e olyan i (1 <= i <= n), amelyre teljesül, hogy az Fi feltétel igaz és az összes Fj (1 <= j < i) feltétel hamis? (Azaz keressük az első Fi feltételt, ami igaz.)
  2. Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
  3. Egyébként, vagyis ha minden Fi feltétel hamis, akkor fejezzük be az összetett művelet végrehajtását.

Természetesen most is ki lehet egy egyébként ággal egészíteni a vezérlést, azaz definiálhatjuk azt a feladatot, amit akkor kell végrehajtani, amikor egyik feltétel sem teljesül. Ekkor az alábbiak szerint módosítsuk a többszörös szelekció formális leírását!

Legyenek Fi logikai kifejezések, Ai és B pedig tetszőleges műveletek 1 <= i <= n-re (azaz mind feltételből, mint A műveletből van n darab). Az Fi feltételekből, Ai és B műveletekből képzett többszörös szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Az Fi feltételek sorban történő kiértékelésével adjunk választ a következő kérdésre: Van-e olyan i (1 <= i <= n), amelyre teljesül, hogy az Fi feltétel igaz és az összes Fj (1 <= j < i) feltétel hamis? (Azaz keressük az első Fi feltételt, ami igaz.)
  2. Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
  3. Egyébként, vagyis ha minden Fi feltétel hamis, akkor hajtsuk végre B-t és fejezzük be az összetett művelet végrehajtását.

A többszörös szelekció folyamatábráin is látható, hogy egész addig, amíg nem találunk igaz feltételt, folytatódnak a feltétel kiértékelések. Ha találunk igaz feltételt, akkor az ahhoz tartozó utasítás végrehajtásával fejeződik be az összetett utasítás végrehajtása, ha egyik feltétel sem igaz, illetve ha van egyébként ág, akkor annak a végrehajtásával.

A többszörös szelekció szerkezeti ábrája a következőképpen alakul:

kep kep

Ahogy azt már az egyszerű szelekciónál is láttuk, a szelekciót egy vízszintesen felezett téglalappal jelöljük. Mivel a dobozkába nem férne el az összes feltétel, és nem is lenne utána egyértelmű, hogy melyik feltételhez melyik utasítás tartozik, így a dobozba csak egy ? kerül. A doboz alatt sorakoznak fel az egyes feltételek, és az azokhoz tartozó műveletek.

A többszörös szelekcióra tulajdonképpen tekinthetünk úgy is, mint egyszerű szelekciókra egymás után, ahol az egyébként ágakhoz tartozik mindig egy újabb egyszerű szelekció:

kep kep

A többszörös szelekció megvalósítására nincs külön C konstrukció, így az egymásba ágyazott egyszerű szelekciókkal tudjuk megvalósítani:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if(F1) {
    A1;
} else if(F2) {
    A2;
    ...
} else if(Fn) {
    An;
} else {
    B;
}

Az if utasítások ismételt alkalmazása

Talán az eddigi példákból már feltűnt, hogy amikor egy kódot írunk, akkor bizonyos dolgok "beljebb" kezdődnek. A kód tabulálása azt a célt szolgálja, hogy a kód áttekinthetőbb legyen, látszódjék, hogy egy utasítás, vagy utasítás blokk mely másik utasítás része.

Láttuk, hogy a függvények utasításai blokkba rendeződnek, a blokkon belüli utasításokat pedig beljebb tabulálva írjuk. Ugyanígy teszünk egy szelekciós utasításunk ágaival is. Az abban levő utasításokat blokkosítjuk, a blokkon belüli utasításokat pedig beljebb tabuláljuk. Amennyiben egy összetett utasítás részutasítása egyetlen utasításból áll, akkor a blokk persze elmaradhat, de fontos, hogy a tabulálás megmaradjon. Egyes esetekben viszont jobb, ha a blokkokat sem hagyjuk el a biztonság kedvéért.

Vegyük a következő esetet, amikor két egyszerű szelekció van egymásba ágyazva:

kep

Írjuk le ezt C kóddal:

1
2
3
4
5
6
if(F1) {
    if(F2)
        A1;
    else
        A2;
}

Mivel a belső if egyetlen utasítás, így akár a zárójelezés elmaradhat. De vajon egyértelmű ebben, hogy az else melyik if-hez tartozik? Mi van, ha elcsúszik a tabulálás?

1
2
3
4
5
if(F1)
    if(F2)
        A1;
else
  A2;

Mivel a C nyelv nem érzékeny az úgynevezett whitespace, azaz nem látható karakterekre (szóköz, tabulátor, sortörés), az else ebben az esetben is a második if-hez tartozik, azaz a két kód ekvivalens. (Csak megjegyezzük: vannak olyan programozási nyelvek, ahol ez nemigaz, vagyis a kód tabulálása, formája az értelmezését is befolyásolja.) Ha azt szeretnénk, hogy az else az első if-hez tartozzon, akkor azt zárójelezéssel jelezni kell:

kep

1
2
3
4
5
6
if(F1) {
    if(F2)
        A1;
}
else
  A2;

Esetkiválasztásos szelekciós vezérlés

Ha a többszörös szelekció valamennyi feltételét átírhatjuk úgy, hogy azok elemek valamilyen halmazokba való tartozását vizsgálja, akkor esetkiválasztásos szelekcióról beszélünk.

Legyen K egy adott típusú kifejezés, és legyenek Hi-k olyan halmazok, melynek elemeinek típusa megegyezik K típusával. Legyenek továbbá Ai tetszőleges műveletek, ahol 1<=i<=n teljesül. A K szelektor kifejezésből, Hi kiválasztó halmazokból és Ai műveletekből képzett esetkiválasztásos szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Értékeljük ki a K kifejezést és folytassuk a 2.) lépéssel.
  2. Adjunk választ a következő kérdésre: Van-e olyan i (1<=i<=n), amelyre teljesül, hogy a K ∈ Hi, és K ∉ Hj, ahol (1<=j<i)?
  3. Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
  4. Egyébként, vagyis ha K nem eleme egyetlen Hi halmaznak sem, akkor fejezzük be az összetett művelet végrehajtását.

Itt is természetesen lehet egyébként águnk, amit akkor hajtunk végre, amikor egyik feltétel sem teljesül. Ekkor a 4. lépést egészítsük ki egy nemüres B művelet végrehajtásával.

Legyen K egy adott típusú kifejezés, és legyenek Hi-k olyan halmazok, melynek elemeinek típusa megegyezik K típusával. Legyenek továbbá Ai és B tetszőleges műveletek, ahol 1<=i<=n teljesül. A K szelektor kifejezésből, Hi kiválasztó halmazokból és Ai és B műveletekből képzett esetkiválasztásos szelekciós vezérlés a következő vezérlési előírást jelenti:

  1. Értékeljük ki a K kifejezést és folytassuk a 2.) lépéssel.
  2. Adjunk választ a következő kérdésre: Van-e olyan i (1<=i<=n), amelyre teljesül, hogy a K ∈ Hi, és K ∉ Hj, ahol (1<=j<i)?
  3. Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
  4. Egyébként, vagyis ha K nem eleme egyetlen Hi halmaznak sem, akkor hajtsuk végre B-t és fejezzük be az összetett művelet végrehajtását.

Amennyiben a szelektor kifejezés, amelynek értékét egy v változóban elmentük benne van valamelyik Hi halmazban, úgy az adott esethez tartozó Ai műveletet hajtjuk végre, egyébként pedig ellenőrizzük a következő halmazba való tartozást.

A szerkezeti ábráknál szintén egy osztott téglalappal jelezzük a szelekciót, ahol a téglalap felső felében megadjuk a szelekciót meghatározó kifejezést, az egyes halmazokat pedig az átláthatóság érdekében a téglalap alatt adjuk meg, a hozzájuk tartozó műveletekkel együtt.

kep kep

A C nyelvben az esetkiválasztásos szelekciót a switch utasítással valósítjuk meg. Fontos, hogy a switch szelektor kifejezés valamilyen felsorolási típusú legyen (azaz nem lehet például valós típus).

A switch utasítás szelektorától csak az függ, hogy mely ponton kezdjük el végrehajtani a switch magját. Ha a végrehajtás elkezdődött, akkor onnantól kezdve az első break vagy return utasításig megy a switch utasításainak a végrehajtása. Ha ilyen nincs, akkor akár az utolsó utasításig. Ebben a fázisban már nincs annak jelentősége, hogy van-e további case vagy default címke. Vagyis ahhoz, hogy a switch utasítás az esetkiválasztásos szelekció megvalósítása legyen, szükséges, hogy az esetkiválasztásos szelekció egy ághoz tartozó utasítások után egy break segítségével megszakítsuk a switch további utasításainak (és így magának a switch utasításnak) a végrehajtását. A break helyett speciális esetekben a return utasítás is megfelelő lehet.

A switch utasítást tehát például a következő implementációval tudja az esetkiválasztásos szelekciót megvalósítani:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
switch(K) {
    case H1:
        A1;
        break;
    ...
    case Hn:
        An;
        break;
    default:
        B;
        break;
}

A kiválasztó halmazok megadása az esetkiválasztásos szelekció kritikus pontja. Algoritmusok tervezése során bármilyen effektív halmazmegadást használhatunk, azonban a tényleges megvalósításkor csak a választott programozási nyelv eszközeit alkalmazhatjuk.

A Hi halmazok elemszáma tetszőleges lehet, a case-ek után viszont csak egy-egy érték állhat. Viszont kihasználhatjuk a switch működési elvét, miszerint a címkék csak a belépési pontot határozzák meg. Ha Hi = {xi,1, xi,2, . . . , x i,ni }, akkor az adott ág lekódolható a következő kódrészlettel:

1
2
3
4
5
6
case x_i1:
case x_i2:
...
case x_ini:
   Ai;
   break;

Feltételes kifejezés

Az előző példában a kiíratás kissé összetettre sikeredett, gyakorlatilag 3 printf függvény hívásával oldottuk meg egyetlen mondat kiírását:

1
2
3
4
5
printf("A dátum ");
if (!jo) {
   printf("nem ");
}
printf("helyes.\n");

Ez lerövidíthető az úgynevezett feltételes kifejezéssel így:

1
2
3
printf("A dátum");
printf(jo ? " " : " nem ");
printf("helyes.\n");

vagy még rövidebben így:

1
printf(jo ? "A dátum helyes.\n" : "A dátum nem helyes.\n");

A feltételes operátor a C nyelv egyetlen három operandusú művelete, amely már a nyelv kezdetei óta a nyelv része. Három kifejezésből áll:

1
kifejezés_1 ? kifejezés_2 : kifejezés_3

A teljes kifejezés kiértékelése során először kiértékelésre kerül a kifejezes_1 kifejezés. Amennyiben ez igaz (azaz az érték nem 0), akkor a teljes kifejezés értéke a kifejezes_2 lesz, egyébként ha hamis (azaz 0), akkor a kifejezés értéke a kifejezes_3 lesz.