Programujeme v C++ (4) - Premenné a funkcie

Programujeme v C++ (4) - Premenné a funkcie
16.02.2009 23:10 | Články | Miroslav Bendík

Dnešná časť seriálu o programovaní v C++ bude venovaná premenným a funkciám. Čaká nás podrobný popis dátových typov. Popíšeme si čo sú to smerníky a ako sa používajú. U funkcií si ukážeme preťažovanie, používanie argumentov a návratových hodnôt.

Premenné

Pod pojmom premenná si môžeme predstaviť miesto v pamäti kde sa ukladá určitý typ informácie. Pri deklarácii premennej musíme určiť o aký typ premennej ide aby kompilátor vedel vyhradiť miesto potrebné na uloženie premennej. Pozor si treba dávať na to, že nie všetky platformy majú veľkosti základných dátových typov rovnaké. Základné typy ktoré môžeme v C používať sú void, char, int, float a double. Pre typy char a int môžeme použiť modifikátory signed a unsigned. Typ int podporuje ešte navyše modifikátory short a long. K týmto základným dátovým typom z C pribudli v C++ 2 nové typy a to bool a wchar_t.

Premenné môžeme podobne ako funkcie deklarovať alebo definovať. Zatiaľ sme v príkladoch pri deklarácii zároveň aj definovali danú premennú. Ak chceme premennú len deklarovať musíme uviesť kľúčové slovo extern ktoré dáva kompilátoru vedieť, že táto premenná bude definovaná niekde v programe. Deklarácie sa používajú v prípade, keď je potrebné zdieľať jednu premennú medzi viacerými zdrojovými súbormi pri oddelenej kompilácii.

/* Deklarácia a definícia premennej */
int a;

/* Deklarácia a definícia premennej  *
 * a jej inicializácia na hodnotu 0  */
int b = 0;

/* Deklarácia premennej */
extern int c;

Deklarácie premenných ktoré majú byť zdieľané medzi rôznymi zdrojovými súbormi sa vkladajú podobne ako deklarácie funkcií do hlavičkových súborov. Potom sa tieto hlavičkové súbory včleňujú do všetkých zdrojových kódov kde sa daná premenná alebo funkcie definuje, alebo používa. Definícia premennej sa musí nachádzať na jedinom mieste.

Vlastnosti a použitie dátových typov

void
Prázdny dátový typ. Najčastejšie sa používa u funkcií ktoré nevracajú žiadnu hodnotu alebo u smerníkov.
char
Do premennej typu char môžeme uložiť 1 Byte (na väčšine architektúr 8 bitov). Z toho vyplýva, že táto premenná môže nadobúdať 256 rôznych hodnôt. Modifikátor signed alebo unsigned určuje či môže nadobúdať aj záporné hodnoty. Štandard C++ neurčuje ktorý typ sa má použiť v prípade, že nebol použitý modifikátor.
int
Premenné typu int môžu obsahovať celé čísla. Veľkosť int môžeme zmeniť modifikátormi short a long. V prípade, že použijeme modifikátor unsigned zväčší sa nám rozsah premennej v kladnom smere.
float, double
Premenné typu float a double slúžia na ukladanie čísel s pohyblivou desatinnou čiarkou. Typ double má dvojnásobnú presnosť oproti typu float.
bool
Typ bool môže nadobúdať len hodnoty 1 alebo 0.
wchar_t
Tento typ slúži podobne ako char na ukladanie 1 znaku. Oproti char je väčší a umožňuje ukladanie medzinárodných znakov.

Veľkosť ktorú zaberá dátový typ alebo premenná sa dá zistiť operátorom sizeof.

#include <iostream>

#define velkost(parameter) \
  cout<<\
    "Velkost " << \
    #parameter << \
    " je "<<sizeof(parameter)<<endl

using namespace std;

int main(int argc, char *argv[])
{
	char a = '\0';
	// zistenie veľkosti ktorú zaberá premenná
	cout<<"Velkost " << "a" << " je "<<sizeof(a)<<endl;
	// zistenie veľkosti ktorú zaberá dátový typ
	cout<<"Velkost " << "char" << " je "<<sizeof(char)<<endl;

	// pomôžeme si preprocesorom
	velkost(short int);
	velkost(int);
	velkost(long int);
	velkost(float);
	velkost(double);

	velkost(wchar_t);
	velkost(bool);

	velkost(char[10]);
	velkost(int[2][2]);
}

Pretypovanie

Pod pojmom pretypovanie rozumieme konverziu jedného dátového typu na iný dátový typ. V niektorých prípadoch dochádza k automatickému pretypovaniu bez zásahu užívateľa (implicitné pretypovanie).

Implicitné pretypovanie

Pri implicitnom pretypovaní dochádza ku konverzii medzi typmi bez nutnosti použitia konverzných operátorov.

int a = 3;
// konverzia int na short int
short b = a;
// konverzia short int na float
float c = b;
// konverzia float na int
int d = c;

Pri konverzii číselných typov z presnejších na menej presné (napr. z double na float) dochádza k zníženiu presnosti. Niektoré kompilátory pri takejto konverzii môžu hlásiť varovanie. Pre potlačenie varovania je možné použiť explicitné pretypovanie konverzným operátorom.

Explicitné pretypovanie

V niektorých prípadoch kompilátor nevie použiť automatickú konverziu (napr. z void * na char *). V takýchto prípadoch je nutné kompilátoru určiť dátový typ na ktorý má pôvodný dátový typ konvertovať. Existujú 2 spôsoby zápisu pretypovania. V C môžeme použiť len starý spôsob. U C++ sú správne oba spôsoby pretypovania.

int a = (int)3.1; // štandardné pretypovanie z C
int b = int(3.1); // funkcionálny zápis (C++)

Existujú aj ďalšie spôsoby pretypovania. Zatiaľ si vystačíme s tými, ktoré som spomenul.

Konštanty

Doteraz sme konštanty vytvárali pomocou preprocesoru (define). Pri použití define preprocesor nahradí všetky výskyty makra našou konštantou. Po tejto náhrade sa v zdrojovom kóde spracovanom preprocesorom objaví naša konštanta všade tam, kde sa na ňu odvolávame. Zároveň sa v rovnakom množstve táto konštanta objaví vo výslednom programe čo pri použití väčších konštánt môže byť problém.

Riešením je vytvorenie konštantnej „premennej”. To dosiahneme vložením kľúčového slova const pred názov premennej (alebo dátového typu). Ďalej v programe nebude možné túto premennú modifikovať.

const int a = 3;
int const b = 3;

V C nebolo možné použiť konštantnú premennú na určenie veľkosti poľa. Z toho dôvodu bolo nutné takúto konštantu definovať pomocou makra. Tieto obmedzenia v C++ neplatia a preto je možné bez obáv použiť konštantnú premennú.

const int a = 3;
int pole[a];

Smerníky

V C++ sa podobne ako v iných nízkoúrovňových programovacích jazykoch nachádza dátový typ smerník. V prvom rade upozorňujem, že používaniu smerníkov je lepšie sa kvôli prehľadnosti vyhnúť.

Pole

Pole je v C++ uložené v podobe smerníka na prvý prvok poľa. K prvkom je možné teda pristupovať buď pomocou indexu alebo pomocou smerníka. V nasledujúcom príklade si ukážeme 4 spôsoby ktorými sa dá toto pole vypísať.

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	int pole[4] = {4, 3, 2, 1};
	/* Výpis */
}

Nasledujúci kód je možné dosadiť namiesto komentára "Výpis".

	for (int i = 0; i < 4; ++i)
	{
		cout<<"Prvok "<<i<<" je "<<pole[i]<<endl;
	}

V tomto príklade som použil operátor []. Ďalej si ukážeme ako napísať kód ktorý by mal rovnaký efekt s použitím smerníkov. Nasledujúce príklady slúžia len na pochopenie smerníkovej aritmetiky. Ak je to možné je lepšie použiť operátor [].

	for (int i = 0; i < 4; ++i)
	{
		cout<<"Prvok "<<i<<" je "<<*(pole + i)<<endl;
	}

Namiesto použitia indexu je možné využiť fakt, že typ pole je v podstate smerník na prvý prvok. Preto sa dá k prvkom dostať pomocou operátora dereferencie (*).

Na typ smerník je možné používať smerníkové operácie.

Smerníkové operácie
OperáciaPopis
* smerníkOperátor dereferencie. Pomocou tohto operátora sa získajú dáta uložené na adrese na ktorú ukazuje smerník.
smerník + nPosun smerníka o n. Ak napríklad smerník ukazuje na 0. prvok poľa potom po operácii smerník + n bude ukazovať na n-tý prvok poľa. Ak chceme zvýšiť smerník o 1 môžeme použiť smerník++.
smerník - nPosun smerníka o n prvkov smerom k začiatku.
smerník - smerníkVýsledkom rozdielu smerníkov je vzdialenosť miest na ktoré ukazujú. Ak napríklad prvý smerník ukazuje na 5. prvok poľa a druhý smerník na 3. prvok poľa ich rozdiel je 2. Smerníky musia patriť do toho istého poľa ináč bude výsledok kvázi-náhodný.
	int *smernik;
	smernik = pole;
	for (int i = 0; i < 4; ++i)
	{
		cout<<"Prvok "<<i<<" je "<<*smernik<<endl;
		smernik++;
	}

V tomto príklade je najskôr definovaný smerník na typ int. Tento typ je veľmi podstatný pretože pri smerníkových operáciách musí program poznať veľkosť prvku na ktorý smerník ukazuje. Potom do smerníka uložíme adresu prvého prvku poľa a. V cykle najskôr vypisujeme číslo uložené na adrese smerníka a potom ho zvyšujeme.

	int *smernik;
	smernik = &pole[0];
	for (int i = 0; i < 4; ++i)
	{
		cout<<"Prvok "<<i<<" je "<<*smernik<<endl;
		++smernik;
	}

Opakom operátora dereferencie (*) je operátor získania adresy (&). Použitím tohto operátora na premennú získame jej adresu v pamäti. Tento príklad je prakticky identický s predchádzajúcim. Premenná pole je smerník na prvý prvok poľa čo je ekvivalentné so zápisom &pole[0].

Operátory new a delete

Pri programovaní si málokedy vystačíme s automatickými premennými. Pre prípady keď potrebujeme dynamicky vytvárať a rušiť premenné tu máme operátory new a delete.

Alokovanie miesta pre 1 premennú

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	int *cislo = new int;
	*cislo = 50;
	cout<<*cislo<<endl;
	delete cislo;
}

Použitie operátora new je veľmi jednoduché. Stačí volať new názov typu a operátor new vráti smerník na alokovanú pamäť. V prípade neúspechu napríklad pre nedostatok pamäte vyvolá výnimku. Takto vytvorené premenné sa automaticky nerušia preto je potrebné po poslednom použití pamäť upratať operátorom delete.

Alokovanie miesta pre jednorozmerné pole

int *cisla = new int[10];

// naplníme dátami
for (int i = 0; i < 10; i++)
{
	cisla[i] = i;
}

// vypíšeme
for (int i = 0; i < 10; i++)
{
	cout<<cisla[i]<<endl;
}
delete[] cisla;

Veľmi podobným spôsobom je možné alokovať a dealokovať jednorozmerné pole. Operátor delete[] sa postará o upratanie každého prvku poľa.

Alokovanie miesta pre dvojrozmerné pole

int **cisla = new int*[10];
for(int i = 0; i < 10; i++)
{
	cisla[i] = new int[5];
}

// uložíme
cisla[0][0] = 88;
// prečítame
cout<<cisla[0][0]<<endl;

for(int i = 0; i < 10; i++)
{
	delete[] cisla[i];
}
delete[] cisla;

V tomto príklade alokujeme pole čísel s rozmermi 10x5 prvkov. Dvojrozmerné pole nie je možné alokovať tak jednoducho ako jednorozmerné pole. Alokácia dvojrozmerného poľa sa skladá z dvoch častí. Najskôr musíme alokovať pole smerníkov. Potom každému prvku poľa smerníkov priradíme smerník na pole čísel.

Pri upratovaní postupujeme postupujeme v opačnom poradí. Najskôr upratujeme polia čísel a nakoniec pole smerníkov.

Funkcie malloc a free

Funkcie malloc a free sú používané prevažne v čistých C programoch. Tieto funkcie majú na starosti iba alokáciu a dealokáciu zatiaľ čo operátory new a delete sa starajú aj o inicializáciu a deinicializáciu prvkov. V nasledujúcom príklade je ukážka použitia funkcie malloc na vytvorenie jednorozmerného poľa.

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	int *cisla = (int *)malloc(sizeof(int) * 10);
	// kontrolujeme či sa alokácia podarila
	if (cisla == NULL)
	{
		cout<<"Nedostatok pamate"<<endl;
		return -1;
	}

	/* ... */

	free(cisla);
	return 0;
}

Na prvom riadku funkcie main je alokácia 10 prvkov typu int. Funkcia malloc má jediný argument a tým je veľkosť alokovaného priestoru v bytoch. Ak chceme teda priestor pre 10 prvkov typu int vynásobíme veľkosť typu int počtom prvkov (10). Návratovou hodnotou malloc je smerník na typ void (void *). My ale potrebujeme smerník na typ int preto použijeme pretypovanie na typ int *.

Po pokuse o alokáciu odtestujeme či alokácia prebehla úspešne. V prípade neúspešnej alokácie bude smerník vrátený funkciou malloc NULL. Po ukončení práce s alokovanou pamäťou treba po sebe upratať volaním funkcie free.

Referencie na premennú

Výklad smerníkov by nebol kompletný keby sme sa nezmienili o referenciách. Pod pojmom referencia môžeme rozumieť odkaz na inú premennú. Referencie síce nemajú so smerníkmi takmer nič spoločné, ale zvyknú sa požívať na podobné účely. Nasledujúci príklad by mal objasniť to ako referencie fungujú.

int a = 3;
int &b = a;
cout << a << endl;
b = 4;
cout << a << endl;

V prvom riadku definujeme premennú a a inicializujeme jej hodnotu na 3. V ďalšom riadku vytvoríme referenciu s názvom b odkazujúcu na a. Odteraz vždy keď budeme pracovať s b bude to ekvivalentné práci s premennou a. Vždy keď kompilátor narazí na referenciu použije na danom mieste skutočnú premennú na ktorú referencia odkazuje. Z toho vyplýva, že vo výslednom spustiteľnom kóde sa už referencie nebudú vyskytovať. Na rozdiel od smerníkov teda nezaberajú v pamäti žiadne miesto. Ak modifikujeme referenciu v skutočnosti pracujeme s pôvodnou premennou a preto sa zmena prejaví aj na pôvodnej premennej.

Funkcie

S funkciami sa stretávame už od začiatku seriálu. Základnou funkciou ktorú v C++ programe musíme mať je funkcia main. O to aby bola automaticky volaná pri štarte programu sa stará samotný prekladač. Je možné použiť 2 formy funkcie main.

int main() {}

a

int main(int argc, char *argv[]) {}

Prvá forma je vhodná ak náš program neprijíma žiadne voľby z terminálu. Argument argc obsahuje počet volieb zadaný pri spustení programu. Druhým argumentom sú samotné voľby s ktorými sme program spúšťali. Počet volieb nikdy nebude nulový pretože prvou voľbou je vždy názov programu. Ak teda zavoláme náš program ./program --help bude v premennej argc hodnota 2 a argv bude obsahovať hodnoty ["./program", "--help", NULL]. Vďaka tomu, že poslednou položkou v argv je NULL je možné prechádzať voľbami bez toho aby sme potrebovali vedieť počet volieb. Tento poznatok sa dá využiť napr. pri volaní funkcie execvp.

Pri deklarácii funkcie poskytujeme kompilátoru návratovú hodnotu funkcie, meno funkcie, počet a typ argumentov.

návratová_hodnota názov_funkcie(zoznam argumentov);

Návratovou hodnotou funkcie môže byť akýkoľvek dátový typ. V prípade, že funkcia nemá návratovú hodnotu musíme uviesť dátový typ void. Z tela funkcie sa vraciame kľúčovým slovom return za ktorým nasleduje hodnota ktorú funkcia vracia. Ak je funkcia bez návratovej hodnoty voláme return bez argumentov.

V zozname argumentov uvádzame typ argumentov ktoré funkcia má. Ich názov môže byť rôzny v deklarácii a definícii. Je dokonca možné názov úplne vynechať. V tele funkcie môžeme k argumentom pristupovať pod tým názvom ktorý sme uviedli v definícii. Ak sme žiaden názov v definícii neuviedli nie je možné sa na tento argument odkazovať. To je vhodné v prípade, že funkcia má viac argumentov než využijeme a nechceme aby kompilátor pri prebytočných argumentoch vypisoval varovanie.

void funkcia(int argument);
void funkcia(int a)
{
// v tele funkcie je možné použiť argument a
}
void funkcia(int)
{
// v tele funkcie nie je možné použiť nepomenovaný argument
}

Funkcie bez argumentov majú zoznam argumentov buď prázdny alebo ako jediný argument je uvedený void.

void funkcia();
void funkcia(void);

C++ podporuje aj variabilný počet argumentov. V takomto prípade musí byť ... ako posledný argument. Všetky argumenty pred ... musia byť povinné.

void funkcia(...);
void funkcia(int argument, ...);

Pozor treba dávať na to, že zápis variabilných argumentov je rôzny pre C a C++. Zatiaľ čo v C++ sa funkcia bez argumentov zapisuje s prázdnym zoznamom argumentov alebo jediným argumentom void v C musíme uviesť void inak bude funkcia považovaná za funkciu s variabilnými argumentmi.

Preťažovanie funkcií

Kompilátor používa na jednoznačnú identifikáciu funkcie jej názov a zoznam argumentov. Je možné teda napísať niekoľko funkcií s rovnakým názvom ale s rôznymi argumentmi. Kompilátor podľa uvedených parametrov rozpozná ktorú funkciu má zavolať.

#include <iostream>

using namespace std;

void funkcia(int)
{
	cout<<"Volame funkciu s parametrom int"<<endl;
}

void funkcia(double)
{
	cout<<"Volame funkciu s parametrom double"<<endl;
}

/*
void funkcia(float)
{
	cout<<"Volame funkciu s parametrom float"<<endl;
}
*/

int main(int argc, char *argv[])
{
	funkcia(2);
	funkcia(2.0);
	funkcia(2.0f);
}

Pri prvom volaní sa zavolá variant funkcie ktorá prijíma ako argument celé číslo (int). Pri druhom volaní je parametrom double a preto sa zavolá funkcia ktorá prijíma ako argument double. Pri treťom volaní je desatinné číslo zapísané ako float čo nezodpovedá žiadnej funkcii. Kompilátor má ale funkciu ktorá prijíma ako argument double čo je podobný typ ako float. Preto nastane automatická konverzia z float na double a tretie volanie prebehne rovnako ako druhé. Ak by sme odkomentovali funkciu ktorá má ako argument float nedošlo by k žiadnej konverzii a zavolala by sa funkcia s argumentom float.

Funkciu nie je možné preťažiť na základe návratovej hodnoty. Pri volaní takejto funkcie by dochádzalo k nejednoznačnosti v prípade, že by sa nevyužívala návratová hodnota a kompilátor by nevedel rozhodnúť ktorý tvar funkcie sa má použiť.

Implicitné hodnoty atribútov

Pri programovaní sa často vyskytuje situácia, kedy potrebujeme napísať funkciu, ktorú často voláme s rovnakou hodnotou niektorých argumentov. Jazyk C++ umožňuje definovať implicitné hodnoty, ktoré sa dosadia v prípade, že tieto argumenty pri volaní vynecháme. V nasledujúcom príklade je ukázané využitie implicitných hodnôt argumentov vo funkcii randomize.

#include <cstdlib>
#include <ctime>
#include <iostream>

using namespace std;

void randomize(int seed = time(NULL))
{
	srand(seed);
}

int main(int argc, char *argv[])
{
	randomize();
	cout<<rand()<<endl;
	randomize(100);
	cout<<rand()<<endl;
}

Predpokladajme, že v našej aplikácii bude funkcia randomize najčastejšie volaná s argumentom seed rovným aktuálnemu času. Preto môžeme deklarovať funkciu randomize s implicitnou hodnotou argumentu seed. Pri každom volaní funkcie randomize bez argumentov sa atribútu seed priradí hodnota vrátená funkciou time. Ak chceme vlastnú hodnotu argumentu seed jednoducho zavoláme túto funkciu s hodnotou tohto argumentu. Implicitnou hodnotou argumentu môže byť nie len funkcia ale akýkoľvek výraz ktorého návratová hodnota sa dá priradiť argumentu..

Funkcia môže mať ľubovoľný počet štandardných a implicitných argumentov. Všetky štandardné argumenty musia byť uvedené pred implicitnými argumentmi.

Smerníky v argumentoch funkcií

Pri volaní funkcie s argumentmi v jazyku C si musíme uvedomiť, že nikdy nepracujeme s týmto argumentom, ale s jeho kópiou. Ak by sme chceli napísať funkciu na výmenu 2 čísel nasledujúcim spôsobom určite by sme neuspeli.

void vymen(int prve_cislo, int druhe_cislo)
{
	int pom;
	pom = prve_cislo;
	prve_cislo = druhe_cislo;
	druhe_cislo = pom;
}

V skutočností sa pri volaní tejto funkcie vytvoria lokálne premenné prve_cislo a druhe_cislo, ktoré sú kópiou pôvodných premenných. Aby sme toto obmedzenie obišli môžeme použiť ako argument smerník. Pri takomto volaní sa síce hodnota ktorú posielame funkcii skopíruje, ale to nás nemusí trápiť. Nemodifikujeme totiž smerník ale dáta na adrese na ktorú tento smerník ukazuje. Takto môžeme pristupovať k rovnakým dátam aj vo vnútri funkcie. Modifikovaná funkcia vymen môže vyzerať takto:

void vymen(int *prve_cislo, int *druhe_cislo)
{
	int pom;

	// uloženie hodnoty na adrese prve_cislo do premennej pom
	pom = *prve_cislo;

	// uloženie hodnoty na adrese druhe_cislo na adresu prve_cislo
	*prve_cislo = *druhe_cislo;

	// uloženie hodnoty premennej pom na adresu druhe_cislo
	*druhe_cislo = pom;
}

Volanie funkcie vymen ak chceme vymeniť premenné a a b vyzerá takto:

vymen(&a, &b);

Použitie referencií vo funkciách

Kód funkcie na výmenu 2 premenných pomocou referencií sa oproti smerníkom značne zjednodušil.

void vymen(int &prve_cislo, int &druhe_cislo)
{
	int pom;
	pom = prve_cislo;
	prve_cislo = druhe_cislo;
	druhe_cislo = pom;
}

Pričom volanie tejto funkcie bude vyzerať takto:

vymen(a, b);

Použitie referencie je oproti smerníkom kratšie, jednoduchšie a menej náchylné na chyby. Nevýhodou oproti smerníkom je to, že nie je hneď na prvý pohľad jasné či pracujeme so skutočnou premennou alebo len s odkazom.

Okrem argumentov môže byť referencia aj návratovou hodnotou funkcie. Treba dať pozor na to, že lokálne premenné sa po návrate z funkcie automaticky zrušia preto nie je možné vrátiť referenciu na lokálnu premennú.

Optimalizácia volania funkcií referenciami

Ako už bolo spomínané pri volaní funkcie s argumentmi sa hodnoty argumentov kopírujú. Jedinou výnimkou sú referencie kde sa vytvorí odkaz na daný argument. Kopírovanie väčších premenných môže byť časovo náročné. Preto sa zvyknú používať referencie namiesto bežných argumentov aj keď v skutočnosti nie sú pri danej funkcii potrebné (nemodifikujeme ich vo funkcii).

Ochrana referencie pred modifikáciou

Pri použití referencií si treba dávať pozor na to, že sa dajú vo funkcii modifikovať. Ak píšete funkciu ktorá by nemala modifikovať referenciu je najlepšie za deklaráciu argumentu pridať kvalifikátor const ktorý zabráni jej modifikácii vo funkcii. Pri používaní takéhoto zápisu je hneď pri pohľade na hlavičku funkcie jasné, či môže byť argument funkciou modifikovaný alebo nie.

Smerníky na funkcie

Smerníky funkcie sa využívajú hlavne pri callback funkciách. Callback funkcia je funkcia ktorá sa posiela ako parameter inej funkcii. Tá je potom buď synchrónne alebo asynchrónne volaná z inej časti kódu.

Synchrónne volanie callback funkcie

Pri synchrónnom volaní callback funkcie hlavný program volá knižničnú funkciu ktorej argument je smerník na funkciu. Knižničná funkcia môže volať túto funkciu z aplikácie. Po dokončení behu knižničnej funkcie sa vracia riadenie do užívateľskej aplikácie.

Asynchrónne volanie callback funkcie

Asynchrónne volania sú typické pre nízkoúrovňové grafické knižnice. Užívateľ si u knižnice cez registračnú funkciu zaregistruje svoju callback funkciu. Program pokračuje v behu v knižničnej funkcii a ak nastane určitá udalosť (napr. stlačenie klávesy) zavolá callback funkciu z užívateľskej aplikácie ktorá túto udalosť spracuje.

Definícia smerníka na funkciu vyzerá nasledovne:

návratová hodnota (*názov_smerníka)(zoznam argumentov);
// smerník na funkciu

návratová hodnota (*názov_smerníka[veľkosť poľa])(zoznam argumentov);
// pole smerníkov na funkciu

So smerníkom na funkciu sa pracuje rovnako ako s funkciou.

názov_smerníka();
názov_smerníka[index poľa]();

Použitie spätného volania funkcie vyzerá nasledovne:

#include <iostream>

using namespace std;

void spusti(void (*funcPointer)())
{
	funcPointer();
}

void ahojSvet(void)
{
	cout << "Ahoj svet" << endl;
}

int main(int argc, char *argv)
{
	spusti(ahojSvet);
	return 0;
}

Záver

V nasledujúcej časti sa už môžete tešiť na štruktúrované dátové typy. Popíšeme si štruktúry, uniony a začneme sa zaoberať už aj triedami a modifikátormi prístupu (public, private).

    • re: 17.02.2009 | 09:55
      Avatar borg Fedora  Administrátor
      na pretypovanie mame v C++ ine operatory. a miesto makier je lepsie pouzit inline funkcie.
      • Re: re: 17.02.2009 | 10:17
        Avatar Miroslav Bendík Gentoo  Administrátor
        Podrobnejšie sa pretypovaním budem zaoberať až pri objektoch. A inline funkcie áno ale v tomto prípade bolo makro použité len na ukážku koľko miesta v pamäti zaberajú jednotlivé dátové typy. Bez makra by bol tento kód o niečo dlhší (musel by som funkcii okrem dátového typu posielať aj dátový typ v ako string zatiaľ čo u makra si môžem dovoliť polslať ho makru ako parameter pričom makro ho prevedie na string).
        • Re: re: 17.02.2009 | 21:31
          Avatar Michal Nánási Ubuntu 11.04  Používateľ
          Ma zmysel davat inline? Vsak kompilator ti to aj tak optimalizuje...
          Hi! I'm a .signature virus! Copy me into your ~/.signature to help me spread!
          • Re: re: 17.02.2009 | 21:39
            Avatar borg Fedora  Administrátor
            makra sa tazsie debuguju na rozdiel od inline funkcii,
            • Re: re: 17.02.2009 | 22:46
              Avatar Michal Nánási Ubuntu 11.04  Používateľ
              Myslel som nepouzivat inline vobec (makra samozrejme tiez).
              Hi! I'm a .signature virus! Copy me into your ~/.signature to help me spread!
          • Re: re: 17.02.2009 | 22:01
            Avatar jv openSuSE 11  Používateľ
            S tym inline si vsak sam mozes optimalizaciu riadit. Neviem o kompilatore, ktory optimalizuje call na vnoreny kod.
            • Re: re: 17.02.2009 | 22:45
              Avatar Michal Nánási Ubuntu 11.04  Používateľ
              Myslis ze vlozi kod funkcie namiesto volania? To robi vacsina kompilatorov. Teda nie vzdy, ale tam, kde to uznaju za vhodne. Napriklad gcc pri -O1 inlinuje male funkcie.
              Hi! I'm a .signature virus! Copy me into your ~/.signature to help me spread!
    • smerniky 17.02.2009 | 11:14
      Jan   Návštevník
      Nemozem si odpustit poznamku:
      bohuzial (tak ako vacsina knih o C++) aj tu sa vysvetluje praca s pointer-ami na priklade s "cislami".
      Pamatam si, ze ked som zacinal s C++ ani za nic na svete som nevedel pochopit aky ZMYSEL ma nieco int *smernik; (ako to funguje viac menej aj ano, ale preco to robit tak ?!) a cloveka to moze len zbytocne odradit, uz nehovoriac o tom, ze zbytocne vznika predstava a zlozitosti prace s pointer-ami.
      Vela novych knih uz postupne od toho upusta a zacina viac menej s pointer-om az neskor, ked to uz clovek viac menej aj sam pochopi zmysel a a vyznam pouzivania pointer-a (function, class).
      • Re: smerniky 17.02.2009 | 22:06
        Avatar jv openSuSE 11  Používateľ
        Ja si zase myslim opak: ak sa neskoro zacne s pointermi, clovek to ma problem pochopit pri zlozitejsich strukturach a pod. Dolezite je si uvedomovat, najma v jazyku C a v assembleri, kde je co ulozene a ako. V C++ uz trosku menej, ale len trosku. V jazykoch s garbage collectorom je dokonca tiez niekedy vyhodne mat predstavu.
    • fce 05.06.2009 | 17:04
      Jaroslav Smid   Návštevník
      Nejlepší jsou takovéto funkce:

      // to je příklad, tohle dělá kulový a ještě to spadne

      const char *data = "\x0F\x0C\x4D\x5D....";

      funkce_co_oznaci_data_za_spustitelna(data);
      void (*funkce)(void) = (void (*)(void))data;
      funkce();
    • re: 15.06.2009 | 11:29
      dusan3838   Návštevník
      wchar_t je preto vacsi, aby mohol ulozit znak aj znaky UNICODE.
      char len znaky ASCII
    • funkce objektu 17.06.2009 | 00:26
      Jarda   Návštevník
      Ještě jste mohli zmínit ukazatel na funkci objektu


      typedef navratovy_typ (TypObjektu::*TFunkceObjektu)(argumenty);

      TypObjektu *o = &objekt;
      TFunkceObjektu fo = &TypObjektu::funkce_objektu;

      (o->*fo)(argumenty);