Esittely latautuu. Ole hyvä ja odota

Esittely latautuu. Ole hyvä ja odota

Funktioesittelyt (declarations) Jos funktiota käytetään ennen sen määrittelyä (definition), se pitää esitellä (declare) - muuten sen tyypiksi oletetaan.

Samankaltaiset esitykset


Esitys aiheesta: "Funktioesittelyt (declarations) Jos funktiota käytetään ennen sen määrittelyä (definition), se pitää esitellä (declare) - muuten sen tyypiksi oletetaan."— Esityksen transkriptio:

1 Funktioesittelyt (declarations) Jos funktiota käytetään ennen sen määrittelyä (definition), se pitää esitellä (declare) - muuten sen tyypiksi oletetaan int ja argumenteille tehdään oletusmuunnokset (C99:ssä funktion käyttö ennen määrittelyä tai esittelyä on virhe). Funktion esittely on samannäköinen kuin määrittelyn alku, funktion runko (koodi) vain puuttuu: return-tyyppi funktionimi(parametrilista);

2 /* Esittely (prototyyppi) */ double keskiarvo (double x, double y); /* Määrittely */ double keskiarvo (double x, double y) { return (x+y)/2; } Parametrien nimet voi esittelyssä jättää pois: double keskiarvo(double, double); // ei suositella

3 K&R -tyyli double keskiarvo(); /* Esittely */ double keskiarvo (x, y) /* Määrittely */ { double x, y; return (x+y)/2; } isum(a, b) /* oletustyyppi int { int a,b; return a+b; } Argumenttien tyyppejä tai määrää ei tarkisteta! Tätä ei pidä käyttää uudessa koodissa.

4 Parametrit ja argumentit Parametri: funktion määrittelyssä ja esittelyssä näkyvä nimi Argumentti: funktiokutsussa funktiolle välitettävä arvo (konventio, muitakin käytäntöjä näkee) C:ssä kaikki parametrit ovat arvoparametreja, argumentit ovat arvoja (passed by value) eivätkä niihin tehdyt muutokset palaudu kutsuvalle koodille.

5 int potenssi (int x, int n) { int result = 1; while (n-- > 0) result *= x; return result; } n on funktiolle lokaali kopio, sen muuttaminen ei vaikuta kutsuvaan koodiin.

6 Taulukkoparametrit int tsumma(int a[], int n) { int i, summa; for (i=0; i<n; i++) summa += a[i]; return summa; } Taulukon koko pitää välittää parametrina - funktio ei voi sitä päätellä. Erityisesti sizeof ei toimi taulukkoparametrin kanssa “luonnollisella” tavalla.

7 Funktiolle voi välittää taulukon koon liian pienenä, jos haluaa käsitellä vain osaa taulukosta: int a[100]; int tsum(int a[], int n) { int r=0; for (i=0; i<n; i++) r += a[i]; return r; } s10 = tsum(a, 10); // 10:n ens. alkion summa

8 Funktio voi muuttaa taulukkoparametrin *alkioita*: void tau_plusplus(int a[], int n) { int i; for (i=0; i<n; i++) a[i]++; return; }

9 Moniulotteisen taulukkoparametrin koko pitää määritellä ensimmäistä ulottuvuutta lukuunottamatta: int t2sum (int a[][10], int n) { int i,j,r; for (i=0; i<n; i++) for (j=0; j<10; j++) r+=a[i][j]; return r; }

10 C99:ssä taulukkoparametri voi olla muuttuvankokoinen: int tsum (int n, int a[n]) // huom. n ensin! // jos esittelyssä halutaan jättää nimet pois: int tsum (int, int a[*]); int tsum (int, int []); Taulukon kokoa ei kuitenkaan tarkisteta automaattisesti! Muuttuvakokoiset taulukot ovat hyödyllisimpiä moniulotteisten kanssa: int tf(int n, int m, int a[n][m], int b[m+n])

11 C99:ssä voi taulukkoparametrin *minimikoon* määrätä avainsanalla static: int tsum (int a[static 3], int n) Tämä on vain optimointivihje kääntäjälle, ohjelman toimintaan se ei muuten vaikuta. Moniulotteisen taulukon tapauksessa vain ensimmäiselle saa antaa static-määreen.

12 C99:ssä voi funktiokutsuissa käyttää vakiotaulukoita: t = tsum ((int[]){ 1, 2, 3}, 3); t = tsum ((int[]){ a, b*2, c+d, 5 }, 4);

13 return-lause Funktion suoritus päättyy return-lauseeseen tai void- tyyppisen myös viimeiseen }-merkkiin. return-lauseen argumentti saa olla mielivaltainen lauseke, se muunnetaan funktion tyyppiä vastaavaksi (kuten sijoituksessa). void-funktiossa return ei ole tarpeen mutta laillinen, ilman argumenttia. Muissa se on käytännössä (muttei muodollisesti) pakollinen: sen puuttuessa funktion arvoa ei saa käyttää.

14 Ohjelman lopetus main() on funktio, jonka loppu on samalla ohjelman loppu. Se voi päättyä joko return -lauseeseen tai exit() - funktiokutsuun: return n; exit(n); ovat ekvivalentteja main() -funktiossa; muissa funktioissa exit() lopettaa (pää!)ohjelman, return vain ao. funktion.

15 Yleensä pääohjelman pitäisi palauttaa statuskoodi. Yleisin konventio: nolla onnistuneesta suorituksesta, jotain nollasta eroavaa muuten. Luettavuuden ja siirrettävyyden parantamiseksi voi käyttää makroja: #include exit(EXIT_SUCCESS); exit(EXIT_FAILURE); Jos main() päättyy ilman return- tai exit()-kutsua, ohjelman paluuarvo voi olla mitä vain.

16 Rekursio Funktio voi kutsua itseään: int kertoma (int n) { if (n<=1) return 1; else return n*kertoma(n-1); } Kaksi (tai useampi) funktio voi myös kutsua toisiaan; tällöin ne pitää yleensä esitellä ensin.

17 Muuttujista ja näkyvyysalueista Tunnisteen (muuttuja, funktio, tyyppi ym) näkyvyysalue (scope) määrää missä kohdassa koodia sitä voi käyttää, ja erityisesti myös mitä käytetään jos samaa tunnistetta on käytetty useaan kertaan.

18 Paikalliset muuttujat Funktion sisällä määritelty muuttuja on ko. funktiolle paikallinen, lokaali: sitä voi käyttää vain sen sisällä: int numerosumma(int n) { int summa=0; // lokaali muuttuja while (n) { summa += n%10; n/=10; } return summa; }

19 Yleisemmin {} -lohkon sisällä määritelty muuttuja näkyy vain siellä, ja peittää näkyvistä ulompana määritellyn: int a(int b) { int i=1; { int i=2; printf(“%d “,i); } // 2 printf(%d\n”, i); // 1 } Muuttujalla sanotaan olevan lohkonäkyvyys (block scope).

20 C99:ssä muuttujan voi määritellä keskellä lohkoakin, silloin se näkyy sen loppuun: { int i=1; { printf(“%d\n”, i); // 1 int i=2; printf(“%d\n”, ++i); // 3 } printf(“%d\n”, i); // 1 }

21 Muuttujan säilyvyys Oletuksena paikallisten muuttujien säilyvyys (storage duration) on “automaattinen”: niille varataan muistipaikka määriteltäessä ja se vapautetaan lohkon päättyessä. Avainsanalla “static” voi määritellä muuttujan pysyväksi niin, että sen arvo säilyy funktion kutsujen välillä.

22 int laskuri(void) { static int k=0; // alustus tehdään vain kerran return ++k; } int main() { int k=7; // ei vaikuta laskuriin printf(“%d\n”, laskuri()); // 1 printf(“%d\n”, laskuri()); // 2 printf(“%d\n”, laskuri()); // 3...

23 Globaalit muuttujat Muuttujan voi määritellä myös funktion ulkopuolella. Silloin sen näkyvyysalue on koko tiedosto määrittelykohdasta alkaen, ja säilyvyys aina staattinen (static-avainsanaa ei tarvita). Globaaleja muuttujia on yleensä syytä välttää.

24 int n=0; int laskuri(void) { return ++n; } int main() { n=5; printf(“%d\n”, laskuri()); // 6 printf(“%d\n”, laskuri()); // 7 return 0; }

25 Ohjelman organisoinnista C on varsin vapaamielinen eri ohjelmaelementtien järjestyksen suhteen, mutta järjestys vaikuttaa. Helpointa on laittaa elementit johdonmukaisesti esim. näin: #include -direktiivit #define -direktiivit tyyppimääritykset globaalien muuttujien määritykset funktioiden esittelyt (pl. main) main() muiden funktioiden määritykset

26 Osoittimet (pointterit) C:n ehkä tärkein (ja vaikein) erikoispiirre on osoittimet (pointterit, engl. pointers). Osoitin on periaatteessa vain kokonaisluku, joka kertoo missä päin muistia tietty muuttuja (tms) sijaitsee (vrt. taulukon indeksi). Osoittimia ei kuitenkaan voi käsitellä aivan samoin kuin kokonaislukuja: ne voivat olla eri kokoisia ja niillä laskemisessa on omat sääntönsä.

27 Osoitinmuuttuja on muuttuja, joka voi sisältää osoittimen, toisen muuttujan (tms) osoitteen - sanotaan, että se osoittaa tai viittaa tähän. Kukin osoitinmuuttuja voi viitata vain yhdentyyppisiin muuttujiin, ja ne määritellään lisäämällä määrityksessä * viitatun tyypin eteen: int *p; double *q; int a, *i; // a normaali int, i int-osoitin Viitattu tyyppi saa olla mikä vain, myös taulukko tai vaikka toinen osoitintyyppi.

28 Osoiteoperaattorilla & saa muuttujan osoitteen, jonka voi sijoittaa osoitinmuuttujaan: int a; int *p; p = &a; Osoitinmuuttujan voi myös alustaa tähän tapaan: int a, *p=&a;

29 Viittausoperaattorilla (indirection operator) * voi käyttää osoittimen (osoitinmuuttajan tai lausekkeen) osoittaman muuttujan sisältöä: int a, *p=&a; *p = 5; printf(“%d\n”, a); // 5 Huom. *:llä on eri merkitys määrittelylauseessa kuin lausekkeessa!

30 Osoite- ja viittausoperaattorit ovat tavallaan toistensa käänteisoperaatioita: a = *&b; // sama kuin “a = b” (jos b muuttuja) a = &*b; // sama kuin “b=a” jos b osoitinmuuttuja // (muuten “*b” ei ole laillinen)

31 Samaan muuttujaan voi osoittaa useampi osoitinmuuttuja yhtaikaa, ja osoitinmuuttujan voi sijoittaa toiseen samantyyppiseen osoitemuuttujaan. int i=3, j, *q; int *p = &i; q = &j; // ei tähteä *! *q = *p; // j=i (3) q = p; // nyt myös q osoittaa i:hin *q = 5; // i=5 printf(“i=%d, j=%d\n”, i, j); // 5, 3

32 Osoitinargumentit Funktion argumentti voi olla osoitin, ja silloin funktio voi muuttaa sen osoittamaa muuttujaa: void int_swap(int *a, int *b) { int t = *a; *a = *b; *b = t; } int x=5, y=6; int_swap(&x, &y); // kutsussa &

33 Osoitinargumenttina voi olla myös osoitinmuuttuja, ja silloin &-merkkiä ei tarvita: int a=5, b=6; int *p = &b; int_swap(&a, p); scanf(“%d”, p); // scanf(“%d”, &b);

34 void min_max(int a[], int n, int *min, int *max) { int i; *min = *max = a[0]; for (i=0; i<n; ++i) { if (a[i]<*min) *min=a[i]; if (a[i]>*max) *max=a[i]; } int t[100] = { 64,2,745,-456,8,5,6 }; // loput nollaa int m, n; min_max(t, 100, &m, &n);

35 Osoitinargumentin voi määritellä vakioksi niin, että sen osoittamaa muuttujaa ei saa muuttaa: int f(const int *a, int j) { *a = 0; // VIRHE a = &j; // OK Tuo ei estä itse a:n (paikallisen kopion) muuttamista. Jos sitä halutaan: int f(int * const a, int j) { { *a = 0; // OK a = &j; // VIRHE Molemmatkin voi tehdä: int f(const int * const a, int j) { //...

36 Funktion paluuarvokin voi olla osoitin: int *min(int *a, int *b) { if (*a<*b) return a; else return b; } int *p, i=5, j=6; p = min(&i, &j); *p = 3; // i=3 *min(&i,&j)=3;

37 Paluuarvo voi olla myös globaalin tai staattisen paikallisen muuttujan osoite: int *laskuri(void) { static int n=0; n++; return &n; } int *p; printf(“%d\n”, *laskuri()); // 1; *laskuri() = 6; printf(“%d\n”, *laskuri()); // 7

38 Myös taulukon yksittäisillä alkioilla on omat osoitteensa: int a[10] = { 1, 2, 3}, *p = &a[5]; int *tmax(int a[], int n) { int i, *t=&a[0]; for (i=0; i<n; i++) if (a[i] > *t) t = &a[i]; return t; } *p=-4; p = tmax(a,10); *p = 6;

39 Osoittimilla laskeminen Osoittimille on määritelty kolme laskutoimitusta: * Kokonaisluvun lisääminen osoittimeen; * Kokonaisluvun vähentäminen osoittimesta; ja * Osoittimen vähentäminen osoittimesta Yleensä nämä ovat hyvin määriteltyjä vain osoittimen osoittaessa (samaan) taulukkoon.

40 Olennaisesti “osoitin + 1” == “osoitin taulukon seuraavaan alkioon”. Osoitteita ajatellen tämä tarkoittaa osoitteen kasvattamista taulukon alkion koolla. Jos sizeof(int)=4 ja int-muuttujan x osoite on 8, &x + 1 osoittaisi osoitteeseen 12. Jos char-muuttujan c osoite olisi 8, &c + 1 osoittaisi osoitteeseen 9. Osoititinten “raa'at” arvot saa printf-formaatilla %p: printf(“%p\n”, &x);

41 int a[10] = { 1, 2, 3 }, *p, *q, n; p = &a[1]; q = p + 5; *q = 6; // a[6]=6 *(q += 2) = 8; // a[8]=8, q=&a[8] *p++ = 11; // a[1]=11, p=&a[2] *--q = 7; // a[7]=7, q=&a[7] n = q - p; // 5

42 int a[N], *p, summa=0; for (p=&a[0]; p<&a[N]; ++p) summa += *p; Huom: p<&a[N] on sallittu vaikka a[N]:ää ei ole - taulukon “viimeistä seuraavan” alkion osoitteen saa laskea tällä tavoin. Taulukon nimeä voi käyttää osoittimena (osoittaa ensimmäiseen alkioon): p=a; while (p < a+N) summa += *p++;

43 Funktion argumenttina taulukkoa kohdellaan kuten osoitinta: void nollaa(int a[], int n) { int *p; for (p=a; p<a+n; p++) *p=0; } Parametriksi voi kirjoittaa osoittimen, vaikutus on sama: void nollaa(int *a, int n)

44 Osoitinta voi myös käyttää kuten se olisi taulukko: int i, a[N], *p = a, sum=0; for (i=0; i<N; i++) sum += p[i]; Yleisesti: p[i] == *(p+i) (mistä seuraa myös että p[i] == i[p]!)

45 Taulukko ja osoitin ovat yleensä keskenään vaihtokelpoisia funktioiden määrittelyissä ja esittelyissä, lausekkeissa ja funktiokutsuissa, mutta eivät muuttujamäärittelyissä eikä esim. sizeof'in (ei funktio vaan operaattori!) kanssa.

46 Kaksiulotteinen taulukko on C:n mielestä yksiulotteinen taulukko, jonka alkiot ovat myös yksiulotteisia taulukoita, ja taulukon nimi on tyypiltään osoitin yksiulotteiseen taulukkoon: int a[10][10]; int (*p)[10]; // sulut välttämättömät p=a; (*p)[1] = 5; // a[0][1] (*++p)[6] = 7; // a[1][6]

47 Kaksiulotteisen taulukon riviä voi käsitellä kuten yksiulotteista taulukkoa yleensäkin: int a[10][10], (*p)[10]; p = &a[2]; // toinen rivi void nollaa(int a[], int n) { for(int i=0; i<n; i++) a[i]=0; // C99 } nollaa(a[3], 10); // nollataan rivi 3

48 Sarakkeen nollaus: int a[5][6], (*p)[6], sar; for (p= &a[0]; p<&a[5]; ++p) (*p)[sar]=0; Silmukassa p osoittaa aina kutakin riviä, *p rivin ensimmäistä alkiota ja (*p)[sarake] ko. rivin alkiota sar. Huom: p on osoitin int[6] -taulukkoon - jollaista *ei* käytetä käsiteltäessä yksittäistä int[6]-taulukkoa (siihen käytettäisiin int-osoitinta).

49 Kaksiulotteista taulukkoa voi kohdella yksiulotteisena: int a[10][10], *p, *q; p=&a[0][0]; q=p+100; while (p<q) *p++=0; Toimii melkein kaikilla kääntäjillä, periaatteessa kiellettyä nykyisin.

50 int *asum(int *a, int *b, int n) { int *p = b; while (p<b+n) *a+=*b; return a; }

51 int a[100], *p=a; *(p+1) * *(p+2)... // toimii int a, b, c, *p=a; *(p+1) * *(p+2)... // EI aina toimi! // muuttujien järjestys muistissa voi olla mikä vain


Lataa ppt "Funktioesittelyt (declarations) Jos funktiota käytetään ennen sen määrittelyä (definition), se pitää esitellä (declare) - muuten sen tyypiksi oletetaan."

Samankaltaiset esitykset


Iklan oleh Google