Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!
Zadejte Vaši e-mailovou adresu:
Kamarád fotí rád?
Přihlas ho k odběru fotomagazínu!
Zadejte e-mailovou adresu kamaráda:
C/C++
Učíme se C (17.díl) - Pole a ukazatel
26. ledna 2001, 00.00 | Umíte správně používat ukazatele a pole? V tomto článku se dovíte, jaká je souvislost mezi poli a ukazateli v C a jestli a kdy je lze zaměnit.
Pole a ukazatel
V jazyce C existuje úzká souvislost mezi datovým typem pole a typem ukazatel. Díky ní se můžeme na výraz typu pole dívat jako na ukazatel, a také s ním podle toho pracovat. Naopak, na ukazatel se zase můžeme dívat jako na pole. Jak to vlastně funguje, to se vám pokusím osvětlit v následujících řádcích.
Aritmetické operace s ukazateli
Zatím jsme při operacích s pointery používali, kromě dereferencí, jen operace přiřazení a porovnávání. Existují ale další, aritmetické operace, které lze při práci s ukazateli využít.
Součet ukazatele a celého čísla
Řekněme, že máme ukazatel bázového typu int ukazující někam do paměti. Pokud k tomuto ukazateli přičteme nějaké celé číslo n, vyhodnocením tohoto výrazu bude adresa paměti o n intových hodnot dál, než kam ukazoval původní pointer. Aby to bylo trochu jasnější, ukážeme si to na příkladu, ve kterém nedefinujeme ukazatel p, jenž bude ukazovat na začátek čtyřprvkového pole a:
|
Následující tabulka ukazuje, kam se vlastně s pointrem p dostáváme, přičítáme-li k němu postupně čísla 1, 2 a 3.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
a[0] | a[1] | a[2] | a[3] | ||||
*p | *(p+1) | *(p+2) | *(p+3) | ||||
15 | 134 | 29 | 104 |
Druhý řádek tabulky ukazuje klasický způsob, jak se dostat k jednotlivým prvkům pole – indexaci. Řádek třetí představuje ekvivalentní zápis pomocí dereferovaného ukazatele. Jak vidíte, přičtení čísla k pointeru neznamená jeho posunutí o jeden byte, ale o celou velikost jeho bázového typu (první řádek tabulky označuje jednotlivé byty). V případě námi použitého typu short int to jsou dva byty. Přičtu-li tedy k našemu pointeru p číslo jedna, výsledkem operace bude pointer ukazující v paměti o dva byty, neboli o jeden short int, dál. Prakticky tedy takto získaný pointer bude ukazovat na druhý prvek pole a. Dereferencí pak tento prvek získáme stejně, jako kdybychom použili indexaci a[1].
Výpis pole pomocí ukazatele:
|
Na proměnné číselného typu můžeme v jazyce C použít operátor inkrementace, který zvyšuje hodnotu proměnné o 1. Tento operátor lze ale použít i s ukazatelovými proměnnými. Inkrementujeme-li ukazatel, jednoduše měníme místo, kam ukazuje, a to stejně, jako kdybychom k ukazateli přičetli číslo 1. Následují zápisy pracující s ukazatelem p jsou tedy sémanticky ekvivalentní.
|
Protože postranní efekt inkrementace přímo změní hodnotu svého operandu, měli bychom být při používání inkrementace opatrní. Například v případě, že bychom "ztratili" ukazatel označující dynamicky alokovanou paměť, ztratili bychom i možnost tuto paměť zase uvolnit. (O dynamické alokaci paměti bude pojednávat některý z příštích dílů.)
V následujícím příkladu přepíšeme všechny znaky řetězce str mezerami:
|
Samotný výkonný příkaz je proveden už při vyhodnocování pokračovací podmínky cyklu, kde jsme využili zkráceného vyhodnocování operátoru &&.
Odečtení celého čísla od ukazatele
Situace s odčítáním ukazatele a celého čísla je analogická operaci sčítání, jenom se posunujeme v paměti opačným směrem. Měli bychom si ale být jisti, že paměť, ke které takto přistoupíme, nám skutečně patří. To ale platí pro jakoukoliv práci s pointery a vlastně i s poli.
Stejně jako můžeme na ukazatel aplikovat operátor inkrementace, můžeme použít i operátor dekrementace.
Rozdíl dvou ukazatelů
V jazyce C je definován i rozdíl dvou ukazatelů stejného bázového typu. Tato operace má smysl hlavně v případě, že oba pointery ukazují do stejného pole. Pak je výsledkem takové operace celočíselná hodnota, která představuje vzdálenost mezi prvky pole, na které pointery ukazují. Tato vzdálenost je měřena v počtu prvků pole, nikoli nutně v bytech.
|
Funkce printf v uvedeném příkladu vypíše hodnotu 3, což je vzdálenost mezi pátým a osmým prvkem pole, na které ukazují pointery p1 a p2.
Zaměnitelnost ukazatele a pole
Jak jste si všimli, aritmetické operace s ukazateli nalézají asi největší uplatnění při práci s poli. Provázanost mezi poli a ukazateli ale zachází ještě dál. Na pole se totiž můžeme dívat jako na pointer ukazující na první prvek tohoto pole. Proto můžeme všechny operace, které jsme prováděli s pointery, dělat i s poli. Můžeme například dereferovat proměnnou typu pole, čímž získáme první prvek tohoto pole. Funguje to ale i obráceně, a tak se na ukazatele můžeme dívat jako na pole.
Uvažujme následující zápis:
|
Přiřazením pole a do ukazatele p se nestalo nic jiného, než že p nyní ukazuje na začátek pole a, tedy na první prvek pole. Budeme-li teď chtít pracovat například s pátým prvkem pole a, můžeme ho získat těmito způsoby:
|
V prvních dvou případech jsme s proměnnými a i p pracovali jako s poli a ve zbylých dvou jako s ukazateli. Všechny možnosti ale představují proces získání pátého prvku pole. Můžeme si všimnout, že zápisy pomocí pointerové aritmetiky tvoří vlastně jen dereferovaný součet identifikátoru a indexu, např. *(a+4). Protože je ale operace sčítání komutativní, a lze tedy použít i zápisu *(4+a), mělo by být možné zaměnit index a identifikátor i při přístupu jako k poli. Tento způsob skutečně funguje, a tak uvedené čtyři způsoby získání prvku pole musíme doplnit ještě o dva další:
|
Existují ale i situace, kdy se rozdíl mezi polem a ukazatelem projeví. Jednou z nich je použití operátoru sizeof(), který vrací paměťovou velikost svého argumentu. Tento operátor aplikovaný na pole vrátí velikost celého pole, tedy všech jeho položek dohromady, kdežto, předáme-li mu ukazatel, vrátí pouze velikost, jakou v paměti zabírá jen tento ukazatel. Druhým případem, kdy se záměna pole za ukazatel neprovede, je při použití operátoru reference. Jeho aplikací na naše pole a bychom získali ukazatel na pole deseti prvků typu int, avšak použitím reference na ukazatel p získáme jen ukazatel na ukazatel bázového typu int. Významným rozdílem je také fakt, že pole není l-hodnotou, a proto do něj nelze přímo přiřazovat. V našem příkladě tedy bez problémů fungovalo přiřazení p=a, ale zápis a=p by již nebyl možný. Stejný problém by nastal také při použití některého z operátorů inkrementace či dekrementace na proměnnou typu pole.
Obsah seriálu (více o seriálu):
- Učíme se C (1. díl)
- Učíme se C (2. díl)
- Učíme se C (3.díl)
- Učíme se C (4. díl)
- Učíme se C (5.díl)
- Učíme se C (6.díl)
- Učíme se C (7.díl)
- Učíme se C (8.díl)
- Učíme se C (9.díl) - Řetězce
- Učíme se C (10.díl) - Vstupně výstupní funkce
- Učíme se C (11.díl) - Formátovaný výstup
- Učíme se C (12.díl) - Formátový specifikátor
- Učíme se C (13.díl)
- Učíme se C (14.díl) - Vícerozměrná pole
- Učíme se C (15.díl) - Ukazatele
- Učíme se C (16.díl) - Modifikátory paměťové třídy
- Učíme se C (17.díl) - Pole a ukazatel
- Učíme se C (18. díl) - Dynamická alokace paměti
- Učíme se C (19. díl) - Práce se soubory I.
- Učíme se C (20. díl) - Práce se soubory II.
- Učíme se C (21. díl) - Parametry funkce main()
- Učíme se C (22. díl) - Ukazatele na funkce
- Učíme se C (23. díl) - Složité deklarace a definice
- Učíme se C (24. díl) - Funkce s proměnným počtem parametrů
- Učíme se C (25. díl) - Bitové operátory a bitové pole
- Učíme se C (26. díl) - Datové typy enum a union
- Učíme se C (27. díl - závěr) - Programové moduly
-
25. listopadu 2012
-
30. srpna 2002
-
10. října 2002
-
4. listopadu 2002
-
12. září 2002
-
25. listopadu 2012
-
28. července 1998
-
31. července 1998
-
28. srpna 1998
-
6. prosince 2000
-
27. prosince 2007
-
4. května 2007