Ohjausrakenteet C:ssä¶
Osaamistavoitteet: Tässä materiaalissa esitetään miten C-kielessä toteutetaan ohjelman toiminnallisuus ohjausrakenteiden kautta. Lisäksi ymmäretään miten tärkeää on tehdä koodista modulaarista ja ulkoasultaan selkeää.
Ohjaurakenteita C-kielessä on seuraavasti:
- lohkorakenteet, eroteltuna aaltosulkeilla
{
koodilause; toinen_koodilause; jne;}
- ehtolauseet
if
jaswitch
- toistorakenteet, eli silmukat
for
,while
jado-while
Tämä on lienee kaikkein suoraviivaisin ja helpoiten omaksuttava osa kurssista, sillä rakenteiden toiminnassa ei C-kielessä ole oikeastaan merkittäviä eroja Pythoniin 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ä suorittaa milloinkin. Pythonissahan vastaava "merkki" on rivinvaihto. Ohjelmalauseen osana voi olla useita lausekkeita, esimerkiksi ehtolause, vertailu tai sijoitus. Lohkon idea on erotella ne ohjelmalauseet, jotka ovat syntaktisesti samaa lausetta. Esimerkiksi ehtolauseessa ehdon toteuduttua suoritettavat käskyt kuuluvat loogisesti yhteen lauseeseen. C-kielellä lohko merkitään aaltosulkeilla
{ }
. Pythonissa lohkot merkittiin sisentämällä, mutta C-kieli ei niistä välitä. Lohkoja tulemme käyttämään jokaisen ohjausrakenteen kanssa, joten ne tulevat kyllä tutuiksi. 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
- 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.
Loogiset operaattorit C-kielessä ovat seuraavat:
!
Looginen negaatio: epätosi&&
Looginen AND||
Looginen OR
Näillä operaattoreilla testataan siis lausekkeiden totuusarvoja, esimerkiksi
lauseke1 && lauseke2
on tosi vain kun molemmat lausekkeet ovat tosia. Eli molemmat numeerisena suurempia kuin nolla.. uint8_t muuttuja1 = 0;
uint8_t muuttuja2 = 5;
int8_t muuttuja3 = -27;
Operaatio muuttuja1 && muuttuja2 (0 && 5) on epätosi, koska muuttuja1 on 0.
Operaatio 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
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')) { ... }
Muistetaan käyttää sulkeita eri ehtolausekkeiden 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¶
Perinteisen if-else-rakenteen lisäksi C-kielessä on moniosainen ehtorakenne switch.
If-else if-else jaarittelua¶
C:n ehtolauseista löytyvät tasan samat
if
-, else if
- ja else
-osiot kuin muistakin ohjelmointikielistä ja toiminta on sama: ehtorakenteen haaroja tarkastellaan kirjoitetussa järjestyksessä, 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");
}
Switch nostaa kytkintä¶
C-kielessä on myös moniosainen ehtorakenne
switch
, joka testaa vastaako muuttujan arvo jotain annetuista kokonaislukuvakioista.Switch-lauseen syntaksi on seuraava:
switch (muuttuja) { case vakio1: lohko1; break; case vakio2: lohko2; break; default: lohko3; 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 sillä on mukava 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, ehdon testaamme ennen jokaista iteraatiota ja sijoitus kertoo meille 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! 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ä. Eli alla, koska ehto
1
on aina erisuuri kuin nolla, ehto on aina totta. Ikuinen silmukka voidaan 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 kun 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¶
Miksi koodilohkoja, ts. aaltosulkeita, kannattaa käyttää? Esimerkiksi allaoleva on sallittua C:tä.. mutta mutta, kummalle if-lausekkeelle else-rakenne alla kuuluu??
if (n > 0)
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.
Ylläkuvatusta esimerkistä huomaamme myös, kuinka vaikeaselkoiseksi sisäkkäiset if-else-rakenteet voivat mennä. Joskus aloitteleva ohjelmoija laatii ohjelmaansa 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.
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) {
// Äkkiä 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 tässä hieman erilaista C-koodia, esimerkiksi vanhassa C89-standardissa muuttujia ei voi esitellä for-loopin alustusrakenteessa, mutta C99:ssä se on mahdollista. Noudatamme kurssilla C89-standarda.
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?