Parametry a ukazatele na funkce v C++

C++ Pokročilé konstrukce v C++ Parametry a ukazatele na funkce v C++

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:)


 

  Aktivity (4)

Článek pro vás napsal Jiří Pipošiar
Avatar
Moc toho tady o sobě prozrazovat nebudu, raději:)) Takže momentálně žiju v Klatovech(velkoměsto v západních Čechách), a 22.5.2003 jsem zde i odmaturoval na střední průmyslové škole. No a novinka - byl jsem přijat na Západočeskou univerzitu - Faku...

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 


Miniatura
Předchozí článek
Reference
Miniatura
Všechny články v sekci
Pokročilé konstrukce v C++

 

 

Komentáře
Zobrazit starší komentáře (11)

Avatar
Kit
Redaktor
Avatar
Kit:

... aby toho v ní nebylo tak málo

Třídy přece mají být jednoduché. Pokud někdo dělá anemické třídy, tak je to jeho chyba. Kvalitně udělaná třída může mít klidně i jen jeden atribut a přesto je plnohodnotná.

Odpovědět 13.10.2013 10:01
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Nevím kam jsi se díval, ale kdybys měl přehled, tak bys věděl, že v businessu jednoznačně dominuje Java a to již docela dlouho. Vývoj v ní je totiž mnohem levnější.

Odpovědět  +3 13.10.2013 10:09
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Kit
Zdeněk Pavlátka:

Aby toho v ní nebylo tak málo a byla k něčemu užitečná. Měl jsem tím na mysli že by se měla starat jen o jednou věc, a přitom nemusela existovat jen kvůli jedné nebo dvěma krátkým funkcím.

Odpovědět 13.10.2013 11:37
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Kit
Redaktor
Avatar
Odpovídá na Zdeněk Pavlátka
Kit:

Mám spoustu tříd se dvěma krátkými metodami a vůbec nemám pocit, že by nebyly užitečné.

Odpovědět 13.10.2013 11:58
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Kit
Redaktor
Avatar
Odpovídá na Zdeněk Pavlátka
Kit:

Třída by měla mít délku zhruba 20-60 řádek. Delší třídy svědčí o chybě v dekompozici, kratší zase o přílišné rozdrobenosti. Když k tomu přidám test, délka se prodlouží na dvoj- až trojnásobek. Ideální délka zdrojáku pro běžnou editaci.

Odpovědět  ±0 13.10.2013 13:41
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Kit
Zdeněk Pavlátka:

Jsem zvyklý na C++, kde se definice oddělují od deklarací, takže ty řádky mi moc neřeknou.

Odpovědět 13.10.2013 13:52
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Kit
Redaktor
Avatar
Odpovídá na Zdeněk Pavlátka
Kit:

Jedna třída bývá obvykle v jednom souboru včetně deklarací a definic. Ve vyšších jazycích a zejména v OOP je toto spojení obvyklé kvůli konzistenci. Jinak by to ani nešlo, protože v nich nebývá k dispozici include ani makra.

Odpovědět 13.10.2013 13:59
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Kit
Zdeněk Pavlátka:

V C++ jsou pro každou třídu dva soubory: neco.h(deklarace) a neco.cpp(definice).

Odpovědět 13.10.2013 14:27
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Kit
Redaktor
Avatar
Odpovídá na Zdeněk Pavlátka
Kit:

O tom vím a také vím, že je to docela pakárna. Naštěstí se tento způsob v novějších jazycích opouští, protože je častým zdrojem chyb.

Odpovědět 13.10.2013 14:31
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
nf fn
Člen
Avatar
Odpovídá na David Čápka
nf fn:

Ano, ale C++ je zatím stále před C#

 
Odpovědět 14. července 12:09
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 21. Zobrazit vše