Az eldöntendő típus a lehető legegyszerűbb adattípus. Egyetlen eldöntendő kérdésre adható választ tartalmaz.
Felvehető értékei:
- true (igaz)
- false (hamis)
bool a = true;
bool b = false;
Ez pontosan egy bit mennyiségű információt jelent:
A típust neve: bool
Ez az elnevezés George Boole matematikustól származik, aki megalkotta a róla boole algebrát.
Műveletek, amik visszatérése bool
Nézzük meg azokat a számokon és egyéb adattípusokon végezhető műveleteket, amiknek az eredménye bool
:
- Egyenlőség vizsgálat: A egyenlő e B-vel?
- Egyenlőtlenség vizsgálat: A és B különböző e?
- Reláció vizsgálata (Most még csak számokon)
- A kisebb-e, mint B?
- A nagyobb-e, mint B
- A kisebb vagy egyenlő-e, mint B
- A nagyobb vagy egyenlő-e, mint B
A == B
A != B
A < B
A > B
A <= B
A >= B
Példakód:
int i1 = 2, i2 = 3;
// Egyenlőségvizsgálat: Bármilyen adattípuson végezhetők:
bool equalityCheck1 = i1 == i2; // i1 és i2 egyenlő-e? // false
bool equalityCheck2 = "Some string" == "Some other string"; // false
bool equalityCheck3 = "Same string" == "Same string"; // true
// Egyenlőtlenségvizsgálat: Bármilyen adattípuson végezhetők:
bool unEqualityCheck1 = i1 != i2; // i1 és i2 nem egyenlő-e? // true
bool unEqualityCheck2 = "Some string" != "Some other string"; // true
bool unEqualityCheck3 = "Same string" != "Same string"; // false
// Csak számokon végezhető érték összehasonlítások:
bool relationCheck1 = i1 > 5; // i1 nagyobb-e, mint 5? // false
bool relationCheck2 = -2 < i2; // -2 kisebb-e, mint i2? // true
bool relationCheck3 = i1 >= i2; // i1 nagyobb vagy egyenlő-e, mint i2? // false
bool relationCheck4 = i1 <= 2; // i1 kisebb vagy egyenlő-e, mint 2? // true
Minden eddig tanult művelet operandusai nem feltétlenül voltak bool
típusúak. Most nézzük meg néhány olyan műveletet, amiknek az operandusai és a visszatérése is bool
típusú
A logikai ÉS művelet
Képzeljük el, hogy egy játékot írunk, amiben a játékos akkor tud lőni ha
- a töltényei száma nagyobb mint nulla
- a legutóbbi lövés óta eltelt legalább 1.5 másodperc.
ÉS
(Ezt az időt nevezzük a játékfejlesztésben “cooldown”-nak.)
Mind a két feltételnek teljesülnie kell, ezért is használtuk az ÉS szót a mondat megfogalmazásában.
A két feltétel mindegyike egy eldöntendő kérdés. Reprezentálhatjuk egy bool
típussal tehát.
A kérdés: A töltények száma nagyobb-e mint nulla?
B kérdés: Eltelt-e legalább 1.5 másodperc a legutóbbi lövés óta.
Az, hogy tudunk-e lőni is egy eldöntendő kérdés és akkor igaz, ha mindkét korábbi feltételre igaz a válasz. Tehát:
Tudunk-e lőni? = A ÉS B
Ez a logikai és művelet és a leírására C#-ban a && operátort használjuk.
int ammo; // Lövedékek száma
float timeSinceLastShot; // Legutóbbi lövés óta eltelt idő
float cooldownTime = 1.5f; // Ennyi ideig kell várakoznu a lövés után
... // Valahogyan kiszámoljuk a fenti értékeket
bool doHeveAmmo = ammo > 0; // Van-e elég lövedékünk?
bool noCooldown = timeSinceLastShot >= cooldownTime; // Lejárt-e a "Cooldowcn"?
bool canShoot = doHeveAmmo && noCooldown; // Tudunk-e lőni?
A logikai VAGY művelet
Képzeljük el, hogy ugyanebben a játékban ugrani is tudunk méghozzá egyszer a földön és egyszer a levegőben (”double jump” vagy “air jump”). Szóval akkor, tudunk ugrani ha
- a földön állunk
- még egyszer sem “double jump”-oltunk.
VAGY
Ebben az esetben a logikai VAGY műveletre van szükségünk, amit a || operátorral jelzünk. (Kettő darab “pipe” (pájp) karakter. Magyar billentyűzeten: AltGr + W)
bool isGrounded; // A földön állunk-e?
int airJumpCountSinceGrounded; // Ennyit ugrottunk a levegőben az elrugaszkodás óta
... // Valahogyan kiszámoljuk a fenti értékeket
bool canAirJump = airJumpCountSinceGrounded == 0; // A levegőben ugorhatunk-e?
bool canJump = isGrounded || canAirJump; // Tudunk-e ugrani?
A logikai KIZÁRÓ VAGY művelet
Ha harmadik fontos logikai művelet, amit megtanulunk a KIZÁRÓ VAGY vagy XOR művelet.
Ez hasonlóan működik, mint a VAGY, azt leszámítva, hogy ha mindkét operandusa igaz akkor a XOR hamis eredményt ad.
Példa lehet erre például egy multiplayer játék ahol két karakter A és B harcol egymással. A feladat hogy egy körön kívülre üssék egymást. A játék egy perces körökre van osztva. Ha letelik a perc, a játék ellenőrzi, hogy valaki győzött-e. Ha senkin nem győzött az egy perces visszaszámlálás előlről kezdődik. Mindig amikor letelik az idő a program ellenőrzi, mindkét játékosra, hogy a körön kívül vannak-e. A játék csak akkor ér véget, ha pontosan egy játékos van körön kívül.
A ^ operátorral fejezzük ki a kizáró vagy logikai műveletet. (Magyar billentyűzeten: AltGr + 3)
bool isPlayerOutOfRing_A; // A játékos a körön kívül van-e?
bool isPlayerOutOfRing_B; // B játékos a körön kívül van-e?
... // Valahogyan kiszámoljuk a fenti értékeket
bool isGameOver = isPlayerOutOfRing_A ^ isPlayerOutOfRing_B; // Vége van-e a játéknak?
Másik valós világ béli példa lehet, a következő: Elmegyek sörözni, ha legalább Péter vagy Pál is kint lesz, akkor viszont nem megyek, ha mindketten ott lesznek, mert ha együtt folyton csak politizálnak.
Igazságtáblák
A logikai ÉS művelet eredménye csak akkor lesz igaz, ha mindkét operandusa igaz, minden egyéb esetben hamis lesz. Hasonlóan a logikai VAGY csak akkor lesz hamis, ha mindkét operandusa hamis.
Ezt az alábbi ábrák szemléltetik. Az ilyen táblázatokat igazságtábláknak nevezzük.
Logikai ÉS igazségtáblája
A | B | A AND B |
False | False | = False |
False | True | = False |
True | False | = False |
True | True | = True |
Logikai VAGY igazségtáblája
A | B | A OR B |
False | False | = False |
False | True | = True |
True | False | = True |
True | True | = True |
Logikai XOR igazségtáblája
A | B | A XOR B |
False | False | = False |
False | True | = True |
True | False | = True |
True | True | = False |
A logikai NEGÁLÁS
A logikai negálás egy egy operandusú művelet. Egy bool-t kap, egy bool-t ad vissza, mindig az ellenkezőt.
True-ból false-t, false-ból true-t.
A ! operátort használjuk a negálásra
A | NOT A |
False | = True |
True | = False |
bool isSunny = true; // Napos-e az ég?
bool isCloudy = !isSunny; // Felhős-e az ég?
Több logikai művelet is létezik, amiket nem tanulunk itt.
Összetett logikai műveletek
Egy sorban bonyolultabb kifejezéseket alkothatunk egyszerű logikai műveletekből.
Ahogy a számoknál itt is zárójelezéssel szabályozhatjuk a műveleti sorrendet.
int num; // Egy szám
bool isPositive = num > 0; // Pozitív szám-e?
bool isNull = num == 0; // Nulla-e?
bool isNatural = isNul || IsPositive; // Természetes szám-e?
bool isEven = num % 2 == 0; // Páros szám-e? (Gondold végig, miért?)
bool isOdd = !isEven; // Páratlan szám-e?
// Olyan számok érdekelnek minket, amik povitívak és párosak,
// vagy negatívak, de páratlanok. Erre akarjuk tesztelni a számunkat.
bool isRight = (isPositive && isEven) || (!IsNatural && isOdd);
// Ezt a kifelyezést akár egy sorben is megírhattuk vola,
// de az kevéssé lett volna olvasható
Kiegészítő anyag: Nevezetes azonosságok
Ahogy a hagyományos algebrában (számtanban), úgy a boole algebrában is léteznek nevezetes azonosságok, amik memorizálása segíthet:
C# kódban kifejezve:
bool p, q;
...
p || q == !(!p && !q) // allways true
p && q == !(!p || !q) // allways true
Hogy mi is az igazi nagy haszna a bool
típusnak azt a vezérlési szerkezeteknél fogjuk látni:
Kiegészítő anyag: Lusta (conditional logical) és mohó kiértékelés
Vegyük az alábbi pédakódokat:
int a, b, c, d, e;
// ... ezek a változók valami módon kapnak értékeket.
// Példakód A :
bool someValue =
(a > b) || (c % 10 + d != e);
// Példakód B :
bool part1 = a > b;
bool part2 = c % 10 + d != e;
bool someValue = part1 || part2 ;
Kérdés, hogy vajon melyik kiértékelés fut le gyorsabban, A
vagy B
?
Javaslom, hogy mindenki álljon itt meg és gondolja ezt végig a válaszát és az indoklást.
Ennek oka, hogy a C# ||
és &&
operátora is úgynevezett lusta vagy más néven feltételes kiértékelést használ. Ez azt jelenti, hogy ha az operátor BAL oldala alapján kiderül, hogy mi az eredmény, akkor ki sem értékeli a JOBB oldalt.
Hiszen: true || <akármi> == true
és ugyanígy false && <akármi> == false
Szóval ha a fenti példában az a > b
kódrészlet eredménye igaz (true
), akkor a c % 10 + d != e
, kifejezés eredményét meg sem kísérli a fordító kiszámolni. Az itteni modulo, összeadás és egyenlőségvizsgálat műveletek elvégzését ekkor tehát megspóroltuk. Ezért lehet az A
példakód valamivel gyorsabb. Persze, ha a bal oldal eredménye hamis (false
), akkor muszáj kiértékelni a jobb oldalt is és ekkor a futási idő megegyezik a két példára.
Ezzel szemben B
példakódban garantáltan kiértékelődik minden, hiszen azokat a vagy (||
) művelet előtt, tőle függetlenül hajtjuk végre.
Ha egy kiértékelés a fent leírt módon működik, akkor az “lusta”, viszont hogyha függetlenül a bal oldali operandus értékétől kiértékeljük a jobb oldali operandust is, akkor úgy nevezett “mohó” kiértékelésről beszélünk.
C#-ban mindkettőre van lehetőség:
Logikai művelet | És: AND | Vagy: OR | Kizáró vagy: XOR |
Mohó | A & B | A | B | A ^ B |
Lusta | A && B | A || B | NINCS |
(A &
, |
és a ^
operátorok nem csak logikai műveletként használhatók, hanem bitműveletként is. Azonban ezekkel itt nem foglalkozunk. Később olvashatsz róla a megfelelő fejezetben:
Bináris adatok, bitműveletek (Hamarosan))
A lusta kiértékeléssel tehát időt spórolhatunk a mohóhoz képest, ám ennek nem csak az optimalizáció szempontjából van jelentősége.
A következők megértéséhez az alábbi leckék átnézése szükséges. Ha ezt még nem tetted meg, javaslom, itt hagyd abba jelen oldal olvasását és később térj vissza.
Metódusok, függvények Összetett típusok és példányaik, Összetett típusok tagmetódusai, Referencia- és értéktípusok
Első példa arra, hogy mikor jelent nagy különbséget a lusta és a mohó kiértékelés:
Ha egy referenciatípusú változóval dolgozunk, akkor ez a változó tartalmazhatja a null
értéket is. Ekkor sok esetben null-tesztet végzünk.
if(variable != null)
variable.DoSomething();
Ha egyéb feltétele is van a műveletnek, akkor mindezt végezhetnénk így is:
if(variable != null)
{
if(variable.someValue = 100)
variable.DoSomething();
}
A fenti leírást lusta kiértékeléssel a következőképp egyszerűsíthetjük:
if(variable != null && variable.someValue = 100 ) // ✅ Lusta kiérékelés ✅
variable.DoSomething(); // Ha null, akkor nem is nézzük tovább
Ekkor a mohó kiértékelés nem használható:
if(variable != null & variable.someValue = 100 ) // 🚫 Mohó kiértékelés 🚫
variable.DoSomething(); // ERROR VESZÉLY
Hiszen ha a bal oldal hamis, azaz variable == null
, akkor a jobb oldalon egy null
-nak akarnánk elkérni a someValue
értékét. Ilyet nem tehetünk, ez futási idejű hibát adna.
Egy másik példa:
Bizonyos esetekben a függvényeknek van úgy nevezett “mellékhatása” is, ami azt jelenti, hogy az eredmény visszaadása mellett, módosítanak is valamit a program belső állapotán. Ha ilyen mellékhatásos függvényeket használunk a lusta kiértékelésű operátor jobb oldalán, akkor nem lehetünk benne biztosak, hogy a mellékhatás végrehajtódik. Ezt ez esetet nem is ragoznám tovább, ugyanis a mellékhatásos műveletek használata mindenképp rossz gyakorlat.
&&
és ||
legyen &
és |
helyett, hiszen a névvel ellentétben a lustan nem csak gyorsabb, de bizonyos esetekben biztonságosabb is.