Ohjausrakenteet C:ssä¶
Osaamistavoitteet: Tässä materiaalissa esitetään miten C-kielessä toteutetaan ja ohjataan ohjelman loogista toiminnallisuutta ohjausrakenteiden avulla. Lisäksi ymmärretään, miten tärkeää on tehdä koodista ulkoasultaan selkeää.
Ohjaurakenteita C-kielessä on seuraavasti:
- Ohjelmalause tarvitaan kertomaan kääntäjälle ohjelman rakenteesta, eli mitä käskyjä ohjelmoija on tarkoittanut suorittaa milloinkin ja missä järjestyksessä. C-kielessä ohjelmalauseet päätetään puolipisteeseen
;
. - Lohkorakenne: koodilohkot jotka toteuttavat jonkun rajatun toiminnallisuuden,. Lohkot rajataan aaltosulkeilla, joiden sisään kirjotetaan ohjelmalauseet
{ lause; lause; lause; ... }
. - Ehtolauseet: haarautumisen toteuttamiseen
if
- jaswitch
. Toiminnallisuus rajataan aaltosulkeilla. - Toistorakenteet (silmukkarakenteet)
for
,while
jado-while
-rakenteilla. Toiminnallisuus rajataan aaltosulkeilla.
Tämä luentomateriaalin osuus lienee suoraviivaisin ja helpoiten omaksuttava osa kurssista, sillä rakenteiden toiminnassa ei C-kielessä ole oikeastaan merkittäviä eroja muihin ohjelmointikieliin nähden.
Totuusarvot¶
Ensiksi kuitenkin on syytä tietää mikä on totta C-kielessä, eli miten katsotaan ohjelmalauseen totuusarvo. C-kielessä se määritellään numeerisena seuraavasti:
- Epätosi, jos arvo on
0
. - Tosi, jos arvo on
muu kuin 0
. Nyt kaikki nollasta poikkeavat arvot, myös negatiiviset 2-komplementtiluvut, merkitsevät totta.
Esimerkki. Sijoituksen jälkeen muuttujan arvo tulkittaisiin tässä todeksi
int8_t onko_lammin = -25;
. Loogiset operaattorit¶
Loogiset operaattorit, joilla voimme tarkastella lausekkeiden totuusarvoja, ovat C-kielessä seuraavat:
- Looginen negaatio
!
- Looginen AND
&&
- Looginen OR
||
Esimerkkejä.
uint8_t muuttuja1 = 0;
uint8_t muuttuja2 = 5;
int8_t muuttuja3 = -27;
int8_t tulos1 = !muuttuja1; // tosi
int8_t tulos2 = muuttuja1 && muuttuja2; // epätosi
int8_t tulos3 = muuttuja2 && muuttuja3; // tosi
Vertailuoperaattorit¶
C-kielen vertailuoperaattorit ovat numeerisia:
- Yhtäsuuri kuin
==
- Erisuuri kuin
!=
- Vertailut
<
,>
,<=
,>=
Muistetaan aiemmasta materiaalista, että C-kielessä myös merkit käsitellään numeroina ASCII-koodauksen mukaan. Eli aina, jos ehto on numeerisesti totta, suoritetaan lauseen koodilohko.
C-kielessä ei voi yhdistää useampaa ehtoa tai vertailua samaan ehtoon, vaan vertailut pitää erottaa ja yhdistää loogisella operaattorilla. Esimerkiksi:
// Python: ('a' <= merkki <= 'z')
if ((merkki >= 'a') && (merkki <= 'z'))
{
...
}
Hox! Bugien välttämiseksi muistetaan käyttää sulkeita vertailu- ja ehtorakenteissa eri lausekkeiden ympärillä, jotta ajatuksemme siirtyy koodiin sellaisenaan. Muutoin tulee olla hyvin selvillä C-kielen operaattorien suoritusjärjestyksestä!
Hox! Toinen yleinen ohjelmointivirhe on käyttää vertailuoperaattorin
==
sijasta sijoitusoperaattoria =
. Samoin bittioperaatio &
voi sekoittua loogisen JA-operaation &&
kanssa. Kääntäjä kyllä yleensä huomauttaa näistä.Ehtorakenteet¶
Ehtorakenteita C-kielessä on kaksi. Perinteinen
if-else
-rakenne ja lisäksi moniosainen ehtorakenne switch
.If-else if-else -jaarittelua¶
C:n ehtolauseista löytyvät ihan samat
if
-, else if
- ja else
-haarat, kuin muistakin ohjelmointikielistä ja toiminta on sama. Haarat testataan aneetussa järjestyksessä ja valitaan se joka ensimmäisenä toteutuu, suoritetaan siihen kuuluva koodilohko ja poistutaan rakenteesta. Ehtolausekkeet laitetaan sulkeiden ( )
sisälle.if (luku < 10) {
printf("Luku on pienempi kuin 10");
}
else if (luku < 100) {
printf("Luku on pienempi kuin 100");
}
else if (luku < 1000) {
printf("Luku on pienempi kuin 1000");
}
else {
printf("Luku on yhtä suuri tai suurempi kuin 1000");
}
Joskus näkee C-kielisiä ohjausrakenteita toteutettuna ilman koodilohkoja. Tämä on ihan laillista C-kieltä, jolloin ehdon toteuduttua suoritetaan vain rakennetta seuraava ohjelmalause.
if (luku < 100)
printf("Luku on pienempi kuin 100");
Hox! Jos halutaan suorittaa useampi ohjelmalause ehdon toteuduttua, on käytettävä lohkoa, joten opetellaan heti alussa aina käyttämään aaltosulkeita!
Switch nostaa kytkintä¶
C-kielessä on myös moniosainen ehtorakenne
switch
, joka testaa vastaako muuttujan arvo jotain annetuista kokonaislukuvakioista. Perusteluna switch
on ehkä selkeämpi vakioarvojen tarkastelussa kuin if-else
-rakenteet.Nyt
switch
-lauseen syntaksi on seuraava. Huomataan, että poikkeavasti lauseessa ei käytetä lohkomerkintöjä vaan break;
-lause kertoo milloin lohko loppuu.switch (muuttuja) {
case VAKIO1:
lause;
lause;
break;
case VAKIO2:
lause;
lause;
break;
default:
lause;
lause;
break;
}
Nyt
switch
-rakennetta käydään ylhäältä alaspäin läpi vertaamalla muuttujan arvoa vakioihin. Jos yhtäsuuruus löytyy, suoritetaan vakiota vastaavan koodilohkoa kunnes törmätään break
-lauseeseen. Sen jälkeen hypätään rakenteesta pois. Koodilohko default
on valinnainen ja suoritetaan vain jos mikään muu ehdoista ei toteudu. Jos
break
jäisi pois, suoritus valuu lohkosta toiseen alaspäin kunnes break
tulee jossain vastaan tai rakenne loppuu. Tämä on joskus hyödyllistä jos tietää mitä tekee, kun nyt voidaan reagoida useisiin ehtoihin samalla lohkolla. Itseasiassa,
switch
-lauseet voitaisiin toteuttaa ihan vastaavalla if-else
-rakenteella:switch(valinta) { if (valinta == 'p') {
case 'p': pituus();
pituus(); } else if (valinta == 'm') {
break; massa();
case 'm': } else if (valinta == 't') {
massa(); tilavuus();
break; } else if (valinta == 'l') {
case 't': lampotila();
tilavuus(); } else {
break; virhe();
case 'l': }
lampotila();
break;
default:
virhe();
break;
}
Tässä ehtojen testaus valuu samallatavoin alaspäin kuin
if-else
-rakenteessa. Eli switch
-rakenne on siis tavallaan turha, mutta se on helppolukuinen tapa toteuttaa vakioarvon testauksia, joilla on monia rinnakkaisia vaihtoehtoja. Lisäksi, rakenne tulee muiden kirjoittamassa koodissa eittämättä vastaan, joten on hyvä tietää mistä on kyse. Toistorakenteet¶
C-kielessä voimme tietysti tehdä sekä numeroitua että ehdollista toistoa.
Numeroitua toistoa¶
Numeroitua toista C-kielessä toteuttaa
Syntaksi on seuraava.
for
-lause. Lause on C-kielessä koodarilta aavistuksen vaativampi kuin muissa kielissä. Syntaksi on seuraava.
for (alustuslause; ehtolause; sijoituslause) { lause; lause; }
Nyt
for
-silmukan avuksi tarvitsemme silmukkamuuttujan, joka kertoo ehtolauseen iteraation. - Alustus-lauseessa annamme silmukkamuuttujalle sen lähtöarvon
- Ehtolauseessa testaamme silmukkamuuttujan arvon
- Jos ehto toteutuu, suoritetaan koodilohko
- Jos ehto ei toteudu, poistutaan silmukasta
- Sijoituslause määritteleen miten silmukkamuuttuja muuttuu seuraavaan iteraatioon, ts. ehdon testaukseen.
Esimerkkejä.
uint8_t i; uint8_t i; uint8_t i;
for (i=0; i < 10; i++) { for (i=10; i > 0; i--) { for (i=0; i >= 20; i+=2) {
tee_jotain(); tee_jotain(); tee_jotain();
} } }
Ensimmäisessä tapauksessa silmukkamuuttujan arvo kasvaa yhdellä, toisessa tapauksessa vähenee yhdellä ja kolmannessa kasvaa kahdella.
Kuten huomataan,
for
-lauseen ehtolausekkeessa voidaan tehdä erilaisia vertailuja. Ja sijoituslauseke voi saada erilaisia muotoja.. itseasiassa siihen käyvät (lähes) kaikki C-kielen operaatiot ja myös funktiokutsu jonka palautusarvoa siis testattaisiin!Ja jotta
for
-lause ei olisi liian yksinkertainen, voimme sen sisällä alustaa useita silmukkamuuttujia, toisistaan pilkulla erotettuna. Näin saamme koodilohkoon kätevästi vaikkapa useita apumuuttujia..int i,j;
for (i=0, j=10; i < 10; i++, j--) {
tee_jotain(i,j);
}
Ehdollista toistoa¶
Ehdollista toistoa voidaan C-kielessä toteuttaa kahdella tavoin.
Testaa ensin, suorita sitten¶
Nyt
while
-lauseessa koodilohko suoritetaan silmukassa niin kauan kuin ehtolauseke on totta. Näinollen while
-silmukka sopii parhaiten tilanteisiin, joissa toistojen lukumäärää ei tiedetä ennalta.Syntaksi on kaikessa lyhykäisyydessään seuraava.
while (ehtolauseke) { lause; lause; }
Yksinkertainen esimerkki. Vieressä C-kääntäjälle ekvivalentti
for
-lause.uint8_t i=0; uint8_t i=0;
while (i < 10) { for (i=0; i < 10; i++) {
tee_jotain(); tee_jotain();
i++; }
}
Joskus
for
-lause voi olla parempi tyylillisesti, koska se pitää silmukan ohjauslauseet rakenteellisesti lähellä toisiaan.Suorita ensin, testaa sitten¶
Taas
do-while
-rakenne suorittaa koodilohkonsa ensin ja testaa ehtolausekkeen vasta sen jälkeen. Jos ehto on epätosi, poistutaan silmukasta. Tämä rakenne voi olla joskus tyylillisesti parempi ratkaisu kuin while
-rakenne, esimerkiksi jos pitää tehdä ehtolauseen muuttujille toistuva operaatio ennen niiden testausta.Syntaksi ei ole enää erityisen ihmeellinen.
do { lause; lause; } while (ehtolauseke);
Otetaas vielä breikki¶
Ohjausrakenteisiin liittyen voimme myös kontrolloida koodin suoritusta lohkon sisällä käskyillä
break
ja continue
. Nyt
break
-käskyllä voimme hypätä ulos mistä tahansa lohkosta jopa kesken sen suorituksen. Esimerkiksi:while (x > 0) {
if (x == JANNA_VAKIO) {
break;
}
x = tee_jotain();
}
Sitten
continue
hyppää toistorakenteen seuraavan iteraatioon, toki suorittaen sijoituslauseen ensin. Esimerkiksi, funktiota tee_jotain
ei suoriteta, jos if-lauseen yhtäsuuruusehto toteutuu. for (x=0; x < 100; x++) {
if (x == JANNA_VAKIO) {
continue;
}
tee_jotain();
}
Ikuinen silmukka¶
Yksi erityistapaus silmukkarakenteiden käytössä on hyvin yleinen sulautetujen järjestelmien ohjelmoinnissa.
Joskus sulautetun ohjelman toiminto toteutetaan yhden ikuisen silmukan sisälle, joka on osa ohjelman
main
-funktiota. Kuten aiemmin todettiin, C-kielinen ohjelma pyörii kunnes tämä funktio loppuu. Joten näppärästi (no ei oikeastaan..) sulautetussa ohjelmassa syötteiden odottelu tehdään ajamalla ikuista silmukkaa. Tällaisesta ratkaisusta käytetään nimitystä superloop-rakenne ja se saattaa olla tuttu esimerkiksi Arduino-ohjelmoinnista. int main() {
while (1) {
if (tarkista_syote()) {
tee_jotain();
}
}
}
Syöte ohjelmalle saadaan vaikkapa pollaamalla, kuten yllä esimerkissä funktiolla
tarkista_syote
, tai keskeytysten avulla. Noh, tästä lisää tulevassa materiaalissa..Vaikka ratkaisu on yksinkertainen, niin se ei ole ohjelmointiteknisesti hyvä. Esimerkiksi koska ehtojen testaus on peräkkäistä eikä rinnakkaista. Toisekseen, suurin osa suoritusaikaa ohjelma testaa lauseita jotka eivät laitteen näkökulmasta toteudu juuri koskaan. Jos laitteessa on resursseja ajaa käyttöjärjestelmää, on parempi ratkaisu antaa sen päättää milloin mikäkin ohjelman toiminnallisuus suoritetaan. Käyttöjärjestelmä usein myös hallitsee laitteen energiankulutusta ohjelmallisesti, mikä voi olla vaikeampaa toteuttaa superloop-rakenteessa.
Palaamme asiaan SensorTagin ohjelmointia opetellessa.
Sananen koodaustyylistä¶
Perustellaanpa lisää miksi aaltosulkeita kannattaa käyttää. Allaoleva koodinpätkä on sallittua C:tä, mutta ei kovin lukukelpoista. Esimerkiksi, nyt jos ohjelmoija sortuu python-tyyppiseen sisentelyyn, kummalle
if
-lausekkeelle else
-rakenne kuuluu?? if (n > 0)
m=n+10;
if (n > 10)
m=n;
else
n+=10;
Esimerkistä huomaamme myös, kuinka vaikeaselkoiseksi sisäkkäiset
if-else
-rakenteet voivat mennä. Joskus aloitteleva ohjelmoija laatii liian pitkiä if-else
-rakenteita (jotka paperillakin vievät tulostuskiintiön tappiin). Myöhemmin sitten bugin löytäminen tälläisestä ohjelmasta on todella epäkiitollista hommaa ja siihen voi mennä oikeasti tuntikausia aikaa. Kurssilla emme tee tällaista koodia, vaan osaamme jakaa ohjelmamme selkeisiin ja maksimissaan ruudun mittaisiin lohkoihin ja funktioihin.
Lopuksi¶
Pythonista opituilla silmukkataidoilla selviää kaikesta tällä kurssilla, kunhan opettelee syntaksin ja erikoistapaukset. Uudet jutut, kuten
do-while
-silmukat ja switch
-rakenteet, ovat hyödyllisiä toisinaan. Eri C-kielen standardit tukevat koodilohkoihin ja ohjausrakenteisiin liittyen hieman erilaista C-koodia. Esimerkiksi vanhassa C89-standardissa muuttujia ei voi esitellä
for
-loopin alustusrakenteessa vaan aiempana koodissa, mutta nykyisissä standardeissa se on mahdollista. for (int a=0; a < 10; a++) {
}
Koska C-kielessä koodin ulkoasulla on vähäinen merkitys, voimme kirjoittaa for-lauseita esimerkiksi tähän tapaan. Ohjelman toimintaa ei todellakaan voisi koodista helposti nähdä..
Loppuun terveisiä assistenteilta: Kurssilla säästämme kaikkien aikaa ja vaivaa, ja kirjoitamme omasta koodista modulaarista käyttäen funktioita ja koodilohkoja sekä ulkoasultaan selvästi luettavaa sisennyksineen, sulkeineen ja aaltosulkeineen.
Anna palautetta
Kommentteja materiaalista?