- automatická, statická a volná paměť
- ukazatele na funkce
- získání adresy funkce
- deklarování ukazatele na funkci
- použití ukazatelů na vyvolání funkce
- úkol
Dnes se podíváme trošku na paměť, resp. její typy a poté zabrousíme zpět k funkcím – rozšíříme si obzory a na chvíli opustíme proklínané ukazatele.
Automatická, statická a volná paměť
C++ má pro datové objekty tři způsoby řízení paměti, které jsou závislé na použitém způsobu alokace paměti: automatická, statická a volná paměť. Datové objekty alokované těmito třemi způsoby se jeden od druhého liší svou dobou existence.
Automatické proměnné
To jsou proměnné definované uvnitř funkcí – rodí se automaticky, když se vyvolá daná funkce a jejich platnost (existence) končí, když funkce končí. Tento typ proměnné tedy existuje pouze tehdy, když program provádí příkazy uvnitř funkce.
Statická paměť
Statická paměť existuje za běhu celého programu. Buď ji definujeme vně funkce, nebo pomocí klíčového slova static:
static int pocet_psu = 88;
Později si o statické paměti řekneme více. Hlavním rozdílem tedy zůstává doba existence – buď za běhu celého programu (statická proměnná) nebo jen při vykonávání určité funkce (automatická proměnná).
Volná paměť
Pomocí operátorů new a delete existují proměnné po takovou dobu, dokud je chcete. Tato volná paměť se ukládá odděleně od paměti používané pro statické a automatické proměnné. Paměť můžete alokovat v jedné funkci a uvolnit v jiné. Není tedy existenčně vázána na život funkce samotné, či dokonce programu. Dobu její existence řídíte vy.
Ukazatele na funkce
Nyní se začneme orientovat zpět na funkce a ukazatele na chvíli opustíme. Ukazatele na funkce probereme jen ukázkově, toto téma si necháme na později. Funkce mají adresy, podobně jako datové objekty. Je možné napsat funkci, která přijímá adresu jiné funkce jako parametr. To první funkci umožňuje nalézt druhou a vykonat ji.
Vezměme si příklad, že chceme navrhnout funkci, která bude něco dělat, a dáme ji programátorům, aby ji použili. Každý programátor bude mít možnost použít svůj vlastní algoritmus, i když část programového kódu naší funkce zůstane. Mechanismus bude spočívat v předání adresy určitého (programátorova) algoritmu funkce, který chce programátor použít, naší funkci. Nic v tom nehledejte, pouze naší funkci předáme adresu programátorova algoritmu. Musíme pro to udělat následující:
- vzít adresu funkce
- deklarovat ukazatel na funkci
- použít ukazatel na funkci na vyvolání funkce
Získání adresy funkce
Získání adresy funkce je doslova primitivní – pouze použijeme jméno funkce bez závorek – tedy samotné jméno. Jestliže je tedy nadruhou() funkce, pak nadruhou je její adresou. Uvažujme, že máme naši funkci, která se jmenuje nadruhou (resp. nadruhou()) a ta bude právě volat tu programátorovu funkci (algoritmus) nesoucí název prog (resp. prog()):
nadruhou(prog); // předává adresu prog() do nadruhou()
Ovšem pozor, neplést si s tímto:
natreti(prog()); //předává návratovou hodnotu prog()
// do nadruhou()
Volání funkce nadruhou() umožňuje vyvolat funkci prog() z funkce nadruhou(). Volání funkce natreti() nejprve vyvolá funkci prog() a poté předá její návratovou hodnotu funkci natreti().
Deklarování ukazatele na funkci
Podobně jako ukazatele na datové typy musí i ukazatel na funkci specifikovat, na jaký typ funkce ukazatel ukazuje. Tedy deklarace funkce by nám měla říct o funkci stejné věci jako její prototyp. Klasický prototyp je zde:
double nadruhou(int); // prototyp
A zde je deklarace ukazatele na funkci:
double (*na_druhou)(int);
// na_druhou ukazuje na funkci, která přijímá
// jeden parametr typu int a navrací typ double
Obecně, chcete-li deklarovat ukazatel na funkci, vytvořte si nejprve klasický prototyp dané funkce a potom její jméno nahraďte výrazem ve tvaru (*název_ukazatele). To z název_ukazatele vytvoří ukazatel na funkci tohoto typu.
Pozor na závorky!
Závorky mají vyšší prioritu než operátor *, takže:
double (*na_druhou)(int); // na_druhou ukazuje na
// funkci, která navrací double
double *na_druhou(int); // na_druhou() je funkce,
// která navrací ukazatel-na-double
Když jste již nadeklarovali na_druhou, můžete mu přiřadit adresu odpovídající funkce:
double nadruhou(int);
double (*na_druhou)(int);
na_druhou = nadruhou;
// na_druhou nyní ukazuje na funkci nadruhou()
Zapamatujte si: nadruhou() musí odpovídat na_druhou jak popisem, tak typem návratu. Neodpovídající řazení kompilátor odmítne!
double med(double);
int vcela(int)
double (*vosa)(int);
vosa = med; // chybně - neodpovídající popis
vosa = vcela; // chybně - neodpovídající návratový typ
Tedy, když chceme, aby funkce nadruhou() použila funkci natreti(), předáme jí její adresu:
nadruhou(int pocet_znaku, double (*funkce)(int));
nadruhou(50, natreti); // funkční volání nadruhou(),
//aby použila natreti()
Jak tedy vidíte, napsání prototypů je ta složitá část, na rozdíl od předání adres, což je velmi jednoduché.
Použití ukazatelů na vyvolání funkce
Dostali jsme se ke konečné části postupu, který spočívá v použití ukazatele na vyvolání funkce, na kterou ukazuje. Nezapomínejte na deklaraci ukazatele! (*nadruhou) hraje stejnou roli jako jméno funkce:
double jedna(int);
double (*dva)(int);
dva = jedna; // dva nyní ukazuje na funkci jedna()
double x = jedna(4); // volání jedna() používá jméno funkce
double y = (*dva)(5); // volání jedna() používá ukazatel dva
Dokonce vám C++ dovoluje použít pf, jako by to bylo jméno funkce:
double z = pf(7); // také volání jedna(), pomocí
// ukazatele pf
I když je to pěkné, budeme používat tvar double y, i když vypadá drastičtěji. Poskytuje nám silnou vizuální připomínku, že programový kód využívá ukazatel na funkci. Jak vidíte, není to zase tak složité, jak se na první pohled zdá.
Možná přemýšlíte, jak mohou být pf a (*pf) navzájem ekvivaletní? Je to z toho důvodu, že pf ukazuje na funkci a *pf je funkce, a proto byste měli pro volání funkce použít (*jedna)(). Druhý názor je, že jméno funkce je ukazatel na funkci, ukazatel by tedy měl fungovat jako její jméno – z toho důvodu byste měli pf() použít jako volání funkce. C++ zastává kompromisní stanovisko, že obě formy jsou správné – povoleny, i když jsou jedna s druhou logicky neslučitelné.
Nyní si ukážeme příklad, jak použít ukazatele v programu:
#include <iostream>
using namespace std;
double vypocet(int);
double nasob(int);
// druhý argument je ukazatel na funkci typu double,
// která přebírá argument typu int
void vypis(int lines, double (*pf)(int));
int main()
{
int code;
cout << "Kolik radku programoveho kodu potrebujete? ";
cin >> code;
cout << "Tady je odhad od vypocet:
";
vypis(code, vypocet);
cout << "Tady je odhad od nasob:
";
vypis(code, nasob);
return 0;
}
double vypocet(int lns)
{
return 0.05 * lns;
}
double nasob(int lns)
{
return 0.03 * lns + 0.0004 * lns * lns;
}
void vypis(int lines, double (*pf)(int))
{
cout << lines << " radku zabere ";
cout << (*pf)(lines) << " hodin
";
}
A výsledek:
Kolik radku programoveho kodu potrebujete? 30 Tady je odhad od vypocet: 30 radku zabere 1.5 hodin Tady je odhad od nasob: 30 radku zabere 1.26 hodin
Program volá funkci vypis() dvakrát, jednou jí předává adresu funkce vypocet(), jednou nasob(). V prvním případě funkce vypis() používá vypocet() k výpočtu nezbytného počtu hodin a ve druhém případě k tomuto výpočtu používá nasob(). Návrh tohoto programu podporuje budoucí rozvoj programu. Když někdo vyvine svůj vlastní algoritmus na odhad času, nemusí přepisovat vypis(). Místo toho pouze dodá svoji vlastní funkci moje() a zajistí, že má správnou signaturu a návratový typ. Tento postup se samozřejmě používá u složitějších programových kódů, kde by bylo přepsání funkce vypis() zdlouhavější a obtížnější. Metoda ukazatele na funkci také dovoluje dotyčnému modifikovat chování funkce vypis(), dokonce i když nemá přístup k jejímu zdrojovému kódu.
Úkol č. 11
Tentokrát bude teorie. Odpovídejte stylem
- vaše odpověď
- vaše odpověď
- Vytvořte prototypy funkcí, které vyhovují následujícím popisům:
- curo() nepřijímá žádné parametry, ani nevrací hodnotu
- prom() přijímá parametr typu int a vrací hodnotu typu float
- mpg() přijímá 2 parametry typu double a navrací double
- summa() přijímá jméno pole typu long a jeho velikost jako hodnotu a navrací hodnotu typu long
- doctor() přijímá řetězový parametr (řetězec se nemá modifikovat) a navrací hodnotu typu double
- meta() přijímá jako parametr strukturu typu boss a nic nevrací
- Funkce program má návratovou hodnotu typu int. Jako parametr přijímá adresu funkce, která má jako parametr ukazatel na const char a která také navrací typ int. Napište prototyp funkce.
- Napište jednoduchý program, kde bude obsažena jedna automatická proměnná, jedna statická a jedna volná paměť (okomentováno, kde je která). Program nemusí mít uživatelský vstup – jde o kód.
Důležitým faktorem je jednoduchost!