Předávání parametrů do a z funkcí (metod)



Funkce je základní modulová část programu (alternativní názvy jsou například: podprogram, funkce, procedura, rutina, metoda ...). Jejím účelem je zlepšení přehlednosti programu. Hlavní výhodou je oddělení logické části programu a možnost jejího vícenásobného využití.

Aby mohly funkce spolupracovat s okolím, je nutné aby sdílely nebo si předávaly data.



Data jsou hodnoty umístěné v paměti. Zabírají tolik paměti, kolik určuje jejich typ (u C je velikost zabrané paměti závislá na použité platformě a překladači). To jakým způsobem se interpretují (jak se s nimi pracuje) určuje také jejich typ. Existují typy základní (celočíselní, s pohyblivou řádovou čárkou) a typy složené (struktura, třída ...). Aby bylo snažší pracovat s proměnnými, dáváme jim jména. Jménem je potom označována činnost s pamětí, ve které je proměnná uložena. Konkrétní hodnota proměnné je umístěna v této paměti.

int ii; znamená, že se v paměti vytvoří postor pro proměnnou typu int (dva nebo čtyři byty v závislosti na překladači) a s těmito bity se bude pracovat prostřednictvím jména ii. Obsah bude interpretován jako číslo celočíselného (bitového) typu.
double ff; zde se vytvoří v paměti prostor pro proměnnou double (například čtyři nebo osm bytů). V tomto prostoru (který je pojmenován ff) je uložena proměnná skládající se z mantisy a exponentu (jejich konkrétní velikost (počet bitů)) záleží na celkové velikosti typu).
int *pi; vytvoří se prostor s velikostí vhodnou pro uložení adresy (jeho velikost závisí na rozsahu adres, používaném paměťovém modelu a způsobu tvorby adresy (segment, offset)). Proměnná tedy značí adresu v paměti, kde je uložená proměnná. U jazyka C je adresa spojená s typem (pointer, ukazatel) což znamená, že překladač ví jak s obsahem uloženým na dané adrese pracovat.

Poznámka: skutečná velikost typu se zjistí pomocí klíčového slova sizeof.



Základní formou je mít data ve společném, globálním prostoru. Všechny funkce k těmto datům mají stejný přístup a prakticky je sdílejí. Výhodou je, že takováto data se nemusejí nijak předávat. Nevýhodou je, že není řízen přístup k proměnné a ta se může měnit prakticky libovolně (nejsou dána pravidla pro změnu). Další nevýhodou je, že takováto data se nedají snadno použít v případě vícenásobného volání (nejmarkantnější je tato vlastnost u vícevláknového využití)-

Další možností je předávat data na místě parametrů funkce („libovolné“ množství), nebo pomocí návratové hodnoty (jediná): návratová_hodnota jmeno_funkce(parametry) ;.
Pokud máme funkci, která má jeden parametr a jednu návratovou hodnotu, potom její definice může vypadat například takhle (předávání hodnotou)

Ctrida funkce (Ctrida aa) {Ctrida pom;... return pom;} a volání
Ctrida bb,cc; cc = funkce(bb);
Mechanizmus tohoto volání je takový, že při volání se na základě proměnné bb vytvoří ve funkci funkce lokální proměnná aa se shodným obsahem. K jejímu vytvoření se používá mechanizmus kopykonstruktoru. Ve funkci je vytvořena další lokální proměnná pom. Obě dvě proměnné (pom a aa) zaniknou na konci funkce (pomocí destruktoru, je-li definován). Vrácení hodnoty je realizováno tak, že je na zásobníku vytvořen prostor pro návratovou hodnotu (dáno jejím typem). Do tohoto prostoru o velikosti daného typu volaná funkce uloží proměnnou (zde je vytvořena pomocí kopykonstruktoru kopie proměnné pom). Volající funkce tuto proměnnou použije (zavolá se operátor =, který zajistí vytvoření kopii návratové proměnné do proměnné cc) a následně je překladačem zrušena (destruktor). Výhodou tohoto volání je, že není možné ovlivnit hodnotu volající proměnné cc. Nevýhodou je vlastní tvorba kopií proměnných (je nutné vyhradit místo v paměti na zásobníku pro proměnné a jejich vlastní vytvoření a zrušení je časově náročné). Z tohoto důvodu se snažíme využití předávání pomocí hodnot minimalizovat.

Druhou možností je využití ukazatelů
Ctrida* funkce (Ctrida * aa) {Ctrida *pom=new Ctrida; *aa=5;... return pom;} a volání
Ctrida bb,*cc; cc = funkce(&bb);delete cc;
Rozdíl je v tom, že se nepředávají proměnné, ale pouze ukazatele (adresy) daných proměnných. Velikost předávaných typů je tedy minimální a tedy i časová a paměťová náročnost. Další výhodou je i to, že pomocí ukazatelů můžeme měnit proměnné vně funkce (zde pomocí *aa ovlivnit hodnotu proměnné bb. Pokud této vlastnosti chceme zamezit, můžeme použít klíčového slova const u parametru Ctrida* funkce (Ctrida const * aa). Vždy ale měníme hodnotu vnější proměnné „o jednu dál“ - jinými slovy změna proměnné aa se vně funkce neprojeví, protože se jedná o lokální proměnnou. Nevýhodou je, že pokud vracíme hodnotu vytvořenou uvnitř funkce (pomocí alokace, ne lokální), musí tuto odalokovat „uživatel“. Pokud tak neučiní, zůstává paměť neodalokovaná. Druhou možností návratu je vrátit hodnotu předanou do funkce (není možné ovšem kombinovat vracení toto a vracení ukazatele na vytvořenou proměnnou, protože v tom okamžiku by nebylo snadné určit zda se má odalokovat (vytvořená) nebo ne (předávaná)). Důležítá vlastnost je, že vracená proměnná musí existovat ve volané i volající funkci – toto splňuje naalokovaná proměnná (protože je v globální společné paměti) a předávaná proměnná (jelikož je předávaná do funkce existuje v obou). Vracet ukazatelem tedy můžeme existující (do funkce předanou) proměnnou, nově vzniklou (lokální) proměnnou můžeme předat jen hodnotou.
Někdy může být „užitečné“ vracení ukazatele na vnitřní prvky třídy. To je ovšem špatná technika, protože tímto jsou vnitřní proměnné „nechráněné“. Je proto možné realizovat vracením konstantních ukazatelů na konstantní hodnotu (kdy ovšem může pomocí konverzí dojít k „odkonstantněnění“ a tedy ke zpřístupnění vnitřní proměnné s možností ji upravit bez vědomí třídy), nebo použít „chytrou“ třídu, která přístup nějakým způsobem zprostředkuje tak, aby nemohlo dojít ke změně proměnné přes předaný ukazatel (například přes dočasnou kopii).
Využití ukazatelů je nevýhodné i z důvodu využití mechanizmů výjimek, kdy se obsahy alokované pomocí ukazatele „neruší“.

Třetí možností je využití referencí
Ctrida& funkce (Ctrida & aa) {Ctrida *pom=new Ctrida; aa=5;... return *pom;} a volání
Ctrida bb, cc; cc = funkce(bb);
Pro tento mechanizmus platí většina z pravidel pro ukazatele. Při předávání parametru pomocí reference, se vytváří nové pojmenování pro předávanou proměnnou (využití aa a bb je tedy rovnocenné – pracuje se se stejným paměťovým prostorem). Je tedy tímto mechanizmem měnit hodnoty předávaných parametrů i vně mimo funkce. Opět je možné změně zamezit pomocí const. Při vracení hodnoty referencí je opět nutné, aby tato existovala vně funkce. Je tedy možné použít return aa, protože aa je reference na vnější proměnnou (je to vlastně paměťový prostor, který zabírá proměnná bb). Není ovšem možné použít výše uvedený příklad, protože překladač není schopen odalokovat proměnné předávané referencí. Příklad by tedy fungoval, proměnné naalokované pomocí proměnné pom by však nebyly nikdy odalokovány. Návratovou hodnotou by byl prostor proměnné pom, což je v pořádku, následně by byla tato hodnota přiřazena do cc což je také v pořádku, ale ukazatel by se následně „ztratil“ a proměnná by navždy zůstala v paměti. Pokud je to nutné, je možné i u návratové hodnoty být const.

S největší pravděpodobností se tedy v praxi setkáme s nějakou kombinací uvedených metod. Nejčastěji ovšem platí, že pokud to jde, tak na místě parametrů uvádíme (konstantní) referenci a na místě návratové hodnoty pokud to jde opět referenci (pro případ, že vracíme vně funkce existující proměnnou) a nebo vracíme hodnotou (pro případ, kdy vracíme novou, vně funkce neexistující hodnotu).





Poslední změna 2009-12-24