Strukture podataka i algoritmi 1

SPA1 - Vežbe 5

Pokazivači

Pokazivači su osnovni koncept u jeziku C koji omogućava direktno manipulisanje memorijom. Pokazivači su promenljive koje sadrže adresu u memoriji kao svoju vrednost, umesto konkretne vrednosti. Ovo omogućava dinamičko alociranje memorije, pristupanje podacima na izabranim lokacijama u memoriji i efikasno rukovanje kompleksnim podacima.

Format ispisa pokazivača je %p.

Deklarisanje pokazivača

Type *variable1; // u opstem slucaju
int *variable2; // pokazivac na int
int* a, b; // <=> int *a, b;

Operator adrese &

int a = 10;
int *pointer = &a;

Operator adrese u jeziku C označava adresu u memoriji promenljive ili funkcije. Simbol ovog operatora je &.

Na primer, ako imamo promenljivu x, &x će vratiti adresu u memoriji gde je smeštena vrednost promenljive x. Adresa je obično izražena u heksadecimalnom formatu.

VAŽNO!!! Operator adrese je unarni operator. Iako se koristi isti simbol, kao za bitovsko AND, jezik C zna da razlikuje ova dva operatora jer je jedan binarni a drugi unarni. Praksa je da se ovaj operator ne razdvaja od imena promenljive. Ovaj operator ima veći prioritet od aritmetičkih operatora. Ovde možete pogledati prioritete operatora.

Operator dereferenciranja *

int a = 10;
int *pointer = &a;
*pointer = 20;
printf("%d\n", a); // ispisace 20

VAŽNO!!! Operator dereferenciranja je unarni operator. Iako se koristi isti simbol, kao za množenje, jezik C zna da razlikuje ova dva operatora jer je jedan binarni a drugi unarni. Praksa je da se ovaj operator ne razdvaja od imena promenljive. Ovaj operator ima veći prioritet od aritmetičkih operatora. Ovde možete pogledati prioritete operatora.

Operator dereferenciranja se na neki način može smatrati inverznom funkcijom operatora adrese. Primenom operatora dereferenciranja se pristupa memorijskoj lokacija na koju “pokazuje” adresa koja se čuva u promenljivom pokazivača (u ovom slučaju, promenljivoj pointer) i na taj način je moguće izmeniti ili uzeti vrednost sa te adrese. Ovo je korisno ukoliko želimo da dinamički alociramo memoriju (biće reči o tome na nekim od sledećih vežbi) ili pristupamo i menjamo vrednosti promenljivih jedne funkcije u nekoj drugoj funkciji.

Primer - zamena vrednosti dve promenljive

Pogrešno:

#include <stdio.h>

void swap_wrong(int x, int y) { // nove lokalne promenljive koje su vidljive samo u ovoj funkciji
  int tmp;
  printf("Adresa x u swap_wrong: %p\n", &x); // adrese ce biti razlicite u odnosu na promenljive iz main funkcije
  printf("Adresa y u swap_wrong: %p\n", &y);
  tmp = x;
  x = y;
  y = tmp;
}

int main() {
  int x = 10, y = 20;

  printf("Adresa x u main: %p\n", &x); 
  printf("Adresa y u main: %p\n", &y);

  swap_wrong(x, y); // prosedjuju se vrednosti promenljivih

  printf("x = %d, y = %d\n", x, y);
}

Ispravno:

void swap(int *px, int *py) { // posto su prosledjene adrese, lako mozemo pristupiti i izmeniti vrednosti koje se nalaze na ovim adresama
  int tmp;
  printf("Adresa x u swap_wrong: %p\n", px); // adrese ce biti iste kao u main funkciji
  printf("Adresa y u swap_wrong: %p\n", py);
  tmp = *px;
  *px = *py;
  *py = tmp;
}

int main() {
  int x = 10, y = 20;

  printf("Adresa x u main: %p\n", &x); 
  printf("Adresa y u main: %p\n", &y);

  swap(&x, &y); // prosedjuju se adrese promenljivih

  printf("x = %d, y = %d\n", x, y);
}

Primer vraćanja više vrednosti

Napisati funkciju koja istovremeno vraća dve vrednosti - količnik i ostatak dva data broja.

#include <stdio.h>

void div_and_mod(int x, int y, int* div, int* mod) {
  printf("Kolicnik postavljam na adresu: %p\n", div);
  printf("Ostatak postavljam na adresu : %p\n", mod);
  *div = x / y;
  *mod = x % y;
}

int main() {
  int div, mod;
  printf("Adresa promenljive div je %p\n", &div);
  printf("Adresa promenljive mod je %p\n", &mod);
  div_and_mod(5, 2, &div, &mod);
  printf("Vrednost promenljive div je %d\n", div);
  printf("Vrednost promenljive mod je %d\n", mod);
}

Pokazivačka aritmetika

#include <stdio.h>

int main() {
  char s[] = "abcde"; 
  int t[] = {1, 2, 3, 4, 5};
  char* ps = &s[0]; // isto je kao da stavimo char *ps = s;
  int* pt = &t[0]; // <=> int *pt = t;

  printf("ps = %p\n", ps);
  printf("ps+1 = %p\n", ps+1); // povecava i smanjuje za velicinu tipa a ne za 1
  // u ovom slucaju ce biti za 1 jer jedan char zauzima 1 byte u memoriji
  printf("ps+2 = %p\n", ps+2);
  printf("ps-1 = %p\n", ps-1);
  printf("ps-2 = %p\n", ps-2);

  printf("pt = %p\n", pt); 
  printf("pt+1 = %p\n", pt+1); // povecava i smanjuje za velicinu tipa a ne za 1
  // povecace za 4 jer je jedan int velicine 4 byte
  // isto vazi i za oduzimanje 
  printf("pt+2 = %p\n", pt+2);
  printf("pt-1 = %p\n", pt-1);
  printf("pt-2 = %p\n", pt-2);

  ps = &s[3];
  printf("s = %p\n", s);
  printf("ps = %p\n", ps);
  printf("ps - s = %d\n", ps - s); // 3

  pt = &t[3];
  printf("t = %p\n", t);
  printf("pt = %p\n", pt);
  printf("pt - t = %d\n", pt - t); // 3

  return 0;
}

Veza između pokazivača i nizova

#include <stdio.h>

int main() {
  char s[] = "abcde"; 
  char* ps = &s[0]; // isto je kao da stavimo char *ps = s;
  
  // 1. nacin prolaska kroz niz

  for (int i = 0; i < 5; i++) {
    putchar(s[i]); // standardno koriscenje niza
  }

  putchar('\n');

  // 2. nacin prolaska kroz niz

  for (int i = 0; i < 5; i++) {
    putchar(ps[i]); // koriscenje pokazivaca kao niz
  }

  putchar('\n');

  // 3. nacin prolaska kroz niz

  for (int i = 0; i < 5; i++) {
    putchar(*(ps + i)); // primena pointerske aritmetike
  }

  putchar('\n');

  // 4. nacin prolaska kroz niz

  // primena pointerske aritmetike
  for (char *pps = ps; *pps; pps++) { // '\0' je false
    putchar(*pps);
  }

  putchar('\n');

  printf("s == ps je %s\n", (s == ps) ? "true" : "false" );

  printf("Adresa pocetka niza i adresa prvog elementa su iste jer je s == &s[0] zapravo %s", (s == &s[0]) ? "true" : "false" );

  printf("sizeof(s) = %ld\n", sizeof(s)); // duzina niza * sizeof(tip niza)
  printf("sizeof(ps) = %ld\n", sizeof(ps)); // najcesce 8 bajtova

  // NAPOMENA: ukoliko se funkcijama prosledjuje niz, zapravo se ne prosledjuje ceo niz nego pokazivac na niz
  // zbog toga su promene nad nizom koje su izvrsene u nekoj funkciji, vidljive i van nje

  return 0;
}