Ohjausrakenteet C:ssä¶
Osaamistavoitteet: Tässä materiaalissa esitetään miten C-kielessä toteutetaan ohjelman looginen toiminnallisuus ohjausrakenteiden avulla. Lisäksi ymmäretään, miten tärkeää on tehdä koodista modulaarista ja ulkoasultaan selkeää.
Ohjaurakenteita C-kielessä on seuraavasti:
- lohkorakenteet, koodilohkot, erotetaan aaltosulkeilla
{
lause; toinen_lause;}
, joiden sisään kirjotetaan lohkon toiminnallisuus. - ehtolauseet
if
jaswitch
. - toistorakenteet, silmukat,
for
,while
jado-while
-lauseilla.
Tämä osuus lienee kaikkein suoraviivaisin ja helpoiten omaksuttava osa kurssista, sillä rakenteiden toiminnassa ei C-kielessä ole oikeastaan merkittäviä eroja muihin ohjelmointikieliin, kuten Python, nähden.
Lauseet ja lohkot¶
Kuten aiemmin on jo vihjailtu, C-kielessä ohjelmalauseet päätetään puolipisteeseen
;
. Tälläinen merkintä tarvitaan kertomaan kääntäjälle ohjelman rakenteesta, eli mitä ohjelmoija on tarkoittanut suorittaa milloinkin ja missä järjestyksessä. Pythonissahan vastaava lauseen lopetuksen "merkki" on rivinvaihto. Ohjelmalauseen osana voi olla useita lausekkeita, esimerkiksi ehtolause, vertailu tai sijoitus. Ja taas lohkossa meillä on useita ohjelmalauseita. Koodilohko siis erottaa ne ohjelmalauseet, jotka ovat syntaktisesti samaa lausetta. Esimerkiksi ehtolauseessa ehdon toteuduttua suoritettavat käskyt kuuluvat loogisesti yhteen lauseeseen. C-kielellä lohko merkitään aaltosulkeilla
{ }
. Lohkoja tulemme käyttämään jokaisen funktion ja ohjausrakenteen kanssa, joten ne tulevat kyllä tutuiksi. Hox! Pythonissa lohkot merkittiin sisentämällä, mutta C-kieli ei niistä välitä.
Totuusarvot¶
Aiemmin käsittelimme C-kielen bittioperaatiot. Nyt opimme lisäksi loogiset operaatiot, jotka kertovat meille lausekkeen totuusarvon. Totuusarvo C:ssä määritellään numeerisena seuraavasti:
- 0 lauseke on epätosi.
int8_t onko_kylma = 0;
on siis epätosi. - muu kuin 0, lauseke on tosi. Tämä pitää sisällään kaikki nollasta poikkeavat arvot, myös negatiiviset 2-komplementtiluvut. Eli siis niin kauan kuin jokin bitti, esim. merkkibitti, on yksi, on lauseke totta.
int8_t onko_lammin = -25;
on siis tosi.
Loogiset operaattorit¶
Loogiset operaattorit, joilla voimme tarkastella totuusarvoja, ovat C-kielessä seuraavat:
!
Looginen negaatio&&
Looginen AND.||
Looginen OR.
Näillä operaattoreilla testataan siis lausekkeiden totuusarvoja, esimerkiksi
(lauseke1 && lauseke2)
on tosi vain, kun molemmat lausekkeet ovat tosia. uint8_t muuttuja1 = 0;
uint8_t muuttuja2 = 5;
int8_t muuttuja3 = -27;
int8_t tulos1 = !muuttuja1; // tosi, koska negaatio nollasta
int8_t tulos2 = muuttuja1 && muuttuja2; // (0 && 5) on epätosi, koska muuttuja1 on 0.
int8_t tulos3 = muuttuja2 && muuttuja3; // (5 && 27) on tosi, koska muuttujat ovat != 0.
Vertailuoperaattorit¶
Lisäksi totuusarvon selvittämisessä voimme tietysti käyttää varsinaisia vertailuoperaattoreita:
==
Yhtäsuuri!=
Erisuuri< > <= >=
Numeerinen vertailu
Jos vertailu toteutuu, suoritetaan lauseen lohkossa oleva koodi.
if (muuttuja1 > muuttuja2) { ... }
if (muuttuja3 != muuttuja2) { ... }
C-kielessä ei voi yhdistää useampaa ehtoa tai vertailua samaan ehtoon. Eli Python-tyylinen vertailu (0 < luku < 10) ei ole mahdollinen. Vertailut pitää eroottaa toisistaan ja yhdistää loogisella operaattorilla. Esimerkiksi.
if ((c >= 'a') && (c <= 'z')) { ... }
Hox! Bugien välttämiseksi muistetaan käyttää sulkeita eri lausekkeiden ympärillä, jotta ajatuksemme siirtyy koodiin sellaisenaan. Muutoin tulee olla hyvin selvillä C-kielen operaattorien suoritusjärjestyksestä!
Hox! Todella yleinen ohjelmointivirhe on käyttää vertailuoperaattorin == sijasta sijoitusoperaattoria =. Kääntäjä yleensä huomaa tämän ja antaa varoituksen.
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: haaroja tarkastellaan kirjoitetussa järjestyksessä ja valitaan se joka ensimmäisenä toteutuu, suoritetaan sille määrätty 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, esimerkiksi:
if (luku < 100)
printf("Luku on pienempi kuin 100");
Tämä on ihan laillista C-kieltä, jolloin ehdon toteuduttua suoritetaan vain rakenteessa oleva ohjelmalause. Jos halutaan suorittaa useampi ohjelmalause ehdon toteuduttua, on meidän käytettävä lohkoa. Tässä usein aloittelevat ohjelmoijat tekevät virheitä, 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. switch on ehkä kätevämpi vakioarvojen tarkastelussa.Switch-lauseen syntaksi on seuraava:
switch (muuttuja) {
case VAKIO1:
// tässä EI käytetä lohkomerkintää {}
lause1_1;
lause1_2;
break;
case VAKIO2:
lause2_1;
lause2_2;
break;
default:
laused_1;
laused_2;
break;
}
Nyt
switch
-rakenne suorittaa vakiota vastaavan koodilohkon, kunnes törmää break
-lauseeseen, ja hyppää lohkosta pois. Jos break jää pois, suoritus valuu lohkosta toiseen alaspäin kunnes break:n törmätään tai lause loppuu. Tämä on joskus hyödyllistä, jos tietää mitä tekee, kun nyt voidaan reagoida useisiin ehtoihin samallatavoin. default
-rakenne on valinnainen ja suoritetaan vain jos mikään muu ehdoista ei toteudu. default:n jälkeen on myös syytä laittaa break, joskun koodia joskus tulevaisuudessa muokataan..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ästä huomataan, että
case
vastaa yhtä if-lauseketta ja default
else-lauseketta. Ehtojen testaus valuu samallatavoin alaspäin kuin if-else-rakenteessa. switch-rakenne on siis tavallaan turha, mutta se on helppolukuinen tapa toteuttaa ehtolauseita, joilla on monia rinnakkaisia vaihtoehtoja. Lisäksi se tulee muiden kirjoittamassa koodissa 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
for
-lause. For-lause on C-kielessä koodarilta vaativampi kuin muissa kielissä. Silmukassa pitää tarkalleen määritellä iteroinnin rajat, ts. mistä lähdetään ja mihin päädytään. Syntaksi on seuraava.
for (alustuslauseke; ehtolauseke; sijoituslauseke) { koodi; lohko; }
Silmukan avuksi tarvitsemme silmukkamuuttujan, joka kertoo iteraation, ts. missä kohti ollaan menossa.
- Alustus-lauseessa annamme silmukkamuuttujalle sen lähtöarvon
- Ehtolauseen testaamme ennen jokaista iteraatiota
- Jos ehto toteutuu, suoritetaan koodilohko
- Jos ehto ei toteudu, poistutaan silmukasta
- Sijoitus määritteleen miten silmukkamuuttuja muuttuu seuraavaan iteraatioon.
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 operaatiot, jopa funktiokutsu!Ja jotta
for
-lause ei olisi liian yksinkertainen, voimme alustaa ja sijoittaa useita muuttujia toisistaan pilkulla erotettuna. Näin saamme koodilohkoon kätevästi vaikkapa apumuuttujia..int i,j;
for (i=0, j=10; i < 10; i++, j--) {
tee_jotain(j); // j argumentiksi
}
Ehdollista toistoa¶
Ehdollista toistoa C-kielessä toteuttaa kahdella tavoin.
Testaa ensin, suorita sitten¶
while
-lauseessa koodilohko suoritetaan kerta toisensa jälkeen, niin kauan kuin ehtolauseke on totta. while
-silmukka sopii parhaiten tilanteisiin joissa toistojen lukumäärää ei tiedetä ennalta.Syntaksi on kaikessa lyhykäisyydessään seuraava.
while (ehtolauseke) { koodi; lohko; }
Yksinkertainen esimerkki. Vieressä vastaava ekvivalentti for-lause.
uint8_t i=0; uint8_t i=0;
while (i < x) { for (i=0; i < x; i++) { // muuttuja x on määritelty jossain aiemmin
tee_jotain(); tee_jotain();
i++; }
}
Usein sulautetuissa järjestelmissä tarvitaan ohjelman pääfunktiossa ikuinen silmukka. Syy tähän selviää myöhemmin, pidetään nyt vielä hieman salaperäistä mystiikkaa yllä. Ikuinen silmukkaa saadaan ehdolla, joka on aina totta, ts. lukuarvo erisuuri kuin 0. Esimerkissä ehtolause vakiolla
1
on aina totta. Ikuinen silmukka voitaisiin myös toteuttaa for-lauseella, jossa ei ole ehtolausetta..while (1) { for (;;) {
tee_jotain(); tee_jotain();
} }
Koodatessa for- ja while-lauseita kohdellaan samanarvoisina, tosin for-lause voi olla parempi koodityylillisesti, koska se pitää silmukan ohjauslauseet rakenteellisesti lähellä toisiaan.
Suorita ensin, testaa sitten¶
do-while
-rakenne suorittaa annetun koodilohkon ensin ja testaa ehtolausekkeen vasta sen jälkeen. Jos ehto on, epätosi poistutaan silmukasta. Tämä rakenne voi olla joskus koodillisesti parempi ratkaisu kun while
-rakenne, esimerkiksi jos pitää tehdä ehtolauseen muuttujille jotain toistuvaa operaatiota ennen niiden testausta!Syntaksi ei ole erityisen ihmeellinen.
do { koodi; lohko; } while (ehtolauseke);
Lisää koodilohkoista¶
Okei, selvä juttu.. mutta miksi aaltosulkeita kannattaa käyttää? Allaoleva on sallittua C:tä.. mutta mutta, kummalle if-lausekkeelle else-rakenne kuuluu??
if (n > 0)
// nyt koodilohkossa vain yksi rivi!
m=n+10;
if (n > 10)
m=n;
else
n+=10;
Python-tyyppisellä sisennyksellä haluamme osoittaa että ensimmäiselle, mutta C-kääntäjä tulkitsee sen kuuluvan jälkimmäiselle! C-kieli ei välitä sisennyksistä. Lisäksi koska emme käytä koodilohkoa, niin ensimmäisen if-lauseen ehdon toteutuessa suoritetaan vain sijoitus
m=n+10
. Jälkimmäinen if-lause ei ole osa ensimmäistä if-lausetta ilman koodilohkoa, vaan ihan oma lause joka suoritetaan aina. Korjaamme asian merkitsemällä lohkot. if (n > 0) {
m=n+10;
if (n > 10) {
m=n;
}
}
else {
n+=10;
}
Nyt, sisennys aaltosulkeiden kanssa tekee C-koodista meille oikein toimivaa ja lisäksi luettavaa.
Hox! Ylläkuvatusta 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). Bugin etsiminen 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 funktioihin.
break ja continue¶
Ohjausrakenteisiin liittyen voimme myös kontrolloida koodin suoritusta lohkon sisällä käskyillä
break
ja continue
. break
-käskyllä voimme hypätä ulos lohkosta (yleensä) kesken sen suorituksen. // Vähemmän jännä esimerkki
for (x=0; x < 100; x++) {
if (x == y) {
// Nyt pois for-loopista!
break;
}
}
// Koodin suoritus jatkuu täältä for-loopin ulkopuolelta
...
continue
hyppää toistorakenteen seuraavan iteraatioon, suorittaen sijoitusoperaation ensin.// Toinen vähemmän jännä esimerkki
for (x=0; x < 100; x++) {
if (x == y) {
continue;
// Tämän jälkeen olevaa koodi ohitetaan ja
// hypätään for-loopin sjoituslausekkseeseen
// ja suoritetaan seuraava iteraatio
...
}
}
Lopuksi¶
Pythonista opituilla silmukankirjoitustaidoilla selviää kaikesta tällä kurssilla, kunhan opettelee C:n silmukoiden syntaksin ja erikoistapaukset. Uudet jutut, kuten do-while-silmukat ja switch-rakenteet, ovat hyödyllisiä toisinaan.
Eri C-kielen standardit tukevat hieman erilaista C-koodia, esimerkiksi vanhassa C89-standardissa muuttujia ei voi esitellä for-loopin alustusrakenteessa vaan aiempana koodissa, mutta C99:ssä se on mahdollista.
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ä..
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?