Funktiot C-kielessä¶
Osaamistavoitteet: Opiskelija tietää millainen on C-kielisen ohjelman rakenne ja osaa käyttää funktioita C-kielisessä ohjelmassa.
Kuten olemme jo johdantomateriaalissa maininneet, C-ohjelmissa on yleensä
main-niminen funktio. Tätä funktiota kutsuu ohjelman käynnistyessä käyttöjärjestelmä tai firmware. Sulautetuissa järjestelmissä prosessori alkaa nollauksen jälkeen tyypillisesti suorittaa koodia ennalta määritellystä muistiosoitteesta. Firmware suorittaa tarvittavat alustustoimet ja kutsuu sen jälkeen main-funktiota. Itse asiassa C-ohjelman suoritus kestää täsmälleen 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. Näin myös tietyn tehtävän toteutus pysyy yhdessä paikassa ja muut ohjelman osat voivat käyttää toiminnallisuutta selkeästi määritellyn funktiorajapinnan kautta.
- 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ää:
#include-käskyllä C-ohjelmaan voidaan sisällyttää kirjastojen tarjoamia tai ohjelmoijan itse tekemiä otsikkotiedostoja.- Esimerkissä
#includesisällyttää stdio.h-otsikkotiedoston. Otsikkotiedosto sisältää esittelyjä, kutenprintf- jascanf-funktioiden prototyypit, joiden avulla kääntäjä tietää, miten näitä kirjastofunktioita käytetään. Funktioiden varsinainen toteutus tulee erikseen C-kirjastosta. - 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ä? Kun kääntäjä kohtaa funktiokutsun, sen täytyy tietää millaista funktiota kutsutaan. Funktion prototyyppi antaa nämä tiedot: funktion nimen, paluuarvon tyypin ja parametrien tyypit.
Esimerkiksi:
float calculate_area(float radius);
kertoo kääntäjälle, että
calculate_area ottaa yhden float-tyyppisen parametrin ja palauttaa float-tyyppisen arvon. Näin kääntäjä voi tarkistaa funktiokutsut, vaikka funktion toteutus tulisi vastaan vasta myöhemmin lähdekoodissa.Varsinainen toteutus voi sijaita myöhemmin samassa lähdekooditiedostossa, toisessa lähdekooditiedostossa tai kirjastossa. Jos kääntäjä tuntee prototyypin mutta vastaavaa toteutusta ei ole käytettävissä koko ohjelmaa rakennettaessa, virhe havaitaan tavallisesti linkitysvaiheessa.
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, jotka kirjoitetaan sulkeiden sisään, määrittävät, mitä arvoja funktio ottaa vastaan. Parametri ja sille funktiota kutsuttaessa välitetty arvo eli argumentti ovat tapa välittää tietoa kutsuvasta koodista funktiolle.
- Parametrit voivat olla mitä vain C-kielen standardoituja muuttujatyyppejä
int, long, double, float, char, structsekä 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);
Avainsanalla
void on funktion esittelyssä kaksi eri käyttötapaa:- Jos
voidon funktion paluuarvon tyyppi, funktio ei palauta arvoa. - Jos
voidkirjoitetaan sulkeiden sisään, funktio ei ota parametreja.
void en_ole_palauttamassa(uint8_t x, uint8_t y);
int32_t en_tarvi_argumenttia(void);
void haluun_olla_rauhassa(void);
Vanhemmissa C-kielen versioissa paluuarvon tyypin pois jättäminen johti siihen, että kääntäjä oletti tyypiksi
int. C99-standardista alkaen paluuarvon tyyppi täytyy määritellä eksplisiittisesti. Parametrien kohdalla (void) ilmaisee yksiselitteisesti, että funktio ei ota parametreja. C99–C17-standardeissa tyhjä parametriluettelo () ei määritä funktion parametreja, kun taas C23-standardissa se tarkoittaa myös, että funktio ei ota parametreja.Selvyyden ja eri C-standardien yhteensopivuuden vuoksi tällä kurssilla käytämme eksplisiittisesti
void-sanaa, kun funktio ei palauta arvoa tai ei ota parametreja.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_alaon 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 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.Monissa sulautetuissa ohjelmissa
main-funktio kuitenkin suoritetaan jatkuvasti eikä se koskaan palaudu.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ä.
- Funktiota täytyy kutsua oikealla määrällä argumentteja.
- Argumenttien tyyppien tulisi vastata parametrien tyyppejä. C-kieli voi muuntaa monia yhteensopivia tyyppejä automaattisesti funktiokutsun yhteydessä, esimerkiksi
int-tyypinfloat-tyypiksi. Tällaiset muunnokset voivat kuitenkin joskus muuttaa arvoa tai johtaa odottamattomaan tulokseen. - C-kielessä parametreille ei voi antaa oletusarvoja.
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) {
...
}
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! Funktiossa voidaan esitellä paikallinen muuttuja, jolla on sama nimi kuin globaalilla muuttujalla. Tällöin paikallinen muuttuja peittää globaalin muuttujan funktion sisällä. Hups!
Globaalia muuttujaa ei korvata eikä muuteta, vaan se on edelleen olemassa. Niin kauan kuin paikallinen muuttuja on näkyvissä, kyseisellä nimellä viitataan kuitenkin paikalliseen eikä globaaliin muuttujaan.
Globaalien muuttujien käyttämiseen on kaksi syytä:
- Niiden arvon täytyy säilyä koko ohjelman suorituksen ajan ja olla useiden moduulin funktioiden käytettävissä.
- Sulautetussa ohjelmoinnissa globaalia muuttujaa voidaan joskus käyttää suurten tietorakenteiden jakamiseen funktioiden välillä ilman, että niitä välitetään joka kerta arvona. Joissakin tilanteissa tämä voi säästää muistia, erityisesti sulautetuissa järjestelmissä. Sulautetussa ohjelmoinnissa tämä on kätevä tapa välittää tietoa ohjelman eri funktioiden välillä ilman, että argumenteista luodaan jatkuvasti kopioita.
- Esimerkiksi suuri
struct-rakenne voidaan määritellä globaaliksi, jos useiden funktioiden täytyy käsitellä samaa tietoa. Tätä tulee kuitenkin käyttää harkiten, koska globaalit muuttujat lisäävät funktioiden välisiä riippuvuuksia ja tekevät ohjelmasta vaikeammin ymmärrettävän ja debugattavan.
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 käyttää myös muista koodimoduuleista laajemmissa C-ohjelmissa, mutta tähän liittyy joitakin 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?