Učíme se C (17.díl) - Pole a ukazatel - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

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:

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.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: