13. diel - Funkcie v jazyku C ++
V predchádzajúcom kvíze, Kvíz - textové reťazce v C++, sme si overili nadobudnuté skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Kvíz - textové reťazce v C++ , sme si predstavili matematickú knižnicu
cmath
. Dnešné tutoriál o programovacom jazyku C ++ je venovaný
veľmi dôležitej téme, ktorým je využívanie funkcií. My sme už
zoznámenie s tým, že kód programu píšeme do funkcie main()
.
To pre naše učebné programy, ktoré vedeli vykonávať len jednu jednoduchú
vec, zatiaľ stačilo. Predstavte si však, že píšete program, ktorý je
dlhý niekoľko sto tisíc riadkov. Určite uznáte, že v takej rezance kóde v
jednom súbore a v jednej funkcii by sa orientovalo veľmi zle. Navyše, ak by
sme chceli vykonať nejakú rovnakú postupnosť príkazov na viacerých
miestach, museli by sme ju buď stále opisovať alebo v kóde skákať z miesta
na miesto. Obe dve možnosti sú opäť veľmi neprehľadné.
Funkcionálne dekompozícia
O rozdelení aplikácie do funkcií sa niekedy hovorí ako o tzv. Funkcionálne dekompozícii. Neľakajte sa termíne, jednoducho si rozmyslíme, čo má naša aplikácia vedieť, a pre rôzne užívateľské funkcie zvyčajne vytvoríme jednotlivé funkcie v zdrojovom kóde. V praxi sa nám bude často stávať, že si budeme tvoriť okrem týchto funkcií ešte nejaké pomocné, napr. Môžeme mať funkciu pre výpis menu aplikácie alebo rozdelíme nejaký zložitý výpočet do viacerých menších funkcií kvôli prehľadnosti.
Funkciám sa niekedy hovorí podprogramy alebo subrutiny. Ak funkcia nevracia
žiadnu hodnotu (viď ďalej), môže sa ju v niektorých jazykoch hovoriť
procedúra. U väčších aplikácií, ktoré majú mnoho funkcií, sa funkcia
združujú do tzv. Modulov. Tie vy dobre poznáte napr. V podobe
#include <iostream>
, ktorým načítame knižnicu (modul) pre
prácu so štandardným vstupom a výstupom (teda pre nás s konzolou). Podobne
sú matematické funkcie sústredené v systémovom module cmath
.
Tieto moduly alebo ak knižnice sa tiež naučíme vytvárať.
Tvorba funkcií
Funkcia je logický blok kódu, ktorý raz napíšeme a potom ho môžeme
ľubovoľne volať bez toho, aby sme ho písali znovu a opakovali sa. Funkciu
deklarujeme v globálnom priestore, niekde nad funkciou main()
.
Bude vyzerať podobne. Pridajme do nášho zdrojového kódu funkciu, ktorá do
konzoly vypíše " Ahoj, vrelo ťa tu vítam! "
. Pre názornosť si
prvýkrát uveďme kompletný zdrojový kód programu:
#include <iostream> using namespace std; void pozdrav() { cout << "Ahoj, vrele te tú vitam!" << endl; } int main() { return 0; }
Kľúčové slovo void
v definícii funkcie označuje, že
funkcia nevracia žiadnu hodnotu. Funkciu teraz musíme zavolať, aby sa
spustila. Musíme to samozrejme urobiť až po tom, čo ju deklarujeme, inak by
ju kompilátor nepoznal (preto sme ju písali nad funkciu main()
).
Do main()
napíšeme tento riadok:
{CPP_IMPORTS}
void pozdrav()
{
cout << "Ahoj, vrele te tú vitam!" << endl;
}
{CPP_MAIN_BLOCK}
pozdrav(); // zavolanie funkcie
{/CPP_MAIN_BLOCK}
Do konzoly by sa v tejto chvíli malo vypísať
Ahoj, vrele te tú vitam
.
Funkcie s parametrami
Funkcia môže mať tiež ľubovoľný počet vstupných parametrov (niekedy
sa im hovorí argumenty), ktoré píšeme do zátvorky v jej definícii.
Parametre ovplyvňujeme správanie funkcie. Majte situácii, keď chceme
pozdraviť nášho používateľa podľa mena. Rozšírime teda existujúce
funkciu o parameter jmeno
a ten potom pridáme s konkrétnou
hodnotou do volanie funkcie:
void pozdrav(string jmeno) { cout << "Ahoj, vrele te tú vitam " << jmeno << endl; }
Funkciu v main(
) následne zavoláme takto:
{CPP_IMPORTS}
void pozdrav(string jmeno)
{
cout << "Ahoj, vrele te tú vitam " << jmeno << endl;
}
{CPP_MAIN_BLOCK}
pozdrav("Karle"); // zavolanie funkcie
{/CPP_MAIN_BLOCK}
Keby sme teraz chceli pozdraviť niekoľko ľudí, nemusíme otrocky písať
znovu a znovu cout << "Ahoj, vrele...
, stačí nám len
viackrát zavolať našej funkcii:
{CPP_IMPORTS}
void pozdrav(string jmeno)
{
cout << "Ahoj, vrele te tú vitam " << jmeno << endl;
}
{CPP_MAIN_BLOCK}
pozdrav("Karle");
pozdrav("Davide");
pozdrav("Marenko");
{/CPP_MAIN_BLOCK}
výsledok:
Konzolová aplikácia
Ahoj, vrele te tú vitam Karle
Ahoj, vrele te tú vitam Davide
Ahoj, vrele te tú vitam Marenko
Návratová hodnota funkcie
Funkcia môže ďalej vracať nejakú hodnotu. Opusťme náš príklad s
pozdravom a vytvorme tentoraz funkciu, ktorá nám spočíta obsah obdĺžnika.
Tento obsah však nebudeme chcieť len vypísať, ale budeme ho chcieť použiť
v ďalších výpočtoch. Preto výsledok funkcie nevypíše, ale vrátime ako
návratovú hodnotu. Funkcia môže vracať práve jednu hodnotu pomocou
príkazu return
. Return okrem navrátenie hodnoty funkciu tiež
okamžite ukončí, ďalšie prípadné príkazy po ňom sa už
nevykonajú. Dátový typ návratovej hodnoty musíme uviesť pred definíciu
funkcie. Pridajte si do programu nasledujúcej funkcii:
int obsah_obdelniku(int sirka, int vyska) { int vysledek = sirka * vyska; return vysledek; }
V praxi by naše funkcie samozrejme počítala niečo zložitejšieho, aby sa
nám ju vyplatilo vôbec programovať. V príklade ale obdĺžnik poslúži
dobre. Funkcia pomenovávame malými písmenami, celými slovami a miesto
medzier používame podčiarknutia. Hoci C ++ samotné je plné skrátené, vy
sa im vyhnite. Je totiž oveľa čitateľnejšie funkcie
datum_narozeni()
ako datnar()
, u ktorej nemusia byť
na prvý pohľad zrejmé čo že to vôbec robí.
Ak by sme teraz chceli vypísať obsah nejakého obdĺžnika, jednoducho
vložíme volanie funkcie do volania cout
. Ako prvý sa spočíta
obsah obdĺžnika, funkcia túto hodnotu vráti a hodnota príde ako vstupný
parameter objektu cout
, ktorý ju vypíše. Ako šírku a výšku
zadajte napr. 10
a 20
cm:
{CPP_IMPORTS}
int obsah_obdelniku(int sirka, int vyska)
{
int vysledek = sirka * vyska;
return vysledek;
}
{CPP_MAIN_BLOCK}
cout << "Obsah obdĺžnikov je: " << obsah_obdelniku(10, 20) << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
Konzolová aplikácia
Obsah obdĺžnikov je 200 cm^2
Pokiaľ vám to príde zmätené, vždy môžete použiť ešte pomocnú premennú:
{CPP_IMPORTS}
int obsah_obdelniku(int sirka, int vyska)
{
int vysledek = sirka * vyska;
return vysledek;
}
{CPP_MAIN_BLOCK}
int obsah = obsah_obdelniku(10, 20);
cout << "Obsah obdĺžnikov je: " << obsah << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
Návratovú hodnotu funkcie sme však nepoužili kvôli tomu, aby sme ju len vypisovali. Využime teraz toho, že je výpis na nás, a výpisy súčet obsahov dvoch obdĺžnikov:
{CPP_IMPORTS}
int obsah_obdelniku(int sirka, int vyska)
{
int vysledek = sirka * vyska;
return vysledek;
}
{CPP_MAIN_BLOCK}
int celkovy_obsah = obsah_obdelniku(10, 20) + obsah_obdelniku(20, 40);
cout << "Sucet obsahu obdĺžnika je: " << celkovy_obsah << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
výsledok:
Konzolová aplikácia
Sucet obsahu obdĺžnikov je: 1000 cm^2
Spomeňme si na minulé príklady, ktoré sme počas nášho seriálu vytvorili. Môžete si ich skúsiť prepísať tak, aby ste volali funkciu. V rámci návrhu by všetok kód mal byť rozdelený do funkcií (a ideálne do modulov, viď. Ďalší lekcie) a to najmä kvôli prehľadnosti. My sme to zo začiatku kvôli jednoduchosť zanedbali, teraz to prosím berte na vedomie
Výhoda funkciou je teda v prehľadnosti a úspornosti (môžeme napísať nejakú vec raz a volať ju treba stokrát na rôznych miestach programu). Keď sa rozhodneme funkciu zmeniť, vykonáme zmenu len na jednom mieste a táto zmena sa prejaví všade, čo značne znižuje riziko chýb. V príklade, kde zdravíme Karla, Davida a Marienku, nám stačí zmeniť text pozdravu vo funkcii a zmení sa vo všetkých troch volaniach. Nemať kód vo funkcii, museli by sme prepisovať 3 vety a v nejakej by sme mohli urobiť chybu.
Rekurzia
Na koniec si urobme ešte odbočku k pokročilejšiemu téme, ktorým je rekurzia. Rekurzívne funkcie je taká funkcia, ktorá v tele volá sama seba. Takáto funkcia potrebuje nejakú informáciu, podľa ktorej spozná, kedy má skončiť (tzv. Ukončenie rekurzia), inak by zavolala seba, tá zas seba a tak až do pádu programu na nedostatok pamäti. Rekurzia sa často používa v algoritmizácia.
Vo funkcionálnych jazykoch sa rekurzia používa namiesto cyklov. Zoberme si
napríklad cyklus for
, ktorý je súčtom čísla od 1
do 10
. Rovnaký výsledok môžeme docieliť aj rekurzia, funkcie
sa buď zavolá znovu s číslom o 1
vyšším alebo sa
ukončí.
int cyklus(int aktualni_index, int konecny_index, int suma) { if (aktualni_index == konecny_index) return suma; return cyklus(aktualni_index + 1, konecny_index, suma + aktualni_index); }
Funkciu by sme zavolali takto:
{CPP_IMPORTS}
int cyklus(int aktualni_index, int konecny_index, int suma)
{
if (aktualni_index == konecny_index)
return suma;
return cyklus(aktualni_index + 1, konecny_index, suma + aktualni_index);
}
{CPP_MAIN_BLOCK}
cout << cyklus(0,10,0) << endl; // začiatok rekurzia
{/CPP_MAIN_BLOCK}
To isté môžeme zapísať pomocou cyklu for
:
{CPP_CONSOLE}
// ekvivalentné zápis s for
int suma = 0;
for (int a = 0; a < 10; a++)
suma += a;
cout << suma;
{/CPP_CONSOLE}
Ako môžete vidieť, čítanie rekurzia nie je tak jednoduché, ako je tomu
u cykle for
. Aby toho nebolo málo, použitie rekurzia sebou nesie
dodatočnú záťaž, pretože sa musí opakovane odovzdávať parametre (viac v
článku o kompiláciu). Všeobecne možno veľkú časť programov, ktoré
používajú rekurziu, prepísať do podoby bez rekurzia. Pre príklad si
napíšeme program, ktorý počíta faktoriál.
Predvedieme si verziu s rekurzia a verziu bez rekurzia.
int faktorial(int x) { if (x == 1) return 1; return x * faktorial(x - 1); }
A funkciu by sme zavolali takto:
{CPP_IMPORTS}
int faktorial(int x)
{
if (x == 1)
return 1;
return x * faktorial(x - 1);
}
{CPP_MAIN_BLOCK}
cout << faktorial(10) << endl;
{/CPP_MAIN_BLOCK}
A alternatíva pomocou cyklu:
{CPP_CONSOLE}
int vysledek = 1;
int x = 10;
for (int i = 2; i <= x; i++)
vysledek *= i;
cout << vysledek << endl;
{/CPP_CONSOLE}
S rekurzia sa môžete často stretnúť v už existujúcich programoch alebo na pohovoroch do práce. Avšak odporúčam rekurziu z dôvodov výkonnostných skôr nepoužívať. Rekurzia dokáže veľmi rýchlo zaplniť zásobník a ukončiť celý program. Naviac je zložitá na chápania, ak vás zmiatla, ešte sa s ňou stretnete minimálne u algoritmov, kde bude dostatok priestoru pre ďalšie pochopenie.
V budúcej lekcii, Riešené úlohy k 13. lekcii C ++ , už trochu nazrieme do OOP. Pozrieme sa na štruktúry, ktoré sú takým medzikrokom medzi procedurálnym programovaním a OOP.
V nasledujúcom cvičení, Riešené úlohy k 13. lekcii C ++, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.