Modulaarisessa ohjelmoinnissa jaetaan ohjelma osiin ...janneku/T080104/SAU14SNS/C/C_OSA3.pdf · 3....

Post on 08-Jul-2020

1 views 0 download

Transcript of Modulaarisessa ohjelmoinnissa jaetaan ohjelma osiin ...janneku/T080104/SAU14SNS/C/C_OSA3.pdf · 3....

46

3. Funktiot

Modulaarisessa ohjelmoinnissa jaetaan ohjelma osiin (moduuleihin), jotka ovat yksinkertaisia ja lyhyitä. Modulaarisuudella pyritään parantamaan ohjelman ymmärrettävyyttä, testattavuutta sekä ylläpidettävyyttä. Funktio ryhmittää ohjelmalauseet yhdeksi ohjelmayksiköksi. Funktio suorittaa jonkin tarkasti rajatun tehtävän.

47

Kaksi tasoa: • Valmiit kirjastofunktiot, esim. printf() ja scanf() • Ohjelmoijan itsensä kirjoittamat funktiot. Funktiot aktivoidaan suorittamalla funktion kutsu, joka on tavallinen C-kielen suoritettava lause: fnimi(); Funktion kutsu aloittaa funktion fnimi() ohjelmalauseiden suorituksen Kun funktion sisältämät lauseet on suoritettu, palataan takaisin siihen ohjelmaan, josta funktiota kutsutaan ja suoritetaan funktion kutsua seuraava lause.

48

lause1; lause2; flause1; fnimi(); flause2; lause4; flause3; Funktion kutsussa sulkeet ovat pakolliset, niiden avulla kääntäjä osaa päätellä että kyseessä on funktio, eikä tavallinen muuttuja.

49

Funktio täytyy määritellä ennenkuin sitä voidaan kutsua. Yleinen muoto yksinkertaistettuna on: funktion otsikko{ paikallisten muuttujien määrittelyt; suoritettavat lauseet; return arvo; } Funktio määritellään otsikkorivin ja lauselohkon avulla ( { ja }-merkkien välissä olevat lauseet). Otsikkorivillä ilmoitetaan funktion tyyppi, nimi ja parametrit. • Funktion tyyppi ilmoittaa funktion palauttaman arvon tyypin, jos funktio ei

palauta mitään niin tyyppinä on void.

50

• Tyyppimäärite void esittää näennäistä tyyppiä, johon ei kuulu mitään arvoja. • Parametrit ilmoitetaan sulkujen sisällä. • Parametrejä voi olla yksi tai useampi, jokaisesta ilmoitetaan erikseen tyyppi

ja nimi. • Jos parametrejä ei ole käytetään tyyppinä void.

51

• Funktion paikalliset muuttujat ovat käytettävissä vain funktion sisällä ja vain funktion suorituksen ajan.

• Suoritettavat lauseet käyttäytyvät kuten main()-funktiossakin. Funktiot on esiteltävä, jos funktioiden määrittelyt sijaitsevat main()-ohjelman perässä tai jossakin toisessa tiedostossa. Funktion esittelyä kutsutaan myös funktion prototyypiksi. Funktion prototyyppi kirjoitetaan ohjelmatiedoston alkuun esikääntäjän direktiivien jälkeen ennen main()-funktiota. Funktion esittelyssä käytetään funktion otsikko-osaa, jonka perään kirjoitetaan puolipiste.

52

Esittelyjen avulla kääntäjä tutkii vastaavatko funktioiden kutsut ja määrittelyt toisiaan. • parametrien määrä, • järjestys ja • tyypit Esimerkkifunktio, joka "simuloi" kissaa: /* kissasimulaattori */ void naukaise( void ) { printf("Miau\n"); } Funktiota kutsuttaisiin. naukaise();

53

Esimerkkiohjelma, jossa käytetään hyväksi kissa-funktiota. /* kissasimulaattori */ #include <stdio.h> void naukaise(void); /* funktion protyyppi, eli esittely */ int main(void) { /* kissa naukaisee */ naukaise(); return(0); } /* main loppuu tähän */ /* funktion naukaise määrittely eli toteutus */ void naukaise(void) { printf("Miau\n"); }

54

Funktion parametrit Parametrien avulla funktiolle välitetään tietoa sen ulkopuolelta ja parametrejä käytetään funktiossa kuten paikallisia muuttujia. Parametrit eroavat paikallisista muuttujista siinä, että niille annetaan alkuarvo funktion ulkopuolelta eli funktion kutsussa. Kuten paikalliset muuttujat eivät parametritkään ole käytettävissä funktion ulkopuolella (ne eivät "näy" funktion ulkopuolelle). Eri funktioissa voi olla saman nimisiä muuttujia ja parametrejä, mutta ne ovat toisistaan täysin riippumattomia.

55

Funktion otsikkossa (sekä esittelyssä että määrittelyssä) esiteltäviä parametrejä kutsutaan muodollisiksi parametreiksi. Funktion kutsussa annetut parametrit ovat todellisia parametrejä. Funktiota kutsuttaessa kopioidaan todellisen parametrien arvot funktion määrittelyssä vastaavassa kohdassa sijaitsevien muodollisten parametrien arvoiksi. Todellisten ja muodollisten parametrien tyyppien täytyy vastata toisiaan (joissakin tilanteissa C:ssä tehdään ns. automaattisia tyypin-muunnoksia ). Todellisten ja muodollisten parametrien nimien ei tarvitse olla samoja. Todellisena parametrina voi olla muuttujan tunnus, vakio, lauseke tai funktionkutsu.

56

Arvon palauttavat funktiot

Funktiosta palataan takaisin kutsuvaan ohjelmaan: • funktion viimeisen lauseen suorittamisen jälkeen ja/tai • return-lauseella Jos funktion on palautettava arvo käytetään return-lausetta, jonka perään kirjoitetaan palautettava arvo. Palautettavan arvon tyypin on oltava funktion otsikossa esiteltyä tyyppiä. Funktiossa voidaan käyttää tyhjää return-lausetta, jos funktio ei palauta mitään ( funktion tyyppi on tällöin void ).

57

Yleensä funktiossa on vain yksi return-lause, mutta mikään ei estä käyttämästä useampaakin (joista vain yksi suoritetaan!). Usean return-lauseen käyttö ei ole suositeltava tapa, koska se saattaa vaikeuttaa ohjelman lukemista ja ylläpitoa.

58

Esimerkki: /* Funktio laskee kertoman n! esiehto: n on kokonaisluku ja suurempi tai yhtäsuuri kuin 0 */ int kertoma(int n){ int i, tulo = 1; for(i = 0; i <= n; i = i + 1){ tulo = tulo * i; } return ( tulo ); /* palauttaa luvun n kertoman */ }

59

• funktion otsikossa ilmoitetaan palautettavan arvon tietotyyppi • funktio ei tulosta arvoaan, vaan palauttaa sen return-lauseella Funktion kommentti kertoo kaiken, mitä funktiosta pitää tietää, jotta funktiota voi käyttää. • mitä funktio tekee • mitä sille pitää antaa syöttietoina • mikä on syötettävien tietojen tyyppi • esiehto kertoo, mitä pitää olla totta ennenkuin funktiota voidaan kutsua Esimerkkiohjelma, joka käyttää edellä esiteltyä kertoma-funktiota.

60

#include <stdio.h> /* funktion kertoma prototyyppi, eli esittely */ int kertoma ( int n ); /* Pääfunktio, joka kutsuu funktiota kertoma */ int main ( void ) { int num, tulos; printf ("Anna kokonaisluku väliltä 0 - 10 > "); scanf ("%d", &num ); /* tarkastetaan luvun arvo ennen funktion kutsumista */ if ( num >= 0 ) { tulos = kertoma ( num ); printf ("Luvun %d kertoma on %d\n", num, tulos ); } else { printf ("Luvun tulee olla positiivinen!"); } return ( 0 ); } /* main päättyy */

61

/* Funktion kertoma määrittely eli toteutus Funktio laskee kertoman n! esiehto: n on kokonaisluku ja suurempi tai yhtäsuuri kuin 0 */ int kertoma ( int n ) { int i, tulo = 1; for ( i = 1; i <= n; i = i + 1 ) { tulo = tulo * i; } return ( tulo ); /* palauttaa luvun n kertoman */ }

62

On myös mahdollista määritellä loogisia funktioita, joissa kokonaisluvut vastaavat loogisia arvoja tosi ja epätosi. /* Funktio, joka ilmoittaa onko argumenttina annettu kokonaisluku parillinen. Funktio palauttaa arvon true (1) jos luku on parillinen, muuten funktio palauttaa arvon false (0) */ bool parillinen(int luku){ bool vastaus; vastaus = ((num % 2) == 0); return(vastaus); }

63

Käyttö ehtolausekkeena int x; printf(“Anna kokonaisluku”); scanf(“%d”, &x); if(parillinen(x)) printf("Luku on parillinen!"); Usean funktion kutsuminen lauseessa: tulos = kertoma(n) / (kertoma(r) * kertoma(n - r));

64

Funktioiden tiedonvälitys Funktion ja sitä kutsuvan ohjelman osan välinen tiedonsiirto näkyy funktion otsikossa, funktiolle ei pitäisi välittää tietoa ns. globaalien muuttujien avulla. Funktiolle välitetään aina vain kopio todellisen parametrin arvosta. Jos funktion halutaan muuttavan kutsuvassa ohjelmassa olevan muuttujan arvoa, välitetään todellisena parametrina muuttujan osoite (oikeastaan kopio osoitteen arvosta). Kun parametrin arvo on muuttujan osoite käytetään nimeä osoiteparametri, muussa tapauksessa on kyseessä arvoparametri.

65

Funktion ja sitä kutsuvan ohjelman väliseen tiedonsiirto on kaksi tapaa: • arvoparametri ja return-lause • osoiteparametri Lisäksi näiden eri variaatiot

66

Funktiot ja osoiteparametrit Funktio voi palauttaa return-lauseella korkeintaan yhden arvon. Jos funktion on palautettava useita arvoja, käytetään osoite-parametreja välittämään tietoa ulos funktiosta. Esimerkki funktiosta, joka palauttaa parametrina annetun reaaliluvun etumerkin, kokonaisosan ja desimaaliosan.

67

#include <stdio.h> #include <math.h> /* erota -funktion esittely */ void erota(double num, char *merkkio, int *kokoaiso, double *desio); int main(void){ double luku, desimaaliosa; char etumerkki; int kokonaisosa; printf(" Anna reaaliluku >"); scanf("%lf", &luku ); erota (luku, &etumerkki, &kokonaisosa, &desimaaliosa); printf("Luvun %.4lf osat\ etumerkki: %c\n", luku, etumerkki ); printf("kokonaisosa: %d\n", kokonaisosa); printf("desimaaliosa: %.4lf\n", desimaaliosa); return ( 0 ); } /* main */

68

/* erota-funktio saa erottaa in-parametrina saamansa reaaliluvun osiinsa. num: in, erotettava reaaliluku *merkkio: out, etumerkki *kokonaiso: out, kokonaisosa *desio: out, desimaaliosa */ void erota(double num, char *merkkio,int *kokonaiso, double *desio ){ double n; /* paikallinen muuttuja */ if ( num < 0 ) *merkkio = '-'; else if ( num == 0 ) *merkkio = ' ' ; else *merkkio = '+'; n = fabs(num); /* luvun n itseisarvo */ *kokonaiso = floor(n); /* kokonaisosa */ *desio = n - *kokonaiso; /* desimaaliosa */ }/* erota */

69

main-funktion parametrit

main()-funktiolle voidaan välittää tietoja kutsuttaessa ns. komentori-viparametrien avulla. Komentoriviparametrit kirjoitetaan ohjelman nimen perään, kun ohjelma käynnistetään komentoriviltä. Käyttöjärjestelmä välittää mitkä tahansa komentoriviparametrit ladattavalle ohjelmalle.

70

Ohjelma, joka tulostaa saamansa komentoriviparametrit: int main (int argc, char * argv[]){ int laskuri; printf("Parametrien lukumäärä: %d\n", argc ); for ( laskuri = 0; laskuri < argc; laskuri++) { printf("Parametri %d on %s\n", laskuri, *(argv+laskuri) ); } return ( 0 ); }

71

Ensimmäisen parametri argc kertoo kokonaislukuna annettujen parametrien lukumäärän. Toinen parametri argv[] on osoitin merkkijonotaulukkoon, jonne kaikki parametrit on talletettu syöttöjärjestyksessä. Ensimmäisen merkkijonotaulukon alkion sisältönä on aina suoritettavan ohjelman koko hakemistopolku.

72

Talletusluokat (Storage classes) C-kielessä on erilaisia talletusluokkia (muistimääreitä), jotka vaikuttavat muuttujien ja funktioiden näkyvyyteen ja elinikään. 1. auto Funktioiden muodollisille parametreille ja paikallisille muuttujille varataan tilaa automaattisesti ja näiden muuttujien käyttämä tila vapautetaan automaattisesti, kun funktion suoritus päättyy.

73

2. extern Funktioille voidaan joissakin tilanteissa välittää tietoa muuttujien avulla, jotka on määritelty kaikkien funktioiden ulkopuolella ns. globaalit muuttujat. Globaali muuttuja "näkyy" kaikissa funktioissa, jotka on määritelty globaalin muuttujan määrittelyn jälkeen. Globaaleille muuttujille varataan tilaa koko ohjelman suorituksen ajaksi. Globaalin muuttujan näkyminen voidaan peittää funktiossa määrittelemällä uusi samanniminen paikallinen muuttuja. Kaikki funktioiden nimet ovat tyyppiä extern, eli funktioiden nimet nimet ovat globaaleja.

74

Globaalien muuttujien käyttö ei ole vaaratonta, esimerkiksi ohjelmoijalla on tarkoitus käyttää paikallista muuttujaa, jolla on sama nimi kuin globaalilla muuttujalla. Kaikki paikalliseen muuttujaan kohdistuvat toimenpiteet suoritetaan globaaliin muuttujaan, josta seuraa melko varmasti virhetilanne, joka voi olla vaikea havaita.

75

Esimerkkiohjelma: #include <stdio.h> int a = 1, b = 2, c = 3; /* globaaleja muuttujia */ int summa(void); /* funktion prototyyppi */ int main ( void ) { printf("%3d\n", summa()); printf("%3d%3d%3d\n", a, b, c ); return (0 ); } /* funktion summa määrittely */ int summa ( void ) { int b, c; /*paikalliset b ja c,globaalit b ja c peittyvät*/ a = b = c = 4; return ( a + b + c ); }

76

Globaalin muuttujan määrittelyssä ei käytetä sanaa extern, muuttujaa esiteltäessä. Sanalla extern kerrotaan kääntäjälle "etsi esiteltävän muuttujan määrittely muualta joko tästä tai toisesta tiedostosta". Esimerkiksi jos edellinen ohjelma kirjoitettaisiin kahteen eri tiedostoon.

77

tiedosto file1.c: #include <stdio.h> int a = 1, b = 2, c = 3; /* globaaleja muuttujia */ int summa ( void ); /* funktion prototyyppi */ int main ( void ) { printf("%3d\n", summa( ) ); printf("%3d%3d%3d\n", a, b, c ); return (0 ); }

78

tiedosto file2.c: /* funktion summa määrittely */ int summa ( void ) { extern int a; /* etsi a:n määrittely muualta */ int b, c; /*paikalliset c ja b, globaalit b ja c peittyvät*/ a = b = c = 4; return ( a + b + c ); }

79

3. register register-muistimääreellä kerrotaan kääntäjälle, että jos vain on mahdollista niin muuttujalle varataan tila koneen keskusyksikön rekistereistä, eikä keskusmuistista. Tarkoituksena on tehostaa ohjelman suoritusta. Määre on vain ehdotus kääntäjälle, jos rekistereitä ei ole joutilaana, varataan muuttujalle tilaa normaalisti keskusmuistista.

80

Tyypillisiä ehdokkaita ovat silmukkalaskurit ja funktioiden parametrit, esimerkiksi: { register int i; for(i = 0; i < LIMIT; i++){ .... } } Lohkosta poistuminen vapauttaa muuttujan i. Jos muuttujan määrittelyssä jätetään tyyppi ilmoittamatta, mutta talletusluokka ilmoitetaan, on tyyppi oletusarvoisesti int.

81

4. static static-määrettä käytetään kahdessa eri tarkoituksessa. Paikalliset muuttujat säilyttävät arvonsa kun samaan lohkoon tullaan uudestaan, toisin kuin automaattisten muuttujien kohdalla. Esimerkiksi funktio, joka toimii eri tavalla riippuen montako kertaa sitä on kutsuttu. void teeJotakin ( void ) { static int lkm = 0; lkm++; if ( lkm % 2 == 0) ... /* tee jotakin */ else ... /* tee jotakin muuta */

82

lauseessa static int lkm = 0; alustetaan muuttujalle lkm arvo 0 vain funktion teeJotakin ensimmäisellä suorituskerralla. static -määreen käyttö globaalien nimien yhteydessä antaa mahdollisuuden rajoittaa muuttujien ja funktioiden "näkyvyyttä". Näkyvyys rajataan vain sen tiedoston loppuun, jossa muuttuja tai funktio on määritelty ts. muuttujat eivät näy funktioissa, jotka on esitelty ennen muuttujan määrittelyä eikä funktioissa, jotka on määritelty eri tiedostoissa.

83

Esimerkiksi: void funktioYksi(void) { ... /* muuttuja v ei ole käytettävissä */ } static int v; /* static external v */ void funktioKaksi ( void ) { ... /* v:tä voidaan käyttää täällä */ }

84

Myöskin funktion näkyminen voidaan rajoittaa esim: static int funktioG(void); /* prototyyppi */ void funktioF(void){ /* funktion määrittely */ ... /*funktioG() "näkyy" täällä, mutta ei muissa tiedostoissa*/ } static int funktioG(void){ ... }

85

C-kielessä globaalit ja staattiset muuttujat alustetaan automaattisesti nollaksi ellei ohjelmoija anna niille alkuarvoja. Automaattisia ja rekisterimuuttujia ei alusteta automaattisesti.

86

Funktiokirjastot C-kielessä on valmiiksi määriteltyä funktioita, jotka on koottu funktiokirjastoihin, jotka on jaettu useisiin pienempiin ryhmiin. Funktiokirjastot otetaan käyttöön #include-komennolla kirjoittamalla komennon perään tarvittavan otsikkotiedoston nimi kulmasulkeissa. #include <stdio.h> Otsikkotiedosto sisältää funktioiden määrittelyt. Kulmasulkeet ilmoittavat kääntäjälle, että sisältö sijaitsee standardikirjasto-hakemistossa. C:n standardikirjasto koostuu 15 otsikkotiedostosta.

87

Otsikkotiedosto Kuvaus assert.h ohjelmien diagnostiikkatoimintoja ctype.h merkkien testausfunktioita locale.h maa- ja kieli-informaatiota tarjoavia funktioita math.h matemaattiset funktiot setjmp.h mahdollistaa ohjelman kulun muuttamisen

funktioilla signal.h poikkeustilanteiden käsittelyfunktiot stdarg.h muuttuja-argumenttien käsittelymakroja stdbool.h loogisen tietotyypin määrittelyt stddef.h tietotyyppien ja makrojen määrittelyitä stdio.h syöttö- ja tulostusfunktioita stdlib.h erilaisia hyötyfunktioita string.h merkkijonojen käsittelyfunktiot time.h päivänmäärä- ja aikafunktioita

88

Matemaattiset funktiot. Funktio Kuvaus abs () kokonaislukumuuttujan itseisarvo fabs() reaalilukumuuttujan itseisarvo sin() luvun sini cos() luvun kosini tan() luvun tangentti asin() luvun arcussini acos() ... atan() ... log() luonnollinen logaritmi log10() 10-kantainen logaritmi exp() eksponenttifunktio sqrt() neliöjuuri pow() luvun korotus potenssiin

89

Aika- ja päivänmääräfunktiot Otsikkotiedostossa time.h määritellään funktioita, joilla käsitellään aikaa ja päivänmäärää. Funktioiden avulla voidaan käyttöjärjestelmältä kysyä aika ja päivänmäärä, sekä muuntaa aikamuotoja. Useissa ympäristöissä päivänmäärä ja aika ilmaistaan sekunteina, joka on kulunut päivänmäärästä 1.1.1970. Funktio kuvaus stime() asettaa ajan ja päivänmäärän time() palauttaa ajan aikana (GMT) asctime() muuntaa ajan muodosta toiseen clock() palauttaa kuluneen prosessoriajan

90

Tiedostossa stdlib.h on useita hyötyfunktioita, joista alla muutamia yleisimmin käytettyjä Funktio kuvaus rand() Palauttaa satunnaisluvun srand() Alustaa satunnaislukugeneraattorin exit() ohjelman keskeytys (yleensä

virhetilanteessa ) abort() ohjelman epänormaali keskeytys mallaoc() muistin allokointi realloc() vapauttaa muistia free() palauttaa allokoidun muistin järjestelmälle system() suorittaa käyttöjärjestelmätoiminnon qsort() lajittelee taulukon, quicksort-algoritmia

käyttäen

91

Merkkienkäsittelyfunktiot Funktio kuvaus isalpha() onko merkki aakkonen iscntrl() onko merkki ohjausmerkki ispunct() onko merkki välimerkki isspace() onko merkki välilyönti isupper() onko merkki iso kirjain islower() onko merkki pieni kirjain tolower() muuttaa kirjaimen pieneksi kirjaimeksi toupper() muuttaa merkin isoksi kirjaimeksi

92

Yhteenveto: • return-lause päättää funktion suorituksen ja palauttaa kontrollin kutsuvalle

ympäristölle. • Jos return-lause sisältää lausekkeen, lasketaan lausekkeen arvo ja sen arvo

palautetaan kutsujalle. • Funktion prototyyppi kertoo kääntäjälle funktiolle välitettävien parametrien

tyypin ja lukumäärän sekä funktion palauttaman arvon tyypin. Jos funktiolle ei välitetä parametreja käytetään sanaa void. Jos funktio ei palauta mitään käytetään sanaa void.

• Funktiolle välitetään parametrina aina kopio todellisen parametrin arvosta,

joka voi olla joko muutujan sisältö tai sen osoite. Välitettävän tiedon tyypin ja järjestyksen täytyy vastata funktion prototyypissä ja määrittelyssä ilmoitettua tyyppiä ja järjestystä.