7. Funkcije

Podeli svoje utiske o poglavlju na anketi OVDE.

Preduslovi za rad:

Poznavanje rada sa promenljivama i nizovima
Poznavanje pokazivača

7.1. Definicija funkcija

Kako rešenja postaju komplikovanija, povećava se šansa za ponavljanjem sličnog koda na više različitih mesta. U slučaju da je neophodno promeniti postojeće rešenje, potrebno je promeniti na svim mestima gde se taj sličan kod nalazi. Ovo je podložno greškama i zbog toga se uvodi pojam funkcije.

Funkcije pomažu oko tzv. dekompozicije problema na manje podprobleme. Ovo pomaže u smanjenju mentalnog napora pri analizi rešenja, tako što će se kod koji je međusobno povezan biti grupisan na jednom mestu. Svaku funkciju moguće je posmatrati kao "crnu kutiju" sa određenim ulaznim vrednostima i jednim rezultatom. Kombinovanjem poziva funkcija, odnosno sklapanjem podproblema, dolazi se do rešavanja problema.

Funkcije su već uveliko korišćene u primerima do sad. Počevši od funkcija za rad sa standardnim ulazom printf i scanf, preko matematičkih funkcija poput pow i sqrt ili za rad sa stringovima, poput strlen. Te funkcije su dostupne preko zaglavlja stdio.h, math.h i string.h. Svi prethodni primeri i programi samo pozivaju ove funkcije koje su definisane na nekom drugom mestu.

 1#include <stdio.h>
 2
 3int zbir(int a, int b)
 4{
 5    int c;
 6    c = a + b;
 7
 8    return c;
 9}
10
11int main()
12{
13    int prvi_broj, drugi_broj;
14
15    printf("Unesite prvi broj: ");
16    scanf("%d", &prvi_broj);
17
18    printf("Unesite drugi broj: ");
19    scanf("%d", &drugi_broj);
20
21    int rezultat = zbir(prvi_broj, drugi_broj);
22    printf("Rezultat sabiranja: %d\n", rezultat);
23
24    return 0;
25}

Iznad je dat primer funkcije zbir koja sabira dva broja na dva načina. Funkciju je moguće deklarisati i definisati na istom mestu, a moguće je napraviti deklaraciju, pa negde kasnije definiciju. Oba načina rezultovaće kodom sa istim ponašanjem, stoga je na izboru programera kako će raditi. Kada su funkcije deklarisane, pa potom definisane, to pomaže main funkciji da "ispliva" ka vrhu, jer je ona ta koja najbitnija. U slučaju korišćenja više .h i .c. datoteka u rešenju, deklaracije funkcija će biti u .h datotekama. Dobro napisane funkcije, sa dobrim imenovanjem, pomažu pri analizi šta program radi bez ulaska u detalje funkcionisanja koda.

Obe varijante primera daju isti ispis:

Unesite prvi broj: 4
Unesite drugi broj: 5
Rezultat sabiranja: 9

7.2. Parametri funkcije

Prilikom poziva funkcije, potrebno je navesti vrednosti sa kojima se ona poziva. Ove vrednosti nazivaju se konkretni parametri ili argumenti. Broj tih vrednosti, kao i njihovi tipovi, navode se prilikom deklaracije funkcije i nazivaju formalni parametri ili parametri. Formalni parametri funkcije predstavljaju promenljive koje važe samo unutar te funkcije, a dobijaju početnu vrednost spolja, odnosno, prilikom poziva. Ova apstrakcija predstavlja način kako funkcija može da radi za bilo koje vrednosti konkretnih parametara dokle god su one odgovarajućeg tipa.

7.3. Prenos parametara u funkciju

Parametre je moguće preneti po vrednosti i po referenci.

 1#include <stdio.h>
 2
 3void fun(int i);
 4
 5int main()
 6{
 7    int i = 5;
 8
 9    fun(i);
10    printf("Vrednost i: %d\n", i);
11
12    return 0;
13}
14
15void fun(int i)
16{
17    i = 3;
18}

Ispis u oba slučaja je različit:

Vrednost i: 5

Kod prenosa po vrednosti, parametar funkcije samo dobija vrednost i promena te vrednosti unutar funkcije neće uticati na promenljivu van funkcije čija je vrednost prosleđena. U slučaju prenosa po referenci, parametar funkcije dobija adresu promenljive izvan funkcije i posredstvom te adrese je moguće uticati na vrednost promenljive izvan funkcije.

7.4. Nizovi i funkcije

U programskom jeziku C, nizovi se isključivo prenose po referenci, odnosno, preneće se adresa početka niza. Ovo daje mogućnost čitanja vrednosti niza, ali i promenu vrednosti unutar niza. Pretpostaviti da postoji niz unetih celobrojnih vrednosti i da je potrebno naći sumu tog niza:

 1int suma(int *a, int n)
 2{
 3    int i, s = 0;
 4
 5    for(i = 0;i < n;i++)
 6    {
 7        s += a[i];
 8    }
 9
10    return s;
11}

Napomena:

Moguća su dva zapisa parametra koji označava niz: int *a ili int a[].
Oba imaju potpuno isto značenje, a to je prenos adrese početka niza u funkciju.

7.5. Povratna vrednost funkcije

Prilikom deklaracije, odnosno definicije funkcije, pre njenog imena navodi se povratni tip, koji predstavlja tip vrednosti koju će funkcija vratiti onom ko je poziva. Unutar funkcije, nakon što se dobije željeni rezultat, on će se "vratiti" pomoću return naredbe.

 1#include <stdio.h>
 2
 3#define FAKTORIJEL_GRESKA -1
 4
 5int faktorijel(int broj);
 6
 7int main()
 8{
 9    printf("Faktorijel od 0: %d\n", faktorijel(0));
10
11    if(faktorijel(-5) == FAKTORIJEL_GRESKA)
12    {
13        printf("Nije moguce dobiti faktorijel od negativnog broja!\n");
14    }
15
16    printf("Faktorijel od 5: %d\n", faktorijel(5));
17
18    return 0;
19}
20
21int faktorijel(int broj)
22{
23    int i, fakt = 1;
24
25    if(broj < 0)
26    {
27        return FAKTORIJEL_GRESKA;
28    }
29    else if(broj == 0)
30    {
31        return 1;
32    }
33
34    for(i = 2;i <= broj;i++)
35    {
36        fakt *= i;
37    }
38
39    return fakt;
40}

Funkcija za traženje faktorijela prima celobrojnu vrednost kao parametar. Pošto ništa ne garantuje da neko ko koristi funkciju neće pozvati sa negativnom vrednošću, potrebno je bilo proveriti vrednost pre računanja faktorijela. Isto tako, postoji tzv. "granični slučaj" za vrednost 0, gde se po definiciji dobija vrednost 1. Za obe ove varijante, funkcija ima posebne if grane u programu i naredbu return. Takođe, za preostale vrednosti, faktorijel se računa po standardnoj formuli, tako da postoji return naredba i za ovaj slučaj. Kada se naiđe na bilo koju od navedenih return naredbi, funkcija će se završiti i biće vraćen rezultat.

Napomena:

Kao način da se pokaže da je došlo do greške, često se izabere vrednost izvan domena koji funkcija može da vrati kao povratna.
U slučaju ovog primera, to je -1. Ovo je isključivo odluka programera koji piše funkciju i poželjno je dokumentovati ponašanje.
Postoje različiti načini, ali ovde je to izvedeno korišćenjem deskriptivnog naziva #define pretprocesorske direktive.

U primeru je funkcija pozvana za sve tri različita skupa vrednosti, kako bi se demonstriralo njeno ponašanje u svim slučajevima:

Faktorijel od 0: 1
Nije moguce dobiti faktorijel od negativnog broja!
Faktorijel od 5: 120

Sa druge strane, postoje i funkcije koje nemaju povratnu vrednost. Kod njih će povratni tip biti void. Ovakve funkcije koriste se uglavnom kada treba nešto upisati/ispisati, bilo na standardni ulaz/izlaz ili u datoteku, što će biti obrađeno itd.

Programski jezik C ne podržava vraćanje više od jedne vrednosti pomoću return naredbe. Funkcije povratnog tipa void i prenos parametara po referenci može da pomogne u tom slučaju. Nizovi kao povratne vrednosti nisu mogući, jedina varijanta je prenos početne adrese putem prenosa po referenci.

 1#include <stdio.h>
 2#include <stdio_ext.h>
 3#include <string.h>
 4
 5#define MAX_IME 31
 6
 7void unos_podataka(char *ime, int *pgodine);
 8void ispisi_kategoriju(char *ime, int godine);
 9
10int main()
11{
12    char ime[MAX_IME];
13    int godine;
14
15    while(1) // beskonacna petlja
16    {
17        unos_podataka(ime, &godine);
18        if(godine == -1) // izlazak iz beskonacne petlje
19        {
20            break;
21        }
22        ispisi_kategoriju(ime, godine);
23    }
24    
25    return 0;
26}
27
28void unos_podataka(char *ime, int *pgodine)
29{
30    printf("Unesite ime: ");
31    fgets(ime, MAX_IME, stdin);
32    int duzina_ime = strlen(ime);
33    if(ime[duzina_ime - 1] == '\n')
34    {
35        ime[duzina_ime - 1] = '\0';
36        duzina_ime--;
37    }
38    else
39    {
40        __fpurge(stdin);
41    }
42    
43    printf("Unesite godine: ");
44    scanf("%d", pgodine);
45    __fpurge(stdin);
46}
47
48void ispisi_kategoriju(char *ime, int godine)
49{
50    printf("Osoba %s pripada starosnoj kategoriji: ", ime);
51
52    if(godine >= 0 && godine < 18)
53    {
54        printf("Dete");
55    }
56    else if(godine >= 18 && godine < 65)
57    {
58        printf("Odrasla osoba");
59    }
60    else if(godine >= 65)
61    {
62        printf("Penzioner");
63    }
64    else
65    {
66        printf("Broj godina mora biti pozitivna vrednost!\n");
67    }
68
69    printf("\n");
70}

Ovaj program daće ispis:

Unesite ime: Mitar
Unesite godine: 81
Osoba Mitar pripada starosnoj kategoriji: Penzioner
Unesite ime: Djordje
Unesite godine: 15
Osoba Djordje pripada starosnoj kategoriji: Dete
Unesite ime: Dimitrije
Unesite godine: 23
Osoba Dimitrije pripada starosnoj kategoriji: Odrasla osoba
Unesite ime: 
Unesite godine: -1

Funkcija ispisi_kategoriju je primer void funkcije koja se koristi za ispis prosleđenih vrednosti. Funkcija unos_podataka prenosi više vrednosti po referenci pomoću kojih će postaviti vrednosti spoljnih promenljivih čije adrese prima.

7.6. Primer funkcija za rad sa nizovima

 1#include <stdio.h>
 2#define MAX_SIZE 30
 3
 4void dodavanje(int *a, int *pn);
 5int suma(int *a, int n);
 6void ispis(int *a, int n);
 7
 8int main()
 9{
10    int a[MAX_SIZE], n;
11    
12    dodavanje(a, &n);
13    ispis(a, n);
14    printf("Suma svih clanova niza je: %d\n", suma(a, n));
15    
16    return 0;
17}
18
19void dodavanje(int *a, int *n)
20{
21    int i;
22
23    do
24    {
25        printf("Unesite broj clanova niza: ");
26        scanf("%d", n);
27    } while(*n <= 0 || *n > MAX_SIZE);
28
29    for (i = 0; i < *n; i++)
30    {
31        printf("a[%d] = ", i);
32        scanf("%d", &a[i]);
33    }
34}
35
36int suma(int *a, int n)
37{
38    int i, s = 0;
39
40    for(i = 0;i < n;i++)
41    {
42        s += a[i];
43    }
44
45    return s;
46}
47
48void ispis(int *a, int n)
49{
50    int i;
51
52    printf("[");
53    for (i = 0; i < n; i++)
54    {
55        if (i > 0)
56        {
57            printf(", ");
58        }
59        printf("%d", a[i]);
60    }
61    printf("]\n");
62}

U ovom primeru navedene su definicije funkcija za unos i ispis, zajedno sa prethodnim primerom za računanje sume elemenata niza. Sve definisane funkcije su pozvane u main funkciji, što omogućava njihovo izvršavanje.

Primer ispisa programa:

Unesite broj clanova niza: 5
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
[1, 2, 3, 4, 5]
Suma svih clanova niza je: 15

Napomena:

Ovaj primer sadrži kombinaciju svega što je spominjano u ovom poglavlju, stoga je preporučeno da se pogleda na kraju.
Ukoliko je potrebno dodati još neku funkciju, poput računanja srednje vrednosti ili traženja najvećeg elementa,
to se može učiniti na sličan način kao i sa funkcijama koje su prikazane u primeru.
Imati na umu da jednom napisana funkcija se može pozvati iz bilo koje druge funkcije.