Sissejuhatus
Tsükkel on üks kõige olulisematest programmeerimise alustaladest, mille abil saame enda määratud koodiploki sisu korrata – st saame valida millised laused koodis korduvad ning mis tingimustel. Korratavat koodiplokki nimetatakse tsükli kehaks. C keeles ümbritsetakse tsükli keha tavapäraselt loogeliste sulgudega { } .
Tsükli keha kordub senikaua kuniks vastavat tsüklit kontrolliv tingimus on tõene. Tsükli tingimuse korrektne koostamine on äärmiselt oluline, et tsükkel töötaks ootuspäraselt ja seeläbi ka meie programm töötaks nii nagu vaja.
Tsükleid on võimalik jaotada eelkontrolliga (entry-controlled) ja järelkontrolliga (exit-controlled) tsükliteks. Tsükli liigi otsustab millal tsükli tingimust kontrollitakse – st kas enne või pärast tsükli keha moodustavate koodilausete käivitamist.
Üldiselt on enamikes programmeerimiskeeltes saadaval sarnane komplekt tsükleid. C keel toetab kolme tsükli liiki – while , do ... while ja for . C keel ei toeta for each tüüpi tsüklit, mida osad keeled toetavad.
Igal tsükli tüübil on omad eelised, mis tingivad ühe või teise tsükli eelistamise vastavalt kasutusjuhule. Sobiliku tsükli tüübi kasutamine annab meile eeliseid nii koodi kirjutamise mugavuses kui ka selguses (loetavuses). Üldiselt on siiski võimalik saavutadasama tulemus ükskõik millise tsükli tüübiga, lihtsalt lahendus võib olla kohmakam.
While tsükkel
Tegu on kõige lihtsama ja levinuima tsüklitüübiga. while tsükkel on eelkontrolliga tsükkel, st esmalt kontrollitakse tsükli tingimust ja vaid siis kui see on tõene, käivitatakse tsükli keha. See võib tekitada olukordi, kus tsükkel ei käivitu mitte kunagi.
Ainuke kohustuslik element while tsükli struktuuris on selle tingimus – st kas tsükli keha tohib käivitada või mitte. Tingimust kontrollitakse iga tsükli korduse eel.
|
1 2 3 4 |
while (condition) { // loop body } |
while tüüpi tsükleid kasutatakse peamiselt siis kui vajalik korduste arv ei ole teada koodi kirjutamise ajal. See hõlmab endas ka olukordi, kus tsüklit korratakse lõpmata arv kordi.
Näide: Kuva kõik kolme astmed alla piirväärtuse.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Set up the calculator const int multiplier = 3; const int limit = 2500; printf("Powers of %d under %d are\n", multiplier, limit); // Set up the product int product = 1; // Calculate the powers while (product < limit) { // Print the current power printf("%d", product); // Calculate the next power product = product * multiplier; } |
For tsükkel
for tsüklit on kõige sobilikum kasutada olukorras, kus iteratsioonide ehk korduste arv eelnevalt teada – teisisõnu see tsükli tüüp sobib ideaalselt loendamiseks. for tsükkel on samuti eelkontrolliga tsükkel – st ka selle tsükli puhul on võimalik, et tsükli keha kunagi ei käivitata.Tsükli struktuuris on fikseeritud kohad algväärtustamisele, tingimusele ja avaldisele. Kõik kolm on eraldatud üksteisest semikooloniga. Just see võimaldab seda tüüpi tsükleid kasutada mugavalt loendamiseks, jättes koodi puhtaks ja hästi loetavaks. NB! Jälgi kindlasti mis ajal teostatakse nimetatud 3 komponenti for tsüklist!
|
1 2 3 4 |
for (initialization; condition; expression) { // Loop body } |
Näide: Järgnev tsükkel loendab nullist üheksani. Märka ka, et tsükkel on kirjutatud C99 standardile vastavalt, mis lubab loendurina kasutatava muutuja i deklareerida tsükli päises.
|
1 2 3 4 |
for (int i = 0; i < 10; i++) { printf("i is %d\n", i); } |
C90 vs C99 standardiga for tsükli deklaratsioon
Standard ISO C90 keelab koodilausete ja muutujate deklaratsioonide omavahelist segamist – st esmalt peab deklareerima kõik muutujad funktsiooni alguses, misjärel võivad tulla koodilaused.
See tähendab, et ka tsükli loendur tuleb deklareerida funktsiooni alguses.
|
1 2 3 4 5 6 7 8 |
void PrintIntArray(int nums[], int n) { int i; for (i = 0; i < n; i++) { printf("%d\n", nums[i]); } } |
Alates C99 standardist tohib muutujaid deklareerida seal kus vaja. Sealjuures on lubatud muutujate deklareerimine tsükli sees. See aitab koodi veidi puhtamana hoida.
|
1 2 3 4 5 6 7 |
void PrintIntArray(int nums[], int n) { for (int i = 0; i < n; i++) { printf("%d\n", nums[i]); } } |
Olulised märkused
- Aine raames tohid kasutada mõlemat deklareerimise viisi, kuid eelistatud on C99 vastavuses olevad deklaratsioonid
- Osad vanematele mikrokontrolleritele mõeldud kompilaatoreid toetavad vaid C90 standardit. Sellisel juhul tsüklis muutuja deklareerimine lõppeb kompileerimise veaga.
- C99 stiilis tsüklit deklareerides jääb tsükliloenduri i skoobiks vaid tsükli keha – st i väärtusele ligipääsu pärast tsükli lõppu ei ole!
Do … while tsükkel
do ... while on ainuke tsükli tüüp C keeles, mis kasutab järelkontrolli. St tsükli keha käivitatakse esimene kord ilma tingimust kontrollimata ning alles pärast tsükli keha täitmist kontrollitakse kas tsüklit tohib korrata.|
1 2 3 4 5 |
do { // Loop body } while (condition); |
do .. while tsüklit on mugav kasutada kui tegevust tuleb alati teostada vähemalt ühe korra, kuid võib esineda vajadus tegevuse kordamiseks – nt kui vajaminev kontrolltingimus ei osutu täidetuks. Hea näide sellest on kasutajalt sisestuse küsimine, mis peab olema programmile sobilik enne programmi jätkumist (nt kasutaja peab sisestama positiivse täisarvu).
Näide: Tsüklit korratakse kuni kasutaja sisestab numbri . Märka, et kuigi userInp on jäetud algväärtustamata, siis antud olukorras pole see probleem. Muutuja väärtust kontrollitakse pärast kasutaja poolt väärtuse sisestamist.
|
1 2 3 4 5 6 7 |
int userInp; do { printf("Enter 0 to exit!\n"); scanf("%d", &userInp) } while (userInp != 0); |
Lõpmatud tsüklid
Lõpmatuid tsükleid kohtab kõige sagedamini sardsüsteeme (mikrokontrollereid) programmeerides – kontroller kordab sama tegevust kogu oma eluaja vältel.
Nagu tsüklitega ikka, siis ükskõik millisest tsüklist on võimalik teha lõpmatu tsükkel. Kõige paremini loetava koodi aga saad tavaliselt kasutades while tsüklit, mille tingimuseks on 1 . C keeles annab ükskõik milline mittenulline väärtus tingimusena tõese ehk true tulemuse.
|
1 2 3 4 5 |
// Infinite loop while (1) { // Loop body } |
Aegajalt võib tekkida vajadus koostada lõpmatu tsükkel, millest on võimalik teatud juhtudel väljuda. Väljumiseks kasutame tingimuslauset vastava olukorra tuvastamiseks ning break lauset tsükli katkestamiseks. Break lause kohta loe hilisemast peatükist.
|
1 2 3 4 5 6 7 8 9 10 11 |
// Infinite loop while (1) { // Loop body // Stop the loop if (exit-condition) { break; } } |
Pesastatud tsüklid
Tsüklite pesastamine on tegevus, kus paigutame ühe tsükli keha sisse teise tsükli. Otseseid piiranguid mitu tsüklit pesastada tohib ei ole, kuid koodi hallatavuse säilitamiseks tasub vältida enama kui kahe tsükli paigutamist üksteise sisse. Kui tekib vajadus rohkem tsükleid pesastada, tuleks kaaluda koodi ümber struktureerimist täiendavateks funktsioonideks.
Tsüklite pesastamisel tohib kasutada ükskõik milliseid tsükli tüüpe, sh segamini. Näiteks võib while tsükli sisse paigutada for tsükli.
Järgnevas näites oleme pesastanud kaks for tsüklit.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <stdio.h> int main(void) { const int n = 5; const int m = 7; for (int i = 0; i < n; i++) { printf("Start of the outer loop iteration i = %d\n", i); for (int j = 0; j < m; j++) { printf("i = %d; j = %d\n", i, j); } printf("End of the outer loop iteration i = %d\n\n", i); } } |
Märka, et välimise tsükli kehaks on kaks väljastuse lauset ning for tsükkel. Mõlemat printf lauset ja for tsüklit käivitatakse n korda.
Sisemine tsükkel kasutab välimisest erinevat tsükliloendurit j . Sisemises tsüklis olevat väljastuse lauset korratakse järjest m korda. Küll aga kuna tegu on pesastatud tsüklitega, siis tegelik sisemise printf lause korduste arv saab olema n * m korda!
Tsüklite pesastamisel pööra erilist tähelepanu tsüklite tingimustele ning loenduritele! Loendurite (nt i ja j ) omavahel segamini ajamine on lihtne tulema. See võib põhjustada tsüklite mitte käivitumist, lõpmatuid tsükleid jpm.
Programmivoo kontrolllaused
Tsüklite käitumist saab manipuleerida kasutades programmivoogu kontrollivaid lauseid break ja continue . Mõlemad laused tekitavad koodis “hüppe” (viidatakse kui jump statement) – st nende tulemusena võib muutuda järgmine täidetav koodilause.
break lause
break lauset kasutatakse tsükli töö katkestamiseks. Üldiselt on eesmärgiks kas tsükli töö varasem peatamine või lõpmatust tsüklist väljumine. Järgnev koodinäide demonstreeribki lõpmatu tsükli katkestamist sobiliku tingimuse täitmisel.|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <stdio.h> int main(void) { int num; while (1) { printf("Enter a positive integer: "); scanf("%d", &num); if (num > 0) { break; } } printf("Integer %d is accepted\n", num); return 0; } |
Märka, et eelnev mudel on identne do .. while tsükli mudeliga. Täpselt samasuguse funktsionaalsusega koodi saab kirjutada kasutades do .. while tsüklit. Kusjuures selle näite korral näeb see ka puhtam välja.
Arvesta, et tsükleid pesastades katkestatakse vaid see tsükkel, milles break lause välja kutsutakse.
Täiendavalt kasutatakse break lauset ka switch lausest väljumiseks.
Continue lause
continue lause eesmärk on vahele jätta tsükli kehas jooksval kordusel veel täitmata koodilaused. Nii nagu break lausegi, mõjutab see ainult tsüklit, mille sees koodilause välja kutsutakse – st tsüklite pesastamisel ei mõjuta see välimisi tsükleid.Olenevalt tsükli tüübist on käitumine erinev. while ja do .. while tsüklites kasutamisel hüpatakse tsükli tingimuse kontrolli juurde. Kui aga tegu on for tsükliga, hüpatakse tsükli avaldise täitmisse (nt i++ ), millele järgneb tsükli tingimuse kontrollimine tsükli järgmise iteratsiooni alguses.
Järgnevas näites jäetakse continue lause abil viimane väljastuslause vahele kui kasutaja sisestab nulli või negatiivse täisarvu. Pane tähele, et tegu on lõpmatu tsükliga, st väljumiseks kasuta klahvikombinatsiooni ctrl+c .
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <stdio.h> int main(void) { while (1) { printf("Enter a positive integer: "); int num; scanf("%d", &num); if (num <= 0) { continue; } printf("Integer %d is accepted!\n", num); } return 0; } |
Modelleerimise tulemusel saame paremini illustreerida continue lause poolt sooritatud hüpet koodilausete täitmise järjekorras. Hüpe toimub kohta, kus tavapäraselt toimuks eelkontrolliga tsükli tingimuse kontrollimine, kuid kuna tegu on lõpmatu tsükliga, siis kontrolli ja väljumisvõimalust mudelis ei eksisteeri.
Tsüklid, millel puudub tsükli keha
C keeles lõppevad kõik laused semikooloniga. Seda on võimalik ära kasutada ka tsüklite kodeerimisel. Näiteks saame panna semikooloni kohe pärast for või while tsükli päist. See tekitab olukorra, kus vastaval tsüklil puudub tsükli keha. Ühest küljest võib olla tegu tahtliku programmeerimisvõttega, kuid selline olukord on ka väga tavapärane alustava programmeerija tüüpviga.
Erandina pane tähele, et do ... while tsükkel nõuab semikoolonit pärast tingimust ning antud peatükk selle tsüklitüübi kohta ei käi!
Keha puudumine kui koodimisviga
Kirjutades while tüüpi tsükli tingimuse lõppu semikooloni loome tsükli, millel puudub keha – semikoolon lõpetab selle tsükli lause. Järgnevas näites jääb muutuja i väärtuseks igavesti väärtusele . Kuigi suurendamise operatsioon i++ on loogeliste sulgude vahel, ei ole see koodiplokk eelneva tsükli kehaks – tsükkel lõppes ekslikult lisatud semikooloniga. Loogeliste sulgude vahel olev koodiplokk täidetakse pärast tsükli lõppu (mis on siinpuhul lõputu).
|
1 2 3 4 5 6 7 8 9 |
int i = 0; // Mistake here causes an infinite loop while (i < 5); { // This IS NOT the loop body due to the semicolon! printf("i = %d\n", i); i++; } |
Modelleerides koodilõigu UML tegevusdiagrammile saame paremini illustreerida vigast käitumist.
Kirjutades for tüüpi tsükli semikooloniga tekitame samamoodi tsükli, millel puudub keha. Enamjaolt koodi jooksutamisel jääda mulje, et tsükkel jäetakse vahele või selle sisu teostati vaid korra, kuigi tegelikult tsükkel toimis ja läbiti täies mahus.
Järgneva näite puhul kordab tsükkel end 5 korda, mis on ootuspärane. Kuna tsükli avaldises paikneb i++ , ei ole tegu lõpmatu tsükliga. Küll aga ei ole ka väljastus antud tsükli keha sees, mistõttu tsüklis puudub igasugune väljastus. Print lause ise paikneb tsükli järel paiknevas koodiplokis ja seetõttu käivitatakse ühekordselt.
|
1 2 3 4 5 6 |
// Mistake here will cause the loop to run "invisible" to the programmer for (int i = 0; i < 5; i++); { // This IS NOT the loop body due to the semicolon! printf("i = %d\n", i); } |
Koodilõigus esitatud käitumist saame pareminimi mõistmiseks jällegi visualiseerida kasutades UML tegevusdiagrammi
Tahtlikult puuduv tsükli keha
Mõningatel juhtudel meeldib programmeerijatele teha võimalikult lühikesi programme – st hoida oma koodiridade arv minimaalsena. Selle saavutamiseks kirjutatakse aegajalt üherealisi tsükleid tahtlikult. Seda on võimalik teha nii while kui for tsüklite puhul. Küll aga ei ole see soovitatav ja sageli isegi peetakse seda kohatuks. Sellise koodi probleemiks peetakse tahtlikku koodi sisu peitmist (nt midagi sellist mida arvutiviiruse kirjutaja võiks teha). Koodi peitmine põhjustab hiljem asjatut ajakulu ja probleeme koodi ülevaatustel ja silumisel.
|
1 2 3 4 5 6 7 8 |
char str[] = "What a wonderful day!"; int i = 0; // Loop to the character past the space while (str[i++] != ' '); printf("The string from the second word: %s\n", &str[i]); |
Tegu on võttega, millest peaksid teadlik olema, kuid samal ajal peaksid ise vältima selle kasutamist! Kirjuta tsüklid välja täies mahus, et neid oleks hiljem lihtsam siluda ja hallata.
Märka ka, et tegelikult viimane programm jookseb kokku kui sõnes puuduvad tühikud – ehk tegu on ka peidetud veaga!






