Předávání proměné hodnotou a odkazem
December 29, 2006 on 9:56 pm | In Programing |Při výuce programování se dříve či později dostanete do fáze, kdy začnete učit dělit program na nějaké celky - funkce či pascalovské procedury. S tím taky souvisí docela podstatné “učivo” a sice předávání proměných hodnotou a odkazem. Předpokládám, že již jste se s tímto setkali, ale i přesto uvedu pár příkladů:
int fukce(int promena_hodnotou, int *promena_odkazem);
procedure funkce(promena_hodnotou : integer; var promena_odkazem : integer);
První fce je zapsána v C, druhá v pascalu. První proměná je předána hodnotou, druhá odkazem. Změním-li uvnitř fce proměnou promena_hodnotou, tak se to vně fce vůbec neprojeví a opačně, změním-li uvnitř fce proměnou promena_odkazem, tak změny se projeví i navenek. Toto je rozdíl mezi těmito způsoby a vlastně taky důvod proč existují dvě různé metody předávání parametrů. Jak je vidět z hlavičky první fce, pokud v jazyce C potřebujeme předat fci proměnou odkazem, tak používáme ukazatele. Jak toto řeší pascal bohužel nevím, ale předpokládám, že hodně podobně (ono to ani mnoha jinými způsoby nejde).
Podtud je vše asi jasné a jednoduché, leč i zde je malé úskalí, na které je potřeba dát si pozor. Mám třeba následující kód:
void fce(int *pole, int zvetsi_na)
{
pole = (int *)realloc(sizeof(int) * zvetsi_na);
}
Úkolem této fce je změnit velikost pole na zvetsi_na prvků. Samozdřejmě, že tento primitivní příklad by šel řešit přímo. Použiji ho proto, že je dostatečně názorný a neobsahuje ani balastní informace (rozuměj kód navíc, který by v tuto chvíli byl nepodstatný). Podstatný je teď ovšem fakt, že fce nebude pracovat správně. Mnoho začínajících programatorů netuší proč a jsou z toho dosti zmatení, ti zkušenější asi hned vidí v čem je problém. Já začnu takříkajíc “od lesa” a na různých příkladech se pokusím vysvětlit pročpak to nefunguje - a taky proč to fungovat ani nemůže.
void Dej_do_x_5(int x)
{
x = 5;
}
...
int x = 15;
Dej_do_x_5(x);
Asi mě máte za úplného vola, ale stejně si myslím, že je nejlepší začít s takto primitivním příkladem. Zde x předávám do fce hodnotou a proto se logicky žádná změna uvnitř fce, neprojeví vně fce. Mám jednoduchou otázku? Co při volání “dávám” do funkce? Ano odpověď je i teď strašně jednoduchá - číslo 15. Proveďme tedy krok k nápravě této logické chyby.
void Dej_do_x_5_druha(int *x)
{
*x = 5;
}
...
int x = 15;
Dej_do_x_5_druha(&x);
Ano, teď už bude vše fungovat dle očekávání. Proměnou x předávám odkazem, takže je vše O.K. Ovšem i zde položím otázku - co při volání dávám do fce? I když se říká, že takto se proměná předává odkazem, ve skutečnosti se vůbec nepředává proměná, ale její adresa - operátor & vrací adresu dané entity (proměné) v paměti. Tento fakt je možná zdřejmý, ale mnoho začínajících programátorů si ho neuvědomuje. V prvním příkladě jsem změnil parametr fce - a to se ve volajícím kódu neprojeví. Nikdy. V druhém příkladě jsem ovšem taktéž nezměnil parametr - tím je přeci adresa. Já jsem pouze změnil hodnotu na kterou ukazuje! A to je obrovský rozdíl. Pokud jste stále zmateni z toho co se vám snažím sdělit, tak nezoufejte. Připravil jsem si ještě dvojici obrázků, která již doufám všechny nejasnosti vyjasní.
První fce:

Druhá fce:

Pokud jste pochopili, co jsem Vám předchozím textem a obrázky snažil vysvětlit, tak bude snadné identifikovat chybu ve fci z počátku zápisku, která měnila rozměr pole. Řekněme, že ji volám takto:
void zmen_velikost(int *pole, int zvetsi_na)
{
pole = (int *)realloc(sizeof(int) * zvetsi_na);
}
int *pole = (int *)malloc(sizeof(int) * 15);
zmen_velikost(pole, 20);
Takže vytvořím pole celých čísel, pro zachování částečné analogie s předchozími obrázky, řeknemě, že leží na adrese ABC99. Dále tuto adresu předám do fce zmen_velikost. Ta tedy vytvoří novou proměnou na adrese EDC98 a přiřadí ji hodnotu ABC99. (tedy uloží adresu do proměné úplně stejně jako jsme to udělali výše). Jenže řádek pole = realloc(), uloží do této proměné novou adresu - třeba BD198. Tedy hodnota proměné na adrese EDC98 je teď BD198. Já jsem změnil hodnotu parametru fce a přepsal tak původní adresu. Tedy vně funkce se vlastně realloc vůbec neprojeví - paměti na adrese ABC99 jsem se vůbec nedotkl…
Možnosti nápravy jsou v podstatě dvě. První je pro mě přehlednější a naučil jsem se jí ještě v době, kdy jsem neměl sebemenší tušení, proč to tak musí být. Hraju-li si uvnitř fce s ukazateli (ve smyslu malloc či realloc), tak je vždy vracím. Tedy přepsal bych hlavičku fce takto:
int *zmen_velikost(int *pole, int zvetsi_na);
Hned vidíte, že fce mění ukazatel, který dostává a proto ho vrací. Druhý způsob je blizký metodě, kterou jsme provedli výše. Tam jsme měli proměnou x a tu jsme potřebovali změnit. Proto jsme do fce dali adresu x. No tady máme proměnou pole, což je sice vlastně adresa, ale nic nám nebrání předat funkci adresu adresy. Tedy adresu poté budeme moci měnit, protože ta již předaná odkazem:
void (int **pole, int zvetsi_na);Ovšem musím přiznat, že druhý způsob mě nenapadl a poradil ho až Zbyněk Sopuch. V naprostě většině případů raději použiji první způsob, protože je podle mého názoru přehlednější, leč Vy si můžete samozřejmě vybrat, který sedne lépe Vám.
{
*pole = realloc(*pole, sizeof(int) * zvetsi_na)
}
...
int *pole = (int *)malloc(sizeof(int) * 15);
zmen_velikost(&pole, 20);
No Comments yet »
RSS feed for comments on this post. TrackBack URI
Leave a comment
Powered by WordPress with Pool theme design by Borja Fernandez.
Entries and comments feeds.
Valid XHTML and CSS. ^Top^