Kapitola 6. Funkce

Obsah

6.1. Vytváření a dokumentace vlastních funkcí
6.2. Pojmenování funkcí
6.3. Návratová hodnota funkce
6.4. Argumenty funkcí a způsob jejich předávání
6.5. Paměťové třídy
6.6. Rekurze
6.7. Cizí funkce
6.8. Opakování

Časová náročnost
Časová náročnost kapitoly: 1 hodina

Funkce může, ale také nemusí, vracet návratovou hodnotu. Funkce může, ale také nemusí, mít argumenty. Již tedy začínáme chápat, že základní stavební kámen C - funkce - poskytuje množství variant. V této části si doplníme znalosti o proměnných. Upřesníme si, jakým způsobem se vytvářejí a kam se umisťují. Vzápětí poté si ukážeme využití těchto nových informací. Vytvoříme funkci, která volá sama sebe - rekurzivní funkci.

Funkce představují základní programovou jednotku, která řeší nějaký problém. Pokud je problém příliš složitý, volá na pomoc další funkci či funkce. Z toho plyne, že by funkce neměla být příliš rozsáhlá. Pokud tomu tak je, stává se funkce nepřehlednou i obtížně modifikovatelnou.

Každý C program obsahuje alespoň jednu funkci - main(). ISO norma jazyka C určuje návratovou hodnotu funkce main() typu int. Co obsahuje tělo této funkce je věcí každého programátora. Ale je jisté, že v ní musí být zapsána nejméně kostra programu. Překladač jazyka C totiž určuje, že právě funkcí main() začíná provádění každého programu. Tato vlastnost činí popisovanou funkci výjimečnou. Jinak se ovšem jedná o funkci, kterou prostě musí programátor napsat.

Na záčátku této kapitoly si ještě ukážeme obecný formát programu s více funkcemi.

Důležité
/* zde vloži hlavičkové soubory */

/* zde umístit prototypy funkcí */

int main(void)
{
 /* .. .. .. .. */
}

návratový-typ funkce1(seznam-argumentů)
{
 /* .. .. .. .. */
}


návratový-typ funkce2(seznam-argumentů)
{
 /* .. .. .. .. */
} 

.
.
.

návratový-typ funkceN(seznam-argumentů)
{
 /* .. .. .. .. */
}

Funkce mohou mít samozřejmě jména. Položka návratový-typ představuje typ dat vracených funkcí. Nevrací-li funkce žádnou hodnotu měl by být její návratový typ void. Nepoužívá-li funkce argumenty, měl by její seznam-argumentů obsahovat klíčové slovo void.

Konkrétnější informace o funkcích si řekneme dále v textu.

Potřebné znalosti
Potřebné znalosti
K zvládnutí obsahu této kapitoly je nutné mít znalosti z kapitoly 2 až 5 - chápat jednotlivé prvky programů, orientovat se v problematice řízení chodu programu - příkazy if-else a switch. Chápat rozdíly a použítí cyklů for, while a do.

6.1. Vytváření a dokumentace vlastních funkcí

Časová náročnost
Časová náročnost: 1 minuta

Uživatelské funkce jsou funkce, které jsme napsali a máme jejich zdrojové texty. Je dobrým zvykem naše funkce precizně dokumentovat a archivovat. A začít můžeme komentáři ve zdrojovém textu. To vše patří k dobrému programátorskému stylu. Když už jsme funkci jednou napsali a odladili, můžeme ji příště s důvěrou používat. A právě dokumentace nám při použití pomůže.

6.2. Pojmenování funkcí

Časová náročnost
Časová náročnost: 5 minut

Důležité Identifikátor funkce je prostě jméno, pod kterým se v programech budeme na funkci odvolávat. Současně se jménem funkce určujeme i datový typ návratové hodnoty funkce.

Nejjednodušší způsob, jak vytvořit funkci, se nazývá definice funkce. Při definici funkci najednou pojmenujeme, určíme typ její návratové hodnoty, typ a názvy jejích argumentů a současně napíšeme kód, který bude při každém volání funkce proveden. Tento kód zapisujeme v těle funkce. Argumenty uvedené při definici funkce označujeme jako formální argumenty. Tyto formální argumenty zastupují v těle funkce argumenty, se kterými byla funkce volána. Těchto skutečných argumentů může být mnoho a jejich identifikátory se jistě budou lišit.

Syntaxe:

Důležité
typ jméno(argumenty)
   {
    tělo funkce
   }

Tělo funkce umístěno v bloku. O bloku jsme se zmínili dříve. Vime tedy, že může obsahovat lokální proměnné. Po nich následuje posloupnost příkazů. Ty definují chování (vlastnosti) funkce. Při definici funkce vytváříme (definujeme) její kód.

Voláním funkce - říkáme, že chceme provést kód, který je funkcí definován. Při volání funkce musí být za jménem funkce vždy uvedeny závorky. V nich pak mohou být uvedeny argumenty, které při volání funkci předáváme. Pokud závorky za jménem funkce neuvedeme, jde o adresu funkce. Adresou funkce rozumíme adresu vstupního bodu funkce. V místě, odkud funkci voláme, jí předáváme skutečné argumenty, které představují identifikátory proměnných a konstant, nebo výrazy.

jméno(argumenty);

6.3. Návratová hodnota funkce

Časová náročnost
Časová náročnost: 12 minut

Když jsme funkci pojmenovávali, určili jsme přitom typ její návratové hodnoty. V těle funkce pak musíme určit, jakým způsobem návratovou hodnotu získáme. Návratová hodnota funkce musí být výsledkem výrazu uvedeného jako argument příkazu return. Typ tohoto výrazu se musí shodovat, nebo být alespoň slučitelný, s deklarovaným typem návratové hodnoty funkce.

Syntaxe návratové hodnoty:

Důležité
return výraz-vhodného-typu;

Na typ funkce, přesněji typ její návratové hodnoty, nejsou kladena žádná omezení. Pokud nás návratová hodnota funkce nezajímá, tak ji prostě po volání nepoužijeme. Děláme to často, například funkce scanf() a printf() hodnotu vrací a my ji ne vždy použijeme. Pokud ovšem chceme deklarovat, že funkce nevrací žádnou hodnotu, pak použijeme klíčové slovo void.

V této chvíli si vyzkoušíme, zda jsme informace o funkcích pochopili správně. Vytvoříme program který na obrazovku vypíše číslice 1 2 3.

src/func_1.c
Příklad 6.1.
/******************************
*	func_1.c
*	19.03.2002
******************************/

#include <stdio.h>

/* Prototypy funkci */
void funkce1(void);
void funkce2(void);

int main(void)
{
 funkce2();
 printf("3");
 
 return 0;
}

void funkce2(void)
{
 funkce1();
 printf("2");
}

void funkce1(void)
{
 printf("1");
} 

V tomto programu volá funkce main() nejprve funkce2(), která volá funkce1(). Po vyvolání funkce1() vypíše 1, předá řízení zpět funkce2(), která vypíše 2 a řízení předá zpět funkci main(), která vypíše 3.

Následující program vypíše druhou mocninu čísla zadaného z klávesnice. Druhá mocnina se získá pomocí funkce mocnina().

src/func_ret.c
Příklad 6.2.
/******************************
*	func_ret.c
*	19.03.2002
******************************/

#include <stdio.h>

/* Prototyp funkce */
int mocnina(void);

int main(void)
{
 int mocnina_cisla;
 
 mocnina_cisla = mocnina();
 printf("Druha mocnina: %d",mocnina_cisla);
 
 return 0;
}

int mocnina(void)
{ 
 int cislo;
 
 printf("Zadejte cislo: ");
 scanf("%d",&cislo);
 
 return cislo*cislo;
}

Příkaz return se v těle funkce nemusí vyskytovat pouze jedenkrát. Pokud to odpovídá větvení ve funkci, může se vyskytovat na konci každé větve. Rozhodně však příkaz return ukončí činnost funkce, umístí návratovou hodnotu na specifikované místo - programátor se o umístění obvykle nestará - a předá řízení programu bezprostředně za místem, z něhož byla funkce volána. Činnost funkce, jejíž návratový typ není void, musí vždy končit příkazem return. Funkce typu void končí rovněž dosažením konce bloku, který tvoří tělo funkce.

6.4. Argumenty funkcí a způsob jejich předávání

Časová náročnost
Časová náročnost: 15 minut

Před každý formální argument musíme při deklaraci či definici funkce uvést jeho datový typ. Současně s tím určujeme i způsob, jakým se budou hodnoty skutečných argumentů předávat argumentům formálním. Dále platí, že pořadí formálních a skutečných argumentů si odpovídá. Tedy že první skutečný argument je v těle funkce zastupován prvním formálním argumentem, druhý druhým, atd. Přirozeně se vyžaduje, aby jejich datové typy byly stejné, nebo alespoň slučitelné. Přesná pravidla uvádí norma ISO C.

Důležité Podstatný je tedy způsob předávání argumentů funkci.
  • Kopie hodnoty skutečných argumentů jsou předány do zásobníku. Formální argumenty se odkazují na odpovídající místa v zásobníku, kde jsou kopie argumentů skutečných. Díky tomu se změna hodnoty formálního argumentu nepromítne do změny hodnoty argumentu skutečného! Tomuto způsobu předávání argumentů se říká předávání hodnotou.

  • Může ovšem nastat situace, kdy potřebujeme, aby funkce vrátila více než jednu hodnotu. Obdobná situace vzniká při předávání rozsáhlého pole. Představa kopírování jeho obsahu do zásobníku je strašná, a u rozsáhlých polí by mohla vést až k nestabilitě aplikace. Řešení pomocí globálních proměnných kategoricky vylučuje dobrý programátorský styl! Tvorba nového strukturovaného datového typu jako návratové hodnoty nemusí být vždy přirozeným řešením. Řešení je předávání nikoli hodnot skutečných argumentů, ale jejich adres. Formální argumenty pak budou ukazatele na příslušný datový typ. Důležitý je ovšem fakt, že se tyto ukazatele budou odkazovat na místa původních, skutečných argumentů. Tím se naskýtá možnost změny hodnot skutečných argumentů. V jazyku C se hovoří o volání adresou.

Nyní si ukážeme jednoduchý program, který volá funkce a předává jim argumenty jak hodnotou tak adresou.

src/func_v-a.c
Příklad 6.3.
/******************************
*	func_v-a.c
*	19.03.2002
******************************/

#include <stdio.h>

int nacti(int *a, int *b)
{
 printf("\nZadej dve cela cisla:");
 return scanf("%d %d", a, b);
} 

float dej_podil(int i, int j)
{
 return((float) i / (float) j);
} 

int main(void)
{
 int c1, c2;
 if (nacti(&c1, &c2) == 2)
   printf("Podil je : %f\n", dej_podil(c1, c2));
 return 0;
}

Funkce nacti() má vracet dvě načtené hodnoty. K tomu nemůžeme použít její návratovou hodnotu. Předáváme jí proto argumenty adresou. Pozor na & adresový operátor před skutečnými argumenty při volání! Bez něj adresy nezískáme. Ještě si však všimneme návratové hodnoty této funkce. Tato návratová hodnota slouží k tomu, aby potvrdila platnost (respektive neplatnost) hodnot argumentů, které předává adresou.

Ještě si ukažme jeden příklad. Pěkným příkladem volání odkazem bude funkce swap(), která zamění dvě celočíselné hodnoty.

src/func_swp.c
Příklad 6.4.
/******************************
*	func_swp.c
*	19.03.2002
******************************/

#include <stdio.h>

void swap(int *i, int *j);

int main(void)
{
 int num1=50, num2=20;
 
 printf("Cislo1: %d, Cislo2: %d\n",num1, num2);
 swap(&num1, &num2);
 printf("Cislo1: %d, Cislo2: %d\n",num1, num2);
 
 return 0;
}

void swap(int *i, int *j)
{
 int temp;
 
 temp=*i;
 *i=*j;
 *j=temp;
}  

Jelikož jsou funci předány ukazatele na dvě celá čísla, zamění se hodnoty, na které tyto ukazatele ukazují.

6.5. Paměťové třídy

Časová náročnost
Časová náročnost: 7 minut

Při deklaraci proměnné můžeme nepovinně uvést i klíčové slovo, určující paměťovou třídu. Paměťová třída určuje překladači náš požadavek na umístění proměnné. Pokud paměťovou třídu neurčíme při deklaraci, platí implicitní pravidla pro určení paměťové třídy.

Důležité

Tabulka 6.1.

Paměťová třídaVýklad
autoUmístění na zásobník, neinicializované
externNevytvářet, bude připojen z jiného modulu
registerUmístit do registru procesoru, neinicializované
staticUmístění do datového segmentu, inicializované nulou
typedefNemá paměťový význam, pojmenovává konstrukci definice
Důležité

Tabulka 6.2.

ModifikátorVýznam
constVyjadřuje neměnitelnost
volatileVyjadřuje neustálou proměnnost - nekešovat

Paměťovou třídu můžeme upřesnit ještě tak zvanou typovou částí. Jedná se o klíčové slovo const a klíčové slovo volatile. První možnost určuje neměnnost, zatímco druhá deklaruje její opak. Jak to chápeme? Představme si dvě současně běžící úlohy se sdíleným paměťovým prostorem. Pokud úlohy sdílejí nějakou společnou proměnnou, kterou navíc mění, nemůže procesor jakkoli optimalizovat přístup k této proměnné.

Následující tabulka uvádí nejdůležitější pravidla pro implicitní určení paměťové třídy. Tedy ukazuje, jakou paměťovou třídu budou proměnné jazyka C.

Důležité

Tabulka 6.3.

ObjektPaměťová třída, výklad, umístění
Globální proměnnéstatic a extern, inicializovány nulou, datový segment
Lokální proměnnéauto, neinicializovány, zásobník
Formální argumentyauto, neinicializovány, zásobník
Definice funkceextern, definice dle kódu, kódový segment

6.6. Rekurze

Časová náročnost
Časová náročnost: 7 minut

Rekurze je proces, ve kterém je něco definováno samo sebou. Aplikujeme-li to na počítačový jazyk, rekuze znamená, že funkce může volat sama sebe. Ne všechny programovací jazky podporují rekurzi. Jazyk C však ano.

Je důležité vědět, že neexistují vícenásobné kopie rekurzivní funce. Existuje pouze jediná. Když je volána funkce, vyhradí se v zásobníku místo pro její argumenty a lokální proměnné. Když je tedy funkce volána rekurzivně, začíná pracovat s novou sadou argumentů a lokálních proměnných, ale kód, který tvoří funkci, zůstává stejný.

Nejčastějším příkladem užití rekurze je výpočet faktoriálu. I my si ukážeme jak vypočíst faktoriál na základě rekurzivní funkce.

src/recurfac.c
Příklad 6.5.
/******************************
*	recurfac.c
*	19.03.2002
******************************/

#include <stdio.h>

int fakt(int n)
{
 return (( n <= 0 ) ? 1 : n * fakt(n-1));
}

int main()
{
 int i;

 printf("\nZadej cele cislo: ");
 scanf("%d",&i);
 printf("Faktorial cisla %d je: %d",i,fakt(i));
 return 0;
}

Nyní si ukážeme ještě jeden jednoduchý program abychom rekurzi dostatečně porozuměli. Naším úkolem bude vypsat na obrazovku čísla od 9 do 0. A použijeme k tomu rekurzivní funkci.

src/recur9-0.c
Příklad 6.6.
/******************************
*	recur9-0.c
*	19.03.2002
******************************/

#include <stdio.h>

void rekurze(int i);

int main(void)
{
 rekurze(0);
 
 return 0;
}

void rekurze(int i)
{
 if(i<10)
 {
  rekurze(i+1);
  printf("%d ",i);
 }  
}

Krátké vysvětlení:

Nejprve je volána funkce rekurze() s argumentem 0. Jelikož je 0 menší než 10 pak volá sama sebe s hodnotou i + 1. To způsobí opětovné vyvolání funkce rekurze() tentokrát s argumentem 1. Tento proces se opakuje dokud není rekurze() volána s argumentem 10. To způsobí návrat z funkce rekurze(). Jelikož se funkce vrací na místo svého vyvolání, provede se příkaz printf() - vytiskne se číslo 9 a vrací se. Vrátí se do místa svojí předchozí aktivace a vytiskne 8. Atd.

6.7. Cizí funkce

Časová náročnost
Časová náročnost: 2 minuty

Z běžného použití známe standardní funkce, uživatelské funkce, podpůrné a nádstavbové funkce. Nejjednodušší možné členění zní: funkce standardní a funkce ostatní.

Standardním funkcím se zpravidla říká knihovní funkce. Jejich deklarace je popsána ve standardních hlavičkových souborech. Deklaracím funkcí se někdy říká prototyp.

Důležité

Norma ISO C vyžaduje prototyp každé funkce, kterou chceme použít. Tento požadavek výrazně zvyšuje bezpečnost - umožňuje typovou kontrolu. Proto musíme začleňovat hlavičkové soubory tehdy, když používáme standardní funkce - hlavičkové soubory obsahují jejich deklarace. U deklarací nevadí, zahrneme-li shodnou deklaraci funkce vícekrát.

6.8. Opakování

Shrnutí
Shrnutí

Časová náročnost
Časová náročnost: 5 minut

Zajímavost z C Krátké zopakování základů spravných pochopení funkcí.

Syntaxe deklarace funkce:

typ jméno(seznam argumentů);

kde:

  • typ představuje typ návratové hodnoty
  • jméno je identifikátor, který funkci dáváme
  • ( ) je povinná dvojice závorek, vymezující deklaraci argumentů
  • seznam argumentů je nepovinný - funkce nemusí mít žádné argumenty, může mít jeden nebo více argumentů, nebo také můžeme určit, že funkce má proměnný počet argumentů. Pokud je argumentů více, oddělujeme je navzájem čárkami. Každý argument musí mít samostatně určen datový typ.

Deklarace popisuje vstupy a výstupy, které funkce poskytuje, ale nedefinuje posloupnost příkazů, které má funkce vykonávat. Deklarace určuje rozhraní funkce. Funkce nemá provádět akce s jinými daty, než která jí předáme jako argumenty. Současně výstupy z funkce mají probíhat jen jako její návratová hodnota. Pokud se funkce nechová uvedeným způsobem říkáme, že má vedlejší účinky, efekty.

Pokud uvedeme pouze definici funkce, na kterou se později v souboru odvoláváme, slouží tato definice současně jako deklarace. Definici smíme uvést jen jednou. Při případné druhé definici by překladač nevěděl, která z nich je platná.

Deklarace funkcí se umisťují do hlavičkových souborů. V případě neshody deklarace a definice funkce ohlásí překladač chybu.

Cvičení
Cvičení

Úkol k textu

Zadání 1)

Napište program, který bude mít alespoň dvě funkce a vytiskne Co se v mládí naučíš, ve stáří zapomeneš.
Řešení

Úkol k textu

Zadání 2)

Napište program, který použije funkci convert(), která vyzve uživatele k zadání částky v korunách a převede ji na dolary (Použijte směnný kurs např. 40 Kč za dolar). Vypište převedenou částku v dolarech.
Řešení

Úkol k textu

Zadání 3)

Co je v tomto programu chybné?
#include <stdio.h>

int f1(void);

int main(void)
{
 double odpoved;
 
 odpoved = f1();
 printf("%f",odpoved);
 
 return 0;
}

void f1(void)
{
 return 100;
}
Řešení

Úkol k textu

Zadání 4)

Co je chybné v této funkci?
void func(void)
{
 int i;
 printf("Zadejte cislo: ");
 scanf("%d", &i);
 
 return i;
}
Řešení

Úkol k textu

Zadání 5)

Je tento program správný? Pokud ne, proč?
#include <stdio.h>

myfunc(int num, int min, int max);

int main(void)
{
 int i;
 
 printf("Zadejte cislo mezi 1 a 10: ");
 myfunc(&i, 1, 10);
 
 return 0;
}

void myfunc(int num, int min, int max)
{
 do
 { 
   scanf("%d", num);
 }
 while(*num<min || *num>max);
}
Řešení

Úkol k textu

Zadání 6)

Vysvětlete rozdíl mezi voláním funkce hodnotou a voláním funkce odkazem.
Řešení

Úkol k textu

Zadání 7)

Co je špatně v této rekurzivní funkci?
void f(void)
{
 int i;
 
 printf("in f() \n");
 
 /* volani f() 10krat */
 for(i=0; i<10; i++) f();
}
Řešení

Úkol k textu

Zadání 8)

Napište program, který vypisuje na obrazovku řetězec znak po znaku pomocí rekurzivní funkce.
Řešení
test6.swf
Test
Kliknutím na ikonu spustíte test.