Tingimuslaused

Tähelepanu!
Leht on alles valmimisel, mistõttu on mõned kohad poolikud!

Siin on põhjalik ülevaade tingimuslausete kohta.

Sissejuhatus

Tingimuslausete abil on võimalik kõige lihtsamal viisil juhtida programmi töövoogu. Lihtsamalt öeldes võimaldavad tingimuslaused muuta programmi tegevusi sõltuvalt tingimusest, mida kontrollitakse.

Näiteks piletimüügisüsteem, mis kontrollib inimese vanust ja selle alusel valib tegevuse, mida programm hakkab täitma. Alaealised ei saa piletit osta, eakatele kehtib soodushind ja ülejäänud peavad tasuma täishinna.

Iga tingimuslause puhul hinnatakse, kas tingimus on tõene või väär. Erinevates programmeerimiskeeltes on need erineval kujul. C89/C90 standardi puhul kasutatakse täisarve, kus tõene on 1 ja väär 0. C99 laienduses saab kasutada otseselt võtmesõnu tõene (true) ja väär (false) kui need on manuaalselt defineeritud või kasutades teeki stdbool.h, milles on need juba defineeritud.

Pane tähele, et mitmetes järgnevates lõikudes on toodud ka negatiivsed näited – mida mitte teha. Need on eristatud erksa taustavärviga.

if-lause

if-lause põhivorm on
if ( expression ) statement

Kus avaldis (expression) on tingimus, mida hinnatakse, ja statement tähistab kõiki lauseid, mis täidetakse, kui tingimus on tõene.

if-lause on tõene, kui avaldise väärtus on nullist erinev! Pane tähele, et kuigi varasemalt öeldi, et 1 tähistab tõeset hinnangut, on tingimuslause tõene ka siis, kui selle väärtus on ükskõik milline nullist erinev arv (sealhulgas -5 või 100). Rohkem infot nõuannete ja nippide all.

Vaatame näidet. Muutuja vanus (age) algväärtus on 10. Tingimuslause kontrollib, kas vanus on alla 18. Kuna tingimus on tõene, täidetakse kõik laused loogeliste sulgude vahel.

Proovi järele

  • Kompileeri ja jooksuta koodi ning vaata, mis juhtub
  • Muuda vanuse (age) väärtus 20 peale. Kas printf() laused täidetakse ka nüüd?
  • Muuda vanuse (age) väärtus 18 peale ja proovi uuesti. Seda nimetatakse piirjuhuks. Piirjuhte peab alati testima, sest just nendega esineb kõige sagedamini vigu.

Võrdlusoperaatorid

Eeldame näidete jaoks järgnevaid deklaratsioone ja väärtusi:

Märkus: C-keeles on võrdlusoperaatorite tulemuseks alati kas täisarv 1 (tõene) või 0 (väär).

Operaator Kirjeldus Näide
== Hinnatakse tõeseks, kui mõlemad operandid on võrdsed (a == c) –> väär
(b == c) –> tõene
!= Hinnatakse tõeseks, kui operandid ei ole võrdsed! (Hüüumärki kasutatakse eituseks) (a != c) –> tõene
(b != c) –> väär
< Hinnatakse tõeseks, kui vasakpoolne operand on väiksem kui parempoolne operand (a < c) –> tõene
(b < c) –> väär
(c < a) –> väär
<= Hinnatakse tõeseks, kui vasakpoolne operand on väiksem või võrdne parempoolse operandiga (a <= c) –> tõene
(b <= c) –> tõene
(c <= a) –> väär
> Hinnatakse tõeseks, kui vasakpoolne operand on suurem kui parempoolne operand (a > c) –> väär
(b > c) –> väär
(c > a) –> tõene
>= Hinnatakse tõeseks, kui vasakpoolne operand on suurem või võrdne parempoolse operandiga (a >= c) –> väär
(b >= c) –> tõene
(c >= a) –> tõene

if-else lause

Et lisada alternatiivset käitumist, mis täidetakse kui tingimus ei ole tõene, saame lisada else lause. Else-plokis olevad laused täidetakse juhul, kui avaldis hinnatakse vääraks.

if ( expression ) statement else statement

Vaatame eelmist näidet ja lisame sinna else lause. Nüüd täidetakse olenevalt muutuja age väärtusest kas if-plokis olevad laused või else-plokis olevad laused.

Proovi järele

Muutuja age algväärtus on nüüd 30. Seega:

  1. Tingimus if (age < 18) hinnatakse vääraks, sest age väärtus on 30.
  2. Koodiplokk ridadel 3 – 6 jäetakse vahele. Seejärel leitakse 7. real else ning täidetakse loogeliste sulgude vahel olevad laused.

Ära kunagi jäta koodiplokki tühjaks! See on halb stiil! Kui sul ei ole midagi lisada koodiplokki, mis täidetakse kui if-lause tingimus on tõene, siis pööra see tingimus vastupidiseks.

Tühjade if-lause koodiplokkide käsitlemine

Sul ei tohiks kunagi olla tühja koodiplokki. Kui see siiski peaks juhtuma, on kaks võimalust, kuidas seda lahendada.

  1. Leia vastupidine tingimus ja kasuta seda
  2. Pööra algne tingimus ümber kasutades inversioonioperaatorit (!)

Võtame eelnevast osast halva näite ja parandame selle. Esiteks, proovime leida vastupidise tingimuse. Et leida inimesi, kes ei ole nooremad kui 18, tuleb otsida inimesi kes on vanemad kui 18 või täpselt 18-aastased.

Levinud viga: tingimuse  (age < 18)  vastupidine tingimus ei ole (age > 18), vaid (age >= 18). Jälgi piirjuhte!

Teine võimalus on inverteerida algne tingimus. Seda saab teha kasutades inversioonioperaatorit (! – hüüumärk). Sellisel juhul võetakse kogu algne tingimus, pannakse see sulgude vahele ja inverteeritakse see.

Pesastatud tingimuslaused

Mitut if-lauset saab üksteise sisse pesastada. Siiski ole sellega ettevaatlik, sest see võib programmi struktuuri nii segi ajada, et vea ilmnemisel kulub selle silumiseks väga palju aega. Enne pesastatud tingimuslausete kirjutamist kaalu, kas saaksid selle asemel kasutada else if lauseid, ühendada mitu tingimust loogikaoperaatoritega või kasutada teisi refaktoriseerimistehnikaid. Vaata allpool olevaid peatükke.

if-else if-else kasutamine

Mitme tingimuse alamkomplekti kontrollimiseks saame kasutada else if lauseid. Kõigepealt kontrollitakse esimest tingimust ja seejärel liigutakse järgmise tingimuse juurde. Kui üks tingimus hinnatakse tõeseks, täidetakse sellele vastav koodiplokk. Pärast esimese tõese tingimuse leidmist järgnevaid tingimusi enam ei kontrollita. Kui ükski tingimus ei osutu tõeseks, täidetakse else lause (kui see on olemas).

Loogikaoperaatorid

C keeles on defineeritud kolm loogikaoperaatorit. Pane tähele, et loogikaoperaatorid on bitwise operaatoritest erinevad.

Eeldame näidete jaoks järgmisi deklaratsioone ja väärtusi:

Operaator Tehe Tulemus on tõene kui … Näide
&& Loogiline JA

Konjunktsioon

… mõlemad pooled on tõesed (a && b) –> väär
(b && c) –> tõene
((a < b) && (b < c)) –> tõene
|| Loogiline VÕI

Disjunktsioon

… vähemalt üks on tõene (a || b) –> tõene
((a > b) || (b > c)) –> väär
((a < b) || (b > c)) –> tõene
! Loogiline eitus

Inversioon

… algtingimus on väär (!a) –> tõene
(!b) –> väär
(!a || !b) –> tõene
(!(a || b)) –> väär

Tõeväärtustabelid

Logiline JA

A B A && B
0 0 0
0 1 0
1 0 0
1 1 1

Loogiline VÕI

A B A || B
0 0 0
0 1 1
1 0 1
1 1 1

Inversioon

A !A
0 1
1 0

Switch lause

Switch lause otsib täpset vastet avaldiste vahel. Kui vaste leitakse, käivitatakse kood kuni switch lause lõpuni või kuni break lauseni. Pane tähele, et kui break lauset ei ole, siis täidetakse ka järgnevates case’des olevad laused. Olenevalt kasutusjuhust võib see olla kas viga või taotluslik käitumine.

Vaikimisi (default) juhtum on valikuline, kuid tungivalt soovituslik. Kui ükski case ei vasta avaldisele, täidetakse vaikimisi juhtumi laused.

Mõned nõuanded ja nipid

Järgnev osa on erinevate viiside kombinatsioon tingimuslausete tõhusaks kasutamiseks.

Ainult muutuja nime kasutamine avaldises

See tehnika tugineb arusaamisele, et täisarv 0 loetakse C keeles väärtuseks väär ( false). Kõiki teisi (nullist erinevaid) väärtusi loetakse mitte-vääraks ( true).

Sellises tingimuses kasutatakse sageli inversioonioperaatorit (!) vajaliku tingimuse kiireks saamiseks.

Proovi järgmist koodi, andes muutuja algväärtuseks 0, 1, -1 jne.

Üherealised tingimuslaused ilma loogeliste sulgudeta

Eelmises näites on näidatud, et täidetavad laused on alati sulgude sees. See ei ole nõue. C keeles võib nii tsükleid kui tingimuslauseid kirjutada ilma loogeliste sulgudeta, aga sellisel juhul kuulub tingimuslause või tsükli juurde ainult esimene lause, mis järgneb tingimusele.

Järgnev näide on täiesti korrektne if-else lause ja töötab ootuspäraselt.

Need on enamasti eelistatud ruumi kokkuhoiu tõttu ja tihti kasutavad neid just kogenud programmeerijad. Mõnikord võib näha, et lause kirjutatakse tingimuslause algusega samale reale, et säästa veel rohkem ruumi.

Üherealiste tingimuslausete probleemid

Vähem kogenud peaksid selliste kompaktsete vormide kasutamisel ettevaatlikud olema. Programmeerimist alustades on mitmeid põhjuseid, miks neid pigem vältida.

1. Ebaühtlane stiil

Pead eristama ja märkama erinevaid viise lausete kirjutamisel. See võib tekitada alguses segadust. Vahel võib mõni lause jääda kahe silma vahele ja see tekitab vigu, mida on tülikas üles leida.

2. Suurem tõenäosus teha kogemata vigu lausete lisamisel või eemaldamisel

Sageli teed koodi kirjutamise ajal või hiljem muudatusi. Ilma piisava kogemuseta võid unustada sulgude lisamise, kui lisad uue rea.

Võtame eelneva vanusekontrolli näite, kus anti teade ainult alaealisusest, ja lisame sinna veel ühe printf() lause, mis meenutab, et on vaja vanemlikku järelvalvet. Kasutame vormindamisel ka taanet, et jääks mulje nagu see kuuluks tingimuslause juurde.

Kui vanus on alla 18, täidetakse kõik 3 printf()  lauset, mis on ootuspärane. Kui aga unustad testida vastupidist juhtu – kui vanus on üle 18, ei pruugi sa isegi teada, et seal on viga. Üle 18-aastaste puhul tuleb ikkagi hoiatus vanemliku järlvalve vajaduse kohta, kuigi see ei olnud arendaja kavatsus.

3. Väldi mitmemõttelisust pesastatud tingimuslausete kasutamisel

Seda nimetatakse ka dangling else probleemiks, millest on täpsemalt räägitud allpool.

Dangling else probleem

See viib meid tagasi pesastatud if-lausete juurde. Vaatame näidet:

Süntaksi mõttes on kood korrektne ja kompileerub. See sisaldab pesastatud if-lauset, mis väljastab kõigile 18-65aastastele pileti täishinna. Funktsionaalselt on see aga vale ja seda nimetatakse dangling else probleemiks. Lihtsamalt öeldes – millise  if lause juurde else kuulub? Probleemi süvendab koodi vormindus, mis jätab mulje, et kood on korrektne. Kuid tuletame meelde, et tühikud ei mõjuta C keelt. Kogu kood võiks olla ühel real ja see ikkagi kompileeruks.

Kui su kompilaatoris on asjakohased hoiatused lubatud, näed selle kohta hoiatust:
warning: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]|

Proovi koodi! Proovi seda nii, et vanus on 20. Seejärel proovi nii, et vanus on 80. Nagu näha, kuulub else viimase if-lause juurde mitte esimese.

Halvasti pesastatud tingimuslausete refaktoriseerimine

Kuigi if-else lauseid on võimalik pesastada, et luua olukordi, mille puhul peab olema täidetud mitu tingimust, lõppeb see sageli halvasti loetava koodiga. Sellist koodi on raske jälgida, siluda ning sageli võib esineda harusid, mis osutuvad kas alati tõeseks või alati vääraks.

Seda saab refaktoriseerida mitmel viisil. Üks võimalus on järjestada tingimused ümber ja kasutada else if lauset.

Sellel on nii plusse kui miinuseid. Plussiks on see, et kood on kergesti loetav. Kõigepealt kontrollitakse spetsiifilisemad juhud ja lõpuks jääb järele üldine juhtum.

Miinuseks võib olla see, et kood peab olema hästi optimeeritud kiiruse jaoks ja sündmus on peamiselt mõeldud täiskasvanutele. Sellisel juhul täidetakse kõige tõenäolisemalt täiskasvanute haru, kuid enne seda kontrollitakse kõiki teisi tingimusi. See muudab koodi aeglasemaks. Refaktoriseerimine hõlmab sageli ka kiiruse või mälukasutuse optimeerimist, mis põhineb tõenäosusteoorial (vastavalt meie andmete iseloomule, milline tingimus on kõige tõenäolisem).

Funktsiooni väljakutsumine tingimuslausetes

Koodi loetavuse parandamiseks tasub see jaotada väikesteks ühe-eesmärgilisteks funktsioonideks. Kui sellised funktsioonid tagastavad väärtuse, saab seda kontrollida tingimuslauses.

Saame kasutada võrdlusoperaatoreid, et võrrelda funktsioonist tagastatud väärtust kriteeriumiga. Vaatame funktsiooni, mis loendab komade arvu sõnes.

Nüüd kutsume selle funktsiooni välja tingimuslauses.

Võid ka kasutada varasemalt tutvustatud võimalusi ja muuta seeläbi kood puhtamaks.

Ilmselt näed palju sarnaselt kirutatud koodi andmete valideerimisel, kus funktsioonid tagastavad ainult true/false väärtusi.

Näiteks võiksid meil olla funktsioonid nagu SentenceBeginsWithChar() , SentenceMoreThanOneWord() , SentenceEndWithPunctuation() , ContainsAVerb() jne – kui kõik need on tõesed, loetakse seda ilmselt korrektselt moodustatud lauseks.

Nagu tavaliselt, saab seda kombineerida teiste tingimuslausetega. Samuti võib tingimuslause sees olla mitu funktsiooni väljakutset, aga need peavad olema formuleeritud vastavalt keele reeglitele.

Omistamised tingimuslausetes

Seda kasutatakse rohkem tsüklites, kuid see esineb ka näiteks andmete valideerimisel. Seda võib pidada kaks ühes opratsiooniks – selle asemel, et kõigepealt omistada väärtus ja pärast kontrollida, saab need kaks kombineerida.

Ternaarne operaator

TODO

Ternaarne operaator on sisuliselt lühike viis kirjutada if-else lauset. Selle eelis on see, et seda saab kasutada kohtades, kus if-else lauset ei saa kasutada, seetõttu nimetatakse seda vahel ka inline if-ks. Keelesüntaksi järgi eraldatakse avaldisi sümbolitega ? ja : .

expression ? expression : expression;

See ei ole eriti loetav, kui sa just ei tea täpselt, mida vaatad. Kirjutame selle veidi arusaadavamalt lahti

(condition_to_check) ? expression(s)_if_true : expression(s)_if_false;

 

De Morgani seadus

TODO