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