2.Clase

Īn acest capitol vom introduce si vom exemplifica notiunea de clasa. Īn pasi succesivi vom construi clasa Person ce va putea fi folosita pentru crearea unei baze de date. Definitia acestei clase este:

class Person

{

public: // functiile interfata

void setname( char const *n);

void setaddress (char const *a);

void setphone (char const *p);

char const *getname (void);

char const *getaddress (void);

char const *getphone (void);

private: //cīmpurile de date

char *name;

char *address;

char *phone;

};

Datele sīnt private, deci pot fi accesate doar de functiile din clasa Person. Aceste functii fie seteaza unui cīmp o anumita valoare (set..()), fie inspecteaza datele (get...()).

3.1. Constructori si destructori

O clasa īn C++ poate contine doua functii speciale care sīnt implicate īn munca interna a clase respective. Ele sīnt numite constructor si destructor.

Constructorul

Functia constructor are prin definitie acelasi nume ca si clasa corespunzatoare. Ea nu are o valoare returnata specifica, nici macar void. De exemplu, pentru clasa Person, constructorul este Person::Person (). Sistemul C++ asigura apelul unui constructor pentru o anumita clasa atunci cīnd este creat un obiect al acelei clase . Este desigur posibil sa definesti o clasa fara nici un constructor explicit: īn acest caz sistemul fie nu face nici un apel, fie apeleaza un constructor fals (ce nu face nimic) īn momentul crearii unui obiect. Daca un obiect este o variabila locala ne-statica īntr-o functie, constructorul este apelat īn momentul executiei functiei. Daca obiectul este o variabila statica sau globala, constructorul este apelat īnaintea īnceperii executiei programului (chiar īnainte de main()).

#include <stdio.h>

// o clasa Test cu o functie constructor

class Test

{ public: Test (); }; //functia constructor declarata public

Test::Test ()

{ puts ("constructorul clase Test este apelat");}

// programul test

Test g; // obiect global

void func ()

{ Test l; // obiect local īn functia func()

puts(" Aici e apelat func");

}

int main()

{ Test x; // obiect local īn functia main()

puts ("functia main()");

func();

return (0);

}

Aici am definit o clasa ce contine doar o functie: constructorul. Constructorul īnsusi nu are decīt o singura actiune: listeaza un mesaj. Programul contine trei obiecte din clasa Test: unul global, unul local īn main() si unul local īn func(). Se poate observa: numele constructorului (acelasi ca al clasei), lipsa valorii returnate si lipsa argumentelor (asa numitul constructor implicit - e posibil īnsa sa fie definiti si constructoru cu argumente). Apelul constructorului īn executia programului are loc īn ordinea: 1) la crearea obiectului global g; 2) la crearea obiectului local x din functia main(); 3) la crearea obiectului local l din functia func().

Pe ecran se va obtine deci īn final:

constructorul clasei Test este apelat

constructorul clasei Test este apelat

functia main()

constructorul clasei Test este apelat

Aici este apelat func

Destructorul

O a doua functie speciala este destructorul (opusul constructorului īn sensul ca ea este apelata īn momentul īn care un obiect īnceteaza sa mai existe). Pentru obiectele ne-statice locale, destructorul este chemat atunci cīnd functia īn care este definit obiectul īntoarce valoarea return; pentru cele statice (globale), destructorul este apelat īnaintea terminarii programului. Chiar daca programul este īntrerupt cu un exit(), vor fi apelati destructorii pentru obiectele existente. Cīnd se defineste un desrtuctor, se respecta urmatoarele reguli:

. Destructorul are acelasi nume ca si clasa, dar precedat de tilda

. Destructorul nu are nici un argument sau o valoare de returnat.

Exemplu:

class Test

{

public: Test (); //constructorul

~Test (); // destructorul

...

};

O prima aplicatie

Una din aplicatiile constructorului si destructorului o reprezinta managementul alocarii memoriei. Exemplificam cu clasa Person. Ea contine trei pointer private, toti de tip char *. Acesti membrii sīnt manipulati prin functiile interfata. Cīnd un nume, adresa sau telefon sīnt definite, va fi alocata memorie pentru memorarea acestor date. Evidentiem operatiile necesare:

. Constructorul clasei se asigura ca toate datele sīnt initial pointeri NULL

. Destructorul va elibera toata memoria alocata

. Definirea unui nume (adresa, telefon), prin intermediul functiilor set..() va consta īn doi pasi. Īn primul, memoria alocata anterior va fi eliberata; īn al doilea sirul (argumentul functiei set..()) este copiat īn memorie

. Inspectarea unei date prin intermediul functiilor get..() va returna un pointer: fie pointerul NULL (data nu e definita), fie un pointer catre zona de memorie alocata datei.

Functiile set..() sīnt prezentate īn continuare. Copierea sirurilor se face printr-o functie imaginara xtrdup(), care copie sirul sau termina executia programului daca memoria nu este suficienta.

// functiile interfata set..()

void Person::setname (char const *n)

{ free (name);

name = xstrdup (n); }

void Person::setaddress (char const *n)

{ free (address);

address = xstrdup (n); }

void Person::setphone (char const *n)

{ free (phone);

name = xstrdup (n); }

Sa observam ca desi instructiunea free(..) este executata neconditionat, nu apar actiuni incorecte: daca datele au fost deja definite, la o noua definire memoria alocata anterior va fi eliberata; daca datele n-au fost īnca definite, pointerii sīnt NULL, iar free (0) nu executa nimic. Mai trebuie precizat ca acest cod este mai familiar programatorilor īn C (ce utilizeaza functia free()), pentru C++ existīnd o instructiune mai potrivita, si anume delete.

// functiile interfata get..()

char const *Person :: getname ()

{ return (name); }

char const *Person :: getaddress ()

{ return (address); }

char const *Person :: getphone ()

{ return (phone); }

Īn final, constructorul, destructorul si definitia clasei Person:

class Person

{ public: Person (); //constructorul

~Person (); //destructorul

// functiile ce seteaza datele

void setname (char const *n);

void setaddress (char const *a);

void phone (char const *p);

//functiile ce inspecteaza datele

char const *getname (void);

char const *getaddress (void);

char const *getphone (void);

private: char * name;

char *address;

char *phone;

};

//constructorul

Person::Person ()

{ name = address = phone = 0; }

//destructorul

Person :: ~Person ()

{ free (name); free (address); free (phone); }

Exemplificam īn continuare cu un posibil program pentru folosirea acestei clase. Functia printperson () va fi utilizata pentru listarea datelor unui obiect de tip Person. Argumentul functiei va fi de tip referinta la un obiect Person. Faptul ca functia nu modifica argumentul (deci obiectul) este clar din prezenta cuvīntului const. Mai remarcam ca destructorul nu este apelat īn mod explicit.

void printperson (Person const &p)

{ printf ("Numele: %s\n", "Adresa: %s\n", "Telefon: %s\n", p.getname(), p.getaddress(), p.getphone()); }

int main ()

{ Person p;

p.setname("Liviu T.");

p.setaddress(" Iasi, Copou");

p.setphone ("123213");

printperson (p)

return (0);

}

Acest cod este doar un exemplu didactic; majoritatea compilatoarelor C++ n-ar putea genera cod executabil. Motivul este ca functia printperson () primeste un argument const, dar apelurile pentru acest argument (get..()) l-ar putea modifica, deci 'constanta' lui p n-ar putea fi garantata. Solutia va fi declararea acestor functii ca nemodificīnd obiectul (explicatii, putin mai tīrziu). S-ar mai putea modifica codul lui printperson () pentru o verificare initiala a existentei unor date concrete (nume, adresa, telefon) si listarea numai acelora (cum ?).

Constructori cu argumente

Īn C++ este permisa descrierea de constructori cu argumente. Pentru clasa Person un asemenea constructor poate fi definit ca utilizīnd trei argumente pentru nume, adresa, telefon:

Person::Person (char const *n, char const *a, char const *p)

{ name = xstrdup (n); address = xstrdup (a); phone = xstrdup (p); }

Constructorul trebuie inclus īn definitia clasei. O declaratie īntr-un fisier header ar putea arata astfel:

class Person

{ public: Person::Person (char const *n, char const *a, char const *p);

.. ... };

Deoarece īn C++ este permisa acoperirea functiilor, o asemenea declaratie poate coexista cu un constructor fara argumente. Utilizarea constructorului cu argumente este ilustrata īn continuare:

int main ()

{ Person a ("Karel", "Brasov", "213342"), b;

. . . }

Ordinea apelului constructorului

Posibilitatea de a defini constructori cu argumente ne permite sa monitorizam exact īn ce moment al executiei programului este creat sau distrus un obiect.

class Test

{ public: Test () ; //constructor fara argumente

Test (char const *name); // constructor cu argumente

~Test ();

private: char *n //datele

};

Test::Test ()

{ n = strdup ("Fara nume");

printf ("Creat obiect Test fara nume\n"); }

Test::Test (chat const *name)

{ n = strdup (name);

printf ("Creat obiect Test cu numele %s\n",n); }

Test::~Test ()

{ printf ("Distrus obiect Test cu numele %s\n", n);

free (n); }

Test globaltest ("global");

void func ()

{ Test functest ("func"); }

int main ()

{

Test maintest ("main");

func ();

return (0);

}

Listigul dupa rularea programului va fi:

Creat obiect Test cu numele global

Creat obiect Test cu numele main

Creat obiect Test cu numele func

Distrus obiect Test cu numele func

Distrus obiect Test cu numele main

Distrus obiect Test cu numele global

Functii membru constante si obiecte constante

Cuvīntul cheie const este deseori folosit īn declaratiile functiilor membru, indicīnd faptul ca aceste functii nu pot altera cīmpurile de date, ci numai inspecta. Exemplu:

class Person

{

public:

....

char const *getname (void) const;

char const *getaddress (void) const;

char const *getphone (void) const;

private: .. ...

};

Dupa cum se poate observa, cuvīntul const apare dupa lista argumentelor. Regula cuvīntului const se aplica si aici: tot ce apare dupa cuvīntul cheie nu poate fi alterat sau nu poate altera datele. Aceeasi specificatie trebuie sa apara si īn definitia functiilor membru:

char const *Person::getname () const

{ return (name); }

O functie membru declarata const nu poate altera nici un cīmp al clasei sale. Scopul functiilor const este acela de a permite crearea de obiecte const. Pentru asemenea obiecte pot fi apelate numai functiile ce nu le modifica, deci cele declarate const (cu singura exceptie: constructorul si destructorul, apelati automat). Sa cream un obiect const din clasa Person:

Person const me ("Profesor", "Iasi", "146141"); //se face initializarea de catre constructor

Instructiunea me.setname ("Student") este deci ilegala.

3.3 Operatorii new si delete

Limbajul C++ defineste doi operatori ce sīnt specifici pentru alocarea si dealocarea de memorie. Ei sīnt new si delete. Fie, de exemplu, o variabila pointer la un int folosita pentru a pointa catre memoria alocata de new, memorie eliberata mai tīrziu de operatorul delete:

int *ip;

ip = new int;

. . .

delete ip;

Fiind operatori, new si delete nu necesita paranteze (ca pentru functiile malloc() si free()).

Alocarea si dealocarea tablourilor

Cīnd operatorul new este folosit pentru alocarea memoriei pentru un tablou, dimensiunea variabilei trebuie plasata, īntre paranteze patrate, dupa tip:

int *intarray;

intarray = new int [20] ; //se aloca 20 de īntregi

Regula sintactica pentru operatorul new este ca el trebuie urmat de un tip, urmat optional de o dimensiune; tiupul si numarul sīnt folosite de compilator pentru calcularea dimensiunii memoriei necesare alocarii. Un tablou este dealocat de operatorul delete:

delete [] intarray;

Īn aceasta instructiune operatorii de tablou [] indica faptul ca este dealocat un tablou. Regula este urmatoare: de cīte ori new este urmat de [], delete trebuie urmat tot de [].