Kurz C++ (6.) V této lekci dostáváme možná k nejobtížnější problematice programování a to k ukazatelům. Čtenář se za prvé dozví, co to je ukazatel, dále se dozví, jak ukazatel získá a jak s ním pracuje. UkazateleKaždá proměnná, kterou v programu deklarujeme, se nachází v paměti počítače, která je rozdělena na buňky o velikosti jednoho bytu, tedy 8 bitů. Každá buňka má nějaké číslo, pomocí kterého ji lze jednoznačně určit. Tomuto číslu se říká adresa. Ukazatel (angl. pointer) je proměnná nebo konstanta, která v sobě udržuje adresu nějaké jiné proměnné (říká se, že ukazatel "ukazuje na proměnnou"), nebo i adresu nějaké libovolné buňky v paměti. Ukazatelem je možné číst nebo měnit hodnotu na adrese, kam ukazuje. Jako vždy, ukážeme si příklad:
Nejdříve jsme deklarovali ukazatel Dále jsme ukazatel nastavili na adresu proměnné Na dalším řádku jsme nepřímo přiřadili proměnné Ukazatel může být pevného datového typu, to znamená, že ukazatel "ví", na jaký datový typ ukazuje, a při dereferenci vrátí přesně ten datový typ. Existuje ještě jeden typ ukazatele, tzv. obecný, který může ukazovat na proměnnou jakéhokoli datového typu, ale datový typ "si nepamatuje", a z tohoto důvodu nemůže být na něm provedena dereference. Obecný ukazatel se deklaruje klíčovým slovem void:
Kompilátor ohlásí chybu na řádku
Poslední dva řádky můžeme spojit do jednoho, je to rozumnější zápis, a určitě elegantnější, protože se vyhneme deklaraci zbytečného ukazatele p2:
Pozor, obecný ukazatel můžeme přetypovat na ukazatel na libovolný datový typ, ale výsledek dereference nebude správný.
Ukazatel může být i prázdný, tedy neukazuje na žádnou adresu. K tomu se používá zvláštní hodnota
NULL, v C++ lze používat i 0.
Možná se ptáte, k čemu vlastně ukazatele jsou. Je pravda, že jsme si neukázali žádné jejich smysluplné použití. Dále vysvětlím souvislost mezi ukazateli a poli. Až si vysvětlíme záznamy ukážeme si další použití významné ukazatelů a záznamů. Dále v kurzu probereme třídy a tam také uvidíte, jak jsou ukazatele důležité. Ukazatele a polePole a ukazatele mají v jazycích C a C++ k sobě velice blízko. Bez ohledu na rozdílnou syntaxi se tyto jazyky dívají na pole a ukazatele stejně: pole je vlastně ukazatel někam do paměti, kde se nachází seznam proměnných stejného typu těsně za sebou. Řekli jsme si, že se meze polí nekontrolují. Teď chápeme, proč tomu tak je: pole je ukazatel, který pouze ukazuje na proměnnou na nějaké adrese, ale nelze říci, kolik je za ní dalších proměnných stejného typu, to ví pouze programátor. Také jsme si řekli, že velikost prvku pole získáme zápisem Ukazovali jsme si, že řetězce lze deklarovat zápisem: Ze skutečností výše uvedených vyplývá i to, že s ukazatelem můžeme zacházet jako s polem,
například:
Existují i případy, kdy pole a ukazatel nejsou totéž. Typický případ je použití operátoru
sizeof:
Velikost pole je 20 (5 * 4), ale velikost ukazatele je 4 (ukazatel je 32bitová proměnná).
Také je nutné si uvědomit rozdíl mezi deklarací pole (popř. řetězce) a deklarací ukazatele.
Aritmetika ukazatelůJednou z předností jazyka C a C++ je aritmetika ukazatelů. To nám dovoluje zacházet s ukazatelem jako s číselnou proměnnou: můžeme k němu přičítat čísla, odečítat, zvětšit a zmenšit, porovnávat s jiným ukazatelem a tak podobně. Například aritmetikou ukazatelů můžeme nahradit použití hranatých závorek (to většinou neděláme, ale někdy se nám to může hodit):
Další použití je posuv v řetězci:
Typické použití je procházení řetězce za účelem nějakého zpracování. Představte si, že bychom potřebovali vypsat
řetězec tak, že přeskakujeme nadbytečné mezery (tj. když jich je více za sebou, vypíšeme jen jednu). Nejdříve musíme vymyslet algoritmus (jak to budeme
provádět): projdeme všechny znaky pole a pokud znak není mezera vypíšeme ho, jinak ho vypíšeme pouze pokud předchozí zpracovaný znak nebyl mezera:
Poznámka: snažil jsem se napsat funkci vypis() tak, aby byla pochopitelná. Ovšem jazyk C++ je známý pro svou stručnost a eleganci, takže si ukážeme, jak naši funkci napsat v tomto duchu. Podmínka cyklu while ( *s != 0 ) bude mít
hodnotu false (0) pokud hodnota *s bude 0, a hodnotu true (1, ale také jakákoli nenulová hodnota)
pokud hodnota *s bude různá od 0. To znamená, že hodnota podmínky je stejná s hodnotou *s . To využijeme k tomu,
abychom napsali cyklus while takto: while (*s) . Je to naprosto totéž.
Dále zaměříme svou pozornost na příkazy if. Trochu vadí, že příkaz pro vypsání se opakuje dvakrát, takže zkusíme napsat podmínku
pro if tak, abychom si vystačili s jedním
if. Platí, že se znak vypíše pokud není mezera nebo pokud mezera je a současně posledně
zpracovaný není mezera. Příkaz if, který tomu odpovídá, je
Jsme skoro u cíle, ale není to úplně ono. Výraz *s == ' ' za druhou závorkou bude vždy pravdivý. Do jeho závorky se
totiž dostaneme pouze pokud
první výraz (*s != ' ' ) je nepravdivý, a to znamená, že výraz *s == ' ' je
pravdivý. Můžeme nadbytečnou podmínku
vypustit, a dostaneme:
Poslední "fígl", který Vám chci ukázat, se týká obou dvou posledních řádků funkce
vypis(). Z předchozích částí víte, ze v C a C++ existuje operátor
++, který zvětší proměnnou o jedničku. Místo s = s + 1 budeme psát s++ . Řekněte, není to hezčí? A teď ta nejlepší část:
operátor ++ psaný za měněnou hodnotou funguje tak, že ještě před zvětšením vrací původní (nezvětšenou) hodnotu - v našem případě původní ukazatel (výsledek s++ bude s).
To využijeme k nastavení proměnné last, a napíšeme: last = *s++ . Takže naše upravená funkce vypadá takto:
Pokud nerozumíte všem úpravám, nic si z toho nedělejte. Jste přece jen na začátku a C++ je dost složitý
jazyk. Pokud se budete alespoň trochu zabývat
programováním, přijdete na tyto skutečnosti sami. Měli byste alespoň pochopit zjednodušení podmínky cyklu
while, je to v C a C++ opravdu používaný zápis.
Ukazatele a funkceKdyž jsme si ukázali funkce pro práci s řetězci uvedl jsem jejich syntaxi, ale značně zjednodušeně. Nyní, vyzbrojení znalostmi o ukazatelích, si můžeme
ukázat hlavičky těchto funkcí. Funkce pro zpracování řetězců mohou dostat jako parametry i řetězce značné velikosti. Kdyby se musel funkci předat
takový řetězec tak, že by se "do funkce" zkopíroval celý, mohlo by to trvat dost dlouho. Proto existuje mnohem lepší způsob předávání řetězců
(a nejenom, ale jakýchkoliv proměnných velkých datových typů): funkci se předá pouze ukazatel na řetězec. Například:
Předávání parametrů tak, že se předá pouze ukazatel, je také předávání odkazem. V minulém dílu jsme si ukázali předávání odkazem operátorem &.
Je to možné i ukazatelem:
Jako parametry aritm a geom samozřejmě předáme adresy proměnných, do kterých se průměry mají uložit, ty získáme operátorem &.
To byl v jazyce C jediný způsob předávání odkazem. V jazyce C++ máme lepší způsob, a to je operátor
& (operátor reference - angl. reference operator),
jak jsme si ukázali minule.
Reference v sobě udržuje adresu nějaké proměnné, ale syntakticky se chová jako ta proměnná. Ale jaký je rozdíl mezi předávání parametru
ukazatelem a referencí? Je to jednoduché, nulová reference neexistuje, nulový ukazatel ano. Tedy předávání ukazatelem můžeme použít tam, kde
chceme mít možnost v parametru nepředat nic. Jako příklad vylepšíme funkci
prumery():
Funkce prumery() nyní umí počítat pouze aritmetický průměr, nebo pouze geometrický, nebo dokonce ani jeden z nich. Budeme-li mít zájem pouze o
aritmetický průměr, zavoláme funkci takto:
Toto bychom referencí neudělali. Ale reference se také hodí, například když naopak chcete mít jistotu, že se skutečně předala nějaká proměnná.
Poznámka: význam klíčového slova const u parametrů string2 a strSource v deklaraci výše uvedených funkcí znamená,
že funkce nemění řetězce předané v těchto parametrech, takže lze předat i konstantní řetězec (deklarovaný také
klíčovým slovem const - const char *str = "Ahoj"; ). Bez klíčového slova const by to možné nebylo.
To je pro tento měsíc všechno. Pilujte, zkoušejte, nebojte se experimentovat, za měsíc nashledanou.
|