Sissejuhatus
Tsükkel on üks kõige olulisematest programmeerimise alustaladest, mille abil saame endale sobiva koodiploki sisu korrata – st me saame valida millised laused koodis korduvad ning mis tingimustel. Korratavat koodiplokki tuntakse ka kui tsükli keha. 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 grupeerida 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 kehasse kuuluvate koodilausete käivitamist.
Erinevates programmeerimiskeeltes on kasutusel erinevat liiki tsükleid. C keeles on meile saadaval kolm – while , do ... while ja for tsükkel.
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). Kusjuures see ei peata meid teist tsükli tüüpi kasutamast, sama tulemus on võimalik saavutada ükskõik millise tsükli tüübiga, lihtsalt tulemus 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 esimese asjana kontrollitakse tsükli tingimust ja vaid siis kui see on tõene, käivitatakse tsükli keha. See tekitab võimaluse, et tsükkel ei käivitu 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 ka endas 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 |
// Set up the calculator int multiplier = 3; int product = 1; int limit = 2500; printf("Powers of %d under %d are\n", multiplier, limit); // 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 teada eelnevalt – teisisõnu on tegu loendamiseks hästi sobiva tsükliga. for 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 eraldi reserveeritud kohad algväärtustamiseks, tingimuseks ja avaldiseks. Kõik need 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 kõik need 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 C90 standardile vastavalt. C99 stiilis deklaratsiooniga tutvu järgnevates peatükkides..
1 2 3 4 5 6 |
int i; for (i = 0; i < 10; i++) { printf("i is %d\n", i); } |
Do … while tsükkel
do ... while on ainuke tsükli tüüp C keeles, mis kasutab järelkontrolli. St selle tsükli keha käivitatakse esimene kord ilma tingimust kontrollimata ning alles pärast tsükli keha täitmist kontrollitakse kas tsüklit tuleks korrata.
1 2 3 4 5 |
do { // loop body } while (condition); |
Sedatüüpi tsüklit on mugav kasutada kui tegevust tuleb alati vähemalt ühe korra teostada, kuid võib esineda vajadus tegevuse kordamisel – nt kui mõni kontrolltingimus ei saa täidetud. Hea näide sellest on kasutaja sisestus, 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, ka 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 antud muutujasse.
1 2 3 4 5 6 7 |
int userInp; do { printf("Enter 0 to exit!\n"); scanf("%d", &userInp) } while (userInp != 0); |
C90 vs C99 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 kõik tsükli loendurid 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
- Selle aine raames on sul lubatud kasutada nii C90 kui C99 standardit.
- Osa riistvara puhul võib tekkida olukordi, kus on vaid C90 kompilaatorid toetatud. 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!
Lõpmatud tsüklid
Lõpmatuid tsükleid kohtab kõige sagedamini sardsüsteeme (mikrokontrollereid) programmeerides – kontroller kordab sama asja kogu oma eluaja vältel (milleks võib olla kümneid aastaid).
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 enamjaolt 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 me tingimuslauset selle sündmuse tuvastamiseks ning break lauset tsükli katkestamiseks.
1 2 3 4 5 6 7 8 9 10 11 |
// Infinite loop while (1) { // loop body // stop the loop if (exit-condition) { break; } } |
Kui väljumise tingimus paikneb sellise lõpmatu tsükli lõpus, siis sageli võib olla mõistlikum see ümber kirjutada do ... while tsüklina! Selline struktuur võiks välja näha järgnev:
1 2 3 4 5 6 |
// Infinite loop do { // loop body } while (!exit-condition); |
Pesastatud tsüklid
Tsüklite pesastamine on tegevus, kus paigutame ühe tsükli kehasse teise tsükli. Otseseid piiranguid mitu tsüklit pesastada tohib ei ole, kuid koodi haldamise mõttes tasub vältida rohkem kui kahe tsükli kasutamist üksteise sees. 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 vaheldumisi. Näiteks võib olla while tsükli sees for tsükkel.
Järgnevas näites oleme pesastanud kaks for tsüklit ning tsüklid on kirjutatud C99 stiilis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <stdio.h> int main(void) { int n = 5; 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 print 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! Vead kus ajad kogemata loenduri i ja j omavahel segamini on kerged tulema ning see võib põhjustada kas olukorra, kus tsükkel ei jookse kordagi või muutub hoopis lõpmatuks tsükliks.
Lisaks tasub meeles pidada, et kuigi erinevate loendurite ja tingimuste kasutamine on tavapärane, siis tegu ei ole reegliga. Oluline on koostada kood vastavalt eesmärgile mida see täitma peab.
Tsüklid millel puudub tsükli keha
C keeles lõppevad kõik laused semikooloniga. Seda on võimalik ära kasutada ka tsüklite puhul. 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 tekitame me tsükli, millel puudub keha – semikoolon lõpetab selle tsükli lause. Enamasti teki i igavesti väärtusele . Kuigi suurendamise operatsioon i++ on loogeliste sulgude vahel, ei ole see koodiplokk antud tsükli kehaks – tsükkel lõppes kogemata pandud 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++; } |
Kirjutades for tüüpi tsükli semikooloniga tekitame samamoodi tsükli, millel puudub keha. Enamjaolt võib meile 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. Küll aga on ainuke tsükli kehas olev lause i++ , mistõttu erinevalt while tsüklist, ei ole siin 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 7 8 |
int i = 0; // Mistake here will cause the loop to run "invisible" to the programmer for (i = 0; i < 5; i++); { // This IS NOT the loop body due to the semicolon! printf("i = %d\n", i); } |
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!