Оперативна меморија рачунара организована је као низ локација са придруженим адресама, где је најмања адресабилна јединица један бајт. Подаци у оперативној меморији записују се у узастопним бајтовима. На пример, податак типа int записују се у четири узастопна бајта, податак типа double у осам узастопних бајтова итд.
Показивачка променљива садржи меморијску адресу податка на кога показује (то је увек адреса бајта са најнижом адресом, односно, адреса првог бајта у низу узастопних бајтова).
Иако су меморијске адресе цели бројеви, показивачки типови се у програмском језику C стриктно разликују од целобројног типа. Такође, у програмском језику C постоји више показивачких типова, где се показивачки тип одређује на основу типа податка на који показује.
Показивачке променљиве декларишемо на следећи начин:
tip *identifikator;
где је tip тип податка на који се показује, а identifikator име показивачке променљиве. Приликом декларације није битно да ли постоји размак између звездице и типа или звездице и идентификатора – звездица се увек односи на идентификатор. У следећем примеру:
int* pa; int *pb; int * pc;
декларисали смо показивачкe променљивe pa, pb и pc које показују на податкe типа int.
Унарни оператор & (којег називамо адресним оператором, односно оператором референцирања) враћа меморијску адресу свог операнда, стим да операнд не може бити константа нити израз. У следећем примеру:
int a = 3, *pa; pa = &a;
у меморијски простор целобројне променљиве a уписана је вредност 3, а у меморијски простор показивачке променљиве pa уписана је меморијска адреса променљиве a. За pa кажемо да показује на a. Ово смо могли записати и у једној линији:
int a = 3, *pa = &a;
Поред улоге означавања показивачких променљивих, унарни оператор * (кога називамо оператором дереференцирања) враћа вредност која се налази у меморијском простору на коју показивачка променљива показује, увек водећи рачуна о типу податка који се у њој налази. У следећем примеру:
int a = 3, *pa = &a; printf("%d", *pa);
у простор променљиве a уписана је вредност 3, у простор променљиве pa уписана је адреса променљиве a и на крају је одштампана вредност на коју показује pa. Шта ће се исписати на излазу? Исписаће се 3 јер је вредност на коју показује pa у ствари вредност променљиве a.
Резимирајмо све до сада. У следећем програму:
#include <stdio.h> int main(void) { int x = 100, *px = &x; printf("x : %d\n", x); printf("&x : %p\n", &x); printf("px : %p\n", px); printf("*px : %d\n", *px); }
на екрану штампамо следеће:
- вредност променљиве x
- адресу променљиве x, тј. &x
- вредност променљиве px
- вредност променљиве на коју показује px, тј. *px
По извршењу датог програма добијамо следећи резултат
x : 100 &x : 010FF7C0 px : 010FF7C0 *px : 100
Јасно је да су једнаке вредности променљиве x и променљиве на коју показује px, као и да је адреса променљиве x једнака са вредношћу променљиве px.
Ако се у изразу дереференцираном показивачу додељује вредност, онда измена његове вредности утиче и на простор на који показује (ако показује на променљиву, мења се садржај те променљиве). У следећем примеру:
int a, *pa; a = 3; pa = &a; *pa = 6;
у простор променљиве a уписана је вредност 3, у простор променљиве pa уписана је адреса променљиве a и у простор на који показује pa уписана је вредност 6. Уписом вредности 6 у простор на који показује pa промењена је и вредност променљиве a на 6!
Генерички показивачи
Поред показивача на податке познатих типова (int, float…), постоје и показивачи код којих није одређен тип података на који показују. Такве показиваче називамо генеричким показивачима и декларишемо их помоћу кључне речи void коју наводимо уместо типа података. Помоћу генеричких показивача не може се приступити подацима у меморији без претходне конверзије у познати тип података. У следећем примеру:
int a; void *pa; a = 3; pa = &a; *(int*)pa = 6; printf("%d", *(int*)pa);
у простор променљиве a уписана је вредност 3, у простор генеричког показивача pa уписана је адреса променљиве a, а у простор на који показује pa уписана је вредност 6 уз претходну конверзију у познати тип података int. Такође, испис на стандардни излаз вредности променљиве на коју показује pa извршен је уз конверзију у познати тип података.
Да конверзије нису извршене, компајлер би упозорио на дереференцирање генеричког показивача и избацио грешку о неправилном коришћењу кључне речи void.
Придруживање идентификатора показивачким типовима
Наредбом typedef могу се придружити идентификатори и стандардним типовима података и показивачима. У следећем примеру:
typedef int ceoBroj; typedef int *pceoBroj; ceoBroj a = 3; pceoBroj pa = &a; printf("%d %p", a, pa);
дефинисан је целобројни тип ceoBroj и показивачки тип који показује на целе бројеве pceoBroj. Декларисана је и иницијализована променљива a која је типа ceoBroj. Декларисан је и иницијализован показивач pa који је типа pceoBroj. Одштампана је вредност променљиве a као и њена адреса, односно вредност показивача pa.
Операције са показивачима
Над показивачима је могуће извршити неколико операција. Могућа је додела вредности једног показивача другом помоћу оператора = након чега оба показивача показују на исти податак. У следећем примеру:
int a = 3, b = 6, *pa = &a, *pb = &b; pb = pa;
декларисане су и иницијализоване две целобројне променљиве a и b и два показивача на њих pa и pb. Након тога, вредност показивача pa додељена је вредношћу показивача pb. То значи да сада оба показивача показују на исти целобројни податак a.
Напомена: грешка је додељивати вредност једног показивача другом, ако показивачи не показују на исти тип података. Изузетак важи за генеричке показиваче у случају када се додељује вредност негенеричког показича генеричком. Помоћу генеричког показивача се свакако не може приступити показиваном податку, па и није битно ког је типа тај податак. Обрнуто не важи – додељивање вредности генеричког показивача не генеричком може довести до грешке.
Може се вршити додела нуле показивачу и поређење показивача са нулом. Вредност нула (NULL) говори да показивач не показује ни на један податак.
Оператори за једнакост == и неједнакост != могу се користити над два показивача истог типа (укључујући и генеричке показиваче) приликом провере да ли они показују на исти податак.
Могуће је поређење два показивача истог типа (осим генеричког) операторима <, <=, > и >=, израчунавање разлике два показивача (осим генеричког) оператором – и израчунавање збира и разлике једног показивача (осим генеричког) са податком целобројног типа. Ове операције имају смисла када се изводе над показивачима на елементе низа, а не на независне податке. Ово ће бити демонстрирано у наредним лекцијама, а сада само наведено као чињеница.