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
1. példa: egyszerű számológép
2. példa: évszakok
tippek, tanácsok - ternáris operátor
1. példa: if-else helyett
2. példa: MAX(num1, num2) preprocesszor makró
3. példa: konstansok kezdőértéke
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.
- isocpp.org - Is the ? : operator evil?
- fluentcpp.com - Replacing an Else-if Sequence With a Ternary Operator
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