Pokazivači (C)

#fax #cs/prog/c [deo jezika C]

— vrednosti su memorijske adrese.
(4B — 32bit računar, 8B — 64bit računar)

int *p1;
int* p2;
int* p3, p4;

// p1, p2, p3 su pokazivači na int
// p4 je tipa int

Operator referenciranja &:

int a = 10, *p;
p = &a;        // adresa a se zapisuje u p

Operator dereferenciranja *:

*p = 5;        // a će postati 5

Opšti (generički) pokazivač void*:
— pokazivač bez tipa, koji ne možemo koristiti bez konvertiranja u potreban tip.

void* pa = &a;
// greška: *pa = 15;

Pokazivač ni na šta:

p = NULL;

const:

int* const pb = &b;
//konstantni pokazivač; greška: pb = &c;

const int* pc;       
//pokazivač preko kojeg nije moguće menjati promenljivu na koju on pokazuje (greška: *pc = 5;), samo čitati


Primer korišćenja pokazivača — swap:

void swap(int *pa, int *pb) {
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

int main() {
	int a = 5, b = 9;
	swap(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}

Pokazivačka aritmetika

tip *p, *q;
int i;
              //neformanlo,
              //u apsolutnim veličinama:
q = p + i     //p + i*sizeof(tip)
i = p - q;    //(p - q)/sizeof(tip)
  • pokazivač p nekog tipa +/- ceo broj i je vrednost p (adresa) pomerena udesno/ulevo na i prostora veličine tog tipa.

  • razlika dva pokazivača istog tipa je tipa int i jednaka je broju prostora veličine tog tipa između njihovih vrednosti.

int main() {
	int a[] = {26, 54, 89};
	int *p = &(a[0]);
	int *q = &(a[2]);
	printf("%d ", q-p);
	p++;
	printf("%d\n", *p);
	return 0;
}
// ispis: 2 54

Pokazivači i nizovi

int a[10], *pa, i;
pa = a;      // isto što i pa = &a[0]

ime niza se konvertuje u pokazivač na 0-ti element niza osim u dva slučaja:

sizeof(a)    // 40 - koliko prostora zauzima ceo niz
sizeof(pa)   // 8 - koliko prostora zauzima pokazivač
&a           // pokazivač na ceo niz;
             // ima istu adresnu vrednost što i pa;
             // ali tip je int (*)[10]
             
             //zbog implicitne kovertacije tipova
             //(pa = a;) je isto što i (pa = &a;)
             
&pa          // pokazivač na pokazivač;
             // tip je int**

U ostalim slučajevima ime niza moguće koristi kao pokazivač i obratno pokazivač moguće koristiti kao ime niza pri pristupanju elemenata, na primer pa[3].

Sledeći izrazi su ekvivalentni (uz nastavak primera):

  a + i  = &a[i] =   pa + i  = &pa[i]  //adresa
*(a + i) =  a[i] = *(pa + i) =  pa[i]  //element niza

int *a[10];     // niz od 10 pokazivača na int
int (*b)[10];   // pokazivač na niz od 10 int-ova
                // tip je int (*)[10]

int niz[10];
b = &niz;

// b + 2 ima vrednost b + veličina 2 niza od 10 int-ova, tj u absolutnim vrednostima b + 80

// b[2] je niz od 10 elementa na absolutnoj adresi b + 80 (izvne deklarisanog, samo za primer), konvertuje se u pokazivač na nulti element tog niza.

// (*b)[3] je isto što i niz[3]

int d[dim1][dim2][dim3]..;

d se konvertuje u pokazivač na 0-ti element niza d - niz tipa int (*)[dim2][dim3]..

Primer:

int A[10][20];

Neka niz A počinje sa adrese 0, tada

izraz         tip               vrednost

&A[0][2]      int*              8
&A[2][0]      int*              2*20*4 = 160
&A[2][6]      int*              (2*20 + 6)*4 = 184


A[2]          int[20]
              //konvertuje se:
              int*              160
A[2] + 6      int*              160 + 6*4 = 184


&A[2]         int (*)[20]       160
&A[2] + 6     int (*)[20]       160 + 6*20*4 = 640 


A             int[10][20]      
              //konvertuje se:
	          int (*)[20]       0
A + 6         int (*)[20]       0 + 6*20*4 = 480


&A            int (*)[10][20]   0
&A + 6        int (*)[10][20]   0 + 6*10*20*4 = 4800

Pokazivači i niske

Konstantne niske se čuvaju u segmentu podataka, niska navedena u tekstu programa u " " se konvertuje u pokazivač (tipa char*) na njen početak u segmentu podataka.
Konstantne niske je nemoguće menjati.

printf("%d", x);  // prvi argument je tipa char*

cahr* p = "informatika";
//greška: p[5] - 'k';
p++;
printf("%s", p);  //ispis: nformatika
char a[] = "informatika"; // u stek okviru kreira se niz dužine 12 i popunjava se karakterima konstantne niske: a[0] = 'i', ..., a[11] = '\0'.
// nisku a već možemo da menjamo (karakrer po karakter)

implementacija strcpy:

void strcpy(char *a, const char* b) {
	while (*(a++) = *(b++));
}
...
//pozivi:
char s[20], t[20].
strcpy(s, "informatika");
strcpy(t, s)

Niz niski:
niz niski.png

Pokazivači na funkcije

— omogućavaju slanje funkcije kao argument druge funkcije.

//prototip funkcije koja vraća pokazivač na double:
double *a(int, float);

//deklaracija pokazivača na funkciju koja uzima int i float i vraća double:
double (*a)(int, float);
//a je tipa double (*)(int, float)

//deklaracija niza pokazivača na funkciju koja uzima int i float i vraća double:
double (*a[3])(int, float);

Isti operatori referenciranja & i dereferenciranja *.

U praktici oni se ne koriste jer ime funkcije, koje je napisano bez ( ), automatski se konvertuje u pokazivač na tu funkciju, i obratno pokazivač, koji je napisan ca ( ), ponaša kao funkcija, na koju pokazuje.

Primer korišćenja:

#include <stdio.h>
#define N 5

void ucitaj_niz(int* niz, int n) {
	int i;
	for (i = 0; i < n; i++)
		scanf("%d", niz + i);
}

void ispisi_niz(int* niz, int n) {
	int i;
	for (i = 0; i < n; i++)
		printf("%d ", niz[i]);
	printf("\n");
}

int Sum(int a, int b) { return a + b; }
int Mul(int a, int b) { return a * b; }
int Div(int a, int b) { return a / b; }
int Mod(int a, int b) { return a % b; }

void map(int* niz_ulaz, int* niz_izlaz, int n, int (*op)(int, int), int k) {
	int i;
	for (i = 0; i < n; i++)
		niz_izlaz[i] = op(niz_ulaz[i], k);
}

int main() {
	int b[N], k;
	int a[N]={13, 54, 19, 77, 92}; //ucitaj_niz(a, N);
	k = 10;                        //scanf("%d", &k);
	
	map(a, b, N, Sum, k); ispisi_niz(b, N);
	map(a, b, N, Mul, k); ispisi_niz(b, N);
	map(a, b, N, Div, k); ispisi_niz(b, N);
	map(a, b, N, Mod, k); ispisi_niz(b, N);
	
	return 0;
}