[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - switch-case és ternáris operátor

[2021. augusztus 27.] [ christo161 ]

Ebben a tananyagrészben a szokásos if-else elágazástól eltérő elágazásokról lesz szó, a switch-case elágazásról, és a ternáris operátorról.

Előző tananyagrész: elágazások, logikai kifejezések
Követlező tananyagrész: tömb, std::array, std::vector, for ciklus

Tartalom

switch-case

A többágú elágazások egy speciális változata a switch-case (érték szerinti elágazás), amit csak akkor használhatunk, ha az összes ágban egy kifejezés értékét vizsgáljuk, és a kifejezés értékétől függően szeretnénk további utasításokat megadni (ezzel szemben az if-elseif-else elágazásokban az utolsót leszámítva minden ágban megadhatunk egy, az előzőtől különböző logikai kifejezést).
A switch-case tulajdonképpen helyettesíthető egymásba ágyazott if-else-if... elágazásokkal, amikben minden esetben ugyanarra a kifejezésre vonatkozó egyenlőségvizsgálat van feltételnek megadva (pl. variable_example == 1, variable_example == -3, vagy esetleg variable_example%3 == 2, variable_example%3 == 1, satöbbi). A switch esetén az egyenlőségvizsgálat nem helyettesíthető más logikai kifejezéssel (pl. variable_example < 0), továbbá csak egész szám típusú változók (pl. int, char) értékeit vizsgálhatjuk switch segítségével (ha pl. double vagy std::string típusú változót, kifejezést írunk a kerek zárójelek közé, fordítási hibát kapunk).

Egy szemléltető kódrészlet a switch-case típusú elágazás működéséről. (Ez csak szemléltető kódrészlet, ha esetleg bemásoljuk egy forrásfájlba, fordítási hibát okoz).

switch (/*expression*/) {
case 1:
  //statements if expression == 1
  break;
case 2:
  //statements if expression == 2
  break;
case 3:
  //statements if expression == 3
  break;
default:
  //statements if expression != any of above
  break;
}

Ha a vizsgált változó char típusú, akkor az egyes ágaknál a vizsgálandó értékeket aposztrófok között kell megadni, pl. case 'a': vagy case 'b':

Fontos: ha az egyik ágban  elhelyezett utasítások végére nem tesszük ki a break; utasítást, akkor azon ág utasításainak végrehajtásakor az egyel alatta lévő ág utasításai is végre fognak hajtódni. Ez akár hasznos is lehet bizonyos esetekben, de erre mindig figyeljünk, ha switch-case típusú elágazást használunk.
Jó gyakorlat lehet, ha a default ág után is rakunk break; utasítást, hátha esetleg valaki később hozzáír még egy ágat a default alá.
Ha egy függvényben helyezünk el switch-case elágazást, akkor break; utasítás helyett return; vagy return /*expression*/; utasítást is írhatunk.

Ha esetleg az egyes ágakban változókat hozunk létre, akkor ahhoz, hogy azok a változók ne legyenek elérhetőek más ágakban, az adott ág utasításait kapcsos zárójelek közé kell tenni. Például:

case 1:
{
  //statements
}
case 2:
{
  //statements
}
//...

1. példa: nagyon egyszerű számológép

Ebben a példában bekérünk a felhasználótól két számot (egy-egy double típusú változóba), illetve egy műveleti jelet (egy char típusú változóba) a két szám bekérése között.
A felhasználó az értékeket akár egy sorban is megadhatja szóközök használatával, vagy azok nélkül (pl. 4.2 / 2.1, 8 % 3, 1 / 0), de külön sorban is megadhat minden egyes értéket.

Ennek az egyszerű példaprogramnak a lényege nyilván a char típusú változóba bekért értéken múlik, amibe a műveleti jelet kérjük be. Ha például a felhasználó +-t adott meg (a char típusú változó értékének), akkor a két szám összegét írja ki a program, ha pedig mondjuk *-t akkor a két szám szorzatát.
Érdekességképp a megadható műveleti jelek között szerepel a % (egészosztási maradék meghatározása) is, mely esetén nyilvánvalóan át kell konvertálnunk a double típusú változók értékét egész számokká, hiszen a maradékos osztás csak egész számok esetén van értelmezve (fordítási hibát kapnánk, ha nem int típusú értékeket adnánk át a % operátornak).

Nullával való osztás, vagy maradékos osztás esetén kilépünk a programból. Erre a main függvényben a return 0; utasítást használhatjuk, még az elágazás végrehajtása előtt. 
Érdemes azért megjegyezni, hogy a return 0; csak a main függvényen belül eredményezi a programból való kilépést, ha ezek az utasítások más függvények blokkjában lennének, ott a return 0; utasítás csak annyit jelentene, hogy az adott függvény a hívás helyére adjon vissza egy 0 értékű számot. Arra majd a függvények tananyagrészben kitérünk, hogy hogyan kell kilépni a programból ha egy (main függvénytől eltérő) függvényben vagyunk.

A példaprogram kódja:

//switch-case example
#include <iostream>

int main () {
  double num1, num2;
  char sign;

  std::cout << "Kerem adjon meg egy szamot, egy muleveleti jelet, majd egy masik szamot\n";
  std::cin >> num1>> sign >> num2;

  if (num2 == 0 && ( sign == '/' || sign == '%' ) ) {
    std::cout << "0-val nem lehet osztani.\n";
    return 0;
  }
  
  std::cout << "Az eredmeny: ";

  switch (sign) {
  case '+':
    std::cout << num1 + num2 << '\n';
    break;
  case '-':
    std::cout << num1 - num2 << '\n';
    break;
  case '*':
    std::cout << num1 * num2 << '\n';
    break;
  case '/':
    std::cout << num1 / num2 << '\n';
    break;
  case '%':
    std::cout << static_cast<int> (num1) % static_cast<int> (num2);
    break;
  default:
    std::cout << "ismeretlen muveleti jel, vagy hibas adatok.\n";
    break;
  }
}

Tipp: kipróbálhatjuk mi történik, ha töröljük, vagy kikommenteljük a 0-val való osztásra vonatkozó kilépést a program forráskódjából, és a program futtatásakor osztást adunk meg műveleti jelként, a második számnak pedig 0 értéket.

2. példa: évszakok

Ez egy jó példa arra, hogy vannak olyan esetek, amikor érdemes elhagyni a break; utasítást egy (vagy több) ág végéről, hogy a vezérlés tovább folytatódjon a következő ág utasításaival.

//switch-case example
#include <iostream>

int main() {
  std::cout << "Which month (1-12)?\n";
  int month= 0;
  std::cin >> month;

  switch(month) {
  case 3:
  case 4:
  case 5:
    std::cout << "Spring.\n"; break;
  case 6:
  case 7:
  case 8:
    std::cout << "Summer.\n"; break;
  case 9:
  case 10:
  case 11:
    std::cout << "Autumn.\n"; break;
  case 12:
  case 1:
  case 2:
    std::cout << "Winter.\n"; break;
  default:
    std::cout << "Not a number of a month.\n"; break;
  }
}

forrás: nagygusztav.hu - C alapok kezdőknek

C++17 vagy újabb szabvány szerint fordított kódban használhatjuk a [[fallthrough]] attribútumot, mellyel jelezzük a fordító számára, hogy szándékosan hagytuk el a break; utasítást, hogy a következő ág utasításai is végrehajtásra kerüljenek, így fordításkor nem kapunk ezzel kapcsolatos warningot.

switch-case egyéb tanácsok, tippek

Ha lehet, if-else helyett switch-case-t használjunk, mert gyorsabb.

A default ágban

Ternáris operátor

Angolul conditional operatornak is szokták nevezni, illetve Javascriptben esetleg short-hand if-else-nek is nevezik. Magyarul láttam már logikai kifejezésnek fordítva, de ebben a tananyagban logikai kifejezés alatt az angol logical expressiont értjük (pl. !error_flag && variable_example < 0). Én inkább egyszerűen csak ternáris operátornak nevezem ebben a tananyagban.
A nevét onnan kapta, hogy három operandusa van, sokáig ez volt az egyetlen három operandusú operátor a C++ nyelvben, a C++20 vagy újabb szabvány szerint értelmezett kódban már használhatjuk a <=> operátort (úgynevezett spaceship operátor is), ami szintén három operandusú, de annak semmi köze a jelenleg tárgyalt ternáris operátorhoz, ezért nehéz összekeverni a kettőt.
(Két operandusú (bináris) operátor például az összeadás (+), értékadás (=), vagy az egyenlőségvizsgálat (==) hiszen ezen operátorok két oldalán két kifejezés állhat, illetve egy operandusú (unáris) például az előjelek, vagy a ++ operátor).

Egy elágazást tömörebben leírhatunk a ? : ternáris operátor segítségével. A ternáris operátor első operandusa a feltétel (általában logikai kifejezés, vagy logikai változó) a kérdőjel előtt, másik két operandusa pedig az elágazás két ága (az igaz és a hamis ág), a kettőspont előtt és után.

A ternáris operátor használatának szemléltetése (két változat):

/*condition*/ ? /*statements if condition is true*/ : /*statements if condition is false*/
/*condition*/ ? /*expression if condition is true*/ : /*expression if condition is false*/

Lényeges különbség az ifhez képest, hogy a ternáris operátor egyes ágaiban nem csak utasítások, hanem kifejezések is állhatnak. (Nyilván ha a ternáris operátor egyik ágában kifejezés áll, akkor a másik ágában is kifejezés kell hogy álljon, és ez ugyanígy igaz utasítás esetén is). Ha a ternáris operátor egyes ágaiba kifejezéseket írunk, akkor magát a ternáris operátoros kifejezést például értékül adhatjuk egy változónak (a változó értéke pedig a feltételtől függően az egyik ágban, vagy a másik ágban lévő kifejezés értéke lesz), vagy kiírathatjuk cout-tal (ekkor a feltételnek megfelelő ágban lévő kifejezés értékét írja ki a cout). Mindkettőre nézünk példát.

Jókat lehet arról vitatkozni, hogy átláthatóbb-e mint az if-else, van aki jobban szereti, van, aki nem.

1. példa: if-else helyett

#include <iostream>

int main () {
	int input_number;
	std::cout << "Please enter a number (with not too many digits):\n";
	std::cin >> input_number;

	std::cout << "The input number (" << input_number << ") is ";
	std::cout << (input_number % 2 == 0 ? "even." : "odd.") << '\n';
}

A ternáris operátort tartalmazó utasítást persze így is megfogalmazhattuk volna:

input_number % 2 == 0 ? std::cout << "even.\n" : std::cout << "odd.\n";

A ternáris operátor használata esetén nem árt odafigyelni a zárójelezésre, különben összetett kifejezések esetén könnyen adódhatnak hibák. Ha például a fenti példában elhagyjuk a ternáris operátoros kifejezést körülvevő zárójeleket, a program le sem fordul.

2. példa: preprocesszor makró

A ternáris operátor használata esetén sok esetben fontos az alapos zárójelezés.

Az interneten gyakran előforduló példa: 

#define MAX(num1, num2) ( (num1) > (num2) ? (num1) : (num2) )

Melyben a prerocesszor segítségével definiálunk egy makrót. Ha lefordítjuk az ezt tartalmazó programot, még a fordítás előtt a preprocesszor a programunkban szereplő összes MAX(num1,num2) szöveget kicseréli erre: ( (num1) > (num2) ? (num1) : (num2) )
Viszont lehet, hogy a példaprogramban valahol szerepel egy összetett kifejezés, aminek a ternáris operátoros kifejezésre cserélendő kifejezés csak egy részkifejezése, például: eredmeny = MAX(num1,num2) * -1;
Ha a definícióban nem lenne ilyen alapos a zárójelezés a ternáris operátoros kifejezésben, akkor ez esetben csak akkor lenne megszorozva -1-el az eredmény, ha a num2 változó lenne a nagyobb, mivel a makró behelyettesítése után a szorzás "összefolyna" a ternáris operátor hamis ágával. Ez a hiba csak zárójelezéssel kerülhető el.

#include <iostream>
#define MAX(num1, num2) ( (num1) > (num2) ? (num1) : (num2) )

int main () {
  int szam_1{}, szam_2{}, eredmeny{};
  std::cout << "Kerem adjon meg ket (nem tul sok szamjegybol allo) szamot: ";
  std::cin >> szam_1 >> szam_2;

  eredmeny = MAX(szam_1, szam_2) * -1;
  std::cout.put('\n');
  std::cout << "A nagyobb szam -1-szerese: " << eredmeny;
}

3. példa: konstansok kezdőértéke

Csak ternáris operátorral lehet megoldani a konstansok értékadásának feltételhez kötését. Például eszerint az utasítás szerint ha a (korábban már definiált) fajlnev (std::string típusú) változó értéke fajl2.txt, akkor az elvalaszto_karakter értéke kettőspont lesz, egyéb esetben szóköz.

const char elvalaszto_karakter = ( fajlnev == "fajl2.txt" ? ':' : ' ');

Nézzük, mindez hogyan nézne ki if segítségével:

/*Hibás kód*/
if (fajlnev == "fajl2.txt") {
	const char elvalaszto_karakter = ':';
} else {
	const char elvalaszto_karakter = ' ';
}

Egy lokális változóra nem hivatkozhatunk azon a blokkon kívül, amiben létrehoztuk.
Egy nem konstans változó esetén meg lehetne tenni, hogy az if blokkján kívül definiáljuk, de az if blokkján belül adunk neki értéket, viszont a konstansok (const típusminősítővel ellátott változók) létrehozásakor meg kell adnunk azok (kezdő)értékét is.

Előző tananyagrész: elágazások, logikai kifejezések
Követlező tananyagrész: tömb, std::array, std::vector, for ciklus

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr4916669800

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása