Programare Competitivă

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: 5

Ce se întâmplă, pas cu pas?

  1. Definim clasa Punct - asta e doar șablonul, nu consumă memorie.
  2. Clasa are două câmpuri (x, y) și două metode (afisare, distantaFataDeOrigine).
  3. public: înseamnă că tot ce urmează poate fi accesat din exterior.
  4. În main, creăm un obiect p de tipul Punct → aici se alocă memorie pentru câmpuri.
  5. Setăm câmpurile lui p (p.x = 3, p.y = 4).
  6. 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âmp sau obiect.metodă()
  • Prin pointer: pointer->câmp sau pointer->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 scris

Aminteș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

  1. O singură clasă, dar două obiecte complet independente.
  2. Metodele se pot apela una pe alta (afisare folosește arie, perimetru, estePatrat).
  3. În interiorul metodelor, accesăm câmpurile direct (lungime, latime) - nu e nevoie de d..

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ă

  1. Datele sunt legate: lungime și lățime ale aceluiași dreptunghi stau împreună.
  2. Mai puține parametri: d.arie() în loc de arie(d.lungime, d.latime).
  3. Ușor de extins: adaug o nouă metodă fără să ating toate apelurile existente.
  4. 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 3

date.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 pointer

Corect: 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ă gunoi

Trebuie 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 leak

Orice 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.
  • struct vs class: 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ă.