Ukazatele d∞lφme do dvou zßkladnφch kategoriφ: ukazatele na data (objekty)
a ukazatele na funkce. Oba dva typy ukazatel∙ jsou specißlnφ objekty, kterΘ
obsahujφ adresy v pam∞ti. Oba dva typy ukazatel∙ majφ r∙znΘ vlastnosti,
·Φely pou╛itφ a pravidla zachßzenφ. Obecn∞ °eΦeno, ukazatelΘ na funkce
se pou╛φvajφ pro p°φstup k funkcφm a pro p°edßvßnφ funkcφ jako parametr∙
jin²m funkcφm. S t∞mito ukazateli nenφ mo╛nΘ provßd∞t ╛ßdnΘ aritmetickΘ
operace. Ukazatele na objekty m∙╛eme inkrementovat a dekrementovat tak,
jak je to p°i prohlφ╛enφ polφ nebo slo╛it∞j╣φch struktur pot°eba.
Na ukazatel na funkci se m∙╛eme dφvat jako na adresu, kde je uchovßn
provediteln² k≤d funkce, tj. na adresu, kam je p°i volßnφ funkce p°edßno
°φzenφ. Ukazatel na funkci mß typ "ukazatel na funkci s urΦit²m poΦtem
a typy parametr∙ a vracejφcφ jist² typ" (v jazyce C na parametrech
nezßle╛φ). Nap°. po deklaraci
void (*fce)(int);
je fce ukazatel na funkci s parametrem typu int, kterß
nic nevracφ.
Nßsledujφcφ konzolovß aplikace ukazuje pou╛itφ ukazatele na funkci. Program
se nejprve zeptß, zda se mß provßd∞t sΦφtßnφ nebo nßsobenφ. Podle tΘto
odpov∞di vlo╛φ do prom∞nnΘ operace ukazatel na funkci seΦti
nebo na funkci nasob. Dßle zadßme dv∞ Φφsla, kterß se pou╛ijφ jako
parametry vybranΘ funkce.
int secti(int a,
int b)
{
return a+b;
}
int nasob(int a,
int b)
{
return a*b;
}
int main(int argc,
char **argv)
{
int (*operace)
(int, int);
int x, y,
volba;
cout <<
"Budeme SΦφtat (1) nebo Nßsobit (2) ?";
do {
cin >> volba;
} while (volba
!= 1 && volba != 2);
if (volba
== 1) operace = secti;
if (volba
== 2) operace = nasob;
cout <<
"Zadej dv∞ celß Φφsla: ";
cin >> x >>
y;
cout <<
"V²sledek je " << operace(x, y) << endl;;
getch();
return 0;
}
S pou╛itφm ukazatele na funkci vytvo°te program vypisujφcφ tabulku
malΘ nßsobilky nebo obdobnou tabulku sΦφtßnφ. V²pis celΘ tabulky °e╣te
jako funkci (ukazatel na funkci provßd∞jφcφ operaci p°edßvejte jako parametr
funkce).
V∞t╣inou se budeme ale zab²vat ukazateli na data. Ukazatel na data musφ
b²t deklarovßn tak, aby ukazoval na n∞jak² konkrΘtnφ typ, a to i v p°φpad∞,
╛e tento typ je void (co╛ vlastn∞ znamenß ukazatel na cokoliv).
Je-li typ libovoln² p°eddefinovan² nebo u╛ivatelem definovan² typ
(vΦetn∞ void), potom deklarace
typ *ptr;
// pozor - neinicializovan² ukazatel
deklaruje objekt ptr typu "ukazatel na typ". Ne╛ zaΦneme
ukazatel pou╛φvat, musφme jej nejprve inicializovat. Prßzdn² ukazatel (ukazatel,
kter² na nic neukazuje) by m∞l obsahovat adresu, u kterΘ je zaruΦeno, ╛e
se bude li╣it ode v╣ech platn²ch adres v danΘm programu (adresa 0). Pro
lep╣φ Φitelnost program∙ je tato adresa (prßzdn² ukazatel) oznaΦena symbolickou
konstantou NULL (je definovßna nap°. v stdlib.h). V╣echny ukazatele
je mo╛nΘ testovat na rovnost Φi nerovnost s hodnotou NULL.
Ukazatel typu "ukazatel na void" nesmφ b²t zam∞≥ovßn s prßzdn²m
ukazatelem. P°i p°i°azovßnφ hodnot ukazatel∙ je nutno, aby ukazatelΘ byly
stejnΘho typu, nebo aby jeden z nich byl typu "ukazatel na void".
Jinak je nutno provΘst p°etypovßnφ.
Podφvejme se na p°φklad. P°edpoklßdejme, ╛e mßme pole prvk∙ typu int.
JednotlivΘ prvky pole m∙╛eme zp°φstup≥ovat pomocφ operßtoru indexace. To
ji╛ znßme z d°φv∞j╣ka.
int pole[] = {5,
10, 15, 20, 25};
int promenna = pole[3];
// hodnota 20
TotΘ╛ m∙╛eme provΘst pomocφ ukazatele:
int pole[] = {5,
10, 15, 20, 25};
int* ptr = pole;
int promenna = ptr[3];
V tomto p°φklad∞ adresa pam∞ti zaΦßtku pole je p°i°azena ukazateli
ptr.
Tento ukazatel je ukazatelem na datov² typ int (p°i deklaraci je
pou╛it symbol *). M∙╛eme deklarovat ukazatel na libovoln² celoΦφseln² datov²
typ, stejn∞ jako na objekty (struktury nebo t°φdy). Po p°i°azenφ, ukazatel
obsahuje pam∞╗ovou adresu zaΦßtku pole a tedy ukazuje na pole (jmΘno prom∞nnΘ
pole pou╛itΘ bez operßtoru indexace, vracφ adresu prvnφho prvku pole).
V tomto p°φpad∞ m∙╛eme pro p°φstup k poli pou╛φvat ukazatel i jmΘno
pole. U dynamick²ch objekt∙ je mo╛no pou╛φt pouze ukazatel (viz dßle).
Aritmetika ukazatel∙ je omezena na sΦφtßnφ, odΦφtßnφ a porovnßvßnφ ukazatel∙.
AritmetickΘ operace na ukazatelφch typu "ukazatel na typ" berou
v ·vahu dΘlku typu typ, tzn. poΦet slabik pot°ebn²ch na uchovßnφ
objektu typu typ. P°i provßd∞nφ aritmetick²ch operacφ se p°edpoklßdß,
╛e ukazatel ukazuje do pole objekt∙. Je-li tedy ukazatel deklarovßn jako
ukazatel na objekt typu typ, potom p°iΦtenφ celoΦφselnΘ hodnoty
k tomuto ukazateli jej posune o tento poΦet objekt∙ typu typ. Mß-li
typ typ velikost 10 slabik, potom p°iΦtenφm hodnoty 5 k tomuto ukazateli
jej posuneme o 50 slabik v pam∞ti dßle. Rozdφlem dvou ukazatel∙ je poΦet
prvk∙ pole, kterΘ navzßjem odd∞lujφ tyto dva ukazatele. Rozdφl ukazatel∙
mß smysl pouze v p°φpad∞, kdy oba ukazujφ do stejnΘho pole.
Hodnotu ukazatele je mo╛nΘ p°evΘst na hodnotu jinΘho typu ukazatele
za pomocφ mechanismu p°etypovßnφ:
char *str;
int *ip;
str = (char *) ip;
Obecn∞ platφ, ╛e operßtor p°etypovßnφ (typ *) p°evßdφ dan² ukazatel
na typ "ukazatel na typ".
V╣echny p°φklady konzolov²ch aplikacφ, se kter²mi jsme se zatφm seznßmili
pou╛φvajφ lokßlnφ alokaci objekt∙. Tzn. pam∞╗ po╛adovanß pro prom∞nnou
nebo objekt je zφskßna ze zßsobnφku programu. V╣echnu pam∞╗, kterou program
pot°ebuje pro lokßlnφ prom∞nnΘ, volßnφ funkcφ apod. bere ze zßsobnφku.
Tato pam∞╗ je alokovßna, kdy╛ je zapot°ebφ a uvol≥ovßna, kdy╛ ji╛ zapot°ebφ
nenφ. To obvykle nastßvß, kdy╛ program vstupuje do funkce nebo jinΘho lokßlnφho
bloku k≤du. Pam∞╗ pro v╣echny lokßlnφ prom∞nnΘ funkce je alokovßna p°i
vstupu do funkce. P°i v²stupu z funkce, je v╣echna pam∞╗ alokovanß funkcφ
uvoln∞na. Toto probφhß automaticky a nemßme mo╛nost urΦit jak a kdy pam∞╗
bude uvoln∞na.
Lokßlnφ alokace mß v²hody a nev²hody. V²hodou je, ╛e pam∞╗ m∙╛e b²t
v zßsobnφku alokovßna velmi rychle. Nev²hodou je, ╛e zßsobnφk mß pevnou
velikost a nem∙╛e b²t zm∞n∞n za b∞hu programu. Pokud nß╣ b∞╛φcφ program
p°eΦerpß kapacitu zßsobnφku, pak je program ukonΦen chybou.
Pro prom∞nnΘ standardnφch datov²ch typ∙ a malß pole je lokßlnφ alokace
vhodn²m °e╣enφm. Kdy╛ ale zaΦneme pou╛φvat velkß pole, struktury a t°φdy,
pak budeme pot°ebovat dynamickou alokaci v hromad∞.
Hromada zahrnuje v╣echnu volnou operaΦnφ pam∞╗ poΦφtaΦe a volnΘ mφsto
na disku (pou╛itΘ pro odklßdacφ soubory). Velikost tΘto pam∞ti b²vß obvykle
asi 100 Mslabik. Hromada je tedy podstatn∞ v∞t╣φ ne╛ zßsobnφk. S dynamicky
alokovanou pam∞tφ se ale h∙°e pracuje a je to takΘ pomalej╣φ. Neobejdeme
se ale bez nφ.
V p°edchozφch kapitolßch jsme se zab²vali konzolovou aplikacφ udr╛ujφcφ
adresß° na╣ich znßm²ch. P°i lokßlnφ alokaci struktury jsme pou╛φvali p°φkazy:
adresar zaznam;
strcpy(zaznam.jmeno
= "Karel");
strcpy(zaznam.prijmeni
= "Novak");
// atd.
P°i dynamickΘ alokaci pou╛φvßme operßtor new a v tomto p°φpad∞
bychom zapsali:
adresar* zaznam;
zaznam = new adresar;
strcpy(zaznam->jmeno
= "Karel");
strcpy(zaznam->prijmeni
= "Novak");
// atd.
Prvnφ °ßdek deklaruje ukazatel na strukturu adresar. Dal╣φ °ßdek
inicializuje tento ukazatel vytvo°enφm novΘ dynamickΘ instance struktury
adresar.
Takto vytvß°φme a zp°φstup≥ujeme objekty. V dal╣φch p°φkazech vidφme nahrazenφ
operßtoru p°φmΘho selektoru slo╛ky (.) operßtorem nep°φmΘho selektoru slo╛ky
(->).
Dynamicky vytvß°enΘ pole struktur vy╛aduje vφce prßce. V lokßlnφ verzi
pou╛ijeme nap°.
adresar seznam[3];
seznam[0].pcs = 53002;
zatφmco v dynamickΘ verzi je nutmo postupovat takto:
adresar* seznam[3];
for (int i = 0; i
< 3; i++)
seznam[i]
= new adresar;
seznam[0]->pcs =
53002;
Vidφme, ╛e musφme vytvo°it novou instanci struktury samostatn∞ pro
ka╛d² prvek pole. P°φstup k datov²m slo╛kßm pole provßdφme operßtorem indexace
a operßtorem nep°φmΘho selektoru slo╛ky.
Operßtory & a * jsou operßtory odkazu (reference) a dereference. Nap°.
&typov²_v²raz
p°evßdφ typov²_v²raz na ukazatel na typov²_v²raz. V╣imn∞te
si, ╛e identifikßtory n∞kter²ch objekt∙ (nap°. jmΘna funkcφ a jmΘna polφ)
jsou v jistΘm kontextu automaticky p°evedeny na typ "ukazatel na objekt".
Operßtor & je mo╛nΘ s takov²mito objekty pou╛φvat, ale jeho v²skyt
je vlastn∞ zbyteΦn² (a matoucφ). Operßtor & °φkß p°ekladaΦi "Dej m∞
adresu prom∞nnΘ a ne obsah prom∞nnΘ".
Ve v²razu
* typov²_v²raz
musφ b²t operand typov²_v²raz typu "ukazatel na typ",
kde typ je libovoln² typ. V²sledek dereference je typ typ.
Pokuste se urΦit co d∞lajφ nßsledujφcφ p°φkazy:
int i, *ukazatel
= &i;
cin >> i;
cout << ukazatel
<< endl << *ukazatel << endl;
P°edpoklßdejte deklarace:
int promenna;
int *ukazatel = &promenna;
int **ukazatel_na_ukazatel
= &ukazatel;
Co provßd∞jφ nßsledujφcφ p°φkazy:
**ukazatel_na_ukazatel
= 10;
*ukazatel = 10;
promenna = 10;
*ukazatel_na_ukazatel
= &promenna;
Neinicializovan² ukazatel obsahuje, stejn∞ jako jinß neinicializovanß prom∞nnß,
nßhodnou hodnotu. Pokus o pou╛itφ neinicializovanΘho ukazatele m∙╛e zp∙sobit
havßrii programu. V mnoha p°φpadech provßdφme souΦasnou deklaraci a inicializaci
ukazatele. Nap°.
adresar* pom = 0;
Pokud se pokusφme pou╛φt ukazatel NULL (ukazatel nastaven² na NULL
nebo nulu), je automaticky procesorem detekovßn pokus o nedovoln² p°φstup
k pam∞ti (program je ukonΦen, ale nemohou nastat r∙znΘ nßhodnΘ chyby).
Pov╣imn∞te si je╣t∞ zßpisu operßtoru * (p°esn∞ji °eΦeno pou╛φvßnφ mezer
p°ed a za *). Je jedno zda zapisujeme
int* i;
int *i;
int * i;
V╣echny tyto zßpisy jsou ekvivalentnφ a je jedno, kterou mo╛nost zvolφme.
Je ale vhodnΘ jednu z t∞chto mo╛nostφ si vybrat a dodr╛ovat ji.
Zadßnφ 4 z kapitoly 15 nynφ zm∞nφme na pou╛itφ dynamickΘ alokace. Nß╣ program
se zm∞nφ takto (hlaviΦkov² soubor z∙stane beze zm∞ny):
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#pragma hdrstop
#include "structur.h"
//---------------------------------------------------------------------
#pragma argsused
void zobrazZaznam(int,
adresar adrZaz);
int main(int argc,
char **argv)
{
adresar* seznam[3];
for (int i
= 0; i < 3; i++)
seznam[i] = new adresar;
cout <<
endl;
int index
= 0;
do {
cout << "JmΘno: ";
cin.getline(seznam[index]->jmeno, sizeof(seznam[index]->jmeno)-1);
cout << "P°φjmenφ: ";
cin.getline(seznam[index]->prijmeni,
sizeof(seznam[index]->prijmeni)-1);
cout << "Ulice: ";
cin.getline(seznam[index]->ulice, sizeof(seznam[index]->ulice)-1);
cout << "M∞sto: ";
cin.getline(seznam[index]->mesto, sizeof(seznam[index]->mesto)-1);
cout << "PsΦ: ";
char buff[10];
cin.getline(buff, sizeof(buff)-1);
seznam[index]->psc = atoi(buff);
index++;
cout << endl;
} while (index
< 3);
clrscr();
for(int i
= 0; i < 3; i++) {
zobrazZaznam(i, *seznam[i]);
}
cout <<
"Zadej Φφslo zßznamu: ";
int zaz;
do {
zaz = getch();
zaz -= 49;
} while (zaz
< 0 || zaz > 2);
adresar pom
= *seznam[zaz];
clrscr();
zobrazZaznam(zaz,
pom);
getch();
return 0;
}
void zobrazZaznam(int
cis, adresar adrZaz)
{
cout <<
"Zßznam " << (cis + 1) << ":" << endl;
cout <<
"JmΘno: " << adrZaz.jmeno << " " << adrZaz.prijmeni <<
endl;
cout <<
"Adresa: " << adrZaz.ulice << endl;
cout <<
" " << adrZaz.mesto <<
endl;
cout <<
" " << adrZaz.psc <<
endl << endl;
}
Zm∞n∞nΘ °ßdky jsou v p°edchozφm v²pisu zobrazeny Φerven∞. Je deklarovßno
pole ukazatel∙, pro ka╛d² prvek pole je vytvo°ena instance, operßtory p°φmΘho
selektoru slo╛ky jsou v cyklu nahrazeny operßtory nep°φmΘho selektoru slo╛ky
a dvakrßt byl pou╛it operßtor dereference. Funkce zobrazZaznam z∙stala
beze zm∞ny.
Toto °e╣enφ nenφ ideßlnφ, bude je╣t∞ vylep╣eno.
Parametry funkcφ v jazyku C jsou volßny hodnotou. To znemo╛≥uje funkci
m∞nit hodnotu parametru tak, aby zm∞n∞nß hodnota byla pou╛itelnß mimo funkci,
tzn. jazyk C nepou╛φvß parametry volanΘ odkazem. Tento problΘm lze vy°e╣it
pomocφ ukazatel∙. Funkci p°edßme jako parametr adresu prom∞nnΘ, s jeji╛
hodnotou pak budeme pracovat. Nap°. nßsledujφcφ funkce zam∞≥uje hodnoty
sv²ch parametr∙:
void zamen(int *a,
int *b)
{
int c = *a;
*a = *b;
*b = *c;
}
Funkci pak vyvolßme nap°. takto:
int x = 10, y = 20;
zamen(&x, &y);
Upravte tuto funkci tak, aby v²m∞na prvk∙ prob∞hla pouze kdy╛ a
> b a aby funkΦnφ hodnota informovala zda k v²m∞n∞ do╣lo. Vyzkou╣ejte
v n∞jakΘm programu.
HlaviΦkov² soubor stdlib.h obsahuje °adu r∙zn²ch funkcφ. Jsou zde
nap°. funkce provßd∞jφcφ celoΦφselnΘ d∞lenφ a urΦujφcφ maximßlnφ a minimßlnφ
hodnotu ze dvou hodnot (div, ldiv, max a min).
Dßle tu jsou funkce provßd∞jφcφ p°evody typ∙ (atoi, atol,
ecvt,
strtod
a °ada dal╣φch). Jsou zde takΘ funkce pro generovßnφ nßhodn²ch Φφsel, s
kter²mi jsme se ji╛ seznßmili. Seznamte se s t∞mito funkcemi pomocφ ukßzkov²ch
program∙ v nßpov∞d∞.
NovΘ pojmy:
Ukazatel je prom∞nnß, kterß obsahuje adresu jinΘ prom∞nnΘ (ukazuje
na jinou prom∞nnou). Proto╛e ukazatel nemß p°φm² vztah k aktußlnφm dat∙m,
pou╛φvßme pojem nep°φm² p°φstup, kdy╛ se tφmto zp∙sobem odkazujeme na data.
Lokßlnφ alokace znamenß, ╛e pam∞╗ pot°ebnß pro prom∞nnou nebo objekt
je zφskßna ze zßsobnφku.
Zßsobnφk je oblast pracovnφ pam∞ti nastavenß programem p°i spu╣t∞nφ
programu.
Dynamickß alokace znamenß, ╛e pam∞╗ pot°ebnß pro objekt je alokovßna
v hromad∞.
Hromada je v╣echna virtußlnφ pam∞╗ na╣eho poΦφtaΦe.
Pam∞╗ alokujeme dynamicky pomocφ operßtoru new.
Dereference ukazatele znamenß zφskßnφ obsahu objektu, na kter² ukazatel
ukazuje.