Reference - detaljnije objašnjenje
Kada pišete programe, reference su nešto najteže za razumevanje. Dovode vas u iskušenje da se "držite onoga što znate" i da pokušate da sve napišete koristeći strukture. Ovo je moguće, ali ćete videti da su tada vaši programi teži za razumevanje i da su ponekad sporiji i veći nego što je potrebno. I referentni tipovi i vrednosni tipovi imaju svoje mesto, a potrebno je da znate kako i kada se koriste. Vrednosti zamislite kao nešto što "opisuje" neki objekat - na primer, iznos novca na računu u banci. Reference zamislite kao pokazivanje na nešto što radije ne biste da pomerate okolo ili nešto što želite da delite - na primer, veliki zvučni efekat koji se koristi za buku motora raznih svemirskih brodova u igrici.
Da biste razumeli problem, pogledajte kako se novcem nekada upravljalo na ostrvu Jap u Tihom okeanu. Plaćanje na ovom ostrvu se zasnivalo na kamenju koje je bilo oko 3,5 metara visoko i težilo nekoliko stotina kilograma. Vrednost "kovanice" u Jap novčanicama bila je neposredno povezana sa brojem ljudi koji su nastradali u tegobnoj plovidbi brodom da bi se to kamenje dovuklo na ostrvo. Što bi se veći i teži kamen dovukao, to bi više vredeo.
Kada nekom plaćate jednim od tih kamena, ne biste ga zaista uzeli u ruke i dali mu ga, jer je taj kamen pretežak. Umesto toga, rekli biste mu "Kamen pored puta na vrhu brda sad je tvoj". Drugim rečima, ljudi na Japu su koristili reference za upravljanje objektima koje nisu želeli da premeštaju okolo. I mi možemo da u našim programima koristimo reference.
Reference i tipovi vrednosti
Veoma je važno da razumete razliku između reference i tipova podataka, jer to izuzetno utiče na to kako se promenljive koriste. Pogledajte sledeći kod:
struct ContactStruct { public string ContactName; public string ContactAddress; public string ContactPhone; public int ContactMinutesSpent; }
Program može da napravi promenljivu tipa ContactStruct jednostavnim deklarisanjem nečega tog tipa:
ContactStruct structPera;
structPera.ContactName = "Pera";
Console.WriteLine(structPera.ContactName);
Ovi iskazi prave promenljivu strukture pod nazivom structPera i zatim postavljaju svojstvo te promenljive ContactName na string "Pera". Kada se ovi iskazi obavljaju, oni rade tačno ono što biste i očekivali: ime "Pera" se prikazuje povezano sa promenljivom StructPera. Na isti način možete da koristite druge članove sa podacima objekta ContactStruct, pošto struktura ContactStruct obuhvata prostor za držanje svakog člana sa podacima.
Hajde da sada napravimo manju izmenu programa i pretvorimo strukturu sa podacima o osobama u klasu:
class ContactClass { public string ContactName; public string ContactAddress; public string ContactPhone; public int MinutesSpent; }
Informacije o ososbama se sada dre u klasi, a ne ui strukturi. Možda očekujete da klasu ContactClass možete da koristite na isti način kao i strukturu ContactStruct:
ContactClass classPera;
classPera.ContactName = "Pera";
Ali kada se ovo kompajlira, dobiće se oruka o grešci:
Error ...: Use of unassigned local variable 'classPera'
Šta se dešava? Da biste razumeli, potrebno je da znate koju operaciju obavlja sledeći red:
ContactClass classPera;
Ovaj iskaz izgleda slično kao i deklaracija promenljive tipa ContactStruct sa nazivom structPera. Međutim, oono što ovaj iskaz pravi nije isto kao kad program deklariše promenljivu tipa struktura. Ono što zaista dobijate kada program obavi prethodni iskaz je referenca nazvana classPera. Takvim referencama je omogućeno da ukazuju na instance klase ContactClass. Možda referencu zamišljate kao privezak za prtljag koji je sa torbom povezan parčetom kanapa ili konca. Ako imate privezak, možete da pratite kanap do objekta sa kojim je povezan.
Ali kada pravite referencu, u suštini, ne dobijate ništa od onoga na šta ta referenca ukazuje. Kompajler to zna, pa zato prikazuje grešku, jer je red :
classPera.ContactName = "Pera";
pokušaj da se pronađe objekat koji je povezan oznakom classPera i da se svojstvo ContactName postavi na "Pera". Ali, pošto ova oznaka trenutno nije povezana ni sa čime (a kompajler to zna), programu se ne dozvoljava da se to izvrši. Kompajler kaže, u suštini: "Pokušavate da naterate program da prati referencu koja još uvek nije postavljena da ukazuje na bilo šta. Prema tome, prinuđen sam da vam dam poruku o grešci 'nedefinisane promenljive'.
Ovaj problem rešavate pravljenjem instance klase i zatim povezivanjem oznake sa njom. Da biste to uradili, koristite iskaz kao što je sledeći:
ContactClass classPera; classPera = new ContactClass(); classPera.ContactName = "Pera";
Obeleženi iskaz pravi novu instancu klase ContactClass na koju se ukazuje referencom pod nazivom classPera. Ovaj međusobni odnos prikazan je na sledećoj slici:
Ono što ključna reč new pravi jeste objekat, a objekat je instanca neke klase. Ovo ćemo ponoviti u posebnom redu:
Objekat je instanca neke klase.
Veoma je važno da ovo razumete.
Obratite pažnju da je na slici odgovarajući objekat nazvan ContactClass, a ne classPera. Ovo je zbog toga što instanca objekta nema identifikator classsPera: ova instanca je jednostavno ona sa kojom je klasa classPera trenutno povezana. Na koji poseban objekat ova referenca ukazuje, može da se promeni dok se program izvršava. Ovo se dešava, na primer, kada program dodeljuje novu vrednost nekoj referenci.
Reference i dodeljivanje vrednosti
Korišćenje referenci za upravljanje objektima menja ponašanje operatore dodeljivanja - operatora koji program koristi za menjanje vrednosto promenljivih. Pogledajte sledeći iskaz:
ContactStruct s1; // Pravljenje strukture s1 sa podacima o osobama ContactStruct s2; // Pravljenje strukture s2 sa podacima o osobama s1.ContactName = "Pera"; // postavljanje naziva promenljive s1 na "Pera" s2 = s1; // Postavljanje vrednosti promenljive s2 na vrednost promenljive s1 s2.ContactName = "Sima"; // postavljanje naziva promenljive s2 na "Sima"
Dve promenljive (s1 i s2) možemo da posmatramo kao imenovane kutije u memoriji, pri čemu svaka kurija sadrži posebnu vrednost. Kada obavljamo dodeljivanje, program kopira vrednost iz jedne kutije u drugu kutiju. Ovi iskazi bi se završili sa dve strukture koje imaju podatke o osobama - jedna koja sadrži ime "Pera", a druga, ime "Sima".
Međutim, kada program počne da koristi reference, stvari se menjaju:
ContactClass c1; // Pravljenje reference klase osoba nazvane c1 koja ukazuje na novu osobu ContactClass c2; // Pravljenje reference klase osoba nazvane c2 koja ukazuje na novu osobu c1.ContactName = "Pera"; // postavljanje imena osobe na koju ukazuje referenca c1 na "Pera" c2 = c1; // Referenca c2 ukazuje na isti objekat kao i referenca c1 c2.ContactName = "Sima"; // postavljanje imena osobe na koju ukazuje referenca c2 na "Sima"
Ovaj niz iskaza radi sa promenljivama tipa ContactClass. Obe oznake su sada povezane sa istim objektom u memoriji, a objekat koji je prvobitno dodeljen na referencu c2 nema više ništa povezano sa njim. Ime osobe je sada "Sima" pošto je poslednje dodeljivanje prebrisalo vrednost "Sima" koja je bila ranije postavljena.
Jezik C# nema problem sa programima koji pridružuju više referenci na jedan isti objekat. Ali, to ima uticaj na ono što se dešava kada se program izvršava.
Kada radite sa referencama, morate da imate na umu da operator dodeljivanja sada radi na ovaj način. Umesto da premešta podatke iz jedne promanljive u drugu promenljivu, on radi tako da dve reference ukazuju na isti objekat u memoriji. Ovo je veoma korisno ako vaš program ima dobar razlog da to uradi - na primer, tako da dva dokumenta u programu za obradu teksta mogu da dele isti račnik - ali je zato veoma zbunjujuće ako to niste nameravali da uradite.
Savet programera:
Reference znaju da prevare, ali su veoma bitne.
Sa objektima i referencama treba biti obazriv. Ne postoji ograničenje za broj referenci koje se mogu pridružiti jednom objektu, pa je neophodno da upamtite da menjanje objekta na koji neka referenca ukazuje može da prilično promeni tu instancu sa tačke gledišta ostalih objekata. Ovo je izuzetno korisno, ali, kao što izreka kaže: "Uz veliku moć dolazi i velika odgovornost".
Objekti bez referenci koje ukazuju na njih
Druga okolnost koju je potrebno da razmotrimo je kada u memoriji završi neki objekat tako da ništa ne ukazuje na njega. Videli smo na prethodnoj slici gde je objekat kome je prvobitno dodeljena referenca c2, završio "viseći" u prostoru, pri čemi ništa ne ukazuje na njega. Što se tiče korišćenja podataka u toj instanci, ona kao i da ne postoji.
U jeziku C# postoji poseban procesn nazvan "skupljač otpada" (engl. garbage collector) čiji eje posao da pronađe i ukloni takve beskorisne stavke. Obratite pažnju na to da kompajler neće da nas zaustavi da napišemo kod koji oslobađa reference naobjekte, sve ovo se dešava kada se program izvršava, ne kada se kompajlira.
Potrebno je da imate na umu da ćete dobiti sličnu posledicu kada referenca na neku instancu izađe iz oblasti primene:
{
ContactClass localPromenljiva;
localPromenljiva = new ContactClass();
}
Promenljiva localPromenljiva je lokalna za ovaj blok. Kada izvršavanje programa napusti ovaj blok, lokalna promenljiva se odbacuje. To znači da se i sama referenca na objekat ContactClass uklanja, što opet znači još posla za skupljač otpada.
Savet programera:
Pokušajte da ne pravite posao skupljaču otpada
Mada ponekad ima smisla da oslobodite stavke koje više nećete da koristite, morate imati na umu da pravljenje i odbacivanje objekata oduzima vreme procesoru. Kada radim sa objektima, brinem o tome koliko sam objekata napravio i uništio. Samo zato što se objekti automatski odbacuju, ne znači da treba da zloupotrebljavate ovu mogućnost. Jedan od načina da poboljšate brzinu rada aplikacije je da imate "spisak slobodnih" objekata koji se trenutno ne koriste. Kada programu zatrena nov objekat, uzima ga iz ovog spiska umesto da pravi nov od početka. Umesto da se odbacuju, neželjeni objekti se smeštaju u spisak slobodnih objekata. Operativni sistem Windows i sam koristi ovaj programerski trik za upravljanje većinom objekata koji održavaju sistem u radu.