[Přednáška 3] | [Obsah] | [Přednáška 5] |
Procedury a funkce označujeme společným pojmem podprogramy. Podprogram je část kódu (programu), samostatně definovaná, kterou je možné volat opakovaně z různých míst kódu. Podprogram může mít tzv. parametry, což jsou data, které se podprogramu předávají ke zpracování. Výhoda podprogramů spočívá v tom, že určitý algoritmus, který potřebujeme používat opakovaně, napíšeme pouze jednou (při definici) a na místech, kde potřebujeme tento algoritmus provést, jej pouze zavoláme. Navíc, řešíme-li nějakou složitější úlohu, rozdělíme si složitý problém na jednodušší dílčí podproblémy, jejichž algoritmus řešení napíšeme právě jako podprogramy.
Funkce je podprogram, který vrací („vyhazuje“) nějakou návratovou hodnotu (vypočtené číslo) a lze jej tedy použít ve výrazu, procedura je podprogram, který nevrací žádnou hodnotu, pouze provede nějakou akci (např. tiskne). Neznamená to ovšem, že procedura nemůže vypočítat nějakou hodnotu, ale ta musí uložit třeba do globální proměnné; v textu si ukážeme i jiný způsob.
Definice začíná specifikací návratové hodnoty, pak následuje identifikátor funkce (jméno funkce) se seznamem formálních parametrů v kulatých závorkách. Je-li parametrů více, oddělují se čárkou. Právě zde je první odlišnost mezi verzí K&R a ANSI. Rozdíl demonstrujeme na funkci, která počítá obsah obdélníka:
Definice podle K&R | Definice podle ANSI |
double obsah_obd(a,b) double a,b; { return a*b; } |
double obsah_obd(double a, double b) { return a*b; } |
Definovali jsme funkci obsah_obd
, která vrací hodnotu (číslo) typu
double
a má dva formální parametry a, b
.
Druhý způsob definice formálních parametrů (podle ANSI) je výhodnější, protože umožňuje překladači lépe kontrolovat skutečné parametry při volání funkce
(ve verzi K&R není nutné typy na dalším řádku uvádět, překladač pak předpokládá typ int
).
Návratová (výstupní) hodnota se vrací pomocí příkazu return vyraz;
,
resp. return (vyraz);
.
Uvnitř těla funkce je možné deklarovat lokální proměnné, které jsou platné pouze uvnitř těla. Deklarace se provádí běžným způsobem, např.:
double obsah_obd(double a, double b) { double obsah; obsah = a*b; return obsah; }Lokální proměnné jsou alokovány automaticky při vstupu do funkce (při volání) na zásobníku (označují se také jako proměnné ve třídě
auto
).
Pokud není v definici uveden typ návratové hodnoty nebo typ parametru, překladač předpokládá implicitně typ int
.
Následujícím zápisem definujeme funkci
obsah_obd(double a, double b) { return a*b; }vracející hodnotu typu
int
(nikoliv proceduru, jak by se mohlo na první pohled zdát). Při návratu se provede
automatická konverze výsledku a*b typu double
na typ int
. Funkce vrátí
celou část obsahu obdélníku.
Je vhodné podotknout, že moderní překladače, které mají nastaven překlad podle pravidel jazyka C++, vyžadují vždy uvést typ a ohlásí
v tomto případě chybu. Z toho také plyne, že je nutné uvádět typ parametru u každého z nich, nelze tedy psát
double obsah_obd(double a, b)
.
Volání funkce se provádí zápisem identifikátoru a uvedením skutečných parametrů v kulatých závorkách:
int main(int argc, char *argv[]) { double a=5.6, b=4; double x=1.2, y=3.5; double obsah; // volani funkce se skutečnými parametry konstantami 3,4 obsah = obsah_obd(3,4); printf("Obsah obdelniku je %lf.\n",obsah); // volani funkce se skutečnými parametry - proměnnými a,b printf("Obsah druheho obdelniku je %lf.",obsah_obd(a,b)); // volani funkce se skutečnými parametry - proměnnými x,y printf("Obsah tretiho obdelniku je %lf.",obsah_obd(x,y)); }Při deklaraci funkce je možné u formálního parametru uvést klíčové slovo
const
. Deklarujeme tím, že parametr
je konstatní a není možné uvnitř těla funkce jeho hodnotu modifikovat; při pokusu o změnu překladač ohlásí chybu.
Konstatní parametr má význam zejména při předávání polí, protože zabrání programátorovi např. v nechtěné změně prvků pole.
Důležité je uvedení klíčového slova const
u knihovních funkcí pracujících s řetězci, tj. s poli typu
char
, kde není znám kód funkce, pouze její prototyp. Programátor, který takto deklarovanou funkci ve svém
programu použije, má jistotu, že obsah řetězce (pole) není ve funkci změněn.
Mezi další vlastnosti definic funkcí v jazyce C platí:
void
:
void tisk_cisel(int a, int b) { printf("%d\t%d",a,b); }Příkaz
return
není nutné na konci procedur uvádět, pouze v případě předčasného (nuceného) ukončení.
Nemá-li funkce (procedura) žádný parametr, píše se klíčové slovo void
do závorek místo seznamu formálních parametrů.
Pokud klíčové slovo void
chybí, znamená to v „klasickém“ C, že o parametrech
není nic známo. V C++ se tyto dva případy nerozlišují, neuvedení klíčového slova znamená taktéž, že funkce parametry nemá.
Procedura nemá žádný parametr | O parametrech není nic známo |
void tisk_hlasky(void) { printf("CHYBA"); } |
void tisk_hlasky() { printf("CHYBA"); } |
Při volání funkce (procedury), která nemá parametry, je nutné psát prázdnou dvojici závorek, jinak bude překladač považovat zapsaný identifikátor za indentifikátor proměnné! Volání výše definované procedury musíme provést takto:
int main(void) { tisk_hlasky(); }
Prototypy funkcí (hlavičky)
Prototypem funkce (hlavičkou) rozumíme uvedení návratové hodnoty, identifikátoru a seznamu parametrů bez těla funkce. Prototypy
funkcí jsou např. uvedeny v hlavičkových souborech. Prototyp funkce je třeba uvést, není-li funkce definována před tím, než je poprvé volána.
Překladač musí při volání totiž vědět, jakou má funkce návratovou hodnotu a parametry, aby mohl provést kontrolu.
Někdy definice kódu funkce ve zdrojové podobě není vůbec k dispozici (některé knihovny existují pouze v přeloženém tvaru .obj, .lib).
Uvedení prototypu funkce funkce se také někdy označuje termínem deklarace, na rozdíl od její definice. Uvedení prototypu je ukončeno středníkem, ale při definici se za identifikátorem středník neuvádí!
Definice funkce před místem volání | Definice funkce za místem volání, uvedení prototypu před voláním |
double obsah_obd(double a, double b) { return a*b; } void main(void) { double S; S = obsah_obd(3,4); } |
double obsah_obd(double a, double b); void main(void) { double S; S = obsah_obd(3,4); } double obsah_obd(double a, double b) { return a*b; } |
(Poznámka: Ve verzi K&R deklarace funkce spočívala pouze v uvedení návratové hodnoty a identifikátoru funkce bez uvedení parametrů, tedy
double obsah_obd(double, double)
.)
Na následujícím obrázku je příklad jednoduché procedury tisk
a odpovídající stav zásobníku těsně před voláním procedury,
kdy jsou již na vrcholu zásobníku kopie hodnot skutečných parametrů. Dále je zobrazen stav zásobníku těsně před ukončením volané funkce.
Hodnota parametru a na zásobníku je změněna, ale hodnota skutečného parametru x (proměnná v hlavním programu) se nemění.
Po návratu z procedury jsou parametry ze zásobníku odstraněny.
Zdrojový kód | Zásobník před voláním procedury tisk |
Zásobník před ukončením procedury tisk |
void tisk(int a, int b) { a++; printf("%d\t%d",a,b); } void main(void { int x = 5; tisk(x,3); } |
// obsah obdelniku jako funkce double obsah_obd_fce(double a, double b) { return a*b; } // obsah obdelniku jako procedura s vystupni promennou void obsah_obd_proc(double a, double b, double *obs) { *obs = a*b; } void main(void) { double obsah; // vypocet pomoci funkce obsah = obsah_obd_fce(3,5); // vypocet pomoci procedury s vystupni promennou // musim predat adresu promenne, kam se ulozi vysledek obsah_obd_proc(3,7,&obsah); // nelze napsat obsah = obsah_obd_proc(3,7,&obsah); // protoze procedura nevraci (nevyhazuje) zadnou hodnotu }Někdy může mít výstupní proměnnou i funkce, tj. vracet de-facto dvě hodnoty. Nastavení hodnoty jiné proměnné mimo návratové hodnoty se často označuje termínem vedlejší, resp. stranový efekt funkce (side effect).
(Poznámka: V jazyce C++ byl přidán další způsob předávání parametrů funkcím, a sice pomocí reference, což odpovídá parametrům předávaným odkazem v jazyce Pascal.)
void razeni(float *pole, int n) { // kód } |
void razeni(float pole[], int n) { // kód } |
Uvedení velikosti pole ve tvaru void razeni(float pole[10])
není možné, překladač hodnotu 10 ignoruje.
float max_prvek(float pole[][3], int n) { // kód // jako skutečný parametr mohu předat // libovolné statické pole, které má první // dimenzi libovolnou a druhou pouze 3 } |
[Přednáška 3] | [Obsah] | [Přednáška 5] |