Materijali/Čas 12

Čas 12 · adresa, dereferenciranje i više rezultata

Pokazivači i funkcije koje menjaju podatke

Povezujemo promenljivu sa njenom adresom, pratimo pokazivač na crtežu i koristimo funkcije koje menjaju podatke pozivaoca ili upisuju više rezultata.

MAPA ČASA

Od ideje do prvog C programa

1
program
2
podaci
3
promenljiva
4
pseudokod
5
C program

Na kraju je dovoljno da:

  • Razlikuj vrednost, adresu i pokazivač.
  • Prati operatore & i * kroz konkretan kod.
  • Napiši funkciju koja menja promenljivu pozivaoca.
  • Vrati dva rezultata preko parametara-pokazivača.
  • Izmeni niz pomoću funkcije i adresa njegovih elemenata.

01 · KORAK

Pokazivač čuva adresu, ne novu kopiju podatka

Promenljiva int a čuva ceo broj. Izraz &a predstavlja adresu te promenljive, a promenljiva int *p može da sačuva takvu adresu. Kada napišemo int *p = &a;, p pokazuje na a.

Izraz *p nije adresa. On znači vrednost objekta na koji p pokazuje. Zato dodela *p = 12; menja a ako p u tom trenutku pokazuje na a.

int a = 7;
int *p = &a;
*p = 12;  /* a je sada 12 */
Prati šta se menjaZa a = 7, b = 20 prati naredbe p = &a; *p += 5; p = &b; *p = a + 3;. Posle svake napiši a, b, na šta p pokazuje i vrednost *p.
Tri različita zapisap je pokazivač, *p je vrednost na adresi, a &a je adresa promenljive a. Znak * u deklaraciji označava tip pokazivača, a u izrazu pristup podatku preko pokazivača.

02 · KORAK

Zvezdica pripada svakom deklarisanom pokazivaču

U deklaraciji int *p, q; samo je p pokazivač, dok je q običan int. Ako su oba imena pokazivači, pišemo int *p, *q; ili, još jasnije, svako u posebnom redu.

Pokazivač mora da pokazuje na važeći objekat pre dereferenciranja. Ne koristimo *p dok p nema ispravnu adresu. Kada samo pratimo osnovni kod, uz svaku naredbu zapisujemo i objekat na koji p trenutno pokazuje.

int a = 1;
int *p = &a;
int *q = &a;
(*q)++;  /* a postaje 2; i *p i *q su 2 */
Ispravi deklaracijuObjasni zašto int *p, q; p = q = &a; nije ispravno. Prepiši deklaraciju tako da su oba imena pokazivači, zatim odredi a, *p i *q posle (*q)++.
Adresa se prikazuje pomoću %pZa prikaz adrese koristi se printf("%p", (void *)p). Ne treba pamtiti ili obećavati konkretnu brojčanu adresu, jer ona zavisi od izvršavanja programa.

03 · KORAK

Funkcija menja original kada dobije njegovu adresu

Običan parametar int x dobija kopiju vrednosti. Promena te kopije ne menja promenljivu iz main-a. Parametar int *x dobija kopiju adrese, pa preko *x može da pristupi objektu pozivaoca.

C formalno sve argumente prenosi po vrednosti. Kada prosledimo &a, kopira se vrednost adrese. Izraz prenos po referenci često se koristi neformalno, ali stvarni mehanizam je prosleđivanje adrese po vrednosti.

void uvecaj(int *x) {
    *x += 10;
}

int a = 5;
uvecaj(&a);  /* a je 15 */
Kopija naspram originalaUporedi void f(int x) { x++; } i void g(int *x) { (*x)++; }. Za pozive f(a) i g(&a) nacrtaj šta funkcija dobija i napiši koja promena ostaje posle povratka.
& u pozivu, * u funkcijiAko parametar ima tip int *, poziv najčešće šalje &promenljiva. U telu funkcije *parametar pristupa toj promenljivoj. Tipovi i nivo pokazivanja moraju da se poklope.

04 · KORAK

Zamena zahteva adrese i privremenu vrednost

Funkcija za zamenu dva broja dobija dve adrese. Pre prve dodele čuva staru vrednost, zatim kroz pokazivače upisuje nove vrednosti u promenljive pozivaoca.

Kod ciklične zamene a dobija staro b, b staro c, a c staro a. Staro a mora da se sačuva pre nego što ga prva dodela prepiše. Ovo je direktno povezano sa pitanjem 9 iz materijala za usmeni.

void ciklicno(int *a, int *b, int *c) {
    int pom = *a;
    *a = *b;
    *b = *c;
    *c = pom;
}

/* poziv */
ciklicno(&a, &b, &c);
Prati četiri dodeleZa a = 2, b = 7 i c = 11 posle svake naredbe zapiši pom, a, b i c. Zatim pokaži šta bi se izgubilo kada ne bi postojala pomoćna promenljiva.
void ne znači da nema rezultata u programuvoid znači da funkcija ne vraća jednu vrednost naredbom return izraz;. Funkcija ipak može da promeni objekte čije je adrese dobila.

05 · KORAK

Jedna funkcija može da upiše više rezultata

Funkcija podeli treba da proizvede količnik i ostatak. Umesto dve povratne vrednosti, ona prima njihove adrese i upisuje kroz *kolicnik i *ostatak. Obična povratna vrednost može da saopšti da li je operacija uspela.

Ako je delilac nula, funkcija vraća 0 pre bilo kakvog upisa. Main tada ne koristi k i o. Ako je delilac dozvoljen, funkcija upisuje oba rezultata i vraća 1.

int podeli(int broj, int delilac, int *k, int *o) {
    if (delilac == 0) return 0;
    *k = broj / delilac;
    *o = broj % delilac;
    return 1;
}
Odvoji status od rezultataNapiši poziv podeli za promenljive broj, delilac, kolicnik i ostatak. Prikaži rezultate samo ako je povratna vrednost 1.
Rezultati se koriste tek posle uspehaZa neuspešan poziv izlazni parametri mogu ostati bez nove vrednosti. Zato main prvo proverava povratni status, pa tek zatim čita rezultate.

06 · KORAK

Funkcija može da menja elemente originalnog niza

Parametar int a[] omogućava funkciji da menja elemente originalnog niza. Ako funkcija samo čita niz, pišemo const int a[]. Broj elemenata se i dalje prosleđuje posebno.

Adresa jednog elementa je &a[i]. Zato postojeću funkciju zameni(int *x, int *y) možemo da pozovemo kao zameni(&a[i], &a[n - 1 - i]) i tako obrnemo niz do polovine.

void obrni(int a[], int n) {
    for (int i = 0; i < n / 2; i++)
        zameni(&a[i], &a[n - 1 - i]);
}
Parovi koji se menjajuZa niz dužine 7 napiši sva tri para indeksa koje funkcija menja. Zatim prati niz 4, -2, 7, 0, 9, 5, 1 posle svakog poziva zameni.
Granica je n / 2Svaki par treba zameniti jednom. Kada bi petlja nastavila kroz ceo niz, druge polovine bi ponovile iste zamene obrnutim smerom i poništile rezultat.

ZATVARANJE

Brza provera pre domaćeg

  1. Koja je razlika između algoritma i programa?
  2. Šta je ime, a šta vrednost promenljive?
  3. Kako svojim rečima čitaš x = x + 1?
  4. Koji red prvog programa računa, a koji prikazuje rezultat?