| ← podmínky a cykly | C/C++ | funkce II → |
Funkce jsou základním stavebním kamenem jazyka C. Zatím jsme se setkali s funkcí main() a s funkcemi printf() a scanf(). V této kapitole se především naučíme vytvářet vlastní funkce. K popisu funkcí definovaných normou ANSI C, které již překladač obsahuje se dostaneme později. Tyto standardní funkce jsou obsaženy ve standardních knihovnách (například printf() a scanf() jsou v souboru <stdio.h>).
Překladač obsahuje i spousty dalších funkcí, jež nejsou v normě ANSI C. Jejich použití si musíte nastudovat v dokumentaci. Jsou to funkce závislé jak na překladači, tak mnohdy na operačním systému, takže jejich použitím se značně snižuje přenositelnost kódu.
Funkce se při překladu usídlí kdesi v paměti. Při její volání program přeskočí do funkce a po jejím provedení se vrátí za místo, kde byla funkce volána. Pokud voláte funkci v programu vícekrát, znamená to jen to, že přeskakujete na místo v paměti, kde funkce je. Vytvářením funkcí tak šetříte paměť počítače (a šetříte i sobě práci, protože tělo funkce napíšete jen jednou a v programu použijete kolikrát chcete).? Na druhou stranu, každé volání funkce stojí nějaký (velmi malý) čas. Pokud však takovou funkci voláte v cyklu tisíckrát, už je to trochu znát. Proto je mnohem efektivnější volat funkci „co nejméně-krát“.
Například:
for (x = 0; x < 1000; x++)
{
printf("Cyklus cislo: ");
printf("%i\n",x);
}
je mnohem pomalejší než
for (x = 0; x < 1000; x++)
{
printf("Cyklus cislo: %i\n",x);
}
Úspora místa a zpřehlednění programu je mnohem důležitější, než časová režie spojená s voláním funkce. Navíc platí, že při změně je daleko snazší a bezpečnější (z hlediska možných programátorských chyb) změnit jednu funkci, než několik míst v programu. Pokud však vytváříte malou funkci, kde je její volání v některých místech kritické, je někdy možné jí nahradit pomocí maker se všemi jejich záludnostmi! Změna makra pak znamená změnu ve všech místech programu, kde je makro použito, stejně jako změna funkce znamená změnu chování programu ve všech místech, kde je funkce volána.
Jazyk C++ tento problém řeší elegantněji pomocí tzv. inline funkcí.
Definice funkce je následující:
navratovy_typ jmeno ([parametry])
{
telo funkce
}
Jméno funkce slouží k jejímu identifikování. Při volání funkce v programu musíme uvést za funkcí kulaté závorky, a to i tehdy, když funkce nemá žádné argumenty. Samotné jméno funkce bez závorek totiž reprezentuje adresu v paměti, kde funkce začíná. Toho se dá využít v odkazech na funkci.
Parametry funkce jsou očekávaná data, která bude funkce zpracovávat. Každý parametr má své jméno a musí být určen jeho datový typ. Pokud funkce nemá žádné parametry, uvádí se v závorkách slůvko void. Pokud je jich více než jeden, oddělují se čárkou. Existují i funkce s proměnlivým počtem argumentů. Parametrem může být celé nebo racionální číslo, struktura, nebo ukazatele. Nemůže to být například pole, které obsahuje x prvků, ale může to být ukazatel na začátek pole.
Funkce má jednu návratovou hodnotu. Její typ se uvádí před jménem funkce. Například funkce main() má vždy návratovou hodnotu typu int. Pokud nechcete, aby funkce vracela nějaká data, jako návratovou hodnotu uveďte void.
Funkci lze kdykoliv ukončit pomocí příkazu return, za který se uvádí hodnota nebo výraz, jehož výsledek se stane návratovou hodnotou funkce. Pokud funkce žádnou návratovou hodnotu nemá, uvádí se return bez hodnoty nebo výrazu. Pokud funkce má návratovou hodnotu, je použití return na konci funkce povinné. Po ukončení funkce (ať již příkazem return, nebo tím že se vykonají všechny příkazy v jejím těle) se pokračuje v provádění kódu za místem kde byla funkce volána. Pokud však příkazem return ukončíte funkci main(), znamená to ukončení programu.
Návratová hodnota funkce main() se vrací operačnímu systému. Zaběhnutá praxe je, že návratová hodnota 0 znamená úspěšné ukončení programu, jakákoliv jiná hodnota určuje číslo chyby.
1: /*------------------------------------------------*/ 2: /* fce1.c */ 3: #include <stdio.h> 5: void tecka(int pocet) 6: { 7: if (pocet <= 0) 8: return; /* kdyby byl pocet unsigned int, bylo by tohle 9: zbytecne */ 10: /* kdyz se podivate na podminku v cyklu tak 11: * zjistite, ze je to zbytecne stejne :-) */ 12: for (; pocet > 0; pocet--) 13: printf(". "); 14: } 16: int mocnina(int x) 17: { 18: return x * x; 19: } 21: int main(void) 22: { 23: tecka(10); 24: printf("-5^2 = %i\n", mocnina(-5)); 25: return 0; 26: } 28: /*------------------------------------------------*/
Výstup programu:
. . . . . . . . . . -5^2 = 25
Při deklaraci funkce se uvádí pouze datový typ návratové hodnoty a typy argumentů. To je vše, co potřebuje překladač k tomu, aby její volání mohl do programu zapsat. Mohou se uvést i názvy argumentů, ale to není nutné. Než je funkce v programu volána, musí být deklarována. Definována může být až později. Pokud funkci předem nedeklarujete, ale rovnou definujete, je definice zároveň i deklarací. Pokud se deklarace s pozdější definicí neshodují, oznámí překladač chybu.
V následujícím programu si funkci nejdříve deklarujeme, použijeme jí v jiné funkci a pak jí teprve definujeme.
1: /*------------------------------------------------*/ 2: /* deklar.c */ 3: #include <stdio.h> 6: float vypocet(int, float, float *); /* deklarace funkce */ 8: int main(void) 9: { 10: float a, b; 12: a = vypocet(5, 0.3, &b); 13: printf("Soucet = %5.2f\nNasobek = %5.2f\n", a, b); 15: return 0; 16: } 18: float vypocet(int a, float b, float *c) 19: { 20: float f; 21: *c = (float) a *b; 22: f = (float) a + b; 23: b = 55.5; /* menim lokalni promenou b, to nema s promennou b ve funkci 24: main nic spolecneho */ 25: return f; 26: } 28: /*------------------------------------------------*/
Výstup z programu:
Soucet = 5.30 Nasobek = 1.50
Tak malý příklad a tolik toho ukazuje. Platnost lokálních proměnných jen uvnitř funkce, využití návratové hodnoty funkce, dopřednou deklaraci funkce a použití ukazatele. Funkci jsme jako třetí argument předali adresu proměnné b a díky tomu mohla funkce na tuto adresu uložit nějakou hodnotu (v našem příkladě násobek čísel a a b).
Pokud funkce volá ve svém těle samu sebe, nebo je volána jinou funkcí ve svém těle, hovoříme o rekurzi. Data, která jsou funkci předávána, se ukládají do takzvaného zásobníku. To je nějaké vyhrazené místo v paměti. Po skončení funkce se data ze zásobníku zase odstraní. Pokud však funkce během svého vykonávání zavolá samu sebe, pak se do zásobníku umístí další data a tak stále dokola. To je samozřejmě velice náročné na paměť a může vést až ke zhroucení programu. Použití rekurze je sice efektní, ale ne vždy efektivní. Proto je třeba mít opravdu dobrý důvod pro používání rekurzí. Následuje ilustrativní příklad. V něm dobrý důvod pro použití rekurze určitě nemáme :-).
1: /*------------------------------------------------*/ 2: /* rekurz.c */ 3: #include <stdio.h> 5: float prvni_funkce(int, float); /* deklarace */ 7: float druha_funkce(int a, float f) 8: { /* definice */ 9: printf("Vola se druha funkce\n"); 10: return prvni_funkce(a, f * (a + 1)); 11: } 13: float prvni_funkce(int a, float f) 14: { /* definice */ 15: printf("Prvni funkce a= %2i, f=%5.2f\n", a, f); 16: if (a <= 0) 17: return f; 18: return druha_funkce(--a, f); 19: } 21: int main(void) 22: { 23: printf("Vysledek je %f\n", prvni_funkce(5, 5.0)); 24: return 0; 25: } 27: /*------------------------------------------------*/
Výstup z programu:
Prvni funkce a= 5, f= 5.00 Vola se druha funkce Prvni funkce a= 4, f=25.00 Vola se druha funkce Prvni funkce a= 3, f=100.00 Vola se druha funkce Prvni funkce a= 2, f=300.00 Vola se druha funkce Prvni funkce a= 1, f=600.00 Vola se druha funkce Prvni funkce a= 0, f=600.00 Vysledek je 600.000000
V příkladě se počítá jakási číselná řada. Druhá funkce má zde slouží jen k nějakému výpisu a k novému zavolání funkce prvni_funkce(). Berte to jenom jako příklad, jistě by jste dokázali daný problém vyřešit mnohem elegantněji.
Bez dopředné deklarace prvni_funkce() bychom tuto funkci nemohli použít v těle druhe_funkce().
Mimochodem, napadlo vás, co by se stalo, kdyby se volání return druha_funkce(--a, f); změnilo na return druha_funkce(a--, f);? V takovém případě by se volala druha_funkce() s hodnotou proměnné a, která by nebyla snížena o jedničku. Ta by se měla snížit až po zavolání funkce. Výsledkem by byl nekonečný cyklus vzájemného volání funkcí. Na podobné problémy si musíte dávat pozor. Logické chyby programátora se odhalují mnohem hůře, než syntaktické chyby jazyka C (ty ostatně odhalí již překladač).
Klasickým příkladem na rekurzi je výpočet faktoriálu (avšak i faktoriál lze vypočítat elegantně bez rekurze).
1: /*------------------------------------------------*/ 2: /* faktor.c */ 3: #include <stdio.h> 5: long double faktorial(long double x) 6: { 7: if (x == 0L) 8: return 1L; 9: return x * faktorial(x - 1L); 10: } 12: int main(void) 13: { 14: int i; 15: for (i = 0; i <= 40; i++) 16: printf("%2i! = %040.0Lf\n", i, faktorial(i)); 17: return 0; 18: } 20: /*------------------------------------------------*/
Výstup je trochu zkrácen:
0! = 0000000000000000000000000000000000000001
1! = 0000000000000000000000000000000000000001
2! = 0000000000000000000000000000000000000002
3! = 0000000000000000000000000000000000000006
4! = 0000000000000000000000000000000000000024
5! = 0000000000000000000000000000000000000120
6! = 0000000000000000000000000000000000000720
7! = 0000000000000000000000000000000000005040
8! = 0000000000000000000000000000000000040320
.....
34! = 0295232799039604140861139076140532498432
35! = 10333147966386144929973846968255251480576
36! = 371993326789901217462530208167145295052800
37! = 13763753091226345045735828383554804299857920
38! = 523022617466601111725872220378936271647539200
39! = 20397882081197443356844789080046496991166857216
40! = 815915283247897734263888042887576837447481294848
Možná jste si všimly, že na výpočtech není něco v pořádku. Nejsou totiž příliš přesné. Platí, že 40! = 39!*40, ovšem
20397882081197443356844789080046496991166857216*40 = 815915283247897734273791563201859879646674288640 a ne 815915283247897734263888042887576837447481294848 (a že je to pořádná chyba).
Je to smutné, ale jazyk C neumí počítat přesně s racionálními čísly. Je to dáno tím, jak jsou čísla v paměti uložena a jak se s nimi počítá. Když vydělíte 10/3 a výsledek znovu vynásobíte třemi, vyjde vám 9.999... Čísla typu float mají zaručenou přesnost cca na 14 „nejvýznamnějších“ míst. (Záměrně neříkám desetinných míst, neboť to záleží na tom, s jak velkým/malým číslem pracujete). Vzhledem k velikosti čísla je chyba sice zanedbatelná, ovšem pokud budete programovat řekněme pro nějaký bankovní úřad, asi by z vás nikdo neměl radost. Naštěstí lze přesně počítat s celočíselnými proměnnými, takže lze podobně velká čísla zpracovávat pomocí několika celočíselných proměnných (a složitých algoritmů).
Zatímco doteď jsme při deklaraci nebo definici funkce určovali jaké bude mít argumenty, nyní se naučíme vytvořit funkci bez přesného počtu a typu argumentů. Vzpomeňte si například na funkce printf() nebo scanf(), které také mají proměnlivý počet argumentů. Argumenty funkce se ukládají do zásobníku. Aby mohla funkce k těmto argumentům přistupovat, musíme jí předat alespoň místo, kde začít. Proto musí mít funkce s proměnlivým počtem argumentů minimálně jeden argument napevno daný. To, že mohou následovat další argumenty, se při definici nebo deklaraci označí pomocí tří teček (tzv. výpustka).
Pro práci s těmito nedeklarovanými argumenty existuje standardní knihovna <stdarg.h>. V ní jsou definovány tři makra. Ukážeme si jejich deklarace:
void va_start(va_list ap, last); type va_arg(va_list ap, type); void va_end(va_list ap);
Výraz va_list vnímejte jako datový typ proměnné ap. Je to proměnná, která reprezentuje nedeklarované argumenty. Proměnnou ap by jste si mohli pojmenovat jakkoliv, ale je dobrým zvykem zůstat u ap. Když si na to zvyknete, je pak čtení takového kódu jednodušší (a nejen pro vás).
Makro va_start inicializuje proměnnou ap pomocí poslední napevno deklarované proměnné ve funkci. Ta je předána jako druhý argument last (díky tomu získá potřebnou adresu do zásobníku s argumenty).
Makro va_arg vrací hodnotu dalšího argumentu v řadě. Jakého typu má být se musí určit druhým argumentem type. Toto makro můžete volat vícekrát Maximálně však tolikrát, kolik je předáno funkci při volání nedeklarovaných argumentů. Jinak by jste se pokoušeli přečíst data, která již nepatří k argumentům funkce.
Makro va_end zajišťuje bezproblémové ukončení funkce (příkazem return). Ačkoliv vám program poběží pravděpodobně i bez volání tohoto makra, nikdy na něj nezapomínejte. V určitých kritických situacích by se vám to vymstilo a po půl roce by jste takovou chybu v programu jen těžko hledali.
Z logiky věci vychází, že jako první musí být voláno makro va_start a makro va_end až před koncem.
Nyní se nabízí otázka, jak zjistí funkce s proměnlivým počtem argumentů, jaké jí byli argumenty předány (jakého typu) a také kolik. Možností je hned několik. Například známá funkce printf() má vždy jako první argument řetězec (přesněji řečeno ukazatel na řetězec; řetězec je znakové pole a pole nelze předávat jako argument funkce). V tomto řetězci hledá speciální sekvence (%s,%c) a podle nich zjišťuje, že má mít další argument a také jakého typu. A tak je přesně dáno, o kolik argumentů a jakých typů si funkce řekne. Další možností, jak určit počet předaných argumentů je, že při deklaraci funkce deklarujete jeden parametr (minimálně jeden stejně vždy musíte mít), který bude obsahovat počet proměnných. Další možností je například definování nějaké zarážky. Třeba že vždy jako poslední argument bude ukazatel s hodnotou NULL, nebo číslo nula atp. To už je jen na vás.
1: /*------------------------------------------------*/ 2: /* fce2.c */ 3: #include <stdio.h> 4: #include <stdarg.h> 6: int maximum(const int pocet, const int znamenko, ...) 7: { 8: va_list ap; 9: int i; 10: int maxS, dalsiS; 11: unsigned int maxU, dalsiU; 13: if (pocet <= 0) 14: return 0; 16: va_start(ap, znamenko); /* znamenko je posledni deklarovana promenna */ 18: if (znamenko) 19: maxS = va_arg(ap, int); 20: else 21: maxU = va_arg(ap, unsigned int); 23: for (i = 0; i < pocet - 1; i++) { 24: if (znamenko) { 25: dalsiS = va_arg(ap, int); 26: maxS = (maxS < dalsiS) ? dalsiS : maxS; 27: } else { 28: dalsiU = va_arg(ap, unsigned int); 29: maxU = (maxU < dalsiU) ? dalsiU : maxU; 30: } 31: } 33: va_end(ap); 34: if (znamenko) 35: return maxS; 36: else 37: return (signed int) maxU; 38: } 40: int main(void) 41: { 42: printf("Maximum = %i\n", maximum(3, 1, 7, 9, -2)); 43: printf("Maximum = %x\n", maximum(7, 1, -5, 8, -2, 5, -6, -1, 4)); 44: printf("Maximum = %x\n", maximum(7, 0, -5, 8, -2, 5, -6, -1, 4)); 45: return 0; 46: } 48: /*------------------------------------------------*/
Výstup z programu:
Maximum = 9 Maximum = 8 Maximum = ffffffff
Parametry pocet a znamenko jsem deklaroval jako konstantní aby bylo jasné, že není moudré jejich hodnotu ve funkci měnit. Není to však žádná povinnost.
| ← podmínky a cykly | C/C++ | funkce II → |
