Funkce

Představte si, že během programování dojdete do stavu, kdy už je algoritmus tak rozsáhlý, že se stane nepřehledným. Například algoritmus, který pracuje s polem (stále ještě učebnicový příklad, tj. relativně přehledný), by mohl vypadat takto:

Když se na algoritmus podíváme, tak zjistíme, že se vlastně skládá z několika částí:

O co jednodušeji bude algoritmus vypadat s použitím funkcí, se můžete podívat na naprosto stejném příkladu jako je výše:

Zde jsme využili tři funkce, jejichž strukturogram samozřejmě také musíme znázornit, ale to už budeme dělat ve zvláštních strukturogramech:

Definice

Funkce je pojmenovaný blok kódu, který lze opakovaně použít. Jedná se o základní a klíčový prvek programování, které Vám umožní organizovat zdrojový kód do menších (a tedy čitelnějších celků).

Vstupy a výstupy

Funkce může být opravdu pouhým blokem kódu, jako například

void pozdrav(){
  printf("Dobrý den!\n");
}

Většinou ale budeme ve funkci, tj. v bloku kódu, pracovat se vstupními nebo i s výstupními proměnnými. Představme si například lehkou modifikaci funkce pozdrav, která pozdraví odlišně v závislosti na vstupní hodnotě typ. Bude-li typ == ‚t‘ (tykání), pozdraví „Ahoj“, bude-li typ == ‚v‘ (vykání), pozdraví „Dobrý den“:

void pozdrav(char typ){
  if (typ == 't'){
    printf("AHoJ!\n");
  }
  else if (typ == 'v'){
    printf("Dobrý den.\n");
  }
} 

Funkce pozdrav má nyní jednu vstupní proměnnou, kterou je typ (datový typ char, čili znak). Tato funkce nemá žádný výstup (funkce má datový typ void = prázdný).

Nejčastěji ale bude mít funkce i nějaký výstup, jako například:

int soucet(int x, int y){
  int vysl;
  vysl = x + y;
  return vysl;
}

Výsledek funkce vracíme pomocí příkazu return.

Z hlediska vykonávání programového kódu si tedy můžeme představit funkci soucet jako blok, která má dva vstupy a jeden výstup:

Předávání parametrů hodnotou

Tak, jak jsme si nadefinovali funkci soucet, předáváme dva vstupní parametry (x a y) způsobem, kterému říkáme předávání hodnotou (call by value).

Funkci voláme například:

int s = soucet(a, b);

Při tomto volání již máme deklarované i inicializované proměnné a, b. Tj. máme konkrétní místa v paměti, kde jsou uložené hodnoty proměnných a i b. V momentě volání funkce se nám vyčlení další dvě místa paměti, která budou pojmenována x a y a hodnota proměnné a se zkopíruje do x, hodnota proměnné b se zkopíruje do y.

Jakékoliv změny x a y tedy nebudou mít žádný vliv na a ani na b.

Proměnným x a y tedy říkáme lokální.

Ověřit platnost tohoto tvrzení si můžeme na následujícím příkladu:

void pokusy(int a, int b){
    a += 10;
    b += 15;
    printf("Uvnitř funkce pokusy je a = %d, b = %d.\n", a, b);
}

void main(){
  int a = 1, b = 5;
  printf("Před voláním funkce je a = %d, b = %d.\n", a, b);
  pokusy(a, b);
  printf("Po volání funkce je a = %d, b = %d.\n", a, b);
}  

Zde vidíme, že i když se hodnota proměnných a a b ve funkci pokusy() změnila, proběhla tato změna pouze lokálně, protože proměnné jsme předávali hodnotou. Při volání funkce pokusy() tedy byly vyčleněny dva nové bloky paměti, do kterých se nakopírovala pro proměnnou a hodnota 1 a pro proměnnou b hodnota 5.

Ve funkci pokusy() se sice přidalo k proměnné a 10 a k proměnné b 15, ale to pouze k lokálním proměnným, což nemělo žádný vliv na proměnné a a b v hlavní funkci.

Deklarace a definice funkce

Při psaní programů v jazyce C dříve či později narazíte na situaci, kdy chcete kód zpřehlednit a rozdělit ho do vlastních podprogramů – funkcí. V Cčku ale platí jedno přísné pravidlo: kompilátor čte kód striktně odshora dolů. Pokud se pokusíte zavolat funkci, o které překladač do té doby neslyšel, program nepůjde přeložit.

Abychom tomu předešli, musíme pochopit zásadní rozdíl mezi deklarací a definicí funkce.

Příklad deklarace funkce:

char nactiPismeno();

Příklad definice funkce:

char nactiPismeno(){
  char pismeno;
  scanf("%c", &pismeno);
  return pismeno;
}

Zkuste si procvičit deklarace funkcí v následujícím drillu:

//vytvořte deklaraci funkce pozdrav bez vstupů, funkce nebude nic vracet



//vytvořte deklaraci funkce writeGreetings bez vstupu, funkce nebude nic vracet



//vytvořte deklaraci funkce, která bude mít vstup x, tj. číslo, které bude vypisovat, funkce nebude nic vracet



//vytvořte deklaraci funkce kladneZaporne, která bude mít vstup x, tj. číslo – bude vypisovat, jestli je x kladné nebo záporné; funkce nebude nic vracet



//vytvořte deklaraci funkce sudeLiche, která bude mít vstup x, tj. číslo – bude vypisovat, jestli je x sudé nebo liché; funkce nebude nic vracet



//vytvořte deklaraci funkce pridejJedna, která bude mít vstup celé číslo i a bude vracet číslo o 1 vyšší



//vytvořte deklaraci funkce odeberDve, která bude mít vstup celé číslo i a bude vracet číslo o 2 nižší



//vytvořte deklaraci funkce secti, která bude mít vstupy dvě celá čísla m a n a bude vracet jejich součet



//vytvořte deklaraci funkce vynasob, která bude mít vstupy dvě celá čísla k a l a bude vracet jejich součin



//vytvořte deklaraci funkce vypisZnak, která bude mít vstup znak c a ten znak vypíše, funkce nebude nic vracet



//vytvořte deklaraci funkce jePlus, která bude mít vstup znak z a bude vracet 1, jedná-li se o plus, resp. 0, nejedná-li se o plus



//vytvořte deklaraci funkce jePismeno, která bude mít vstup znak c a bude vracet 1, jedná-li se o písmeno, resp. 0, nejedná-li se o písmeno

Předávání proměnných odkazem

V předchozí kapitole jsme si ukázali, že při předávání proměnných hodnotou se vlastně hodnota kopíruje do jiného místa paměti (do lokální proměnné) a tudíž tím zatěžujeme paměť. U jednoduchých proměnných nám to tolik vadit nemusí, ale u větších proměnných (pole, matice, řetězce, struktury) už by zatížení paměti mohlo být větší. Proto budeme namísto konkrétní hodnoty předávat pouze odkaz (pointer) na proměnnou (call by reference).

Zkusíme použít velmi podobný příklad tomu výše uvedenému, jen namísto hodnot a a b budeme předávat adresu proměnné a (adresa a = &a) a adresu proměnné b (adresa b = &b). Funkce tedy bude přijímat nikoliv hodnotu, ale pointer (nikoliv a, ale *a; resp. nikoliv b, ale *b):

void pokusy(int *a, int *b){
  *a += 10;
  *b += 15;
  printf("Uvnitř funkce pokusy je a = %d, b = %d.\n", *a, *b);
}
void main(){
  int a = 1, b = 5;
  printf("Před voláním funkce je a = %d, b = %d.\n", a, b);
  pokusy(&a, &b);
  printf("Po volání funkce je a = %d, b = %d.\n", a, b);
}

Jak to vypadá v paměti, si můžeme ukázat na následujícím obrázku:

Vidíme tedy, že zde jsme nepotřebovali alokovat další paměť pro lokální proměnné, ale předáváme pouze odkazy (pointery) na již existující proměnné a tedy ve funkci měníme přímo dané proměnné.

Opět si můžete procvičit drill s deklaracemi funkcí, které využívají předávání parametrů odkazem:

//vytvořte deklaraci funkce prictiJedna, která bude mít vstupně-výstupní parametr celé číslo i; funkce nebude nic vracet



//vytvořte deklaraci funkce vynasobDvema, která bude mít vstupně-výstupní parametr celé číslo m; funkce nebude nic vracet



//vytvořte deklaraci funkce vratMax, která bude mít vstupní parametry: pole celých čísel pole, počet prvků v poli pocet; funkce bude vracet prvek s maximální hodnotou z pole



//vytvořte deklaraci funkce vratMedian, která bude mít vstupní parametry: pole celých čísel pole, počet prvků v poli pocet; funkce bude vracet medián (hodnotu prostředního prvku z pole)



//vytvořte deklaraci funkce vypoctiPrumer, která bude mít vstupní parametry: pole celých čísel pole, počet prvků v poli pocet; funkce bude vracet prvek s průměr prvků pole (pozor – průměr musí být desetinné číslo)



//vytvořte deklaraci funkce vypisText, která bude mít vstupní parametr pole znaků ret; funkce nebude nic vracet



//vytvořte deklaraci funkce vypisPismena, která bude mít vstupní parametr pole znaků ret; funkce nebude nic vracet



//vytvořte deklaraci funkce vypisCisla, která bude mít vstupní parametr pole znaků str; funkce nebude nic vracet



//vytvořte deklaraci funkce pocetCisel, která bude mít vstupní parametr pole znaků str; funkce bude vracet počet znaků, které jsou čísly



//vytvořte deklaraci funkce pocetAcek, která bude mít vstupní parametr pole znaků retezec; funkce bude vracet počet malých nebo velkých písmen A



 

 

Nebo zkuste drag-n-drop kvízy:

  1. Pomocí tažení prvků myší vytvořte deklaraci funkce, která vrátí průměr prvků v poli.

Přetáhněte prvky na správná místa:
int
pocet
int *
pole
float
vratPrumer(
,
)

2. Pomocí tažení prvků myší vytvořte deklaraci funkce, která vrátí počet všech písmen v řetězci.

Přetáhněte prvky na správná místa:
int
retezec
char *
pocetPismen(
)

3. Pomocí tažení prvků myší vytvořte deklaraci funkce, která vrátí počet konkrétního znaku v řetězci.

Přetáhněte prvky na správná místa:
retezec
char *
char
hledanyZnak
int
pocetZnaku(
,
)

Generovaný podcast o funkcích si můžete poslechnou zde:

Podcast byl vygenerován automaticky na základě této stránky a na základě webu https://www.geeksforgeeks.org/c/c-functions/

Příklady

Příklady na funkce si můžete zobrazit zde.