Kurz C++, Lekce 5
Jiří Pipošiar, 18.6.2003

Předávání parametrů
Možná že si myslíte, že na předávání parametrů není nic zajímavého. Ono však je. A nejen zajímavého, ale mnohdy i důležitého.
Tak popořadě. Parametry můžeme předávat několika způsoby - hodnotou, odkazem nebo pomocí ukazatelů. Předávání parametrů odkazem probereme později, až budete vědět co jsou to reference a jak se používají.

Předávání parametrů hodnotou
Tento způsob má své výhody i nevýhody. Prvně vysvětlím, co to vlastně vůbec znamená. Připomeneme si definici funkce na následujícím výpisu kódu:

void Prohod(int a, int b)
{
   int pom;
   pom = a;
   a = b;
   b = pom;
}

Ve výpisu je funkce, která má za úkol přehodit hodnoty proměnných předaných jako parametry. Mimo to zde vidíte i pár zvláštností. Tou hlavní bude asi datový typ void specifikující návratovou hodnotu. To může samo o sobě znamenat dvě věci: funkce nebude vracet žádnou hodnotu, jako v našem případě, nebo může funkce vrátit jakoukoli hodnotu a obejít tak typovou kontrolu jazyka. Ale to jsem jen odběhl od tématu.
Vraťme se opět k předchozímu výpisu. Pokud se toto pokusíte přeložit a spustit, zjistíte, že tato funkce nefunguje tak jak má. Důvod je prostý - je to tím, že pokud předáváme parametry hodnotou, tak funkce pracuje pouze s kopiemi původních proměnných, a nemůže je tedy žádným způsobem měnit - to činí vnitřek funkce jakoby izolovaný od okolí.
Protože se hodnota kopíruje, musí se tedy alokovat nová paměť pro úschovu parametrů. Z toho tedy plyne že není vhodné používat tento způsob pro velké objekty(jako jsou například instance tříd které probereme později).
Aby tento příklad fungoval, musíme předat parametr odkazem nebo pomocí ukazatele, viz níže.

Předávání parametrů pomocí ukazatelů
Jak již sem se výše zmínil, tak aby předchozí funkce fungovala, musíme použít parametry jako ukazatele. Není na tom celkem nic složitého - jenom funkci trošku pozměníme, bude pak vypadat následovně:

void Prohod(int* a, int* b)
{
   int pom = *a;
   *a = *b;
   *b = pom;
}

Jak vidíte, změnila se i hlavička funkce - jako parametry teď vyžaduje proměnné typu ukazatele na int. Protože s ukazateli již trochu pracovat umíme, tak byste měli pochopit i to, co se děje v těle funkce. Ale pro pořádek to raději popíši. Tak na první řádce si vytváříme pomocnou proměnnou, do které hned uložíme hodnotu proměnné *a Na dalším řádku uložíme na místo, kam ukazuje prom. a, hodnotu v místě kam ukazuje prom. b. No a na posledním řádku je to obdobné, jen použíjeme pomocnou proměnnou pom, která byla vlastne rovna proměnné a.
Pokud nyní tuto funkci zakomponujete do nějakého programu, tak zjistíte, že už pracuje tak jak má. Tady je důkaz:

//lekce5.cpp
#include <iostream>
using namespace std;

void Prohod(int*, int*);

int main(void)
{
    int pocetDevcat = 10, pocetChlapcu = 20;
    cout << "Pocet devcat je " << pocetDevcat
         << " a chlapcu je " << pocetChlapcu << endl;
    Prohod(&pocetDevcat, &pocetChlapcu);
    cout << "Pocet devcat je ted " << pocetDevcat
         << " a pocet chlapcu " << pocetChlapcu << endl;
    return 0;
}

void Prohod(int* a, int* b)
{
   int pom = *a;
   *a = *b;
   *b = pom;
}

I v tomto výpise jste si určitě všimli pár věcí, které jsou vám cizí, a na které jsem v minulých dílech zapomněl poukázat.
První věcí je prototyp funkce Prohod(). Vidíte že chybí názvy parametrů. Není to chyba, jazyk C++ totiž nepotřebuje při deklaraci funkce znát jména jejích parametrů, vždyť s nimi nepracuje. Prototyp slouží jen pro kontrolu typů...
Je tu ještě další věc, kterou vidíte na prvním řádku v těle funkce main(). Tou je hromadné vytváření proměnných - snad nemusím dále rozebírat, je to zřejmé. Jen snad musim upozornit, že můžete udělat chybu při hromadném vytváření ukazatelů, zde by to vypadalo takto:
int* pA, * pB;
a ne takto:
int* pA, pB;

Druhý způsob by totiž vytvořil jeden ukazatel pA, a proměnnou pB typu int, což většinou nechceme.

Dále vidíte volání funkce Prohod(). Také vidíte, že když předáváme parametry jako ukazatele, nestačí jen pozměnit funkci. Musíme tedy této funkci ty ukazatele i předat - a to pomocí operátoru "&" jak již jistě víte z předchozích lekcí.
Ostatnímu byste již měli rozumět...

Ukazatele na funkce
Toto bude poněkud složitější část, tak ji čtěte pozorně. Ukazatele na funkce se v praxi používají zvlášť pro různé důmyslné fígly, hookování ale i cokoli jiného, vždyť víme že jazyk C++ dovoluje téměř vše:)
Takže postupně. Co to vlastně ten ukazatel na funkci je? No podle názvu nutno usoudit, že bude ukazovat na nějakou funkci - ano, je to pravda. Teď se asi divíte, že může být funkce také někde uložená. A ona je. Stejně jako jakákoliv data, pouze v jiné části paměti...
Teď zase trochu teorie. Pokud program narazí na volání funkce, uloží si aktuální pozici ve strojovém kódu, přeskočí na adresu funkce která indikuje její začátek, a začne se vykonávat tělo této funkce. Jakmile narazí na příkaz return, vrátí se vykonávání programu na tu adresu, kterou si prve uložil a bez starosti si pokračuje dál... (pozn.: Od tohoto se liší inline funkce, o kterých si povíme jindy.)

Tak jsme si v jednoduchosti řekli, co to ukazatele na funkce jsou, tak teď by neuškodil nějaký menší příklad. Bude se jednat o deklaraci ukazatele na předchozí funkci Prohod(). Poté ukazateli přiřadíme i adresu této funkce a využíjeme toho v praxi:
//...
void Prohod(int*, int*); //Deklarace funkce
//...

void (*pf)(int*, int*) = Prohod; //ukazatel na fci

int a = 100, b = 200;

(*pf)(&a, &b); //Použití ukazatele
pf(&a, &b); //To samé, jen jinak zapsané

I když to na první pohled nevypadá zrovna přátelsky, dá se to celkem jednoduše odvodit. Pokud budeme chtít vytvořit ukazatel na jednoduchou funkci (tím myslim funkci se základními datovými typy), tak většinou stačí jen opsat hlavičku funkce, změnit její jméno, přidat před něj hvězdičku a uzavřít ho do závorek. Kdyby tam ty závorky nebyly, tak by to překladač chápal jako že chceme vytvořit funkci, jejíž návratovou hodnotou bude nějaký ukazatel (v našem případě ukazatel na void).
Pokud budeme chtít ukazateli přiřadit nějakou adresu, jednoduše to provedeme pomocí jména funkce bez závorek a parametrů.Samotné jméno funkce tedy reprezentuje její adresu.
Pokud se podíváte dále do zdrojového kódu, uvidíte volání funkce Prohod pomocí jejího ukazatele. A jak vidíte tak máme dvě možnosti. Jednu, která vypadá hrozivě, ale opticky nás upozorňuje, že používáme ukazatel. Ta druhá vypadá jako normální volání funkce. A proč jsou si tedy obě dvě ekvivalentní? No jako vysvětlení zde vypíši úryvek z knihy Mistrovství v C++ (Stephen Prata, 1998):

"Historicky vzato, jedna myšlenková škola zastává názor, že je to z toho důvodu, protože pf ukazuje na funkci, je *pf funkce; a proto byste měli pro volání funkce použít (*pf)(). Druhá škola zastává názor, že jelikož jméno funkce je zároveň ukazatel na funkci, ukazatel by měl fungovat jako její jméno. Z toho důvodu byste měli pf() použít jako volání funkce. C++ zastává stanovisko, že obě formy jsou správné, i když jsou jedna s druhou logicky neslučitelné.

U složitějších a komplexnějších ukazatelů to bude už horší. Uveďme si menší příklad:
float (*(*pf1)(int*, float))(int*);

Tak zavřete pusu a čtěte dál:)) To co jste před chvíli viděli, je ukazatel na funkci se dvěma parametry (int*, float), která vrací ukazatel na další funkci s parametrem typu int* a návratovou hodnotou typu float.
A jak jsem se k tomu dostal? No pokud v tom nic nevidíte, nic se neděje, taky s tim někdy mívám problémy:) Zkusme se na to podívat, jako bysme byli kompilátor. Začneme hezky odprostředka - od jména funkce. Podíváme se napravo od jména, kde nic není (končíme jakmile narazíme na uzavírací závorku). Tak se mrkneme doleva - vidíme hvězdičku, jedná se tedy o ukazatel. Koukneme zase doprava a přečteme seznam parametrů. A zase doleva - zase hvězdička, takže návratová hodnota bude další ukazatel. Tak teda kouknem zase doprava - další seznam parametrů, hmm. No a nakonec, jak jinak, kouknem doleva, kde na nás kouká další návratová hodnota:) (pozn.: Převzato z knihy Myslíme v jazyku C++, Bruce Eckel, 2000).
Pokud to vezmeme konkrétně, tak nám z toho vznikne taková skládačka. Tak tedy ještě jednou: Začnem zase uprostřed (pf1 je ...), mrknem doprava - nic, pak doleva - "*"(...ukazatel...), a doprava (...na funkci se dvěma parametry toho a toho typu, která vrací...), a doleva (...ukazatel...), a doprava (...na funkci s jedním parametrem, která vrací), a doleva (...hodnotu typu float).
No tak vidíte, že na tom zas tak nic moc není. Prostě si to nechte přes noc v hlavince přeležet, a za nějakou dobu to klidně vysypete z rukávu:)

Kurz C++, Lekce 4 Seznam lekcí