Jazyk C - smerník (pointer)
Smerníky sú najdôležitejšou časťou jazyka C, ale aj najužitočnejšou a najzradnejšou. Pri správnom použití vedia veľmi uľahčiť život programátora, ale pri nesprávnom, nastane strach a hrôza. Preto poďme pekne poporiadku.
Učebné texty
Obsah
Definícia smerníka
- Smerník
- (anglicky pointer, česky ukazovatel) je obyčajná premenná, ktorá obsahuje adresu inej premennej.
S použitím smerníkov sa stretávame pri spracovaní
- premenných,
- polí,
- reťazcov,
- parametrov funkcií,
- dynamických objektov,
- funkcií (smerník na funkciu).
Definícia v jazyku C
- Smerník ako dátový typ neexistuje
- So smerníkom sa vždy spája nejaká premenná
- smerník na int, smerník na double, smerník na char, atď. ...
- Definujme ui ako ukazovateľ na typ int
- int *ui;
- Definujme uf ako ukazovateľ na typ float
- float *uf;
Súvislosť operátora referencia (&) a dereferencia (*)
definujme nasledujúce:
int *ui;
int i;
Tieto 2 premenné nemajú nič spoločné. ui je smerník na int, i je premenná typu int.
- *ui
- hodnota, na adrese ui (ui je ukazovatel na int)
- &i
- adresa premennej i (i je premenná typu int)
- ui
- adresa v pamäti, kde je uložená hodnota smerníka
Príklad 1[1]
Bez použitia premennej x priamo pri výpočte zvýšte jej hodnotu o 10.
1 #include<stdio.h>
2 int main(void)
3 {
4 int x = 0 ; //do x sme vložili hodnotu 0
5 int* smernik; // !!!smerník na int alebo smerník typu int
6
7 smernik = &x; // do smerníka sme uložili adresu x
8
9 printf("npred pricitanim:x = %d",x);
10 *smernik = *smernik + 10;
11 // na adresu ,kde je x ,sme pričítali 10
12 // * dereferenčný operátor
13 // & referenčný operátor
14
15 printf("npo pricitani: x = %d",x);
16 getchar();
17 }
Analýza programu
Prvé dva riadky by mali byť jasné. Máme premennú typu int, preto potrebujeme vytvoriť smerník typu int.
Pozrime sa bližšie na riadok č. 5 :
smernik = &x;
Znaku & sa hovorí tiež referenčný operátor. Vďaka nemu získame adresu premennej x. Keby sme napísali smerník = x, tak by sme nezískali veľa. Do smerníka by sme si uložili namiesto adresy hodnotu premennej. V našom prípade by to bola 0. Takže smerník by nám ukazoval na adresu 0 a nie na adresu, kde je uložená hodnota premennej x.
Riadok č. 10:
*smernik = *smernik + 10
Znaku * sa hovorí tiež dereferenčný operátor. Vďaka tomuto operátoru získame obsah na adrese, na ktorú ukazuje. Využili sme to na ľavej strane výrazu: *smernik = *smernik + 10
Vďaka dereferenčnému operátoru vieme tiež zapísať hodnoty na adresu kam ukazuje. Využili sme to na pravej strane : *smernik = *smernik + 10.
Riadok *smernik = *smernik + 10; robí to isté ako x = x + 10;
Operátor & sa používa pre premenné. Konštanty a výrazy nemajú adresu, preto tento operátor s nimi nemôžme využiť.
Príklad chybných príkazov:
smernik = &10 ; // chyba 10 je konštanta => nemá adresu
smernik = &(x + 10) ; // chyba (x+10) je výraz
smernik = 1 ; // už sme spomínali, smerník by teraz ukazoval niekam na adresu 1
Nulový smerník NULL
- Smerník nemôže mať hodnotu 0 (znamenalo by to, že odkazuje na adresu 0), ale môže mať hodnotu NULL.
- Hodnota NULL je definovaná ako
- #define NULL 0
- #define NULL ((void *) 0)
- Ak má smerník hodnotu NULL, tak ukazuje na nič
Z predchádzajúceho vyplýva jedna vec: smerník môžeme definovať aj na typ void
Smerník a pole
Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.
Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestnenie prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0]; data + i to isté ako &data[i], čo môžeme zapísať v tvare
data + i == &data[i]
Aplikáciou operátora dereferencia * na obe strany výrazu dostaneme rovnosť
*(data+i) == data[i]
Ak sa i zvyšuje o 1, referencované miesto pamäte sa zvyšuje o počet bytov (dĺžku), daný veľkosťou daného typu.
Jazyk C netestuje hranice polí, čo umožňuje zápis i čítanie mimo rozsahu poľa, pravda, so všetkými z toho vyplývajúcimi dôsledkami. Za kontrolu dodržania rozsahu poľa je preto zodpovedný programátor.
Použitie smerníka a poľa si ilustrujeme časťou programu
int *p, data[10];
p=&data[0];
potom *p = data[0] a my môžeme namiesto data[0] v ľubovoľnom výraze používať *p, resp. pre prvok data[i] *(p + i), lebo
p + i == &data[i]
*(p + i) == data[i]
Z uvedeného by sa mohlo zdať, že identifikátory data a p sú ekvivalentné. Je tu však jeden podstatný rozdiel, ktorý spočíva v tom, že data je symbolická smerníková konštanta, zatiaľ čo p je smerníková premenná. Preto operácie ako p++, p = data sú prípustné, zatiaľ čo data++, resp. data = p nie sú povolené. Majme napr.
float array[100];
fp = &array[0];
potom fp++ ukazuje na array[1]. Podobne fp = fp + 10 bude ukazovať na array[10]. Predpokladajme, že
fp1 = &array[100];
Potom fp-- ukazuje na array[99], resp. fp1 = fp1 - 10 alebo fp1 -= 10 bude ukazovať na array[90].
Na smerníky (ak sa vzťahujú na prvky toho istého poľa) je možné aplikovať aj 4 relačné operátory (<, <=, >, >= ) a testovať ich rovnosť, resp. nerovnosť ( ==, != ). Použitie týchto operátorov ilustruje následujúci príklad pre nájdenie najväčšieho prvku v poli data.
Viac o smerníkovej aritmetike nájdete v [2].
Dynamické prideľovanie pamäti
Pomocou mechanizmu smerníkov si dokážeme alokovať orčitú veľkosť pamäti vo svojom programe.
Alokácia pamäti
Definujme si smerník na int nasleddovne:
int *ui;
Do takto novo vytvorenej premennej (resp. smerníka) nemôžeme priradiť žiadnu hodnotu, pretože pre daný smerník sme nealokovali miesto v pamäti, kde bude ukladať svoj obsah. Nasledujúci zápis je chybný!
*ui=3; // !!!chyba
Pokúšame sa do smerníka uložiť hodnotu 3, ale túto hodnotu nemáme ešte kde uložiť. Potrebujeme si alokovať miesto.
Operátor new
operátor new (namesto fukcie malloc jazyka C používame operátor new jazyka C++ kvôli jednoduchšej syntaxi) alokuje pre smerník potrebné miesto v pamäti. Pri neúspechu vracia hodnotu NULL.
int *ui; // definícia smerníka ui na int
ui=new int; // pre smerník ui si alokujeme miesto o veľkosti 1 int
int *uk=new int; // zápis v jednom riadku
Dealokácia pamäti
Pamäť alokovaná operátorom new zostáva rezervovaná až do konca behu programu. Počas behu programu je môžeme uvoľniť použitím operátora delete. Použitie:
delete ui;
delete uk;
Polia a ukazovatele
Pri alokácii pamäti pre ukazovateľ si nemusíme alokovať len pre daný dátový typ, ale môžeme si alokovať ľubovoľne veľkú oblasť pamäti (samozrejme s obmedzením veľkosti dostupnej pamäti). Majme:
int *ui=new int[10];
- ui je ukazovateľ na typ int.
- Alokovali sme si pamäť o veľkosti 10 integerov
Popis:
- *ui predstavuje hodnotu prvého alokovaného integera
- *(ui+1) predstavuje druhého alokovaného integera
- Môžme teda tentu smerníkpoužiť nasledovne
- cin>>*ui>>*(ui+1);
- cout<<*ui<<*(ui+1);
- ui môžeme chápať ako pole celých čísel o veľkosti 10
- Ak je teda ui pole celých čísel o veľkosti 10, môžeme k nemu pristupovať ako ku klasickému jednorozmernému poľu:
- cin>>ui[0]>>ui[1];
- cout<<ui[0]<<ui[1];
Dealokovanie ui
delete []ui;
Viacrozmerné dynamicky alokovateľné polia
Deklarujme nasledovné:
int *pp=new int[4];
Vieme, že nasledujúce výrazy sú ekvivalentné:
pp[0]==*pp
pp[1]==*(pp+1)
pp[2]==*(pp+2)
pp[3]==*(pp+3)
Dvojrozmerné dynamicky alokované pole
Definujme nasledovné:
int n=10;
int **M;
M je smerník na smerník na int.
Alokujme pre M pole pozostávajúce z n smerníkov na celé čísla:
M=new int*[n];
Po vykonaní tohto výrazu je M pole smerníkov na int. Do tohoto poľa ešte nemôžeme nič ukladať, pretože bunky tohto poľa sú smerníky a ešte sme si nealokovali pre ne miesta, kde budú uložené hodnoty. Pre každý prvok poľa M (prvky sú smerníky na int) si alokujme miesto o n integeroch:
for(int i=0;i<n;i++)
M[i]=new int[n];
Takto sme vytvorili dvojrozmerné pole celých čísel o veľkosti n×n. Zaujímavosťou tohto riešenie je že n je premenná a jej hodnotu môžme v programe meniť. Pri statických poliach musel byť rozmer konštantný.
Celý kód ešte raz:
int **M=new int*[n]; // smerník na smerník na int
for(int i=0 ; i<n ; i++) // M[] je pole smerníkov na typ int
M[i]=new int[n]; // každému prvku M[i] alokujeme pole integerov o veľkosti n
Príklad 2
Vytvorme program na nájdenie maximálneho prvku poľa[3]: Riešenie pomocou statických polí
#include<stdio.h>
int main(void)
{
int i,j;
int pocet;
int max;
int pole[100]; //predpokladáme, že maximum bude 100 čísel
//vypýtame si počet zadávaných čísel
printf("Zadajte pocet zadavanych cisel: ");
scanf("%d",&pocet);
//načítame a uložíme jednotlivé čísla do pola
for(i = 0;i<pocet;i++)
{
printf("Zadajte %d. prvok pola: ",i+1);
scanf("%d",&pole[i]); //teraz prístup cez index
}
//jednoduchý cyklus na nájdenie maxima
max = pole[0];
for(i = 1;i<pocet;i++)
{
if (max<pole[i])
{
max = pole[i];
}
}
printf("n Maximalny prvok je : %d",max);
}
Riešenie pomocou dynamicky akokovaných polí
#include<stdio.h>
int main(void)
{
int i,j;
int pocet;
int* max; // pozor!!! Tu je smerník!
int *pole; //predpokladáme, že maximum bude 100 čísel
//vypýtame si počet zadávaných čísel
printf("Zadajte pocet zadavanych cisel: ");
scanf("%d",&pocet);
pole=new int[pocet]; // vytvoríme si presne také pole aké potrebujeme
//načítame a uložíme jednotlivé čísla do pola
for(i = 0;i<pocet;i++)
{
printf("Zadajte %d. prvok pola: ",i+1);
scanf("%d",pole+i); //teraz prístup cez smerníkovú aritmetiku
// ekvivalent je : scanf("%d",&pole[i]);
}
//jednoduchý cyklus na nájdenie maxima
max = pole; //zmena!!!, ekvivalent je: *max = pole[0];
for(i = 1;i<pocet;i++)
{
if (*max<pole[i]) //aj tu sa porovnávajú hodnoty!!!
{
max = pole+i; // ekvivalent: *max=pole[i];
}
}
printf("n Maximalny prvok je : %d",*max);
}
Priamejšie riešenie by bolo, ak by sme namiesto smerníka *max použili premennú max:
#include<stdio.h>
int main(void)
{
int i,j;
int pocet;
int max;
int *pole;
printf("Zadajte pocet zadavanych cisel: ");
scanf("%d",&pocet);
pole=new int[pocet];
for(i = 0;i<pocet;i++)
{
printf("Zadajte %d. prvok pola: ",i+1);
scanf("%d",&pole[i]);
}
max = pole[0];
for(i = 1;i<pocet;i++)
{
if (max<pole[i])
{
max=pole[i];
}
}
printf("n Maximalny prvok je : %d",max);
}
Referencie
- ↑ Programujeme v jazyku C - smerníky I. - http://www.inet.sk/clanok/3586/programujeme-v-jazyku-c-smerniky-i
- ↑ Smerník a pole - http://people.tuke.sk/igor.podlubny/C/Kap9.htm
- ↑ Smerníky v jazyku C http://www.inet.sk/clanok/3682/programujeme-v-jazyku-c-smerniky-ii-polia