Professional Documents
Culture Documents
Klase II
Klase II
int main ( )
{
hido h ;
h.b = 12 ; //GRESKA: b je privatni clan klase!
h.set ( 3 ) ;
int a = h.get ( ) ;
const hido hc ; //LOSE: const objekat inicijalizovan
//slucajnim automatskigenerisanim konstruktorom!
hc.set ( 2 ) ; //GRESKA: za const objekat ne mogu se
// pozivati obicne funkcije clanice!
int b = hc.get ( ) ;
return 0 ;
}
2. Statički članovi klase
Članovi podaci i funkcije članice neke klase, navođenjem ključne
reči static ispred deklaracije ili definicije, mogu da budu
deklarisani kao statički članovi klase.
int main ( )
{
first f ;
second s ;
s.set ( f ) ;
s.set2 ( f, 23 ) ;
test ( f ) ;
return 0 ;
}
4. Prijatelji klasa (nastavak)
Kada postoji potreba da sve funkcije članice jedne klase budu
prijatelji druge klase, moguće je čitavu prvu klasu proglasiti
prijateljskom u drugoj i na taj način omogućiti da se sva imena i
članovi iz druge klase mogu koristiti unutar prve. Jedna klasa se u
drugoj proglašava prijateljskom tako što se u definiciji druge navodi
sledeće:
friend class ime_prijateljske_klase ;
Ukoliko je prijateljska klasa prethodno definisana ili samo
deklarisana, moguće je upotrebiti i kraću formu:
friend ime_prijateljske_klase ;
Važno je naglasiti da prijateljstvo nije simetrična relacija, tj. ako je
klasa Y deklarisana kao prijatelj u klasi X, to ne znači da je
istovremeno klasa X prijatelj klase Y. Osim toga, prijateljstvo nije ni
tranzitivna relacija, tj. ako je klasa B prijatelj klase A , a klasa C
prijatelj klase B, to ne znači da je klasa C automatski
prijatelj klase A. Takođe, treba istaknuti da se prijateljska relacija
ne nasleđuje. Nameće se pitanje da li koncept "prijatelja" narušava
princip enkapsulacije kao jedan od postulata objektno orijentisanog
programiranja? S obzirom da sam projektant unutar
definicije klase deklariše prijateljske funkcije, a da prijateljska
relacija nije ni tranzitivna ni nasledna, može se reći da se princip
enkapsulacije ne narušava.
Ako globalne funkcije prijatelji neke klase imaju pristup svim
članovima klase, kao i funkcije članice, nameće se dilema o tome u
kojim situacijama je bolje koristiti globalne prijateljske funkcije od
funkcija članica klase. Ne postoje stroga pravila koja bi dala odgovor
na ovu dilemu, već preporuke koje kažu da su globalne
prijateljske funkcije bolje u sledećim situacijama:
1. kada funkcija treba da pristupa članovima više klasa,
2. ako funkcija ne zahteva argument kao lvrednost,
3. kada postoji potreba da se omogući implicitna (automatska)
konverzija tipa argumenta,
4. prilikom preklapanja nekih operatora.
Praktično, može se reći da globalne prijateljske funkcije ne
predstavljaju ponašanje objekta klase, već posebne usluge u vezi sa
datom klasom. Zbog toga ih sečesto naziva "uslugama klase" ( class
utilities ).
5. Konstruktor kopije
Kada se u programskom jeziku C++ jedan objekat
neke klase inicijalizuje drugim objektom isteklase, tada program
prevodilac članove podatke prvog objekta inicijalizuje redom
vrednostima koje sadrže članovi podaci drugog objekta.U određenim
situacijama to ne odgovara potrebama programera, pa je
uprogramskom jeziku C++ uvedena mogućnost da programer
kontroliše proces inicijalizacije jednog objekta drugim istog tipa. U
pitanju je poseban konstruktor koji se poziva svaki put kada se
jedan objekat inicijalizuje drugim objektom iste klase. To je
konstruktor koji može da se poziva sa jednim argumentom tipa
svoje klase, a naziva se konstruktor kopije (copy-constructor).
Za klasu X , konstruktor kopije deklariše se nasledeći način:
X (const X & ) ;
Važno je istaknuti da se konstruktor kopije ne može deklarisati tako
da kao argument prima objekat vlastite klase po vrednosti, već
isključivo po referenci.
Ako se ne definiše konstruktor kopije za neku klasu, program
prevodilac automatski generiše podrazumevani javni konstruktor
kopije. Ovaj konstruktor vrši kopiranje članova podataka na način
opisan na samom početku dela glave posvećene konstruktoru
kopije, tj. članovi podaci se redom kopiraju. Ovakav način kopiranja
naziva se i "plitkim" kopiranjem (shallow copy).
Primer:
class test
{
int x, y ;
public:
test ( int a, int b ) : x( a ), y( b ) { }
} ;
int main ( )
{
test t1 ( 3, -1 ) ; test t2 ( t1 ) ;
//Poziva se automatski kreiran
// konstruktor kopije.Clanovi objekta
// “t2” imaju iste vrednostikao clanovi objekta “t1”.
return 0 ;
}
Ovakav način kopiranja u većini slučajeva odgovara potrebama,
međutim u određenim situacijama može da izazove probleme.
Takav slučaj prikazan je u sledećem primeru:
class test2
{
int * px ;
public:
//Konstruktor dinamicki alocira memoriju.
test2 (int x) : px (new int (x)) { }
//Konstruktor kopije.
test2 (const test2 & c) : px ( new int ( *c.px ) ) { }
//Destruktor oslobadja dinamicki alociranu memoriju.
~test2 ( ) { delete px ; }
} ;
int main ( )
{
test2 i1(5), i2(i1) ;
return 0 ;
}
Kada u prethodnom primeru ne bi bio definisan konstruktor kopije,
program prevodilac bi generisao podrazumevani javni konstruktor
kopije kojim bi se, kao što je prethodno opisano, redom kopirali
članovi jednog objekta u članove drugog objekta. U prikazanom
primeru član podatak px objekta i2bi kopiranjem iz i1 dobio isti
sadržaj kao i članpodatak px iz objekta i1.
Odnosno, px iz i2pokazivao bi na isti dinamički alociran deo
memorije kao i px iz i1. Problem bi nastao na kraju mainfunkcije,
kada bi se pozivali destruktori kreiranih objekata (i1 i i2), jer bi
oba destruktora pokušali da oslobode istu dinamički alociranu
memoriju, što bi dovelo do neregularnog prekida rada programa.
6. Konstruktor kopije (nastavak)
Da bi se prevazišao prethodno opisani problem definisan je
konstruktor kopije koji rezerviše posebnu lokaciju u dinamičkoj
memoriji u koju se kopira sadržaj iz dinamički alocirane memorije
na koju pokazuje pokazivač iz objekta koji se koristi za
inicijalizaciju. Pokazivač u novokreiranom objektu čuva adresu nove
dinamički alocirane lokacije. Na ovaj način, prilikom ukidanja
objekata, svaki destruktor oslobađa "svoju" dinamički alociranu
lokaciju. Ovakav način kreiranja jednog objekta iz drugog, istog
tipa, naziva se"duboko" kopiranje (deep copy).
Prethodni primer predstavlja jedan od slučajeva kada je neophodno
definisati konstruktor kopije. Kod klasa koje, kao članove podatke,
imaju pokazivače na dinamički alociranu memoriju, u većini
slučajeva neophodno je definisati konstruktor kopije. Sledećim
primerom se pokazuju neki slučajevi u kojima se poziva konstruktor
kopije, kao i ostale "specijalne" funkcije članice:
#include <iostream>
using namespace std ;
class test
{
public:
test ( )
{ cout << "default" << endl ; }
test ( const test & f )
{ cout << "copy" << endl ; }
~test ( )
{ cout << "destructor" << endl ; }
} ;
void func ( const test a, consttest & b )
{ cout "func" <<endl ; }
int main ( )
{
test x ; //Poziva sedefault konstruktor.
test y = x ; //Poziva sekonstruktor kopije.
func ( x, y ) ; //Poziva se konstruktor kopije
// za prvi argument.
return 0 ;
}
//REZLTAT IZVRSAVANJA
default
copy
copy
func
destructor
destructor
destructor
class B
{
int n ;
public :
explicit B(int x) : n(x) { }
} ;
class C
{ public : C (A a) { } } ;
class D
{ public : D (C c) { } } ;
void func ( A a, B b ) { }
void func2 ( C c ) { }
int main ( )
{
A x(1) ;
B y(2) ; //U redu: eksplicitni poziv.
func ( x, y ) ;
A g = 2 ;
B h = 3 ; //GRESKA: nije eksplicitni poziv!
func ( 0, -4 ) ; //GRESKA: pokusaj implicitne
konverzije
// drugog parametra!
func ( -2.1 , y ) ;
C z(x) ;
C t(1), q = 2 ; //GRESKA: konverzija nije neposredna!
func2 ( 3 ) ; //GRESKA:konverzija nije neposredna!
D d(z) ;
D n(1) ; //GRESKA: konverzija nije neposredna!
return 0 ;
}
8. Ugnežđene klase
Primer:
class out
{
int x ;
public:
class inner //Definicija ugnezdjene klase.
{
static int a ;
int b ;
public:
int c ;
void f ( out ) ;
} ;
static int y ;
void g ( inner ) ;
} ;
int out::y = 5 ;
int main ( )
{
out a ; //Objekatokruzujuce klase.
out::inner b ; //Objekat ugnezdjene klase.
b.f(a) ;
return 0 ;
}
9. Lokalne klase
Klase je moguće definisati i unutar funkcija. Takve klase nazivaju se
lokalnim klasama. Imena ovakvih klasa imaju oblast važenja
blokova u kojima se definicije nalaze. Unutar ovakvih klasa
dozvoljeno je korišćenje samo imena tipova, statičkih
članova funkcije i konstanti tipa nabrajanja iz okružujuće funkcije.
Pristupanje članovima klase iz okružujuće funkcije podleže kontroli
pristupa, tj. okružujuća funkcija nema nikakva posebna prava
pristupa članovima lokalne klase. Funkcije članice lokalnih klasa
moraju se definisati unutar definicija klasa (inline). Lokalne klase ne
mogu da imaju statičke članove.
Primer:
int main ( )
{
static double pi = 3.14 ;
class local //Definicija lokalne klase.
{
double d ;
static int s2 ; //GRESKA: lokalna klasa ne moze
//imati staticke clanove!
public:
int s ;
void test ( ) { d = pi ; } //Ispravno:unutar
lokalne klase dozvoljeno je
// koriscenje statickih objekataiz okruzujuce
// funkcije.
void test2 ( ) ; //GRESKA: funkcija clanica
lokalne
// klase mora biti definiasna u telu klase!
} ;
local l ;
l.d = 0 ; //GRESKA: d je privatni clan lokalne klase!
l.test ( ) ;
return 0 ;
}
10. Pokazivači na članove klase
Pokazivači na članove klase (class member pointers) predstavljaju
poseban izvedeni tip podataka različit od "klasičnih" pokazivača na
objekte. Treba razlikovati pokazivače na članove podatke od
pokazivača na funkcije članice. Prvi, tj.pokazivači na članove
podatke deklarišu se na sledeći način:
T X ::* identifikator ;
T predstavlja tip podatka člana klase, X predstavlja klasu na čiji član
podatak pokazuje pokazivač, aidentifikator predstavlja ime
pokazivača.
Ovakvi pokazivači suštinski se razlikuju od "klasičnih" pokazivača,
jer za nestatičkečlanove podatke ne sadrže adresu podatka na koji
pokazuju, već sadrže vrednost pomeraja (offset) uodnosu na
početak strukture podataka koja predstavlja objekat klase u
memoriji. Pokazivači na statičke članove podatke sadrže adresu
podatka poput "klasičnih" podataka.
Pokazivači na funkcije članice deklarišu se na sledeći način:
T ( X ::* identifikator ) ( <lista_tipova_argumenata> ) ;
identifikator predstavlja ime pokazivača na funkciju
članicu klase, X predstavlja klasu u kojoj se nalazi funkcija članica,
a T i lista_tipova_argumenata određuju tip funkcije članice na
koju pokazuje pokazivač. Za razliku od pokazivača na član
podatak klase, ovi pokazivači sadrže adresu koda funkcije članice
poput običnih pokazivača na funkcije. Ipak, ovi pokazivači se
razlikuju od običnih po tome što funkcije na koje pokazuju u listi
argumenata uvek imaju i pokazivač na objekat tipa klase u kojoj se
funkcija članica nalazi (this pokazivač). Naravno, ovo se odnosi na
pokazivače na nestatičke funkcije članice. Pokazivači na
statičke funkcije članice isti su kao pokazivači na globalne funkcije.
Za pokazivače na nestatičke funkcije članice važi da se njihovo ime
ne može implicitno konvertovati u pokazivač
na funkcije članice klase, za razliku od imena globalne funkcije čije
ime može da se implicitno konvertuje u pokazivač na odgovarajući
tip funkcije.
Važno je naglasiti da je nad pokazivačima na nestatičke članove
klasa moguće primenjivati samo operacije poređenja (== i !=), te
operator dodele (=). Pokazivači na članove klase čuvaju rezultat
primene adresnog operatora nad imenom člana klase. Mogu da
budu deklarisani i kao const i/ilivolatile.
Korišćenjem pokazivača na člana klase, konkretnom članu
objekta klase moguće je pristupiti na dva načina:
- pomoću operatora .* (objekat .* pokazivac_na_clana_klase),
- pomoću operatora ->* (pokazivac_na_objekat ->*
pokazivac_na_clana_klase).
Primer:
class R
{
int x, y ;
void test1 ( ) { } ;
public:
float z ;
void test ( int ) { } ;
} ;
class Q
{ public: float k ;} ;
int main ( )
{
R a, b ;
Q c ;
float * f ;
int R::* pri = &R::x ; // GRESKA: x je privatni
clan klase R!
float R::* prf = &R::z ;
float Q::* prf2 = &Q::k ;
prf2 = prf ; // GRESKA: prf i prf2 nisu istog tipa!
f = prf ; // GRESKA: prf i fnisu istog tipa!
a.*prf = 1.2 ; // a.z = 1.2 ;
b.*prf = -4.2 ; // b .z = -4.2 ;
c.*prf = 0.3 ; // GRESKA: prfnije pokazivac na
clana klase Q!
c.*prf2 = 0.3 ; // c.k = 0.3 ;
R * pr = & a ;
pr->*prf = 0 ; // pr->z = 0;
void ( R::* mfp ) ( ) ;
void ( R::* mfp2 ) ( int ) ;
mfp = & R::test1 ( ) ; // GRESKA: test1 je privatna
clanica klase R!
mfp2 = & R::test ;
( a.* mfp ) ( ) ; // OPASNO:pokazivac mfp ima slucajan
sadrzaj!
( a.* mfp2 ) ( 1 ) ; //a.test(1) ;
( pr->* mfp2 ) ( 1 ) ; // pr->test(1) ;
return 0 ;
}
11. Strukture u programskom jeziku C++
Strukture u programskom jeziku C++ imaju sve osobine klasa osim
činjenice da su im članovi podrazumevano javni.
Primer:
struct H
{
int x ;
void fun ( ) { }
private:
void whs ( ) { }
} ;
int main ( )
{
H a, b ;
a.x = 5 ; //Ispravno, x je javniclan strukture.
b.fun ( ) ; //Ispravno, fun je javna funkcija clanica
// strukture.
a.whs ( ) ; //GRESKA: whs je privatna
clanica strukture!
return 0 ;
}
12. Imenski prostori
U velikim programima često se javljaju problemi prilikom korišćenja
globalnih imena, tj. imena iz oblasti važenja datoteke. Naime, veliki
programi obično se sastoje iz većeg broja *.cpp i *.h datoteka koje,
između ostalog, imaju određen broj globalnih imena. Globalna
nestatička promenljiva ili objekat, kao i globalna funkcija u jednoj
datoteci, vidljivi su i iz ostalih datoteka. Ukoliko se desi da se neko
ime pojavi kao globalno u više *.cpp datoteka, mogu da nastanu
problemi prilikom povezivanja programskih modula (linking).Sličan
problem može da se desi i ukoliko se u više *.cpp datoteka uključi
istu *.h datoteku koja sadrži globalna imena.
Da bi se prevazišao ovaj problem u programski jezik C++ uveden je
koncept imenskih prostora (namespaces). Oni služe za grupisanje
globalnih imena tako što svaki imenski prostor predstavlja posebnu
oblast važenja imena. Ako se različiti delovi programskog koda
stave u različite imenske prostore, eliminiše se mogućnost
"konflikta" imena.
Imenski prostor definiše se na sledeći način:
namespace identifikator { <sadržaj imenskogprostora> }
namespace predstavlja ključnu reč programskog jezika
C++, identifikator predstavlja ime imenskog prostora, a sadržaj
imenskog prostora je programski kôd.
Važno je primetiti da se na kraju definicije imenskog
prostora ne nalazi zn ak ";".
Unutar jednog imenskog prostora svi globalni identifikatori iz tog
prostora koriste se bez ikakvih ograničenja. Izvan imenskog
prostora, globalni identifikatori iz imenskog prostora moraju se
koristiti uz pomoć operatora za razrešenje dosega i to na sledeći
način:
ime_imenskog_prostora :: globalni_identifikator
Ukoliko unutar jedne oblasti važenja imena postoji potreba da se
koristi neko ime iz nekog imenskog prostora bez navođenja imena
imenskog prostora i operatora za razrešenje dosega, moguće je
upotrebiti sledeću naredbu:
using ime_imenskog_prostora :: globalni_identifikator ;
using predstavlja ključnu reč u programskom jeziku C++.
Ukoliko postoji potreba da se unutar jedne oblasti važenja imena
koriste sva imena iz nekog imenskog prostora bez navođenja
njegovog imena i operatora ::, moguće je upotrebiti sledeću
naredbu:
using namespace ime_imenskog_prostora ;
Važno je naglasiti da su imenski prostori "otvoreni", što znači da se
identifikator koji označava ime imenskog prostora može koristiti za
više definicija imenskog prostora, pri čemu će svaka nova definicija
dodati novi sadržaj istom imenskom prostoru označenom tim
identifikatorom. Odnosno, program prevodilac neće prijaviti grešku
ukoliko naiđe na više definicija imenskog prostora sa istim imenom.
Primer:
namespace A
{
int x = 12 ;
float a = -1.0 ;
void hint ( ) { }
class T
{
public:
int x ;
} ;
}
namespace B
{ int a = 3 ; }
int main ( )
{
T t ; //GRESKA: tip T nijeglobalno vidljiv!
A::T t ; //Ispravno.
using A::T ; //Ukljucivanje imena T iz imenskog
prostora A.
T t2 ; //Sada moze i ovako.
using namespace B ;
int x = a ; //Ispravno, ‘a’ iz imenskog prostora B.
usingnamespace A ; //Ukljucivanje citavog imenskog
prostora A.
float y = a ; //GRESKA: koje‘a’ je u pitanju,
// ‘a’ iz A ili ‘a’ iz B ?!
float y = A::a ; //Moraeksplicitno da se navede koje
// ‘a’ je u pitanju.
return 0 ;
}