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.

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:
short int a[4]={15,134,29,104};
short int *p;

p=&a[0];

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:
short int i, a[4]={15,134,29,104};
short int *p;

p=&a[0];
for (i=0; i<4; i++) printf("%d, ", *(p+i));


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í.

++p;
p=p+1;


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:
char str[]="Vymaž mě";
char *p;

p=&str[0];

while ( *p && (*p=' ') ) p++;

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.

int a[10];
int *p1, *p2;

p1=&a[4];
p2=&a[7];

printf("%d", p2-p1);

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:
int a[10];
int *p;

p=a;

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:

a[4]
p[4]
*(a+4)
*(p+4)

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ší:
4[a]
4[p]

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.