Introducere în clase
Până acum, în programele noastre, am lucrat cu tipuri de date simple (int, char, double) și uneori cu structuri (struct) pentru a grupa date înrudite. Clasele sunt pasul următor - o modalitate de a grupa date împreună cu funcțiile care acționează asupra lor.
Clasele sunt fundamentul Programării Orientate pe Obiecte (OOP) - una dintre cele mai importante paradigme de programare.
Ce e o clasă?
O clasă e un șablon pentru crearea de obiecte. Un obiect are:
- Date (numite câmpuri sau atribute) - ce “știe”
- Operații (numite metode) - ce “poate face”
Analogie: tiparul și prăjitura
Clasa e ca un tipar de prăjituri. Poți folosi același tipar ca să faci 100 de prăjituri diferite - fiecare prăjitură e un obiect al clasei “Prăjitură”.
Clasa Prajitura:
Câmpuri: aroma, greutate, forma
Metode: mananca(), impacheteaza()
Obiecte:
prajitura1 = Prajitura("ciocolată", 150g, rotundă)
prajitura2 = Prajitura("vanilie", 200g, pătrată)
Două obiecte diferite, dar același tipar (aceleași câmpuri și metode).
Altă analogie: planul unei case
Un plan de casă descrie cum arată casa - câte camere, unde e bucătăria, dimensiuni.
Din același plan se pot construi multe case. Fiecare casă e un obiect, planul e clasa.
De ce avem nevoie de clase?
Să vedem o problemă reală. Scriem un program pentru o școală care gestionează elevi. Fiecare elev are: nume, clasa, medie.
Fără clase
string nume[100];
int clasa[100];
double medie[100];
int n = 0;
void adaugaElev(string numeNou, int clasaNoua, double medieNoua) {
nume[n] = numeNou;
clasa[n] = clasaNoua;
medie[n] = medieNoua;
n++;
}
void afisareElev(int i) {
cout << nume[i] << " clasa " << clasa[i] << ", media " << medie[i];
}Probleme:
- Datele legate sunt separate în vectori diferiți.
- Funcțiile primesc indici sau multe argumente.
- Dacă vreau o nouă proprietate (vârsta), trebuie să modific toate locurile.
- Codul devine haotic pe măsură ce crește.
Cu clase
class Elev {
public:
string nume;
int clasa;
double medie;
void afisare() {
cout << nume << " clasa " << clasa << ", media " << medie;
}
};
Elev elevi[100];
int n = 0;Acum un Elev e un singur lucru
cu propriile date și propriile metode. Organizare clară, ușor de
extins.
Struct vs Class - ce diferă?
În C++, struct și class sunt
aproape identice. Singura diferență e
accesul implicit:
struct Punct {
int x, y; // implicit PUBLIC - oricine poate citi/scrie
};
class Punct {
int x, y; // implicit PRIVATE - doar metodele clasei pot accesa
};Când folosești fiecare?
struct→ când ai doar date (fără logică complexă). Ex: punct, pereche.class→ când ai obiecte cu comportament complex și vrei să ascunzi detalii.
În acest capitol folosim class
ca să explicăm conceptele OOP.
Primul exemplu complet: clasa Punct
#include <fstream>
#include <cmath>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Punct {
public:
double x, y;
void afisare() {
fout << "(" << x << ", " << y << ")";
}
double distantaFataDeOrigine() {
return sqrt(x * x + y * y);
}
};
int main() {
Punct p;
p.x = 3;
p.y = 4;
p.afisare();
fout << "\nDistanta: " << p.distantaFataDeOrigine();
return 0;
}date.in (nefolosit):
date.out:
(3, 4)
Distanta: 5Ce se întâmplă, pas cu pas?
- Definim clasa
Punct- asta e doar șablonul, nu consumă memorie. - Clasa are două câmpuri (
x,y) și două metode (afisare,distantaFataDeOrigine). public:înseamnă că tot ce urmează poate fi accesat din exterior.- În
main, creăm un obiectpde tipulPunct→ aici se alocă memorie pentru câmpuri. - Setăm câmpurile lui
p(p.x = 3,p.y = 4). - Apelăm metodele cu sintaxa
p.metodă().
Vocabularul OOP
Termeni pe care îi vei întâlni frecvent:
| Termen | Definiție |
|---|---|
| Clasă | Șablonul/tiparul - definiția |
| Obiect (sau instanță) | O utilizare concretă a clasei |
| Câmp (sau atribut, membru de date) | Variabilă din interiorul clasei |
| Metodă (sau funcție membru) | Funcție din interiorul clasei |
| Instanțiere | Crearea unui obiect dintr-o clasă |
| OOP | Programare Orientată pe Obiecte |
Nu te îngrijora dacă termenii par mulți acum - vin natural pe măsură ce lucrezi cu clase.
Cum creăm obiecte (3 moduri)
1. Pe stivă (cel mai comun)
Punct p; // obiect pe stiva
p.x = 3;
p.y = 4;Memoria se eliberează automat când ieșim din funcție. Simplu și rapid.
2. Pe heap (cu new)
Punct *p = new Punct;
p->x = 3; // atenție: ->, nu . (p e pointer)
p->y = 4;
delete p; // trebuie să eliberăm manual!Folosim pointeri când vrem memorie dinamică. Nu uita
delete, altfel ai memory leak.
3. În vector
Punct v[100];
v[0].x = 3;
v[0].y = 4;Ca la vectorii obișnuiți - un vector de obiecte.
Accesarea membrilor:
. vs ->
Regula e simplă:
- Obiect direct (pe stivă sau în vector):
obiect.câmpsauobiect.metodă() - Prin pointer:
pointer->câmpsaupointer->metodă()
Punct p;
Punct *pp = &p;
p.x = 5; // prin obiect
pp->x = 5; // prin pointer
(*pp).x = 5; // echivalent cu pp->x - dar mai urat scrisAmintește-ți: -> e
prescurtare pentru “dereferențiază și apoi
accesează”.
Exemplu mai complex: clasa Dreptunghi
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Dreptunghi {
public:
double lungime, latime;
double arie() {
return lungime * latime;
}
double perimetru() {
return 2 * (lungime + latime);
}
bool estePatrat() {
return lungime == latime;
}
void afisare() {
fout << "Dreptunghi " << lungime << "x" << latime;
fout << " (arie=" << arie() << ", perimetru=" << perimetru() << ")";
if (estePatrat()) fout << " - e patrat!";
fout << "\n";
}
};
int main() {
Dreptunghi d1, d2;
d1.lungime = 5;
d1.latime = 3;
d1.afisare();
d2.lungime = 4;
d2.latime = 4;
d2.afisare();
return 0;
}date.out:
Dreptunghi 5x3 (arie=15, perimetru=16)
Dreptunghi 4x4 (arie=16, perimetru=16) - e patrat!Observă câteva lucruri importante
- O singură clasă, dar două obiecte complet independente.
- Metodele se pot apela una pe alta
(
afisarefoloseștearie,perimetru,estePatrat). - În interiorul metodelor, accesăm câmpurile
direct (
lungime,latime) - nu e nevoie ded..
Clase vs funcții globale - comparație
Aceeași problemă poate fi rezolvată și cu funcții globale:
// Abordare procedurală (funcții globale)
double arie(double lungime, double latime) {
return lungime * latime;
}
double perimetru(double lungime, double latime) {
return 2 * (lungime + latime);
}
int main() {
double lungime1 = 5, latime1 = 3;
double lungime2 = 4, latime2 = 4;
cout << arie(lungime1, latime1);
cout << arie(lungime2, latime2);
}// Abordare orientată pe obiecte
class Dreptunghi {
public:
double lungime, latime;
double arie() { return lungime * latime; }
double perimetru() { return 2 * (lungime + latime); }
};
int main() {
Dreptunghi d1, d2;
d1.lungime = 5; d1.latime = 3;
d2.lungime = 4; d2.latime = 4;
cout << d1.arie();
cout << d2.arie();
}De ce clasa e mai bună
- Datele sunt legate: lungime și lățime ale aceluiași dreptunghi stau împreună.
- Mai puține parametri:
d.arie()în loc dearie(d.lungime, d.latime). - Ușor de extins: adaug o nouă metodă fără să ating toate apelurile existente.
- Cod citibil: logica e împachetată în clasă.
Cei 4 piloni OOP
OOP se bazează pe patru concepte fundamentale:
1. Încapsulare
Ascundem detaliile interne. Utilizatorul clasei vede doar ce trebuie să vadă. Restul e privat.
Ex: folosești o telecomandă fără să știi cum funcționează circuitele pe interior.
2. Abstractizare
Reprezentăm concepte reale (mașini, elevi, cărți) ca obiecte cu proprietăți și comportament clare.
3. Moștenire
O clasă poate moșteni de la altă clasă - preia toate câmpurile și metodele ei, și adaugă/modifică ce are nevoie.
Ex: Student moștenește Persoana,
adăugând câmpul universitate.
4. Polimorfism
Același cod poate lucra cu tipuri diferite, comportându-se diferit pentru fiecare.
Ex: metoda afisare() produce rezultate diferite
pentru Cerc, Patrat,
Triunghi, chiar dacă apelul e același.
Vom aprofunda fiecare pilier în lecțiile următoare.
Un exemplu care arată puterea claselor
Să facem un mini-program cu mai multe obiecte:
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Cerc {
public:
double raza;
double arie() {
return 3.14159 * raza * raza;
}
};
int main() {
int n;
fin >> n;
Cerc cercuri[100];
for (int i = 0; i < n; i++)
fin >> cercuri[i].raza;
// Calculăm aria totală
double total = 0;
for (int i = 0; i < n; i++)
total += cercuri[i].arie();
fout << "Aria totala: " << total;
return 0;
}date.in:
3
1 2 3date.out:
Aria totala: 43.982...Frumos, curat, ușor de înțeles. Un vector de obiecte, fiecare își știe treaba.
Greșeli frecvente
1. Uitarea ; la
finalul clasei
class Punct {
int x, y;
} // UITAT `;` - eroare de compilare!
int main() { ... }Clasele în C++ trebuie încheiate cu
;. E greșeala #1 a începătorilor.
2. Confuzia . și
->
Punct p;
Punct *pp = &p;
p->x = 5; // GRESIT - p nu e pointer
pp.x = 5; // GRESIT - pp e pointerCorect: p.x pentru obiect, pp->x
pentru pointer.
3. Crearea obiectelor fără inițializare
Punct p; // p.x și p.y au valori ALEATOARE
cout << p.x; // afișează gunoiTrebuie să inițializezi câmpurile înainte de folosire. Cel mai bine folosești constructori (vezi lecția 122).
4. Memory leak cu
new
Punct *p = new Punct;
// uităm delete → memory leakOrice new trebuie însoțit de un
delete. Mai bine, evită new când poți
folosi obiecte pe stivă.
5. Uitarea public:
class Punct {
int x, y; // implicit PRIVATE la class!
};
int main() {
Punct p;
p.x = 5; // EROARE - x e privat
}La class, trebuie explicit public:
sau folosești struct.
Ce să reții
- Clasă = șablon pentru crearea de obiecte.
- Obiect = instanță concretă a unei clase.
- Câmpuri = datele obiectului.
- Metode = funcții care acționează pe obiect.
structvsclass: diferă doar accesul implicit (public vs private).- Sintaxă:
obiect.metoda()pentru obiect,pointer->metoda()pentru pointer. - Cei 4 piloni OOP: Încapsulare, Abstractizare, Moștenire, Polimorfism.
- Nu uita
;după definiția clasei. - Clasele organizează codul: date + funcții care lucrează pe ele stau împreună.