Co je to funkce?
Ač to někoho z Vás možná překvapí, s funkcemi, nebo spíše s funkcí, jsme se již setkali. Byla to funkce main. Tato funkce se automaticky zavolá při spuštění každého programu, napsaném v C++. Funkce je vlastně podprogram, který dokáže nějakým způsobem zpracovat data a vrátit nějakou hodnotu. Každá funkce má svůj název a v případě, že se tento název objeví někde v průběhu provádění programu, předá se řízení do těla funkce s tímto názvem. Říkáme tomu volání funkce. Po provedení těla funkce se program vrátí zpět a pokračuje na řádku, který následuje za voláním funkce.
Proč se funkce používají?
Představte si, že píšete program, ve kterém často provádíte nějaký výpočet - pro jednoduchost uvažujme například výpočet faktoriálu nějakého čísla. K tomu, abychom faktoriál spočítali, můžeme použít například cyklus. Jestliže se nám v programu bude tento výpočet opakovat, bude se nám opakovat i kód cyklu. Napíšeme tedy několik řádků kódu, přičemž budou víceméně stejné. To už na první pohled není příliš praktické. Proto máme funkce. Vytvoříme si tedy funkci, která vypočítá faktoriál nějakého čísla a v programu pak vždy, když budeme potřebovat znát faktoriál, zavoláme pouze naší funkci. S použitím funkcí je i pak samotný kód přehlednější a snadněji udržovatelný.
Návratový typ a parametry funkce
Když voláme funkci, očekáváme, že provede nějaký úkol a vrátí nám výsledek. Pod pojmem výsledek si můžeme představit cokoliv - číslo, hodnotu true nebo false, pointer na objekt (bude vysvětleno v dalších dílech) atd. U každé funkce, kterou v programu vytvoříme, musíme říct, jaký výsledek bude vracet - lépe řečeno, jaký datový typ bude vracet. Zkusme si představit primitivní funkci, která bude sčítat dvě celá čísla typu int. Sečteme-li taková dvě čísla, výsledek bude opět celé číslo typu int - proto jako návratový typ funkce zvolíme int.
Otázkou je, jaká dvě čísla má funkce sečíst (mám na mysli konkrétní hodnoty). Tyto čísla musíme funkci nějakým způsobem sdělit. K tomu nám slouží parametry funkce. U parametrů opět musíme uvést jejich datový typ.
int Soucet(int cislo1, int cislo2);
Takto by mohla vypadat deklarace funkce Soucet. První klíčové slovo (int) udává, že funkce bude vracet hodnotu typu int. Následuje název naší funkce, tedy Soucet. To, co je v závorce, je seznam parametrů. Naše funkce má za úkol sčítat dvě čísla, stačí tedy dva parametry.
Deklarace a definice funkce
Při používání funkcí je nutné funkci nejdříve deklarovat, poté definovat. Deklarací oznámíme kompilátoru název, návratový typ a parametry funkce. Jednoduše řečeno tím kompilátoru řekneme "bude existovat taková a taková funkce, která vrací to a to a má parametry takové a takové". Deklarace funkce se nazývá prototyp funkce. Definicí funkce říkáme kompilátoru něco jiného - a to jak přesně funkce funguje.
Deklarace funkce
Deklarace funkce se dá provést třemi způsoby:
- Prototyp funkce napíšeme do souboru, ve kterém se funkce používá
- Prototyp funkce napíšeme do jiného souboru a tento soubor se pak pomocí direktivy #include zahrne do programu
- Funkci definujeme předtím, než ji zavoláme - v tomto případě definice funkce funguje jako svoje vlastní deklarace
Třetí bod absolutně nedoporučuji. Není totiž dobré, když se funkce v souboru musí psát v daném pořadí - v případě pozdějších změn programu se kód velmi špatně předělává. Další problém třetího bodu je ten, že máme-li nějakou funkci A, která volá funci B, přičemž funkce B volá opět funkci A, není možné vůbec třetí bod použít. Nelze totiž definovat funkci A před funkcí B a zároveň funkci B před A. Po delší době programovaní sami zjistíte, že třetí způsob opravdu není vhodný, proto vždy pište protopyp funkce - u velmi banálních programů je možné udělat výjimku.
Ukázka funkce, rozbor kódu
Dost bylo teorie, podívejte se tedy proto na následují kód, ve kterém používáme funkci Soucet.
#include <iostream>
using namespace std;
int Soucet(int cislo1, int cislo2); // prototyp funkce
int main()
{
int vysledek;
vysledek = Soucet(3, 5); // volani funkce a prirazeni vracene hodnoty do promenne vysledek
cout << vysledek;
return 0;
}
int Soucet(int cislo1, int cislo2) // zde jiz neni strednik
{
return cislo1 + cislo2;
}
Tento kód na obrazovku vypíše číslo 8. Nejprve se podívejte na prototyp funkce. S tím už jsme se setkali výše, všimněte si ale, že za ním následuje středník. Teď se podívejte na to, jak samotná funkce pracuje. Jde o primitivní funkci, její tělo se vešlo na jediný řádek. Klíčové slovo return říká, co má funkce vrátit. V našem případě vrací součet čísel cislo1 a cislo2. Pozor - pokud byste pod řádek, kde je return, dopsali nějaký kód, neprovedl by se! Jakmile program dojde ke slovu return, funkce končí a další případný kód je ignorován. Nyní je třeba ještě objasnit volání funkce, které se provádí ve funkci main. Vidíte, že v závorce za názvem funkce už jsou jen dvě čísla. Proměnná cislo1 tedy dostane hodnotu 3, cislo2 bude mít hodnotu 5. Zavolá se funkce, ta vrátí součet těchto čísel a ten se přiřadí do proměnné vysledek.
Zkusíme si napsat ještě jednu funkci, tentokrát pro výpočet faktoriálu. Pro ty, kteří ještě nevědí, nebo kteří již zapomněli, co to faktoriál čísla je, uvádím krátké připomenutí. Faktoriál čísla n (značíme n!) je číslo, které dostaneme součinem čísel menších nebo rovných n, pokud n je kladné. Čili 5! = 120, protože 5 * 4 * 3 * 2 * 1 = 120. Podotýkám, že faktoriál čísla 0 je 1 - opravdu tomu tak je, i když se to nezdá. Jak by tedy mohla vypadat funkce?
int Faktorial(int n)
{
if (n == 0)
return 1;
int vysledek = n;
for (int i = n - 1; i > 0; i--)
{
vysledek *= i;
}
return vysledek;
}
Všimněte si, že klíčové slovo return jsem zde použil dvakrát. V případě, že chceme zjistit faktoriál čísla 0, víme, že je to 1, nemusíme tedy nic počítat, vrátíme jedničku a konec. V případě, že podmínka neplatí, faktoriál čísla vypočítáme pomocí cyklu. Zde si můžete všimnout, že cyklus běží "směrem dolů", ne tedy od 0 a výše, jako tomu bylo v dílech s cykly.
Nyní si uvedeme ještě jeden příklad, ve kterém si ukážeme, že je možné mít nějakou funkci a z této funkce volat ještě další funkci. Jako pěkný příklad nám k tomu poslouží výpočet kombinačního čísla. Opět krátké připomenutí - kombinační číslo je číslo tvaru n nad k, přičemž jeho hodnota se vypočítá jako n! / [k!(n-k)!]. Vidíme, že při výpočtu se počítají i faktoriály, využijeme tedy již výše uvedenou funkci pro výpočet faktoriálu. Celý kód by pak mohl vypadat takto:
#include <iostream>
using namespace std;
int Faktorial(int n);
int KombinacniCislo(int n, int k);
int main()
{
cout << KombinacniCislo(5, 2) << "\n";
return 0;
}
int Faktorial(int n)
{
if (n == 0)
return 1;
int vysledek = n;
for (int i = n - 1; i > 0; i--)
{
vysledek *= i;
}
return vysledek;
}
int KombinacniCislo(int n, int k)
{
return Faktorial(n) / (Faktorial(k) * Faktorial(n-k));
}
Tento kód vypíše na obrazovku číslo 10 (pět nad dvěma je skutečně 10). Podívejte se na řádek ve funkci main, kde se vypisuje hodnota na obrazovku. Určitě vidíte, že jsme si hodnotu, kterou nám funkce KombinacniCislo vrací, nepřiřadili do žádné proměnné, ale rovnou jsme ji vypsali. Co se týče funkce KombinacniCislo, je velmi jednoduchá - jde v ní jen o matematický výpočet.
Myslím, že v tuto chvíli by Vám měli být funkce alespoň trochu jasné. Aby toho však nebylo málo, zmíníme ještě věc, která souvisí s funkcemi, a tou je rekurze.
Rekurze
V předchozím příkladu jsme viděli, že není problém, když nějaká funkce volá jinou funkci. Není však žádný problém, pokud funkce bude volat sama sebe. Na první pohled možná trochu nelogické, ale jde to. Tomu se říká rekurze. Existují dva typy rekurze - buď funkce volá sama sebe, nebo funkce A volá funkci B, která opět volá funkci A. V tomto díle si ukážeme jeden klasický školní příklad, kdy funkce bude volat sama sebe.
Některé problémy se právě za použití rekurze dají řešit velmi pěkně. V případě, že píšeme nějakou rekurzivní funkci, je třeba si dát pozor, abychom neudělali nějakou drobnou chybu, která by mohla vést k selhání programu. Je totiž potřeba si uvědomit, že zavolá-li funkce sebe samotnou, nově zavolaná funkce opět zavolá sebe samotnou - a tak pořád dokola. Tyto funkce tedy vyžadují jistou podmínku, při které se rekurze ukončí. Pokud jste se ještě s rekurzní nikdy nesetkali, asi Vám to bude na první pohled připadat trochu chaotické. Není to ale až tak složité, vzpomínám si, že já jsem také hned rekurzi úplně nechápal.
Schválně jsem dnešní díl spojil i s trochou matematiky (těm, kteří nemají matematiku příliš v lásce, se omlouvám) - ukázali jsme si výpočet faktoriálu pomocí cyklu. Výpočet faktoriálu však lze provést i rekurzivně. Takto vypadá kód:
int Faktorial(int n)
{
if (n == 0)
return 1;
else
return n * Faktorial(n - 1);
}
V těle funkce je nejprve podmínka, při které se rekurze ukončí. Zavoláme-li funkci a předáme-li ji číslo 0, vrátí nám číslo 1. V opačném případě se zavolá znovu funkce Faktorial, předáváme ji však číslo o jedničku menší. Zkusme si rozebrat, jak to bude v případě, že budeme zjišťovat faktoriál čísla 3 (který je 6).
Při prvním volání podmínka neplatí, voláme tedy funkci znovu, předáváme ji však číslo 2 a hodnotu, kterou vrátí, násobíme číslem 3. Při volání funkce s parametrem 2 však podmínka stále neplatí, funkce se tedy volá znovu - tentokrát s parametrem 1. Opět se provede volání funkce, tentokrát už naposledy, neboť ji předáváme číslo 0 a tím už je podmínka splněna - funkce vrací jedničku. Tato jednička je pak zpětně násobena dvojkou, následně trojkou. Vychází číslo 6 a rekurze končí.
Přiznávám, že je to trochu komplikované, ale pokud budete programovat, určitě rekurzi pochopíte. Další využití rekurze je třeba u některých operací, které se provádějí nad binárními vyhledávacími stromy, nebo například u vyhledávání půlením intervalu. O tom ale někdy příště.
Pro tento díl je to vše, doufám, že jste funkce alespoň trochu pochopili, budeme se s nimi totiž setkávat i v dalších dílech. Nakonec přeju všem čtenářům pěkné Vánoce a do Nového roku spoustu programátorských úspěchů.