4.Functii si date de tip static

Fiecare obiect al unei clase are propriul sau set de functii si date publice sau private, accesul facīndu-se īn functie de statutul ales (private sau public). Īn anumite situatii este de dorit ca unul sau mai multe cīmpuri sa fie accesibile tuturor obiectelor dintr-o clasa. Un exemplu de asemenea situatie este numele directorului de start īntr-un program care scaneaza recursiv directoarele discului. Un alt exemplu este o variabila 'flag' care sa indice o anumita initializare: numai primul obiect creat al clasei face initializarea, punīnd 'flagul' pe 'yes'. Aceste situatii se īntīlnesc si īn coduri C, unde anumite functii trebuie sa acceseze anumite variabile. Solutia īn C este de a defini toate functiile īn acelasi fisier sursa, iar variabilele de tip static: īn acest fel variabilele sīnt cunoscute doar īn acest fisier. O alta solutie este de a da variabilei īn cauza un nume neuzual (_6DODU), sperīnd ca nici o alta parte a programului nu va utiliza numele din greseala. Ambele solutii sīnt neelegante.

C++ permite utilizarea de functii si date statice, comune tuturor obiectelor unei clase.

5.1 Date statice

O data membru al unei clase poate fi declarata static, indiferent de zona īn care a fost inclusa (publica sau privata). O asemenea data este creata si initializata o singura data, īn contrast cu datele ne-statice, care sīnt create pentru fiecare obiect al clasei. O data static este creata la īnceputul programului, dar face totusi parte din clasa. Datele static declarate public sīnt ca variabilele normale:ele pot fi accesate de īntregul cod folosindu-se numele lor, precedat de numele clasei si de operatorul scope. Exemplu:

class Test

{

public:

static int public_int;

private:

static int private_int;

}

int main ()

{

Test::public_int = 145; // ok

Test::private_int = 12; // gresit, nu poate fi accesata

return (0);

}

Fragmentul de cod nu este pentru un compilator C++: el ilustreaza numai declaratia, nu si definitia datelor membre statice.

Date statice private

Penru ilustrarea utilizarii unei date membru statice declarata private, iata urmatorul exemplu:

class Directory

{

public: // constructori, destructori, etc.

.

.

private: // data membru

static char path [];

};

Data membru path este o variabila private static. Ea exista īntr-un singur exemplar, chiar daca se creeaza mai multe obiecte din clasa Directory. Aceasta data poate fi inspectata sau alterata de constructor, destructor sau orice alta functie membru al clasei Directory.

Deoarece constructorii sīnt apelati pentru fiecare nou obiect al clasei, datele static nu sīnt niciodata initializate de constructori, ci cel mult modificate. Motivul este ca datele static exista īnainte de apelul constructorului primului obiect din acea clasa. Datele static pot fi initializate īn timpul definitiei lor, īn afara tuturor celorlalte functii membru, ca si variabilele globale. De obicei, definitia si initializarea datelor static apar īn fisierul sursa ce contine si definitia clasei. Data membru path poate fi definita si initializata īn fisierul sursa al constructorului:

// data membru static: definitie si initializare

char Directory::path [200] = "/usr/local";

// constructorul implicit

Directory::Directory ()

{

.

.

}

Trebuie retinut ca definitia unei date statice poate apare īn orice fisier sursa (dar numai o singura data). Īn declaratia clasei membrii statici sīnt doar declarati: numai la definitia lor tipul si numele clasei apare explicit. De asemeni, dimensiunea datei poate fi omisa īn declaratie, dar este necesara la definitie.

Un al doilea exemplu de folosire al unei date private static este dat īn continuare. Clasa Graphics defineste comunicarea programului cu un device grafic. Pregatirea initiala al device-ului, care īn cazul nostru ar reprezenta o trecere din modul text īn cel grafic, este o actiune a constructorului si depinde de o variabila statica nobjects (un 'flag'). Variabila respectiva numara obiectele de tip Graphics prezente la un moment dat. Cīnd se creeaza primul obiect, este initializat si modul grafic (de catre constructor). Īn mod similar, destructorul face trecerea de la modul grafic la cel text daca nu mai exista nici un obiect grafic.

class Graphics

{

public: // constructorul, destructorul

Graphics ();

~Graphics ();

//alte functii de interfata

.

.

private: // numara obiectele

static int nobjects;

void setgraphicsmode (); // functie ipotetica de trecere īn modul grafic

void settextmode (); // trecere īnapoi īn modul text

}

// data membru statica

int Graphics::nobjects = 0;

// constructorul

Graphics::Graphics ()

{

if (! nobjects)

setgraphicsmode ();

nobjects++;

}

// destructorul

Graphics::~Graphics ()

{

nobjects--;

if (! nobjects)

settextmode ();

}

Este evident ca daca sīnt definiti mai multi constructori, fiecare va trebui sa incrementeze nobjects si daca e posibil sa initializeze modul grafic.

Date statice public

Datele membru pot fi declarate si īn sectiunea public, desi nu este recomandat (violarea principiului de ascundere a datelor). De exemplu, daca data membru statica path ar fi declarata īn sectiunea public, īntregul cod al programului ar avea acces la aceasta variabila. Si īn acest caz variabila path va trebui definita, iar īn fisierul sursa va trebui sa contina definitia :

char Directory::path [200].

5.2 Functii membru static

Īn afara de datele static, C++ permite si declararea de functii static. Conform conceptului, aceste functii vor putea fi aplicate tuturor obiectelor din acea clasa. Functiile static pot adresa doar date static din acea clasa; cele ne-statice sīnt inaccesibile (daca nu ar fi asa, carui obiect ar apartine ?). Īn acelasi timp, functiile statice nu pot apela functii ne-statice din clasa respectiva. Acest lucru este dat de faptul ca functiile static nu contin pointerul this. Functiile care sīnt statice si care sīnt declarate īn sectiunea public pot fi apelate fara specificarea unui obiect al clasei:

class Directory

{

public: // constructori, destructori

.

.

// functiile static publice

static void setpath (char const *newpath);

private: // stringul static

static char path [];

};

// definitia variabilei static

char Directory::path [199] = "/usr/local";

// functia static

void Directory::setpath (char const *newpath)

{

strncpy (path, newpath, 199);

}

int main ()

{

// Alternativa (1): apelul setpath() fara nici un obiect din clasa Directory

Directory::setpath ("/etc");

// Alternativa (2): cu un object

Directory dir;

dir.setpath ("/etc");

return (0);

}

Īn acest exemplu, functia setpath() este o functie public static. C++ permite si definirea functiilor private static: īn acest caz functia poate fi apelata numai de un obiect al clasei (nu si din afara). De asemeni, functia va putea accesa numai datele statice sau alte functii statice.

Mostenirea

Cīnd programam īn C, īn mod obisnuit vedem solutia problemei sub forma top-down: functiile sīnt definite īn functie de alte sub-functii, si tot astfel pīna ajungem la functiile sistemului. Īn top se gaseste main() care va apela restul functiilor. Īn C++ dependentele īntre cod si date pot fi definite īn termeni de clase dependente de alte clase. Aceasta seamana cu notiunea de compunere, īn care obiectele contineau ca date obiecte din alte clase. Dar relatia descrisa īn continuare este de un tip diferit: o clasa poate fi definita prin intermediul alteia, definita anterior, aceasta ducīnd la situatia īn care noua clasa 'mosteneste' toate functiile clasei vechi, dar īsi adauga propriile functii. Īn locul unei compuneri, unde o clasa contine alta clasa, vom avea o 'derivare', īn care o clasa data devine alta clasa. Un alt sinonim folosit pentru derivare este 'mostenirea': noua clasa mosteneste functionalitatea clasei existente, desi aceasta din urma nu apare ca data specifica īn noua clasa. Vom numi clasa initiala - clasa de baza-, iar noua clasa, - clasa derivata. Derivarea claselor este deseori folosita atunci cīnd se exploateaza la maxim posibilitatile limbajului C++. Asa cum am vazut si īn primul laborator, clasele sīnt identificate īn procesul de analiza al problemei, dupa care obiectele (ale claselor definite) vor reprezenta entitati ale problemei. Clasele sīnt plasate īntr-o ierarhie, īn care cele mai putin functionale sīnt plasate īn vīrf, iar celelalte, prin derivare, capata noi functionalitati.

Sa consideram o clasificare a vehicolelor pentru a construi o ierarhie de clase. Prima clasa va fi Vehicle, care va avea ca functionalitate posibilitatea de a seta sau afla greutatea unui vehicol. Urmatorul nivel īn ierarhie va contine : land-, water- si air-vehicle.

5.3. Tipuri de relatii

Relatia īntre clasele propuse reprezinta diferitele tipuri de vehicole. Clasa Vehicle este deci 'cel mai mare numitor comun' al ierarhiei. Iata un exemplu de implementare:

class Vehicle

{

public: // constructori

Vehicle ();

Vehicle (int wt); // interfata

int getweight () const;

void setweight (int wt);

private: // data

int weight;

}

Pentru a reprezenta vehicole care calatoresc pe uscat, definim o noua clasa ce pastreaza functionalitatea clasei Vehicle, dar adauga si noi informatii. De exemplu sa presupunem ca sīntem interesati de viteza sī de greutate. Relatia īntre Vehicle si Land poate fi reprezentata si printr-o compozitie, dar ar fi gresit: compozitia ar sugera ca vehicolul Land contine un vehicol, pe cīnd relatia este de fapt: vehicolul Land este un tip special de Vehicol. O relatie īn termenii unei compozitii ar introduce si cod īn plus. De exemplu, considerīnd urmatorul cod (īn care este folosita compozitia):

class Land

{

public:

void setweight (int wt);

private:

Vehicle v; // compunere cu Vehicle

};

void Land::setweight (int wt)

{

v.setweight (wt);

}

Folosind compozitia, functia setweight() al clasei Land serveste doar la pasarea argumentului catre Vehicle::setweight(). Deci īn ceea ce priveste greutatea, apare numai un cod redundant. Relatia este mai bine pusa īn evidenta prin derivare: Land este derivata din Vehicle, Vehicle fiind clasa de baza.

class Land: public Vehicle

{

public: // constructori

Land ();

Land (int wt, int sp);

// interfata

void setspeed (int sp);

int getspeed () const;

private: // data

int speed;

};

Prin postfixarea clasei Land īn definitia sa cu public Vehicle se defineste derivarea: clasa Land va contine toate functionalitatile clasei Vehicle plus propriile informatii. Acestea constau īntr-un constructor cu doua argumente si o functie interfata pentru accesarea datei speed. Pentru ilustrarea folosirii acestei clase, iata un exemplu:

Land veh (1200, 145);

int main ()

{

printf ("Vehicle weighs %d\n"

"Speed is %d\n",

veh.getweight (), veh.getspeed ());

return (0);

}

Acest exemplu demonstreaza si doua aspecte ale derivarii. Īn primul rind, getweight() nu este un membru direct al clasei Land, si totusi este apelat cu numele veh.getweight(). Acest membru face parte implicita din clasa, este 'mostenit' de la 'tatal' Vehicle. Īn al doilea rīnd, desi clasa derivata Land contine acum functionalitatile lui Vehicle, partile private ale clasei Vehicle ramīn private (īn sensul ca pot fi accesate doar de functiile membru ale obiectelor Vehicle). Asta īnseamna ca functiile membru din Land trebuie sa utilizeze functiile interfata (getweight(), setweight()) pentru a accesa cīmpul weight. Aceasta restrictie este necesara pentru a pastra conceptul de ascundere a datelor. Clasa Vehicle poate fi, mai tīrziu, recodata; clasa Land ramīne neschimbata. Īn exemplul urmator presupunem ca urmatoarea clasa, Auto, este capabila sa reprezinte greutatea, viteza si numele unei masini. Clasa este derivata din clasa Land:

class Auto: public Land

{

public: // constructori

Auto ();

Auto (int wt, int sp, char const *nm);

Auto (Auto const &other); // constructorul copy

Auto const &operator= (Auto const &other); // asignarea

~Auto (); // destructorul

// interfata

char const *getname () const;

void setname (char const *nm);

private: // data

char const *name;

};

Deoarece clasa Auto este derivata din clasa Land care este derivata din clasa Vehicle, putem vorbi de o derivare īn lant.

5.4. Constructorul unei clase derivate

Asa cum am putut vedea din definitia clasei Land, exista un constructor pentru a seta si greutatea si viteza obiectului. O implementare 'slaba' este urmatoarea:

Land::Land (int wt, int sp)

{

setweight (wt);

setspeed (sp);

}

Implementarea are dezavantaje: compilatorul C++ va genera cod pentru apelul constructorului implicit al clasei de baza la fiecare apel al constructorului clasei derivate (daca nu se specifica expres altfel). Acest lucru este echivalent cu o compozitie de obiecte. Solutia corecta este de a apela direct constructorul clasei Vehicle ce asteapta un argument int. Pentru a obtine acest lucru, sintaxa cere ca apelul constructorului clasei de baza sa urmeze lista de argumente a constructorului clasei derivate.

Land::Land (int wt, int sp)

: Vehicle (wt)

{

setspeed (sp);

}

6.5. Redefinirea functiilor membru

Actiunea tuturor functiilor definite īn clasa de baza poate fi redefinita īn clasa derivata. Sa presupunem, de exemplu, ca sistemul de clasificare a vehicolelor trebuie sa fie capabil sa reprezinte autocamioanele, care constau īn din doua parti: camionul propriu-zis si remorca. Fiecare parte are propria sa greutate, īnsa getweight() trebuie sa returneze greutatea totala. Deci definitia clasei Truck va fi derivata din clasa Auto, dar va avea un cīmp de date suplimentar. Convenim sa reprezentam greutatea camionului īn clasa Auto, cea a remorcii aparīnd distinct īn noua clasa:

class Truck: public Auto

{

public: // constructori

Truck ();

Truck (int engine_wt, int sp, char const *nm,

int trailer_wt);

// interfata: setarea a doua greutati

void setweight (int engine_wt, int trailer_wt);

// returnarea greutatii

int getweight () const;

private: // data

int trailer_weight;

};

// examplu de constructor

Truck::Truck (int engine_wt, int sp, char const *nm,

int trailer_wt)

: Auto (engine_wt, sp, nm)

{

trailer_weight = trailer_wt;

}

Sa observam ca clasa Truck contine doua functii deja prezente īn clasa de baza:

. Functia setweight(). Redefinirea īn Truck nu pune probleme deosebite. Noua definitie o va acoperi cea veche, din Vehicle, deci va putea fi apelata numai cu doua argumente.

. Functia getweight(). Are acelasi numar de argumente ca si functia din clasa Vehicle.Īn acest caz clasa Truck redefineste functia membru.

Urmatorul cod reprezinta redefinirea functiei getweight():

int Truck::getweight () const

{

return ( Auto::getweight () + //suma greutatii camionului

trailer_weight); //si a remorcii

}

Sa observam ca apelul Auto::getweight() selecteaza explicit functia din clasa Auto. O instructiune de forma return (getweight()+trailer_weight); va duce la o recursie infinita.

5.5. Mostenirea multipla

C++ permite sī derivarea unei clase din mai multe clase, creīnd asa numita mostenire multipla (clasa cu mai multi parinti). De exemplu, clasa Engine va contine functii pentru stocarea urmatoarelor informatii: numar de serie, puterea, tipul combustibilului, etc..

class Engine

{

public: // constructori

Engine ();

Engine (char const *serial_nr, int power, char const *fuel_type);

// functii necesare datorita prezentei pointerilor īn definitia clasei

Engine (Engine const &other);

Engine const &operator= (Engine const &other);

~Engine ();

// interfata

void setserial (char const *serial_nr);

char const *getserial () const;

void setpower (int power);

int getpower () const;

void setfueltype (char const *type);

char const *getfueltype () const;

private: // data

char const *serial_number, fuel_type;

int power;

};

Pentru a reprezenta un obiect Auto, dar cu toate informatiile despre motor, se poate deriva o noua clasa, MotorCar, din clasa Auto si din clasa Engine. Functionitatile ambelor clase vor fi mostenite de clasa noua:

class MotorCar: public Auto, public Engine

{

public: // constructori

MotorCar ();

MotorCar (int wt, int sp, char const *nm,

char const *ser, int pow, char const *fuel);

};

MotorCar::MotorCar (int wt, int sp, char const *nm,

char const *ser, int pow, char const *fuel)

: Engine (ser, pow, fuel), Auto (wt, sp, nm)

{

}

Cīteva observatii:

. Cuvīntul cheie public este prezent īn fata ambelor nume ale claselor de baza, aceasta deoarece implicit, īn C++, derivarea este private.

. Noua clasa nu introduce functii noi, ci numai le combina pe cele pre-existente (agregheaza tipuri mai simple īntr-un tip complex - abilitate a C++ des utilizata).

. Constructorul, care asteapta sase argumente, nu contine cod propriu. sarcina lui este numai de a activa constructorii claselor de baza.

Ca observatie asupra sintaxei: dupa lista de argumente sīnt apelati cei doi constructori, fiecare cu propria lista de argumente. Ordinea īn care sīnt apelati este data de ordinea derivarii. Primul constructor apelat este cel al clasei Auto, deoarece MotorCar este mai īntīi derivat din Auto.

Īn final, o regula de recunoastere sintactica: relatia 'este' īnseamna derivare (Motorcar 'este' si un Auto, si un Engine), iar relatia 'are' īnseamna compunere (de exemplu, MotorCar 'are' un Engine (motor), caz īn care īn clasa MotorCar va trebui sa apara un obiect de tip Engine). Īn acest ultim caz va trebui sa definim explicit functiile de interfata: (am presupus ca īn clasa MotorCar, cu compunere, exista un obiect engine din clas Engine)

void MotorCar::setpower (int pow)

{ engine.setpower (pow);}

int MotorCar::getpower () const

{ return (engine.getpower ()); }

// etcetera, la fel si pentru set/getserial(), si set/getfueltype()

Asemenea probleme (redundanta codului) sīnt evitate folosind derivarea.

5.6. Conversia īntre clasele de baza si cele derivate

Atunci cīnd se utilizeaza mostenirea īn definirea unei clase, se poate spune ca un obiect al clasei derivate este īn acelasi timp si un obiect al clasei de baza. Acest lucru are consecinte importante.

Conversii īn asignarea obiectelor

Sa definim doua obiecte, unul al clasei derivate si unul al clasei de baza:

Vehicle

v (900); // vehicol cu greutatea de 900 kg

Auto

a(1200, 130, "Ford");//automobil cu greutatea de 1200 kg, viteza de 30 km/h, //marca Ford

Obiectul a este initializat cu o valoare specifica. Totusi, deoarece Auto este īn acelasi timp si Vehicle, se poate face asignarea obiectului de baza cu obiectul derivat: v = a; Efectul asignarii este ca obiectul v primeste valoarea 1200 pentru cīmpul weight. Un Vehicle nu are cīmpurile speed si name, deci aceste date nu vor fi asignate. Asignarea inversa ( a unui obiect drivat cu unul de baza) nu este acceptata, deoarece pot exista cīmpuri (speed sau name) care nu pot primi o valoare. Regula generala: daca obiectul din stīnga are toate cīmpurile celui din dreapta, se poate face asignarea, altfel nu. Aceasta regula se refera srtict la operatorul de asignare (=) cel implicit. Se poate, de exemplu, redefini operatorul = īn clasa Auto astfel īncīt sa poata fi executata si instructiunea a = v;

Auto const &Auto::operator= (Vehicle const &veh)

{

setweight (veh.getweight ());

.

. //cod referitor la asignarea cīmpurilor neexistente īn Vehicle (speed si name)

}

Conversii īn asignarea pointerilor

Sa definim urmatoarele obiecte si pointeri:

Land l (1200, 130);

Auto a (500, 75, "Daf");

Truck t (2600, 120, "Mercedes", 6000);

Vehicle *vp;

Putem asigna vp cu adresele celor trei obiecte din clasele derivate: vp = &l; vp = &a; vp = &t;. fiecare asemenea asignare fiind legala. De fiecare data se face o conversie implicita a tipului clasei derivate īn tipul clasei de baza, deoarece vp este un pointer la un obiect de tip Vehicle. Īn schimb, folosind vp pot fi apelate numai functiile membru ce manipuleaza cīmpul weight, deoarece este singura functionalitate a clasei Vehicle. Aceasta restrictie are un efect deosebit de important mai ales pentru clasa Truck. Dupa asignarea vp = & t; pointerul vp va pointa catre un obiect Truck, īnsa apelul vp - > getweight() va īntoarce 2600, si nu 8600 (adica suma celor doua greutati, obinuta la apelul t.getweight()). Deci cīnd o functie este apelata prin intermediul unui pointer, tipul pointerului si nu cel al obiectului pointat determina ce functie este accesibila. Exista deigur si o cale de a ocoli conversia implicita, folosind operatorul cast:

Truck truck;

Vehicle *vp;

vp = &truck; // vp pointeaza catre un obiect truck

.

.

Truck *trp;

trp = (Truck *) vp;

printf ("Make: %s\n", trp->getname ());

Codul va fi executat numai daca vp va pointa īntr-adevar catre un obiect Truck si deci va exista o functie getname(). Altfel se va obtine o eroare.

5.7. Stocarea pointerilor clasei de baza

Faptul ca pointerii unei clase de baza pot fi utilizati pentru adresarea claselor derivate poate fi folosit pentru dezvoltarea unor clase generale care sa proceseze obiectele claselor derivate. Un exemplu tipic de asemenea proces este stocarea obiectelor, fie īntr-un vector, fie īntr-o lista, arbore, etc. Clasele definite astfel īncīt sa stocheze alte clase sīnt denumite īn general clase de stocaj (sau clase container). Ca exemplu prezentam clasa VStorage, folosita pentru stocarea pointerilor catre Vehicle. Pointerul actual poate adresa fie catre un obiect Vehicle, dar si un obiect Auto sau Track. Definitia clasei este urmatoarea:

class VSTorage

{

public: // constructori, destructor

VStorage ();

VSTorage (VStorage const &other);

~VStorage ();

VStorage const &operator= (VStorage const &other); // reacoperirea asignarii

// interfata:

void add (Vehicle *vp); // adauga Vehicle* īn zona de stocare

Vehicle *getfirst (void) const; // gaseste primul Vehicle*

Vehicle *getnext (void) const; // gaseste urmatorul Vehicle*

private: // data

Vehicle **storage;

int nstored, current;

};

Facem urmatoarele observatii:

. Clasa contine trei functii de interfata: una pentru a adauga un Vehicle īn lista, alta pentru a regasi primul Vehicle si ultima pentru a obtine urmatorul pointer.

Un exemplu de folosire este urmatorul:

Land l (200, 20); // greutate 200, viteza 20

Auto a (1200, 130, "Ford"); // greutate 1200 , viteza 130, marca Ford

VStorage garage; // zona de stocare

garage.add (&l); // adauga la zona de stocare

garage.add (&a);

Vehicle *anyp;

int total_wt = 0;

for (anyp = garage.getfirst (); anyp; anyp = garage.getnext())

total_wt += anyp->getweight ();

printf ("Total weight: %d\n", total_wt);

Acest exemplu demonstreaza modul īn care tipurile derivate (Auto, Land) sīnt convertite implicit la tipul de baza (Vehicle), astefel īncīt pot fi stocate īn VStorage. Functia getweight(), fiind definita īn clasa de baza, poate fi deci folosita pentru calcularea greutatii totale a vehicolelor stocate.

. Clasa VStorage contine si functiile necesare pentru asignarea unui obiect cu un alt obiect, adica reacoperirea asignarii si constructorul copy.

. Zona de date a clasei este cuprinsa īn sectiunea private: un tablou de pointeri catre obiecte Vehicle si doi īntregi, pentru numarul obiectelor stocate si pentru indexul curent (returnat de getnext()).

Sa retimen deci faptul ca C++ permite procesarea tuturor claselor derivate folosind o clasa de baza. Pare paradoxal ca aceasta clasa data ca exemplu, VStorage, poate stoca toate clasele derivate din Vehicle, chiar si cele viitoare. Faptul este posibil deoarece VStorage utilizeaza un protocol, definit īn Vehicle si obligatoriu deci penru toate clasele derivate.

Dezavantajul clasei VStorage este urmatorul: cīnd adaugam un obiect Truck, fragmentul de cod precedent nu va afisa suma totala a greutatilor (se aduna numai greutatea camionului, nu si a remorcii, adica acea greutate continuta de obiectul Vehicle si returnata de functia anyp->getweight(). Exista, desigur, si un remediu (functii virtuale).

INAPOI INAINTE

--------------------------------------------------------------------------------

Hosted by Integrasoft | Graphics by C!PR!AN | Copyright (C) 2000 Bogdan PADURARU All rights reserved.