Documente Academic
Documente Profesional
Documente Cultură
Curs9 Clase
Curs9 Clase
Clase.
2. Declararea claselor.
O clasă reprezintă un tip definit de utilizator. Declararea unei clase se face într-o manieră
asemănătoare declarării structurilor şi conţine atât date cât şi funcţii (metode) şi putem declara variabile de
acest tip nou. Un obiect este un exemplar sau o instanţă a unei clase (în vechea terminologie obiectul este
echivalent unei variabile, iar clasa este echivalentul unui tip definit de utilizator).
Considerăm conceptul Data, pe care-l reprezentăm printr-o structură şi un set separat de funcţii
de manipulare:
struct Data{
int a, l, z; // reprezentare
};
void initD(const Data&, int, int, int); //initializare
void ad_a(const Data&, int); //adauga un an
void ad_l(const Data&, int); //adauga o luna
void ad_z(const Data&, int); //adauga o zi
1
Valeriu Iorga Programare în C / C++
Nu există o legătură implicită între date şi funcţiile de manipulare a lor. Pentru a stabili această
legătură, declarăm funcţiile ca membre ale structurii:
struct Data{
int a, l, z; // reprezentare
..// functii de manipulare
void initD(int, int, int); // initializare
void ad_a(int);
void ad_l(int);
void ad_z(int);
};
Funcţiile declarate în definirea clasei (structura este o clasă) se numesc funcţii membre şi pot fi
apelate numai de variabile de tipul corespunzător (obiecte) folosind sintaxa de acces la membrii structurii:
Data d;
d.initD(2001, 10, 15);
d.ad_a(3);
. . .
Definirea funcţiilor membre, în afara clasei se face folosind operatorul de vizibilitate (rezoluţie),
care indică faptul că funcţia aparţine clasei specificate înaintea acestui operator.
void Data::initD(int aa, int ll, int zz){
a = aa;
l = ll;
z = zz;
};
Declararea unei clase se face în mod asemănător cu declararea unei structuri. Cuvântul struct
este înlocuit prin class, iar câmpurile se separă în date membre şi funcţii membre (sau metode). În plus,
se pot preciza specificatori de acces la membri. Aceştia pot fi:
private – membrii sunt accesibili numai din interiorul clasei
public – membrii sunt accesibili din afara clasei
protected – membrii sunt accesibili din interiorul clasei şi din clasele derivate
Domeniul de definiţie al clasei este cuprins între începutul definiţiei clasei (class nume) şi
sfârşitul definiţiei.
Domeniul unui specificator de acces se întinde din locul unde apare, până la următorul specificator
de acces. Dacă nu apare nici un specificator de acces, accesul la membrii clasei nu va fi permis din afara ei
(implicit se consideră specificatorul private).
Datele clasei (definind structura sau starea) fiind private sunt “încapsulate”, adică ascunse
utilizatorului. Acesta nu le poate accesa direct, ci numai prin intermediul unor funcţii de acces publice, care
aparţin interfeţei ce defineşte comportarea clasei. (de obicei datele sunt private şi funcţiile sunt publice.)
//declararea clasei (interfata)
class Data{
int a, l, z; // reprezentare
public:
void initD(int, int, int);
void ad_a(int);
void ad_l(int);
void ad_z(int);
};
Variabilele ce apar în declaraţia clasei (datele membre), fiind private, pot fi folosite numai de către
funcţiile membre.
Funcţiile membre din partea publică pot fi accesate de către oricine. O structură este o clasă cu toţi
membrii publici.
2
Valeriu Iorga Programare în C / C++
Un alt exemplu - clasa punct; are ca date membre – abscisa şi ordonata, iar accesul la ele va fi
oprit; ca funcţii membre vom prevedea: iniţializarea, funcţii de acces la date, de setare date şi afişarea.
#include <iostream.h>
#include <iomanip.h>
// declaraţia clasei
class punct{
private:
double x0, y0; // date membre
public:
void init(double x=0, double y=0){
x0 = x;
y0 = y;
};
void setx(double x){ x0 = x; };
void sety(double y){ y0 = y; };
double getx(){ return x0; };
double gety(){ return y0; };
void afisare(){
long f = ios::fixed;
cout << setiosflags(f) << setw(6) << setprecision(2);
cout << “(“ << x0 << “,” << y0 << “)” << endl;
};
};
void main(){
punct z1, z2, z;
z1.init(1, 2);
z2.setx(3);
z2.sety(4);
z.init(z1.getx(),z2.gety());
z.afisare();
}
Se preferă ca definirea funcţiilor clasei (în afara cazurilor în care acestea sunt foarte mici), să se
facă în afara clasei, funcţiile membre fiind date numai prin semnăturile lor. În acest caz putem separa
definirea clasei de implementarea ei. Utilizatorului clasei i se asigură ca interfaţă, numai definirea clasei,
fiindu-i ascunsă implementarea ei.
// definirea clasei – fisierul punct.h
class punct{
private:
double x0, y0; // date membre
public:
void init(double x=0, double y=0);
void setx(double x=0);
void sety(double y=0);
double getx();
double gety();
void afisare();
};
3. Operatorul de rezoluţie.
Întrucât definirea funcţiilor se va face în afara domeniului de definiţie al clasei, numele funcţiei
trebuie să fie însoţit de numele clasei, şi să fie separat de aceasta prin operatorul de rezoluţie sau
vizibilitate ( :: ). Aşadar, implementarea clasei, (fişierul punct.cpp) va fi:
3
Valeriu Iorga Programare în C / C++
// fisierul punct.cpp
#include <iostream.h>
#include <iomanip.h>
void punct::init(double x, double y){
x0 = x;
y0 = y;
};
void punct::setx(double x){ x0 = x; };
void punct::sety(double y){ y0 = y; };
double punct::getx(){ return x0; };
double punct::gety(){ return y0; };
void punct::afisare(){
long f = ios::fixed;
cout << setiosflags(f) << setw(6) << setprecision(2);
cout << “(“ << x0 << “,” << y0 << “)” << endl;
};
};
De obicei declararea clasei (interfaţa) şi definirea ei (implementarea) se fac în fişiere separate.
Declaraţia unei clase se face într-un fişier antet. Un fişier antet trebuie inclus o singură dată în fişierul
programului.
Pentru a evita includerea multiplă de fişiere antet se utilizează directive #ifndef sub forma:
#ifndef _simbol_
#define _simbol_
declaraţie clasă (fişier antet)
#endif
Programul care utilizează clasa punct este un program constituit din mai multe fişiere, care vor
alcătui un proiect. El va conţine o directivă de includere a fişierului de interfaţă punct.h pentru definirea
clasei. La prima întâlnire a fişierului antet, simbolul nu este definit, incluzându-se fişierul antet. După
prima includere, simbolul fiind definit se ignoră porţiunea cuprinsă între #define şi #endif.
Fişierul de implementare conţine definiţiile metodelor, directiva de includere a fişierului antet al
interfeţei şi alte directive de includere de fişiere.
#include <iostream.h>
#include “clasa.h”
// definitii funcţii membre ale clasei
Programul utilizator (al treilea fişier) va conţine: includerea diverselor fişiere antet, includerea
fişierului antet al clasei şi funcţia main().
#include <iostream.h>
#include “clasa.h”
void main(){
. . .
}
4. Funcţii membre inline.
Funcţiile mici, cu puţine instrucţiuni, apelate frecvent se compilează ineficient (se rezervă spaţiu
în stivă pentru parametrii şi rezultat, se efectuează salt la funcţie şi apoi salt pentru revenire). Mult mai
eficientă ar fi expandarea apelului funcţiei prin corpul ei, ca în macroinstrucţiuni. Apelul unei funcţii
declarate inline este înlocuit la compilare prin corpul funcţiei (apel realizat prin expandare). O funcţie
inline nu trebuie să conţină bucle.
4
Valeriu Iorga Programare în C / C++
Funcţiile membre definite în interiorul clasei sunt în mod implicit inline, dar este posibil ca şi
funcţii definite în afara clasei să fie declarate explicit inline (expandate).
class Data{
public:
int zi();
. . .
private:
int a, l, z;
};
// functie declarata in mod explicit inline inafara clasei
inline int Data::zi(){return z;};
5
Valeriu Iorga Programare în C / C++
6. Constructori şi destructori.
Iniţializarea asigurată de funcţia membru init() este lăsată la laţitudinea utilizatorului. Este de
preferat o iniţializare mai sigură a obiectului, care să se facă în mod implicit la declararea obiectului.
Iniţializarea obiectelor se face prin intermediul unor funcţii speciale numite constructori.
Folosirea funcţiei initD() pentru iniţializarea obiectelor clasei Data este nesigură – putem
uita să facem iniţializarea sau să facem o iniţializare repetată.
Un obiect poate fi declarat neiniţializat sau iniţializat. Exemple:
Complex z; // obiect neiniţializat
Complex z1(-1.5, 2.); // obiect initializat
O soluţie sigură o reprezină declararea unei funcţii având scopul explicit de iniţializare a
obiectelor. O asemenea funcţie poartă numele de constructor.
Constructorul este o funcţie publică, care iniţializează datele membre, având acelaşi nume cu
numele clasei şi care nu întoarce nici o valoare.
class Data{
. . .
public:
Data(int, int, int); // constructorul
. . .
};
Considerăm definiţia clasei Complex:
#include <stdio.h>
class Complex{
private:
double re, im;
public:
void scrie();
};
void Complex::scrie(){
printf(“(%4.1lf , %4.1lf)\n”, re, im);
};
void main(){
Complex z; //obiect neinitializat
z.scrie();
}
Programul este corect din punct de vedere sintactic. Lipsa unui constructor definit de utilizator,
care să iniţializeze obiectele este remediată de compilator. Astfel, pentru clasa C, dacă nu se defineşte în
mod explicit un constructor, compilatorul generează în mod implicit constructorul: C::C(){}, care
crează datele membre ale clasei. Acest constructor implicit nu face nici o iniţializare a datelor membre, aşa
că se vor afişa nişte valori foarte mari, reprezentând conţinutul memoriei neiniţializate.
Pentru a evita aceasta, se recomandă programatorului să îşi definească un constructor implicit
(fără parametri), care să iniţializeze datele membre (în exemplul nostru iniţializarea la 0 pentru partea reală
şi cea imaginară a obiectului număr complex). Constructorul implicit acţionează asupra obiectelor
neiniţializate.
6
Valeriu Iorga Programare în C / C++
7
Valeriu Iorga Programare în C / C++
8
Valeriu Iorga Programare în C / C++
Complex z2;
Z2 = Complex(1., -2.);
crează un obiect temporar, pe care îl copiază într-un obiect existent.
Copierea unui obiect folosind operatorul de atribuire realizează o copiere membru cu membru a
obiectului sursă (din dreapta) în obiectul destinaţie (din stânga)
Data d=d1; //atribuire
Data *pd = new Data(d1); //creare obiect anonim initializat
Copia se face membru cu membru. Obiectul ce urmează a fi copiat este transmis prin referinţă.
Compilatorul va genera pentru clasa C, un constructor implicit de copiere: C::C(const C&); care
realizează copierea datelor membru cu membru (copiere superficială). Exemplu:
punct p1;// apeleaza constructorul implicit scris de programator
// care asigura initializarea la (0, 0)
punct p2(2,5); //initializare explicita (ctor de initializare)
punct p3(p2); //initializare prin copiere (ctor de copiere)
Constructorul implicit de copiere nu funcţionează corect în caz că obiectul alocă dinamic
memorie. În acest caz programatorul trebuie să-şi definească propriul constructor de copiere, care să
realizeze o copiere profundă, a datelor indicate de pointeri şi nu o copiere a pointerilor.
Obiectele clasei pot fi copiate prin atribuire. Copierea se face membru cu membru. Exemplu:
Complex z1(-1., 2.);
Complex z = z1;
Data d1=Data(2004,10,5);
Iniţializarea este mai eficientă decât atribuirea. În cazul atribuirii există o valoare (veche) a
obiectului, care trebuie ştearsă şi prin atribuire se copiază valoarea nouă a obiectului din dreapta
operatorului de atribuire.
Vom considera un exemplu în care se alocă dinamic memorie pentru datele membre. Fie clasa
persoana, prevăzută cu mai mulţi constructori şi un destructor:
class persoana{
private:
char* nume;
char* adresa;
char* cnp;
public:
persoana(char* n=””,char* a=””,char* c=””);
persoana(persoana& p);
~persoana();
};
persoana::persoana(char* n, char* a, char* c){
nume=new char[strlen(n)+1];
strcpy(nume, n);
adresa=new char[strlen(a)+1];
strcpy(adresa, a);
cnp=new char[strlen(c)+1];
strcpy(cnp, c);
};
persoana::persoana(persoana& p){
nume=new char[strlen(p.nume)+1];
strcpy(nume, p.nume);
adresa=new char[strlen(p.adresa)+1];
strcpy(adresa, p.adresa);
cnp=new char[strlen(p.cnp)+1];
strcpy(cnp, p.cnp);
9
Valeriu Iorga Programare în C / C++
};
persoana::~persoana(){
delete [] nume;
delete [] adresa;
delete [] cnp;
};
Dacă o clasă conţine date membre constante sau referinţe, acestea nu pot fi iniţializate în corpul
constructorului, ci printr-o listă de iniţializare plasată după antetul constructorului şi separată de acesta prin
Constructorii şi destructorii sunt apelaţi implicit la definirea, respectiv distrugerea obiectelor.
Obiectele sunt construite în ordinea declarării lor şi sunt distruse în ordine inversă declarării.
Clasele care conţin membri const şi membri referinţe trebuie să aibe un constructor definit de
programator, întrucât referinţele trebuiesc iniţializate.
În absenţa unor declarări explicite, compilatorul furnizează următoarele funcţii membre:
- un constructor implicit
- un constructor de copiere
- un operator de atribuire
- un destructor implicit
- un operator de adresare
7. Pointerul this.
O funcţie care operează asupra unei structuri, o va accesa prin intermediul unui parametru pointer
la această structură. O funcţie membru al unei clase, apelată dintr-un obiect, pentru a accesa membrii date
ai obiectului, va avea un parametru implicit pointer la obiect. Acest pointer poartă numele de pointerul
this, şi se declară într-o clasă C ca:
C* const this;
Fiind cuvânt cheie, el nu poate fi declarat explicit; fiind un pointer constant el nu poate fi
modificat. Referirea la datele membre x0 şi y0 se face prin intermediul acestui pointer ascuns ca this-
>x0 şi this->y0. O funcţie membru care returnează un obiect o poate face prin return *this
(returnarea unui pointer la obiect se face prin return this).
8. Membri statici ai claselor.
În fiecare obiect, pentru datele membre nestatice se crează copii. Pentru funcţiile membre există o
copie unică.
Pentru datele membre statice există o singură copie care va fi partajată de către toate obiectele.
Funcţiile membre statice sunt accesibile numai în fişierul în care sunt definite, fiind independente
de obiecte (instanţele claselor).
O funcţie statică poate fi apelată:
ca funcţie membră a unui obiect
independent de obiect, folosind operatorul de rezoluţie.
Pointerul this nu poate fi folosit de către funcţia statică, deoarece nu îi este transmis, aşadar
funcţia statică nu poate accesa decât date membre statice şi date globale.
Clasa Data depinde de variabila statică azi. Aceasta va aparţine clasei, dar nu şi obiectelor
clasei.
class Data{
int a, l, z;
static Data azi; //data membra statica
public:
Data(int aa=0, int ll=0, int zz=0);
10
Valeriu Iorga Programare în C / C++
. . .
static void set_data_crta(int aa, int ll, int zz);
};
11
Valeriu Iorga Programare în C / C++
}
10. Funcţii care returnează referinţe.
Pentru clasa complex, am definit funcţiile care asigură accesul partea reală, respectiv imaginară
a unui număr complex,:
double real(){ return re; };
double imag(){ return im; };
Dacă am dori modificarea părţii reale a unui număr complex printr-o atribuire de forma:
z.real()=5.;
constatăm că funcţia astfel definită nu poate apărea în partea stângă a unei atribuiri (nu este o L-valoare).
Acest neajuns se remediază impunând funcţiei să returneze o referinţă la obiect, adică:
double& real(){ return re; };
Funcţiile de actualizare a datei ad_a(), ad_l(), ad_z() nu întorc valori. Pentru asemenea
funcţii este util să întoarcem o referinţă la obiectul actualizat, astfel încât operaţiile să poată fi înlănţuite de
forma:
d.ad_a(1).ad_l(1).ad_z(1);
Funcţiile se declară cu valoare de întoarcere referinţă la Data:
class Data{
Data& ad_a(int);
Data& ad_l(int);
Data& ad_z(int);
};
11. Probleme.
1. Definiţi şi implementaţi clasa Cerc, având ca dată membrău Raza, un constructor şi ca funcţii membre
Aria şi Circumferinta.
2. Definiţi şi implementaţi clasa Cilindru, având ca date membre Raza şi Inaltimea cilindrului şi
ca funcţii membre: un constructor, Aria şi Volumul.
3. Definiţi şi implementaţi clasa Televizor, având ca date membre: Volumul şi Canalul şi ca funcţii
membre: un constructor, Pornire, Oprire, Schimbare_Volum, Schimbare_Canal.
12
Valeriu Iorga Programare în C / C++
5. Specificaţi, proiectaţi şi implementaţi o clasă care poate fi folosita într-un program pentru a simula o
combinaţie de deschidere a unui seif.
Seiful are un buton circular cu numere de la 0 la 39 şi pentru deschiderea lui este necesară
formarea unei combinaţii de 3 numere, fie acestea x, y şi z.
Pentru deschiderea seifului, se roteste în sens orar butonul până la formarea numărului x, apoi se
roteste în sens antiorar până la formarea lui y, şi în final se formează z prin rotire în sens orar.
Clasa Seif va avea un constructor, care iniţializează combinaţia codului de deschidere cu 3
numere date ca argumente (ca argumente implicite se iau 0,0,0).
Sunt prevăzute următoarele funcţii membre:
- modificarea codului de deschidere la o noua combinaţie de 3 numere
- rotirea butonului într-o directie dată, până când se formează un număr dat
- închiderea seifului
- încercarea de a deschide seiful
- testarea stării seifului (deschis sau închis)
- indicarea combinaţiei de cod încercate pentru deschidere.
6. Specificaţi, proiectaţi şi implementaţi o clasă Punct3, folosite pentru reprezentarea punctelor (prin 3
coordonate x,y,z) în spaţiul 3-dimensional.
Asiguraţi următoarele funcţii membre:
- constructor pentru setarea unui punct într-o poziţie specificată (implicit în 0,0,0)
- mutarea unui punct cu valori specificate pentru cele 3 direcţii
- aflarea coordonatelor unui punct
- rotaţia unui punct cu un unghi specificat de-a lungul uneia dintre axele x,y sau z.
Coordonatele noului punct x',y',z' sunt:
rotaţie în jurul axei x:
x'=x
y'=y*cos(t)-z*sin(t)
z'=y*sin(t)+z*cos(t)
13
Valeriu Iorga Programare în C / C++
8. Definiţi şi implementaţi clasa Calendar având ca date membre: An, Luna, Zi, NumeZi
(enumerarea de Luni până Duminica) şi ca funcţii membre:
-un constructor
-obţinerea datei curente
-modificarea datei curente
-incrementarea datei curente
-afişarea datei curente.
Se va defini o functie prieten pentru citirea datei curente.
Se vor defini de asemeni funcţii nemembre cu 2 parametri date calendaristice:
- Anterior care întoarce rezultatul boolean true/false după cum data argument 1 este înaintea sau
după data argument 2 şi
- Interval care întoarce numărul de zile cuprins între cele două date argumente.
14