C++ programozás 9. – Feltételvizsgálatok

Feltételvizsgálatok, avagy merre menjek?

A programozási nyelvek egyik alapja az elágazás, más néven szelekció. Programjainkban szinte minden esetben előfordul az, hogy valamilyen pontnál dönteni kell, hogy most erre vagy arra induljunk, ami valamilyen feltételtől függ. Például:

  • Ha a szám kisebb, mint 0, akkor a szám negatív.
  • Ha a szám kettővel osztva 0 maradékot ad, akkor a szám páros.
  • Ha a szám a [100;999] intervallumban van, akkor a szám 3 számjegyű.
  • Ha a testtömegindexem 25 vagy attól nagyobb, akkor túlsúlyos vagyok.

Ezek a példák olyan értelemben egyformák, hogy bizonyos feltétel teljesülése esetén a program elvégezhet olyan utasítást, amit a feltétel nem teljesülése esetén kihagy.

A feltételvizsgálatokat több típusra bonthatjuk, attól függően, hogy hány elágazásra bomlik és hogyan kezeljük az úgynevezett “egyéb” ágat. Nézzük az első, legegyszerűbb típust:

if( feltétel )
    utasítás;

// vagy blokk használata esetén

if( feltetel )
{
    utasitas1;
    utasitas2;
    ..
    utasításN;
}

Ezek tehát azok az alkalmazási formák, melyeknél a feltétel teljesülése esetén a program végrehajtja a feltételhez kötött utasítást vagy utasításblokkot, egyébként nem csinál semmit sem, csak átugorja őket. Személy szerint én jobb szeretem az egy utasításos feltételvizsgálatnál is kitenni a blokk határolókat, hogy ha később bővíteni kell, nehogy kimaradjon. Igaz, ezt inkább a diákok miatt teszem így, mert náluk ez valóban gondot szokott okozni, hogy elfelejtik. A feltételvizsgálatok alapja a logikai kifejezés, melyet előzőleg már ismertettem.

Nézzünk pár példát. A kiemelt sorokat külön megmagyarázom, ezeket érdemes megjegyezni azért, mert ezek olyan alapvizsgálatok, melyekkel sokszor fogsz a programozás során találkozni.

Adott egy szám. Vizsgáld meg, hogy negatív-e a szám és írd ki, ha igen.

int szam = 25;
if( szam < 0 )
{
    cout << "A szam negativ." << endl;
}

Adott egy szám. Vizsgáld meg, hogy páros-e a szám és írd ki, ha igen.

int szam = 25;
if( szam % 2 == 0 )
{
    cout << "A szam paros." << endl;
}

A kiemelt sort érdemes megjegyezni, mert ez olyan alapfeltétel, amelyikkel sokszor találkozol majd a programozásban: Ez a párosságot vizsgálja. Ha egy számot kettővel osztunk és a maradék 0, az azt jelenti, hogy a szám páros. Felhívnám a figyelmet itt egy tipikus hibára, amellyel nagyon sokszor találkozom. Sokan így vizsgálják a páratlanságot:

if( szam % 2 == 1 ) ...

Mi ezzel a gond? Negatív számok esetén ha kettővel osztom a számot és az páratlan volt, akkor a maradék nem 1 lesz, hanem -1, tehát ez a feltétel a -3 számot párosnak adná meg, ami nyilvánvalóan helytelen. Viszont a maradék így lehet 1 vagy -1 is, ami mindjárt két vizsgálatot jelent:

int szam = 25;
if( szam % 2 == 1 || szam % 2 == -1 )
{
    cout << "A szam páratlan." << endl;
}

A kiemelt sorban a két részfeltételt (1 vagy -1 a maradék) egy LOGIKAI VAGY kapcsolattal köti össze, így a program teljesen hibátlan lesz. De vegyük észre azt, hogy egy szám csak páros vagy páratlan lehet, más eset nincs. A két eset egymás ellentettje. Másképp megfogalmazva: ha nem páros, akkor páratlan. Így picit egyszerűbb ellenőrizni, hiszen a párosságot egy feltétellel vizsgálhatom. Lássuk, mire gondolok:

int szam = 25;
if( szam % 2 != 0 )
{
    cout << "A szam páratlan." << endl;
}

A kiemelt sorban ha a maradék nem nulla (ami ugye 1 vagy -1 is lehet), akkor a szám biztosan páratlan. Ezt sem árt megjegyezni a későbbiekre való tekintettel. Adott egy szám. Vizsgáld meg, benne van-e ebben az intervallumban: [10;40] és írd ki, ha igen.

int szam = 25;
if( szam >= 10 && szam <= 40 )
{
    cout << "A szam benne van az intervallumban." << endl;
}

Az intervallum vizsgálatnál láthatod, hogy akkor van benne egy adott szám egy intervallumban, ha annak alsó határánál nagyobb vagy egyenlő ÉS a felső határánál kisebb vagy egyenlő. Ezt a két részfeltételt a LOGIKAI ÉS művelettel kell összekötni.

A következő típusú feltételvizsgálat az, amikor megjelenik az “egyéb” ág. Formailag ez a következőképp néz ki:

if( feltétel ) utasítás1;
else utasítás2;

// vagy blokkok használata esetén

if( feltetel )
{
    utasitas1;
    utasitas2;
    ..
    utasításN;
}
else
{
    utasításX;
}

Ebben az esetben szintén adott egy feltétel, melyhez kapcsolódik valamilyen utasítás vagy blokk, de a feltétel nem teljesülése esetén is van teendő, ilyenkor az else ág utasításai futnak le (végrehajtódnak). Ennél a típusnál a blokkok használata miatt 4 különböző forma különíthető el, de nem fogom mindet felsorolni példával, az utolsó példa esetén láthatjuk, hogy akár keverhetjük az egy utasításos formát az egyik ág esetén blokkhasználattal a másik ágnál. Fontos az, hogy itt a két ágból az egyik utasításai minden esetben lefutnak. Vagy a feltételhez kötöttek, vagy minden más esetben. Ha egy szám nem páros, akkor páratlan. Ha a pénzfeldobás eredménye nem fej, akkor írás. Ezt a szerkezetet tehát olyan esetekben lehet használni, amikor az elágazás két oldala egymás ellentettje. viszont ha egy autó nem benzinüzemű, az nem jelenti azt, hogy csak dízel lehet.
A következő esetben már két ágnál is több létezik. Formailag ez így néz ki:

if( feltétel1 ) utasítás1;
else if( feltétel2 ) utasítás2;
else utasítás3;

// vagy blokkok használata esetén

if( feltétel1 )
{
    utasítás1;
    utasítás2;
    ...
    utasításN;
}
else if( feltétel2 )
{
    utasításX;
}
else
{
    utasításY;
    utasításZ;
}

Az utolsó blokkos példa esetén láthatjuk azt, hogy a blokkok használata mindig a példától függ. Ahol több utasítás kapcsolódik egy ághoz, oda kötelező blokkot tenni, az egy utasítású ágakhoz nem kötelező (de lehetne).

Ilyen 3 ágú elágazásra jó példa a pozitív-negatív vizsgálat.

int szam = (int)(Math.random()*21)-10;
if( szam < 0 )
{
    cout << "A szam negativ." << endl;
}
else if( szam > 0 )
{
    cout << "A szam pozitiv." << endl;
}
else
{
    cout << "A szam nulla." << endl;
}

Egymást kiegészítő ágak vannak, vagyis egy szám a három eset valamelyikének megfelelő lehet. Van else ág, mert mindenképp valamilyen kategóriába be kell sorolni a számot, kihagyni nem lehet. Viszont a feltételeket másképp is megadhatom, más sorrendben vizsgálom, hogy milyen a szám. Ez az else ágra is hatással van, hiszen akkor ott mást jelent majd az “egyéb”. Lássuk akkor az előző példa megoldását másképp:

int szam = rand() % 21 - 10;
if( szam = 0 )
{
  cout << "A szam nulla." << endl;
}
else if( szam < 0 )
{
  cout << "A szam negativ." << endl;
}
else
{
  cout << "A szam pozitiv." << endl;
}

A kétféle feltételvizsgálat a működése során ugyanazokra a számokra ugyanolyan eredményeket ad. Egyszerűen csak más sorrendben adtam meg a feltételeket. Az else if ágak száma nem kötött, bármennyit lehet használni belőle. Nem ritka a 6-7 ágú feltételvizsgálat sem.

int honap = rand() % 12 + 1;
if( honap == 3 ||  honap == 4 || honap == 5 )
{
    cout << "Tavasz" << endl;
}
else if( honap == 6 ||  honap == 7 || honap == 8 )
{
    cout << "Nyar" << endl;
}
else if( honap == 9 ||  honap == 10 || honap == 11 )
{
    cout << "Osz" << endl;
}
else
{
    cout << "Tel" << endl;
}

Az ilyen típusú többágú feltételvizsgálatok, ahol egy változó különféle értékeit kell vizsgálni, könnyebben és hatékonyabban megoldható egy speciális többágú feltételvizsgálatnak, melyet switch-nek nevezünk. Erről majd később bővebben írok.

Végezetül jöjjön az a típus, amikor csak az if és else if ágak szerepelnek, de else ág nincs. Adott egy változó, mely egy számot, egy adott ember testmagasságát tárolja. Meg kell vizsgálni, hogy az illető a testmagassága alapján túl magas vagy túl alacsony-e. Ha a testmagassága normális, akkor ne írjunk ki semmit sem. Itt kihasználom egy későbbi lecke, a véletlen számok sorsolásának módszerét is:

// a testmagassag a [140;200] intervallumban legyen
int magassag = rand() % 61 + 140;
if( magassag < 160 )
{
    cout << "Tul alacsony" << endl;
}
else if( magassag > 190 )
{
    cout << "Tul magas" << endl;
}

Ilyenkor két egymással összefüggő feltételt vizsgálok meg, de nincs else ág, vagyis lehetséges, hogy egyik feltétel sem teljesül és a program kihagyja ezeket az utasításokat. A programrész 160 alatti magasság esetén kiírja a Tul alacsony, 190 felett pedig a Tul magas szöveget. Átlagosnak vélt magasság esetén semmit nem ír ki.

A többágú feltételvizsgálatok esetén minden esetben csak egyetlen ág utasításai kerülnek végrehajtásra. Sőt, nem és értékeli ki az összes feltételt a program, az első teljesült feltétel esetén végrehajtja az ahhoz kapcsolt utasításokat és a többit automatikusan átugorja, meg sem vizsgálja. Maradva a hónapos példánál. Ha a hónap száma mondjuk 5, akkor az első feltétel lesz igaz, kiírja a program, hogy Tavasz és rögtön az else ág végére ugrik, a többi esetet nem vizsgálja. Nyilván ezt akkor kell így megoldani, ha a feltételek egymást kizáró vizsgálatokat tartalmaznak. Egy hónap csak egy évszakhoz tartozik, és ha megvan a megfelelő évszak, a többi vizsgálat felesleges. Nézzük ugyanezt a hónapvizsgálatot másképp:

int honap = rand() % 12 + 1;
if( honap == 3 ||  honap == 4 || honap == 5 )
{
    cout << "Tavasz" << endl;
}
if( honap == 6 ||  honap == 7 || honap == 8 )
{
    cout << "Nyar" << endl;
}
if( honap == 9 ||  honap == 10 || honap == 11 )
{
    cout << "Osz" << endl;
}
if( honap == 12 ||  honap == 1 || honap == 2 )
{
    cout << "Tel" << endl;
}

Ez is ugyanazt eredményezi, csak itt egyrészt nincs else ág, tehát a Tel évszakot is külön meg kell vizsgálni, másrészt itt hiába teljesül 4. hónap miatt az első feltétel, mivel a többi feltétel nem az első elágazása (nem else if ágak), ezért ilyenkor minden feltételt a többitől függetlenül megvizsgál teljesen feleslegesen. Ez ugyanis nem egy többágú feltételvizsgálat, hanem 4 egymástól független eset, melyeknek semmi köze egymáshoz. Az összefüggő többágú feltételvizsgálat esetén sem mindig mindegy, hogy milyen sorrendben adom meg a vizsgált feltételeket. Erre jó példa a szökőév vizsgálat, amit majd külön feladatként ismertetek. Álljon itt akkor egy példa független és egy többágú feltételvizsgálatra:

// 1. példa
int szam = rand() % 50 + 1;
if( szam % 3 == 0 )
{
    cout << "A szam oszthato harommal." << endl;
}
if( szam % 5 == 0 )
{
    cout << "A szam oszthato ottel." << endl;
}
// 2. példa
int szam = rand() % 50 + 1;
if( szam % 3 == 0 )
{
    cout << "A szam oszthato harommal." << endl;
}
else if( szam % 5 == 0 )
{
    cout << "A szam oszthato ottel." << endl;
}

Nézzük meg mit ír ki az 1. és a 2. példa a következő számokra:

Szam 1. példa
független
2. példa
többágú
4
10 A szam oszthato ottel. A szam oszthato ottel.
9 A szam oszthato harommal. A szam oszthato harommal.
15 A szam oszthato harommal.
A szam oszthato ottel.
A szam oszthato harommal.

A probléma a 15-ös számnál bukik ki, amikor a szám mindkét feltételnek megfelel. Az 1. példa megvizsgálja a 3-mal oszthatóságot és az 5-tel oszthatóságot is minden esetben. A többágú ellenőrzés esetén a 15-re teljesül a 3-mal oszthatóság – amit a program ki is ír – de az 5-tel oszthatóságot már meg sem vizsgálja, hiszen többágú vizsgálat esetén csak akkor vizsgálja a további feltételeket a program, ha az előzőek közül egyik sem teljesült. A vizsgálat első igaz feltételnél meg fog állni! Ilyenkor a független ellenőrzés a jó megoldás, hiszen nem egymást kizáró feltételekről van szó, hiszen a 15 mindkét számmal osztható.

(A következő lecke a Switch lesz, ha megírom, addig is lépjünk a következőre)

Következő lecke: Függvények

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .