Dnes si povíme něco o funkcích a o tom, jak může uživatel vytvářet své vlastní funkce, kterými může zjednoduššit a zprehlednit svůj program.
V jazyce C se každý, byť sebemenší program obsahuje alespoň jednu funkci. Jak jsme si již řekli a několikrát ukázali, jedná se o funkci main. Pokud při programování budeme (a že budeme) psát rozsáhlejší program, tak budeme potřebovat prostředek, který nám umožní na první pohled složitý problém rozložit do menších částí, a to z několika důvodů.
- Uživatelské funkce lze volat opakovaně, tedy to, co jednou naprogramujeme, můžeme používat opakovaně.
- Původně složitý problém můžeme rozdělit na množství dílčích úkonů, které již není problém naprogramovat, a takový kód je snáze udržovatelný.
- Můžeme používat lokální proměnné, které jsou viditelné pouze v prostoru dané funkce, čímž se vyhneme problémům se jmény a takový kód může pracovat paralelně.
Úvodní pojmy
Ještě než si ukážeme několik možností, jak vytvářet naše vlastní funkce a jak je používat podíváme se na dva pojmy se kterými se setkáte. Jsou jimi deklarace a definice funkce.
- Deklarací fce říkáme implementaci pouze to, že někde bude existovat funkce s daným názvem, návratovým typem a parametry.
- Definicí fce rozumíme samoté „výkonné“ tělo, tedy kód, který daná funkce vykonává.
Nutno poznamenat, že deklaraci ve své podstatě používat nemusíme, ale potom narážíme na problém, že funkce musí být definována před svým prvním použitím, aby o ní překladač věděl. To může na první pohled vypadat jako samozřejmost, ale později, až budeme mít více funkcí, kde z jedné budeme volat druhou atp., se můžeme do problému tak zamotat, že deklarace pro nás bude vysvobozením.
Každá funkce v jazyce C je dána jednak svým názvem, chcete-li identifikátorem, návratovým typem a seznamem parametrů. Název funkce by měl být volen tak, aby hned z názvu bylo patrné, co funkce dělá. O návratovém typu jsme se již zmiňovali. Návratový typ určuje jakého typu bude výsledek, který od funkce získáme. Pokud se bude jednat o funkci pro provádění nějakého výpočtu, návratovým typem bude nepochybně číselná hodnota. abychom se nezabývali pouze teorií bude nejlepší ukázat si takovou jednoduchou funkci prakticky. V prvním příkladu uvedu pro srozumitelnost obsah celého zdrojového souboru, ale později budu uvádět pouze samostatné funkce.
#include
void razitko(void) {
printf("********\n");
printf("* AHOJ *\n");
printf("********\n");
}
int main() {
razitko();
return 0;
}
Toto je ukázka ukázka jednoduché funkce, která v sobě dělá pouze to, že vytiskne slovo AHOJ a rámeček, což pro nás není teď vůbec důležité. Na příkladu vidíme deklaraci jednoduché funkce, která se jmenuje razitko
. Tato funkce má návratový typ void
, který značí že funkce nevrací žádnou hodnotu. Její seznam parametrů je také prázdný, a proto nemůžeme provádění funkce nijak ovlivnit. Na tomto příkladu si ještě ukážeme, jaký je smysl deklarace. Příklad, tak jak je v tomto tvaru bude bez problému fungovat, ale problém nastane, pokud funkci razitko
přesuneme, až za funci main
. Pokusíme-li se program přeložit, skončí neúspěchem a obdržíme zprávu ve smyslu, že taková funkce neexistuje. Možností, jak tento problém řešit, je využití deklarace, kterou uvedeme před funkci main
.
void razitko(void);
Nyní již překladač o existenci funkce razitko
ví, a při přakladu nebude protestovat.
Aby bylo možno chování funkcí ovlivnit, případně pomocí nich zpracovávat nějaká data, můžeme je funkci předat pomocí parametrů. Počet parametrů je prakticky neomezený, ale měli bychom se řídit tím, že přespříliš parametrů nesvědčí o dobrém návrhu. Na následujícím příkladu si ukážeme příklad funkce na výpočet n-té mocniny celého čísla.
int mocnina(int n, long int x) {
long int navrat = x;
while(n > 1) {
navrat = navrat * x;
n--;
}
return navrat;
}
Pokud se podíváme na následující funkci, vidíme, že má jako návratový typ uveden int
, tedy celé číslo, a přebírá dva parametry. Tyto parametry představují lokální proměnné, pro které je alokována paměť pokaždé, když dojde k zavolání funkce. Jednak je to parametr n
, který říká o jakou mocninu se jedná, a druhý x
, který představuje číslo, jehož mocninu chceme vypočítat. Výsledek výpočtu vrátíme volajícímu pomocí příkazu return
. Použití námi definované funkce je jednoduché.
int x = mocnina(8, 2); // cislo bude obsahovat 2^8 tj. 256
Tím jsme si ukázali nejjednodušší použití funkcí a ještě se podíváme na rekurzi, tedy případ, kdy funkce volá sebe samu. Rekurze jako možnost řešení některých problémů se dnes celkem často používá, ale mezi začátečníky není zrovna nejoblíbenější. Tato technika se využívá u problémů, které jsou ve své podstatě rekurzivní, jako je výpočet faktoriálu, třídění polí atp. K tomu, aby daný problém bylo možné řešit rekurzivně, musí splňovat následující podmínky:
- Problém musí být řešitelný pomocí elementárních úkolů, které jsme schopni realizovat.
- Musí být jasně patrné, kdy je daný problém vyřešen.
Jako obvyklý příklad na rekurzi se používá výpočet faktoriálu, ale touto technikou můžeme provést i výpočet n-té mocniny, kterou jsme před chvílí počítali iteračně pomocí cyklu.
int mocnina (int n, int x) {
if(n > 1)
return x * mocnina(n - 1, x);
}
Tato funkce volá opakovaně sama sebe s tím, že pokaždé sníží hodnotu parametru n
o jedničku. Postupně dochází k „zanořování“ a konec běhu algoritmu je dán podmínkou n > 1
. Rekurzivní zpracování má samozřejmě také nevýhody, jako je vysoká paměťová náročnost, neboť pro každou instanci funkce dochází k alokaci dalších proměnných. Rekurzivní řešení problémů je téma na samostatný díl, proto se mu budeme v brzké době věnovat. Tím bych se chtěl také omluvit za dlouhé mezery mezi jednotlivými díly. Nyní již budou vycházet častěji.