[2] Klase, pokazivači, reference

[2] Klase, pokazivači, reference

offline
  • Fil  Male
  • Legendarni građanin
  • Pridružio: 11 Jun 2009
  • Poruke: 14644

[1] Klase





C++ provides the power; you supply the judgment



Programiranjem često rešavamo kompleksne probleme te je poželjno da se koriste i odgovarajuće reprezentacije objekata konkretnog problema.

Recimo, ukoliko treba da rešimo neki problem vezan za automobilsku industriju, barataćemo sa instancama (objektima) klase automobil, koje će imati odgovarajuće promenljive (recimo: brojBrzina, brojCilindara) i metode (recimo: Ubrzaj(), PromeniBrzinu() i druge).

Dakle, objekat predstavlja instancu neke klase. Klase predstavljaju korisnički definisane tipove.

Iz primera se vidi da promenljive čuvaju stanje nekog objekta, dok se metode odnose na ponašanje tog objekta. U ovom smislu, objekat predstavlja kolekciju određenih stanja i ponašanja.

* Napomena: više o klasama je pisano u člancima za C#, tako da preporučujem čitanje tih članaka. Koncept je isti, a sintaksa skoro pa jednaka.

Deklaracija klase:

class Automobil {      unsigned int brojBrzina;      unsigned int brojCilindara;      void Ubrzaj(); };

Exclamation Napomena: uočimo karakter ";" na kraju deklaracije klase! U programskim jezicima Java i C# nema karaktera ";" kod deklaracije klasa!

Deklaracije klase ne alocira (rezerviše) memoriju za klasu Automobil.
--> deklaracija služi da kompajler "obavestimo" šta predstavlja klasa Automobil, dakle, kakve podatke sadrži i šta može da radi.
--> Na osnovu ovoga kompajler zna koliko memorijskog prostora treba da "ostavi sa strane" objekte klase Automobil.

Pošto int zauzima 4 bajta, a imamo dve promenljive u klasi, --> objekat ove klase će biti "težak" 8 bajtova. Metode se ne uzimaju u obzir kod utvrđivanja veličine u memoriji.

Prilikom kreiranja koda programa treba, dakle, obratiti pažnju sa kakvim promenljivim, tj. sa kakvim tipovima promenljivih baratamo. Tip promenljive ukazuje na:

a) veličinu promenljive u memoriji,
b) kakvu vrstu podatka promenljiva može da čuva,
c) kakve akcije se mogu izvršiti nad promenljivima.


Dalje, treba obratiti pažnju i na stil programiranja. Ukoliko se držite jednog stila programiranja (davanja imena promenljivima, metodama i sl. ) lakše ćete protumačiti kod. Firme obično propisuju ova stilska pravila (standardni kodeks).


Definisanje objekta:

Automobil lada;

Ovim se definiše objekat klase (tj. tipa) Automobil. Ovo je korak u kome se rezerviše memorija za objekat.

Kako su sve članice klase privatne (ako se ne naznači drugačije), na sledeći način je moguće javno pristupiti članicama klase:

class Automobil {      public:      unsigned int brojBrzina;      unsigned int brojCilindara;      void Ubrzaj(); };

* Opet vidimo razliku u sintaksi sa, recimo, srodnim jezicima Javom i C# ; kod njih se ispred svake promenljive definiše modifikator pristupa (dakle, public int brojBrzina;
public int brojCilindara; )

Bitno je reći da će public biti sve članice:
a) do navoda ključne reči private
b) do kraja deklaracije klase


Primer:




U primeru se vidi generalno pravilo dobrog dizajna - članice klase treba da su privatne. Pošto su promenljive privatne, moramo imati neke metode koje će služiti za manipulaciju nad tim podacima (čitanje i izmena vrednosti promenljivih). Te metode se nazivaju Accessor metode. Na ovaj način se omogućava odvajanje detalja implementacije i upotrebe (tj. manipulacije).
Accessor metode su tzv. Setteri i Getteri, koje su public metode i služe za promenu vrednosti, odnosno čitanje vrednosti promenljivih.

Citat Stroustrup-a: "The C++ access control mechanisms provide protection against accident—not against fraud."

Arrow Objekat pristupa public članicama klase vrši sa operatorom "."
Arrow Obratiti pažnju i na označavanje da metoda (implementirana izvan klase) pripada nekoj klasi, tj. ": :"

Konstrukor služi za instanciranje/inicijalizaciju objekta neke klase. Konstruktor, tehnički gledano, predstavlja posebnu metodu:
- koja ima naziv isto kao i naziv konkretne klase
- koja ne vraća povratnu vrednost

C++, za razliku od C# i Jave, nema garbage collection (koji se brine o "čišćenju" objekata), te prilikom deklarisanja konstruktora, treba deklarisati i odgovarajući destruktor. Destruktor se formira na sledeći način: "~"+naziv konstruktora. Dakle, ako je konstruktor: Automobil(), destruktor će biti ~Automobil().

Idea dok konstruktori mogu imati ulazne argumente, destruktori ne primaju argumente.

Idea Jedna klasa može imati više konstruktora (sa različitim parametrima).

Idea Ukoliko ne deklarišemo konstruktor/desktruktor, generiše se podrazumevani konstruktor/destruktor.
Exclamation Default constructor/destructor nema argumenata i ne radi ništa!
(primetimo da u prošlom primeru nije eksplicitno naveden konstruktor, te u tom slučaju, kako smo rekli, kompajler obezbeđuje default konstruktor. Zbog toga prethodni kod nije generisao grešku Smile )

U sledećem primeru, biće upotrebljen non default konsruktor (prethodni primer će biti za nijansu proširen):







[2] Pokazivači




Pokazivač ili pointer je promenljiva koja čuva memorijsku adresu.

Pogledajmo sledeću sliku:




Na slici je prikazano:

- vrlo prosta šema memorije na računaru. Dakle, memorija računara je podeljena na sekvencijalno označene memorijske lokacije, zvane adrese.
Svaka promenljiva se nalazi na unikatnoj lokaciji u memoriji, tj. na određenoj adresi.

- pointer a koji pokazuje na promenljivu b.

* promenljiva b se nalazi na memorijskoj lokaciji 1462 i čuva vrednost 17
* pokazivač a se nalazi na memorijskoj lokaciji 874 i čuva memorijsku adresu promenljive b, dakle: 1462


Idea pomoću adresnog operatora & može se videti memorijska adresa neke promenljive, Vidimo sledeći primer:




Oznake memorijske lokacije zavise od operativnog sistema i konkretnog kompajlera.
// U primerima šeme memorije, obično adresu apstrahujemo sa brojčanom vrednošću.


Podsetimo se, od tipa promenljive zavisi koliko će biti rezervisano i dodeljeno memorijskog prostora. Pogledajmo još jednu šemu:




Pointer "nekiPointer", koji treba da čuva adresu neke promenljive tipa int, ćemo deklarisati na sledeći način:

int *nekiPointer = NULL;

Dakle, na ovaj način je nekiPointer deklarisan da čuva adresu promenljive koja može biti tipa int.

Što se tiče dalje manipulacije sa pointerima, ponašaju se isto kao i svaka druga promenljiva.

Idea Razlika između promenljive tipa int i pointera koji pokazuje na neki int je ta što promenljiva tipa int čuva integer, a pointer čuva adresu.

Exclamation Neka vam bude obavezna praksa da inicijalizujete pointere !

- ukoliko se pointer inicijalizuje sa konstantom NULL - zove se Null Pointer
- pointer koji nij inicijalizovan se zove divlji pointer (wild pointer) i ovo treba strogo izbegavati.

Pridruživanje adrese pointeru:

unsigned short int nekiInt = 45;
unsigned short int * nekiPointer = NULL;
nekiPointer = &nekiInt;


Idea Pristup nekoj promenljivoj preko njenog pokazivača naziva se indirekcija.

Idea Mnoge firme koriste konvenciju davanja imena pointera, tako što ime pointera počinje sa malim "p". Na primer: pGodina.

Idea Operator "*" se naziva indirection operator, ili dereference operator.

Ukoliko treba nekoj promenljivoj da dodelimo vrednost na čiju adresu gađa pointer, to ćemo učiniti ovako:

unsigned short int primer;
primer = *nekiPointer;


Dakle, opet se koristi indirection operator (*) i označava "vrednost na ovoj adresi."

// dakle uzimamo vrednost sa adrese koju čuva nekiPointer i dodeljujemo promenljivoj "primer".


Exclamation Kao što se može videti, operator " * " ima dvojaku ulogu:
- deklaracija pointera
- ukazivanje na samu vrednost, ne na memorijsku adresu

// Mala digresija: " * " predstavlja i operator množenja, ali kompajler zna "da odabere" pravu funkciju operatora, na osnovu konteksta.


Pokazivači se najčešće koriste za tri zadatka:

- upravljanje podataka na heap-u (free store)
- pristup članicama klase
- prenos promenljivih po referenci


Naime, postoje sledeća polja memorije:

Citat:The stack
Code space
Global name space
Registers
The free store (heap)


- na stack-u se skladište lokalne promenljive i parametri funkcija
* kada funkcija vrati neku vrednost, lokalne promenljive se odbacuju
* stack se automatski čisti, kada funkcija vrati vrednost (samim tim i promenljive gube svoj opseg (scope))

- u Code space-u se skladišti kod.

- globalne promenljive se skladište na global name space-u.

- registers se koriste za interne funkcije održavanja (npr. stanje na vrhu stack-a i pokazivač za instrukcije)

- skoro sav preostali deo memorije predstavlja free store, odnosno heap.
Objekti na heap-u su trajni (perzistentni) i "ostaju" i posle return-a funkcije (za razliku od stack-a, gde ovo nije slučaj ! )


* alokacija memorije na heap se vrši sa ključnom reči "new":

unsigned short int * pPointer; pPointer = new unsigned short int; *pPointer = 72;

// new vraća memorijsku adresu
// *pPointer = 72; --> stavi vrednost 72 na lokaciju gde pointer pokazuje

* ne zaboravi da obrišeš pointer: ključna reč delete.
Dakle, kada se završi rad sa nekom memoijskom lokacijom, potrebno je da se pobriše pointer. Upotrebom ključne reči delete, oslobađa se memorija na heap-u i deklariše kao raspoloživa.

* delete se mora koristiti inače će se javiti memory leak
--> fora je u tome da pointer predstavlja lokalnu promenljivu, i kao takav nestaje kada funkcija vrati vrednost (tj. kada izgubi scope). Međutim, memorija na koju pointer pokazuje nije lokalnog karaktera i ne oslobađa se automatski.
Dakle, ukoliko se izgubi pokazivač na tu lokaciju, ona će ostati rezervisana (ukoliko se ne oslobodi sa delete), jer ne postoji neki drugi način da se otkrije koja memorijska lokacija se treba osloboditi (i opet staviti na raspolaganje).
Ova situacija se zove memory leak jer se ovako zauzeta memorije ne može povratiti sve dok se program ne završi (u toku rada programa, memorija kao da je "iscurela".)

Arrow uvek treba koristiti ključnu reč delete
Citat:delete pPointer;

// oslobodi memorijsku lokaciju na adresi na koju pointer pokazuje.

Idea treba napomenuti da se ovim ne briše pointer! Znači, pointeru je i dalje "u opticaju" i može mu se dodeliti neka druga adresa.

Animal *pDog = new Animal; //alocates the memory delete pDog;  //frees the memory pDog = NULL; //sets pointer to null //... delete pDog; //legit


Primer:
Demonstracija pointera i ključne reči delete




+ Vidi kod


Ovo je primer bez mehanizama provere na grešku. Jedan od mehanizama "defanzivnog programiranja" je da testiramo da li je pointer null pre inicijalizacije.


Idea Primer memory leak-a:

(dodela druge adrese pointeru bez prethodnog brisanja memorije na koju pokazuje)

Citat:unsigned short int *pPointer =new unsigned short int; //rezervise se memorija
*pPointer =72; //upisuje se vrednost u memoriju
pPointer =new unsigned short int; //rezervise se drugi memorijski prostor (prethodni nije oslobodjen)
*pPointer =84; //upisuje se vrednost u "drugi" memorijski prostor


* Ne postoji način da se oslobodi prva memorija, budući da nema pokazivača, te se više ne zna lokacija tog prostora. Oslobodiće se tek po završetku programa.

* Pre linije: "pPointer =new unsigned short int;", treba staviti: "delete pPointer;" da ne bi bilo curenja memorije.

Exclamation Svaki put kada se koristi ključna reč "new" u programu, takođe treba biti i odgovarajući delete, kako bi memorija opet bila dostupna za upis na heap-u.


Primer: eksplicitno brisanje sa heap-a i implicitno brisanje sa stack-a




+ Vidi kompletan kod

* pristup clanicama preko pointera, primer: (*pKido).GetNumber();
* ili preko "point to operator" tj. "->", primer: pKido->GetNumber();
// i ovo je "indirection operator"

Primer upotrebe operatora:




+ Vidi kompletan kod


* dve varijable članice su pointeri ka integerima
* konstruktor inicijalizuje pointere da pokazuju na vrednost u memoriji (heap)
* destruktor briše alociranu memoriju
* main funkcija nema pojma da su itsDetected i itsRemoved pointeri koji pokazuju na neku lokaciju na heap-u. Oslobođena je znanja o memorijskoj lokaciji, jer zna samo za GetDetected() i SetDetected(). Dakle, svi detalji o implenetaciju su sakriveni unutar klase (baš kako i treba da bude).
* Posmatrajmo liniju "delete Virut;"
Kada se Virut obriše na liniji 40, poziva se njegov destruktor (~Malware) i on briše promenljive članice klase (koji su pointeri !) . E sad, kako oni pokazuju na Integere, njihovi implicitni destruktori će biti pozvani.


Idea ključna reč this predstavlja pointer ka tekućem objektu. Svaka metoda klase ima ovaj "skriveni parametar". Primer upotrebe:

Citat: public:
Rectangle();
~Rectangle();
void SetLength(int length) { this->itsLength =length;}
int GetLength() const { return this->itsLength; }


+ Vidi kompletan kod


* Napomena: this pointeri se ne brišu eksplicitno! O ovome ne moramo da brinemo jer se o tome brine kompajler.



Exclamation Pored memory leak-a, još jedna bitna stvar kod pointera, a na koju treba da obratimo pažnju, su takozvani: dangling, stray ili wild pointeri (divlji ili zalutali pokazivači). O čemu se ovde radi?

--> divlji pokazivač se kreira kada pozovemo delete nekog pokazivača pokazivača (i samim tim oslobodimo memoriju na koju pokazuje), ali ga ne postavimo na NULL nakon oslobađanja memorije!

Budući da je oslobođena memorija dostupna za neki drugi upis, ukoliko ne NULL-ujemo pokazivač, on će pokazivati na istu memorijsku lokaciju, u kojoj se sada nalazi neki drugi sadržaj Exclamation

Ukoliko se u programu i dalje koristi daj pointer, rezultat tog programa je tada vrlo nepredvidiv!

(ilustrativan primer je, da imamo memorisan broj neke kompanije na telefonu. Kompanija se može ugasiti ili preseliti i taj broj postaje dostupan za neku drugu firmu. Dakle, ako pozovemo taj memorisani broj (koji je u međuvremenu dodeljen nekoj drugoj firmi), dobićemo uslovno rečeno - nepredviđen rezultat (umesto da se završi posao sa prvobitnom firmom, komunicira se sa drugom firomom i prekida se poziv, pri čemu posao nije obavljen)).

Arrow Naravoučenije - uvek kada pozovemo delete pointera, postavimo taj pointer da pokazuje na NULL ili nullptr.

// Više o tome - u ovoj temi: http://www.mycity.rs/C/Analiza-situacije-Stomping-on-a-C-pointer.html


Idea Još jedan primer rada sa pokazivačima, koji će biti jasniji kroz iteracije u debugger-u:

+ KOD





[3] Reference




Referencu možemo shvatiti kao sinonim, alternativno ime za objekat, gde se svaka manipulacija sa objektom - direktno odnosi na "imenovani" objekat.

Za kreiranje reference se koristi reference operator - &.

int &rNekaReferenca = 55;

Idea Napomena: & predstavlja i:
- reference operator
- i address operator.

// Nisu isti operatori u pitanju (iako je oznaka ista) i kompajler zna na sta se tačno odnosi koji operator.


Primer:




+ KOD

Exclamation Reference moraju biti inicijalizovane (za vreme kreacije)


Arrow Reference treba koristiti jer se sa njma postiže veća efikasnost programa.
- Svaki put kada se funkciji prosledi objekat po vrednosti --> pravi se kopija tog objekta.
- Svaki put kada se vrati objekat iz funkcije (return) po vrednosti --> opet se pravi kopija nekog objekta.

Ovi objekti se ne kopiraju na heap, već na stack (dakle, ako prenosimo po vrednosti) i na taj način imamo utrošak i vremena i memorije.

Prenos objekta po vrednosti je pogodan za manje objekte (tipa int), ali za veće objekte, korisnički definisane ovaj oblik nije pogodan, stoga se prenos treba vršiti po referenci. U jednom od narednih članaka će biti opisan prenos po referenci i napredne manipulacije sa funkcijama.



Refernce VS pokazivači

- pokazivači, ukoliko nisu inicijalizovani ili kada se brišu, treba da budu NULL (nullptr).
- referenca ne može biti NULL (ako se desi da referenca bude null, doći će do greške ili nepredviđenih situacija).

- reference se lakše koriste i većina C++ programera preferiraju refernce, umesto pokazivača. Savet je da se pokazivači ne koriste ukoliko reference mogu da "završe posao".
- Pointeri nude veću fleksibilnost, ali su komplikovaniji za upotrebu. Sa njima treba postupati vrlo oprezno.

- Reference bolje sakrivaju detalje u kodu, što će se videti u kasnijim primerima serijala (em nema potrebe za neprestanim dereferenciranjem promenljive, em je indirekcija skrivena).

- Dok se pokazivači mogu pokazivati na jedan objekat, a potom na drugi
- reference se vezuju samo za jedan objekat (svaka manipulacija nad referencom se odnosi baš za taj konkretni objekat, + adresa reference predstavlja memorijsku adresu posmatranog objekta kome je refernca alias) .





Trivia/Napomene



* Sve članice klase su podrazumevano privatne. Dakle, može im se pristupiti jedino na nivou metoda same klase.

* Inicijalizacija predstavlja deklaraciju promenljive uz dodelu vrednosti (npr. int i = 5Wink

* Ako napišemo sopstveni konstruktor, kompajler neće da obezbedi default konstruktor! Dakle, ako naš konstruktor prima neki parametar, kompajler neće obezbediti default konstruktor. Ukoliko želimo konstruktor bez parametara, moraćemo ga sami programirati.

* Reference moraju biti inicijalizovane! Ukoliko deklarišemo referencu, a ne inicijalizujemo je, dobićemo compile-time error.

* Ukoliko potražimo adresu reference, vratiće adresu svoje "mete" (budući da predstavljaju alias nekog objekta [mete]).

* Reference se ne mogu menjati, u smislu da se dodele drugom objektu !!!
--> kada se definiše, referenca predstavlja alias samo za jedan objekat.
Pseudokod:
Citat:int intOne;
int &rSomeRef = intOne; //alias za intOne
....
int intTwo = 8;
rSomeRef = intTwo; // nije alias za intTwo !
// ova linija ce zapravo intOne dodeliti 8, referenca nece promeniti alias!


* Objekat može da bude NULL ukoliko iz nekog razloga ne može da se alocira memorija na heapu Exclamation
--> budući da reference ne mogu biti NULL, dobra praksa defanzivnog programiranja je da se objekat provo proveri da li je NULL, i kada se utvrdi da nije NULL --> da se onda može incijalizovati referenca, da bude alias tom objektu.

Primer:
Citat:int *pObjekat = new Objekat();
if (pObjekat != NULL) { int &rObjekat = *pObjekat; }



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
Ko je trenutno na forumu
 

Ukupno su 437 korisnika na forumu :: 9 registrovanih, 1 sakriven i 427 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 1567 - dana 15 Jul 2016 19:18

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: ALBION101, Alojz Hauptman, amstel2, Bane san, Cobi026, djordje92sm, kybonacci, nick93ts, Sonyboy