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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> int main(void) { int age = 10; if (age < 18) { printf("You are underage!\n"); printf("You are not allowed to the event without parenthal supervision!\n"); } return 0; } |
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:
1 2 3 |
int a = 10; int b = 20; int c = 20; |
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 statementVaatame 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.
1 2 3 4 5 6 7 8 9 10 11 |
int age = 30; if (age < 18) { printf("You are underage!\n"); printf("You are not allowed to the event without parental supervision!\n"); } else { printf("Age is verified, you are welcome to participate!\n"); } |
Proovi järele
Muutuja age algväärtus on nüüd 30. Seega:
- Tingimus if (age < 18) hinnatakse vääraks, sest age väärtus on 30.
- 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.
1 2 3 4 5 6 7 8 |
if (age < 18) // DO NOT DO THIS!!! This is incorrect! { } else { printf("Age is verified, you are welcome to participate!\n"); } |
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.
- Leia vastupidine tingimus ja kasuta seda
- 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!
1 2 3 4 |
if (age >= 18) { printf("Age is verified, you are welcome to participate!\n"); } |
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.
1 2 3 4 |
if (!(age < 18)) { printf("Age is verified, you are welcome to participate!\n"); } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (age >= 18) { if (age < 65) { printf("Tickets for adults: 10 euros\n"); } else { printf("Tickets for seniors: 4 euros\n"); } } else { printf("Minors only allowed with parental supervision\n"); } |
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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int age = 30; if (age < 18) { printf("Minors only allowed with parental supervision\n"); } else if (age >= 65) { printf("Tickets for seniors: 4 euros\n"); } else { printf("Tickets for adults: 10 euros\n"); } |
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:
1 2 3 |
int a = 0; int b = 10; int c = 20; |
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.
1 2 3 4 5 6 7 8 9 10 11 |
switch (expression) { case constant or expression: statements break; case constant or expression: statements break; default: statements } |
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.
1 2 3 4 5 |
int inputDataValidation = 0; if (!inputDataValidation) // any non-zero value will evaluate to true (notice inversion) { printf("The data validation has failed!\n"); } |
Ü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.
1 2 3 4 5 |
int age = 30; if (age < 18) printf("You are underage!\n"); else printf("Age is verified, you are welcome to participate!\n"); |
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.
1 2 3 4 5 6 7 8 |
int age; printf("How old are you?\n"); scanf("%d", &age); if (age < 18) exit(1); printf("Welcome to the event!\n"); |
Ü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.
1 2 3 4 5 |
if (age < 18) printf("You are underage!\n"); printf("You are not allowed to the event without parental supervision!\n"); printf("The tickets cost 20 euros!"\n); |
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:
1 2 3 4 5 6 |
int age = 20; if (age >= 18) if (age < 65) printf("Price for full ticket: 10 euros\n"); else printf("Minors are not allowed without a guardian\n"); |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <stdio.h> int main(void) { int age = 20; if (age >= 18) { if (age < 65) { printf("Price for full ticket: 10 euros\n"); } else { printf("Price for elderly: 4 euros\n"); } } else { printf("Minors are not allowed without a guardian\n"); } return 0; } |
Seda saab refaktoriseerida mitmel viisil. Üks võimalus on järjestada tingimused ümber ja kasutada else if lauset.
1 2 3 4 5 6 |
if (age < 18) printf("Minors are not allowed without a guardian\n"); else if (age >= 65) printf("Price for elderly: 4 euros\n"); else printf("Price for full ticket: 10 euros\n"); |
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).
1 2 3 4 5 6 |
if (age >= 18 && age < 65) printf("Price for full ticket: 10 euros\n"); else if (age >= 65) printf("Price for elderly: 4 euros\n"); else printf("Minors are not allowed without a guardian\n"); |
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.
1 2 3 4 5 6 7 8 9 10 11 12 |
int NumOfCommas(char *str) { int count = 0; int i = 0; while (str[i]) { if (str[i] == ',') count++; i++; } return count; } |
Nüüd kutsume selle funktsiooni välja tingimuslauses.
1 2 3 4 5 |
char *t = "Hello, world!"; if (NumOfCommas(t) > 0) { printf("The text has at least 1 comma!\n"); } |
Võid ka kasutada varasemalt tutvustatud võimalusi ja muuta seeläbi kood puhtamaks.
1 2 3 |
char *t = "Hello, world!"; if (NumOfCommas(t)) printf("The text has at least 1 comma!\n"); |
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