Funktiot C-kielessä¶
Osaamistavoitteet: Opiskelija tietää millainen on C-kielisen ohjelman rakenne ja osaa käyttää funktioita C-kielisessä ohjelmassa.
Kuten olemme jo johdantomateriaalista ounastelleet, jokaisessa C-kielisessä ohjelmassa on on aina vähintään yksi funktio, nimeltään
main
. Tätä funktiota käyttöjärjestelmä (tai firmware) kutsuu automaattisesti, kun ohjelman suoritus alkaa. Itseasiassa, C-kielisen ohjelman suoritus kestää tasan niin kauan kuin main-funktion suoritus. Mutta tästä lisää laiteläheisessä osuudessa..Kurssilla ajattelemme C-kielistä ohjelmaa modulaarisesti eri itsenäisten toiminnallisuuksien kokoelmana. Jokaisella toiminnallisuudella/tehtävällä on omat syötteet/parametrit, toiminnallisuus ja vaste/tulokset. Modulaarisessa ohjelmoinnissa tehtävät toteutetaan erillisinä moduuleina / aliohjelmina eli C-kielessä funktioina. Näin ohjelman toimintalogiikka perustuu funktioiden tulosten ja argumenttien välittämiseen muuttujina funktiolta toiselle. Esimerkiksi mittaustulosten lukeminen lämpötila-anturilta sulautetussa järjestelmässä:
- Alusta ja kalibroi lämpötila-anturi
// Funktio lämpötila-anturin alustamiseen void alusta_anturi() { // Koodi I2C-kommunikaation asettamiseen aseta_I2C(); // Koodi anturin rekisterien konfiguroimiseen konfiguroi_anturi_rekisterit(); } // Funktio I2C-kommunikaation asettamiseen void aseta_I2C() { // Laiteriippuvainen koodi I2C-kommunikaation alustamiseen // Esim.: Aseta kellotaajuus, ota I2C-yksikkö käyttöön, jne. } // Funktio anturin rekisterien konfiguroimiseen void konfiguroi_anturi_rekisterit() { // Laiteriippuvainen koodi anturin konfiguroimiseen // Esim.: Aseta resoluutio, mittaustila, jne. }
- Kysy mittaustulos anturilta
// Funktio lämpötilan lukemiseen anturilta float lue_lampotila() { // Lähetä komento anturille mittauksen aloittamiseksi laheta_mittaustilaus(); // Lue tiedot anturin ulostulorekisteristä int raakadata = lue_anturi_data(); // Muunna raakadata lämpötilaksi float lampotila = muunna_lampotila(raakadata); return lampotila; } // Funktio mittauskomennon lähettämiseen anturille void laheta_mittaustilaus() { // Laiteriippuvainen koodi komennon lähettämiseen anturille // Esim.: Kirjoita tiettyyn rekisterin osoitteeseen I2C:n kautta } // Funktio tiedon lukemiseen anturista int lue_anturi_data() { // Laiteriippuvainen koodi tiedon lukemiseen anturin ulostulorekisteristä // Esim.: Lue tavut tietystä rekisterin osoitteesta I2C:n kautta int data = 0; // Paikka varattu todelliselle luetulle datalle return data; } // Funktio raakadata lämpötilaksi muuntamiseen float muunna_lampotila(int raakadata) { // Koodi raakadata muuntamiseen helposti luettavaksi lämpötilaksi // Esim.: Sovella anturikohtainen kaava float lampotila = raakadata * 0.01; // Esimerkkimuuntokerroin return lampotila; }
- Tulosta mittaustulos näytölle
// Funktio lämpötilan näyttämiseen näytöllä void nayta_lampotila(float lampotila) { // Koodi lämpötila-arvon muotoiluun ja lähettämiseen näyttöön // Esim.: Lähetä tiedot LCD:lle tai sarjaterminaaliin printf("Nykyinen lämpötila: %.2f°C\n", lampotila); }
main
-funktio toimii yleensä C-ohjelman lähtöpisteenä, mutta eri toimintojen toteutus tehdään muissa funktioissa, joita kutsutaan main
-funktiosta.int main() { // Alusta lämpötila-anturi alusta_anturi(); // Lue lämpötila float lampotila = lue_lampotila(); // Näytä lämpötila nayta_lampotila(lampotila); return 0; }
Hox! Pythonissa vastine main-funktiolle olisi seuraava:
def main():
# Pääohjelman logiikka menee tähän
print("Tämä on pääfunktio.")
if __name__ == "__main__":
main()
Hox! Kurssilla kaikki C-kielen harjoitustehtävätkin tulevat olemaan erilaisten funktioiden toteutuksia.
Tällainen ohjelmointitapa tarjoaa useita etuja, joista mainittakoon:
- Kapselointi (eng. encapsulation): Jokainen funktio kapseloi tietyn osan laitteiston toiminnasta. Kapseloinnin tarkoituksena on suojata tehtävän/objektin sisäistä tilaa, jotta tietoja voidaan muokata vain hallitulla tavalla.
- Modulaarisuus (eng. modularity): Ohjelma jaetaan pienempiin ja hallittavampiin sekä itsenäisiin moduuleihin, joista kukin edustaa tiettyä toiminnallisuutta. Tavoitteena on tehdä ohjelmasta helppotajuinen ja ylläpidettävä.
- Uudelleenkäytettävyys(engl. Reusability): Funktiot voidaan käyttää uudelleen ohjelman muissa osissa. Funktiota ei tarvitse kirjoittaa uudelleen.
Ensimmäinen C-ohjelma¶
Tarkastellaan aluksi yleisesti C-kielisen ohjelman rakennetta. Nyt ei vielä tarvitse tietää mitä kaikkea allaolevassa koodissa tapahtuu, vaan hahmottaa C-ohjelman rakenteen neljä oleellista osaa.
/**************************************
* Ensimmäinen c-ohjelma
**************************************/
/*
* Esikääntäjäkäskyt
*/
// Kirjasto stdio mukaan ohjelmaan
#include <stdio.h>
// Vakion PI esittely
#define PI 3.14159
/*
* Ohjelman sisäisten funktioiden ja muuttujien esittely
*/
// Esitellään oma funktio laske_ala prototyypin avulla
float laske_ala(float sade);
/*
* Pääohjelman main toteutus
*/
int main() {
// Pääohjelman sisäisten muuttujien esittely (ja alustus)
float ympyran_ala = 0.0, ympyran_sade = 0.0;
// Pääohjelman toiminnallisuus
printf("Anna ympyrän säde: ");
scanf("%f", &ympyran_sade);
ympyran_ala = laske_ala(ympyran_sade);
printf("Ympyrän pinta-ala on: %.2f\n", ympyran_ala);
// Pääohjelman (funktion) palautusarvo
return 0;
}
/*
* Funktio: laske_ala, laskee ympyrän alan annetusta säteestä
* Argumentit: sade, ympyrän säde
*/
float laske_ala(float sade) {
// Funktion sisäisten muuttujien esittely (ja alustus)
float pinta_ala = 0.0;
// Funktion toiminnallisuus
pinta_ala = PI * sade * sade;
// funktion palautusarvo
return pinta_ala;
}
Katsotaanpas siis mistä osista C-kielinen ohjelma rakentuu:
1. Esikääntäjäkäskyt ohjaavat ohjelman käännösprosessia ja niillä on useita eri tarkoituksia. Tässä vaiheessa tarvitsee tietää:
1. Esikääntäjäkäskyt ohjaavat ohjelman käännösprosessia ja niillä on useita eri tarkoituksia. Tässä vaiheessa tarvitsee tietää:
- Voidaan liittää C-ohjelmaan omia ja kääntäjäympäristön valmiita kirjastoja.
- Esimerkissä käytetään C-kirjastoa stdio, joka tarjoaa funktioita syötteen ja tulostuksen käsittelyyn.
- Voidaan määritellä vakiota ja makroja ohjelmiin.
- Esimerkissä luotiin vakio PI ohjelman käyttöön.
2. Ohjelman sisäisten funktioiden ja muuttujien esittely prototyypin avulla. Prototyyppi kertoo ohjelmalle millaisia funktiota sen käytössä on.
- Esimerkin prototyyppi
float laske_ala(float sade);
kertoo että käytössä on laske_ala-niminen funktio ja sen että se ottaa argumenteikseen ja palauttaa liukulukuja. - Itseasiassa kun liitämme kirjastoja C-ohjelmaan, usein niiden toteutuksista liitetään vain funktioiden prototyypit, vakiot, ym määritykset. Tästä lisää kohta..
3. Pääohjelman
main
-funktion toteutus.- Esimerkissä pääfunktiossa kysytään käyttäjältä ympyrän säteen ja lasketaan funktion laske_ala avulla sen ala.
4. Ohjelmassa esiteltyjen sisäisten funktioiden toiminnallisuus.
- Esimerkissä funktion
laske_ala
-toteutus eli ympyrän alan laskeminen, kun säde annetaan.
Funktion prototyyppi¶
Funktion esittely C-kielisessä ohjelmassa tehdään sen prototyypin kautta. Prototyyppi on yleisesti muotoa:
paluuarvon_tyyppi
, minkätyyppisen paluuarvon funktio palauttaa suorituksen loputtua.funktion_nimi
, nimi millä funktiota kutsutaan ohjelmassa.( muuttujatyyppi1 muuttuja1, muuttujatyyppi2 muuttuja2, ...)
, näillä määrityksillä funktio kertoo minkätyyppisiä parametrejä se ottaa vastaan ja millä muuttujanimellä niitä voidaan funktiossa käsitellä.
Esimerkki. Funktio nimeltään
laske_ala
palauttaa float-tyypin liukuluvun ja ottaa sisäänsä float-tyypin liukuluvun (muuttujassa sade). // Esitellään oma funktio laske_ala prototyypin avulla
float laske_ala(float sade);
Miksi prototyyppi?¶
Okei, mutta miksi tarvitaan prototyyppiä? Menemättä syvälle kääntäjien maailmaan, todetaan että C-kielessä prototyyppi mahdollistaa funktion käytön ohjelmassa ennenkuin sen koodi on käännetty. Esimerkissä siis
main
-funktiossa voidaan jo käyttää laske_ala
-funktiota ennenkuin sen koodi on tullut kääntäjää vastaan, kun se käy läpi C-kooditiedostoa. Kääntäjä siis luottaa tässä ohjelmoijan lupaukseen, että prototyypin mukainen funktion toteutus tulee vielä joskus käännösprosessin aikana vastaan. Kääntäjä tarkistaa prototyypin löydettyään vain funktiokutsun syntaksin, eli että kutsutaan funktioita oikein nimen ja parametrien osalta.
Tästälähin, kurssilla, luodessamme funktioita muistamme ensin aina esittelyn prototyypillä! Jos toteutusta ei löydy ohjelman kooditiedostoista tai mukaan otetuista kirjastoista, kääntäminen loppuu virheilmoitukseen. Hyvin käyttäytyvät kääntäjätkin lopettavat koko käännösprosessin ja varoittavat jos prototyyppi ohjelmasta puuttuu..
Koodimesimerkissä yllä:
// prototyyppi lupaa että tällainen funktio löytyy
float laske_ala(float sade);
// Prototyypin avulla voidaan funktiota käyttää
int main() {
ympyran_ala = laske_ala(ympyran_sade);
}
// itse funktion toteutus
float laske_ala(float sade) {
float pinta_ala = 0.0;
pinta_ala = PI * sade * sade;
return pinta_ala;
}
Palataan vielä esikääntäjäkäskyihin sen verran, että itseasiassa käsky
Tämä funktio löytyy tiedostosta stdio.h. Näin saamme vastaavasti tuotua valmiita kirjastofunktioita käyttöön ohjelmassamme prototyypin avulla.
#include
hakee ohjelmaamme käyttämiemme scanf
- ja printf
-funktioiden prototyypit. Tämä funktio löytyy tiedostosta stdio.h. Näin saamme vastaavasti tuotua valmiita kirjastofunktioita käyttöön ohjelmassamme prototyypin avulla.
Hox! C-kielen harjoitustehtävätkin edellyttävät prototyyppiä tehtävävastauksessa..
Funktion parametrit¶
Funktion parametrit ovat funktion sisäisten muuttujien esittelyjä sekä funktion palautusarvon tyypin määrittely. Parametri, ja sen funktiolle välittyvä arvo eli argumentti, on siis tapa välittää tietoa funktiolle sitä kutsuvasta koodista ja ulos funktiosta sitä kutsuvaan koodiin.
- Parametrit voivat olla mitä vain C-kielen standardoituja muuttujatyyppejä
int, long, double, float, char, struct
sekä johdettuja muuttujatyyppejä ja taulukkoja. - Jos parametrejä on useita, ne erotetaan pilkulla.
- Funktio voi ottaa sisäänsä erityyppisiä parametrejä.
Esimerkkikoodissa yllä esitellään funktio niin, että se ottaa sisäänsä yhden liukuluku-tyyppisen argumentin.
float laske_ala(float sade);
Mikäli funktio ei ota argumentteja tai ei palauta arvoa, käytetään selvyyden vuoksi niiden paikalla esittelyssä varattua sanaa
void
. void en_ole_palauttamassa(uint8_t x, uint8_t y);
int32_t en_tarvi_argumenttia(void);
void haluun_olla_rauhassa(void);
Sinällään
void
-sana ei ole pakollinen tällaisissa funktion esittelyissä, mutta kääntäjät tällöin olettavat funktioista asioita.. esimerkiksi ilman void
ia kääntäjä voi olettaa sen palauttavan int-tyypin kokonaisluvun ja tästä voi seurata varoituksia / virheilmoituksia. Selvyyden vuoksi on syytä aina käyttää void
-sanaa tarpeen mukaan. Hox! Esimerkissä
main
-funktio on toteutettu ohjelmaan ilman parametrien määrityksiä. Jossain kehitysympäristöissä työasemaohjelmoinnissa törmäätte main-funktioihin, joille on määritelty parametrit. Nämä sisältävät ohjelmalle käynnistäessä annetut komentoriviparametrit. Tämä on työasemalla ihan ok, mutta sulautetuissa järjestelmissä tämä ei ole yleisesti käytössä ja siksi sitä ei kurssiesimerkeissä näy.Funktion toteutus¶
Funktion toteutus tehdään modulaarisesti oman koodilohkonsa sisälle, joka C-kielessä merkitään/rajataan aaltosulkeilla omaksi toiminnalliseksi kokonaisuudekseen. (Pythonin syntaksissa aaltosulkeita vastaava rajaus tehtiin tabulaattorin avulla.)
/*
* Funktio: laske_ala, laskee ympyrän alan annetusta säteestä
* Argumentit: sade, ympyrän säde
*/
float laske_ala(float sade) {
// Funktion sisäisten muuttujien esittely (ja alustus)
float pinta_ala = 0.0;
// Funktion toiminnallisuus
pinta_ala = PI * sade * sade;
// Funktion palautusarvo
return pinta_ala;
}
Funktion toteutus jakaantuu sekin kolmeen osaan:
- Funktiossa yleensä esitellään sisäisiä, ts. paikallisia, muuttujia.
- Paikalliset muuttujat ovat olemassa vain funktion sisällä sen suorituksen ajan ja ne katoavat kun suoritus loppuu.
- Yllä esimerkissä siis muuttuja
pinta_ala
on paikallinen muuttuja. - C-kielen nykyinen standardi sallii kyllä muuttujien esitellyn keskellä koodia, mutta edelleen aloittelevan ohjelmoijan on selkeintä on esitellä muuttujat kootusti funktion toteutuksen alussa.
- Funktion toteutus C-kielen lauseina, jotka päättyvät puolipisteeseen.
- Funktion palautusarvon asetus.
- Esimerkissä palautetaan siis paikallisen muuttujan
pinta_ala
:n arvo.
Funktion toteutus paketoidaan aaltosulkujen sisään.
Funktion palautusarvo¶
C-kielessä funktio voi
return
-lauseella palauttaa vain yhden arvon, joka on tyypillisesti funktion tulos ja/tai tieto siitä miten funktion suoritus onnistui. Palautusarvo voi olla mitä tahansa C-kielen muuttujatyyppiä, paitsi taulukko (palataan tähän). Huomaa, että palautusarvo voi olla jotain monimutkaista, kuten struct
.Esimerkissä yllä funktio
laske_ala
palauttaa sisäisen muuttujan pinta_ala
arvon, jonka main
-funktiossa ottaa talteen sen sisäinen muuttuja ympyran_ala
. int main() {
float ympyran_ala = 0.0;
ympyran_ala = laske_ala(ympyran_sade);
}
C-kielessä
main
-funktio on erikoistapaus, koska sitä kutsuu käyttöjärjestelmä / firmware, ja sen suorituksen loputtua myös ohjelman suoritus loppuu. Näinollen sen palautusarvolla voidaan käyttöjärjestelmälle kertoa tietoa mitä ohjelman suorituksessa tapahtui, esimerkiksi menikö kaikki ok funktion suorituksessa vai tapahtuiko jokin virhe. Tässä syystä
main
-funktiot yleensä palauttavat numeroarvon viimeisenä käskynään. Tässä sovitusti nolla
kertoo, että ohjelman suoritus onnistui virheittä ja muut palautusarvot tarkottavat jotain virhettä suorituksessa, joka ei välttämättä siis kaatanut ohjelmaa. Tällaiset virhekoodit on käyttöjärjestelmän / firmwaren määrittelemiä ja niihin ei kurssilla mennä syvemmälti.int main() {
...
// homma ok
return 0;
}
Funktion kutsuminen¶
C-kielen syntaksissa funktiota kutsutaan, kuten muissakin kielissä, nimellä ja antamalla parametrien arvot.
int main() {
float ympyran_sade = 2.45;
ympyran_ala = laske_ala(ympyran_sade);
}
C-kielessä pitää funktiokutsussa olla tarkempi kuin joissain muissa kielissä.
- Funktiolle pitää kutsuessa antaa täsmälleen oikea määrä argumentteja oikeassa tyypissä. Jos muuttujat eivät ole haluttuja tyyppiä, kääntäjäparka joutuu itse tekemään muunnoksen tyypistä toiseen ja tulos voi olla muuta kuin toivottu.
- C-kielessä ei voi antaa parametreille oletusarvoa.
Lisäksi, C-kielen funktiokutsussa voisi olla esimerkiksi muuttujan tilalta vaikka toinen funktiokutsu, jonka palautusarvo näin ollen välitettäisiin kutsuttavalle funktiolle argumenttina. C-kielessä voidaan siis ketjuttaa funktioita.
kutsu_minua(kutsutaan_kaveria(hei_palauta_jotain(eiku_palauta_sina())));
int add(int a, int b) {
return a + b;
}
int multiply(int x, int y) {
return x * y;
}
int result = multiply(add(2, 3), 4); // Ensin lisätään 2 ja 3, sitten kerrotaan tulos luvulla 4
Sulautetut funktiot¶
Sulautettuja järjestelmiä ohjelmoidessa meidän täytyy tietää hieman lisää funktioiden sielunelämästä ollaksemme tehokkaita ohjelmoijia.
C-kielen ominaisuus on, että kääntäjä tekee funktion 'argumenteistä funktion sisäisen muuttujan, jonne argumentin arvo kopioituu. Tässä onkin iso sudenkuoppa, koska laitteissa on usein vain vähäinen määrä muistia. Tällöin muisti on (valitettavan) helppoa käyttää loppuun ohjelman ajonaikana, jolloin tyypillisesti ohjelma kaatuu muistiosoitusvirheeseen. Työasemaohjelmoinnissa tämä asia ei ole niin kriittinen, koska muistia on gigatavuja vs muutama kilotavu sulautetussa laitteessa.
Tarkastellaan asiaa esimerkin kautta. Alla siis meillä on
main
-funktiossa muuttuja ympyran_sade
, joka annetaan laske_ala
-funktiolle parametriksi paikalliseen muuttujaan sade
. Näin ollen argumentin arvo kopioituu eli on ohjelman ajon aikana tallessa kahdessa eri muuttujassa. int main() {
...
float ympyran_sade = 2.45;
ympyran_ala = laske_ala(ympyran_sade);
...
}
float laske_ala(float sade) {
...
}
Okei no tässä ei nyt ole isompaa hätää, koska liukulukumuuttuja tyyppiä
float
tarvitsee vain 4 tavua muistia.Mutta dramatisoidaanpa asiaa esittelemällä funktio
laheta_viesti
, joka ottaa argumentikseen rakenteen, jossa on 4 tavun kokoinen taulukko lahetysosoite
ja kahden kilotavun kokoinen viestimerkkijono. // Määritellään rakenne viestiä varten
typedef struct {
char lahetysosoite[4]; // 4 tavun osoite
char viesti[2048]; // 2 kilotavun viesti
} Viesti;
// Funktion prototyyppi
void laheta_viesti(Viesti viesti_kotiin);
...
// Esitellään viestirakenne ja täytetään se tiedoilla
Viesti viesti_kotiin = {"ABCD", "Tämä on viestin sisältö..."};
...
// Kutsutaan funktiota rakenteen kanssa
laheta_viesti(viesti_kotiin);
...
Nyt funktiossa
laheta_viesti
tehtäisiin 2048 merkin viestistä ja 4 tavun osoitteesta kopio paikalliseen rakenteeseen, mikä kuluttaisi lisämuistia aina kun funktiota kutsutaan. Vaara piilee juuri tässä: ohjelman suorituksen aikana voi olla, että meillä on riittävästi muistia käytettävissä, mutta ohjelman edetessä muistia ei enää olekaan riittävästi. Tällainen hiljainen virhe on hankala, koska sen syy ei ole käyttäjälle (tai ohjelmoijalle) ilmeinen.Auts... miten sitten voidaan välittää suuria tietomääriä tai taulukoita funktioille? Palaamme hetken päästä, kun esittelemme uusia muuttujatyyppejä, ja myöhemmässä materiaalissa tutustumme osoitinmuuttujiin.
Uusia muuttujatyyppejä¶
Yllä todettiin, että funktion sisällä määritellyt paikalliset muuttujat ovat olemassa vain siinä funktiossa missä ne esitellään sen suorituksen ajan. Tämä koskee myös
main
-funktiota, sen muutoin erikoislaadusta huolimatta. Opimme juuri, että muuttujia voidaan välittää argumenttien ja paluuarvojen kautta funktioiden välillä, mutta tämän lisäksi voimme C-kielessä esitellä globaaleja ja staattisia muuttujia.
Globaali muuttuja¶
Globaali muuttuja määritellään kaikkien funktio-määritysten ulkopuolella, jolloin se on käytettävissä kaikkialla koodimoduulin/tiedoston funktioissa ihan samoin kun funktioiden paikallset muuttujat, sillä erolla ettei globaalia muuttujaa tarvitse esitellä funktion toteutuksen alussa.
Esimerkki globaalin muuttujan käytöstä.
/*
* Ohjelman sisäisten muuttujien ja funktioiden esittely
*/
float ou_jes_olen_globaali = 3.14;
/*
* Pääohjelman main toteutus
*/
int main() {
float olen_paikallinen_main = 3.14;
printf("Tulostetaan main:ssa globaali muuttuja: %f\n", ou_jes_olen_globaali );
ou_jes_olen_globaali = 2.71828;
tulosta_muuttujat(olen_paikallinen_main);
return 0;
}
/*
* Funktio: tulosta_muuttujat
*/
void tulosta_muuttujat(float olen_argumentti) {
float olen_paikallinen = 3.14;
printf("Tulostetaan funktiossa argumentti: %f\n", olen_argumentti);
printf("Tulostetaan funktiossa paikallinen muuttuja: %f\n", olen_paikallinen );
printf("Tulostetaan funktiossa globaali muuttuja: %f\n", ou_jes_olen_globaali );
}
Hox! Jos globaali muuttuja esitellään uudestaan funktion toteutuksessa, se itseasiassa yliajaa globaalin muuttujan esittelyn funktion sisällä ja luo uuden paikallisen samannimisen paikallisen muuttujan.. hups.
Globaalien muuttujien käyttämiseen yleisesti on kaksi syytä:
- Ne ovat elossa koko ohjelman suorituksen ajan ja niiden käyttöalue ohjelmakoodissa on laajempi, ts. kaikki modulin funktiot.
- Niitä voidaan käyttää funktioiden parametrien ja paluuarvon sijaan säästämään muistia. Sulautettujen ohjelmoinnissa kätevää välittää tietoa ohjelman eri funktioiden välillä, ilman että argumenteistä tehtäisiin kopioita jatkuvalla syötöllä.
- Tässä onkin yksi keino käsitellä
structs
sulautetussa C-ohjelmassa, kun tehdään niistä globaaleja, niin ei tarvitse välittää niitä funktion parametreinä.
Tämän seurauksena tosin virheiden jäljittäminen ohjelmassa voi vaikeutua, koska no.. ne ovat käytettävissä todellakin kaikkialla ohjelmassa ja sikäli hyönteisten jäljitys vaikeutuu. Globaalit muuttujat myös aiheuttavat riippuvuuksia toiminnallisuuksien (funktioiden) välille, joten ohjelman modulaarisuudesta voidaan joutua tinkimään ja koodiin voi ilmestyä vaikeaselkoisia bugeja..
Lopuksi vielä tiedoksi, että globaaleja muuttujia voidaan myös välittää laajemmissa C-ohjelmissa koodimoduleista toiseen, mutta tähän liittyy isoja sudenkuoppia, joten emme käsittele asiaa tarkemmin.
Staattinen muuttuja¶
Staattinen muuttuja on myös hyödyllinen jossain tapauksissa. Ideana on että muuttuja alustetaan kerran ja se säilyttää arvonsa funktiokutsujen välillä. Tällöin muuttujan esittelyssä käytetään määrettä
static
. Esimerkkinä funktion paikallisen muuttujan käyttö laskurina.
void laskuri(void) {
static uint64_t lukumaara=0;
lukumaara++;
}
Funktiossa muuttujan
lukumaara
arvo lähtee alustusarvosta nollasta ja kasvaa yhdellä aina kun funktiota kutsutaan. Vaikka muuttujan arvo säilyy, se on edelleen vain paikallinen muuttuja eikä käytettävissä funktion ulkopuolella.Tiedoksi, että
static
-määreellä on muitakin käyttötapoja, mutta emme mene niihin kurssilla. Lopuksi¶
Koodaustyyliin liittyen, funktion koko (ohjelmalauseiden määrä) on syytä pitää kohtuullisena. Nyrkkisääntönä, jos funktion rivimäärä ei mahdu kerralla ruudulle, voi miettiä funktion jakamista pienempiin funktioihin. Tämä maksaa loppupeleissä vaivan ohjelman virheiden metsästyksen, koodin ylläpidettävyyden ja lukukelpoisuuden myötä.
Funktioilla pelaaminen pääsee itseasiassa vauhtiin vasta C-kielen standardi- ja oheislaitteiden kirjastoihin tutustuessa sekä omia ohjelmia tehdessä!
Anna palautetta
Kommentteja materiaalista?