OOP e Classi in C++
Classi, oggetti, costruttori, distruttori, incapsulamento e overloading degli operatori.
I 4 Pilastri dell'OOP
🔒 Incapsulamento
Nascondere i dettagli interni. I dati (attributi) sono privati, accessibili solo tramite metodi pubblici.
🌳 Ereditarietà
Una classe può estendere un'altra, riutilizzando e specializzando il codice esistente.
🎭 Polimorfismo
Stesso interfaccia, comportamenti diversi. Funzioni virtuali e overriding.
🏛️ Astrazione
Modellare entità del mondo reale nascondendo la complessità implementativa.
Prima Classe C++
#include <iostream>
#include <string>
using namespace std;
class Rettangolo {
private: // Attributi privati (incapsulamento)
double larghezza;
double altezza;
public: // Interfaccia pubblica
// Costruttore con parametri + valori di default
Rettangolo(double l = 1.0, double a = 1.0) {
// Validazione all'interno del costruttore
larghezza = (l > 0) ? l : 1.0;
altezza = (a > 0) ? a : 1.0;
}
// Getters (restituiscono valori)
double getLarghezza() const { return larghezza; }
double getAltezza() const { return altezza; }
// Setters (modificano valori con validazione)
void setLarghezza(double l) { if (l > 0) larghezza = l; }
void setAltezza(double a) { if (a > 0) altezza = a; }
// Metodi di calcolo
double area() const { return larghezza * altezza; }
double perimetro() const { return 2 * (larghezza + altezza); }
bool eQuadrato() const { return larghezza == altezza; }
// Stampa le info
void stampa() const {
cout << "Rettangolo " << larghezza << "x" << altezza
<< " | Area: " << area()
<< " | Perim: " << perimetro()
<< (eQuadrato() ? " [Quadrato]" : "") << "\n";
}
};
int main() {
Rettangolo r1(5.0, 3.0); // Costruttore con parametri
Rettangolo r2(4.0); // a = 1.0 (default)
Rettangolo r3; // 1x1 (default)
Rettangolo quadrato(6.0, 6.0);
r1.stampa();
r2.stampa();
r3.stampa();
quadrato.stampa();
// Accesso tramite getter/setter
r1.setLarghezza(10.0);
cout << "\nDopo setLarghezza(10): ";
r1.stampa();
return 0;
}
Costruttori, Distruttori e this
#include <iostream>
#include <cstring>
using namespace std;
class Stringa {
private:
char *dati;
int lunghezza;
public:
// Costruttore di default
Stringa() : dati(nullptr), lunghezza(0) {
cout << "Costruttore default\n";
}
// Costruttore con parametro
Stringa(const char *s) {
lunghezza = strlen(s);
dati = new char[lunghezza + 1]; // Alloca dinamicamente
strcpy(dati, s);
cout << "Costruttore(\"" << dati << "\")\n";
}
// Costruttore di copia (deep copy)
Stringa(const Stringa &altro) {
lunghezza = altro.lunghezza;
dati = new char[lunghezza + 1];
strcpy(dati, altro.dati);
cout << "Costruttore copia(\"" << dati << "\")\n";
}
// Distruttore: chiamato automaticamente quando l'oggetto esce dallo scope
~Stringa() {
cout << "Distruttore(\"" << (dati ? dati : "") << "\")\n";
delete[] dati; // Libera memoria allocata con new
}
// Operatore di assegnamento
Stringa& operator=(const Stringa &altro) {
if (this != &altro) { // self-assignment check
delete[] dati;
lunghezza = altro.lunghezza;
dati = new char[lunghezza + 1];
strcpy(dati, altro.dati);
}
return *this; // Ritorna riferimento a sé stesso
}
void stampa() const {
cout << "\"" << (dati ? dati : "") << "\" (len=" << lunghezza << ")\n";
}
};
int main() {
cout << "--- Creazione ---\n";
Stringa s1("Hello");
Stringa s2("World");
cout << "\n--- Copia ---\n";
Stringa s3 = s1; // Costruttore di copia
Stringa s4;
s4 = s2; // Operatore di assegnamento
cout << "\n--- Stampa ---\n";
s1.stampa(); s2.stampa(); s3.stampa(); s4.stampa();
cout << "\n--- Fine scope (distruttori) ---\n";
return 0;
}
Overloading degli Operatori
#include <iostream>
#include <cmath>
using namespace std;
class Vec2D {
public:
double x, y;
Vec2D(double x = 0, double y = 0) : x(x), y(y) {}
// Operatori aritmetici
Vec2D operator+(const Vec2D &v) const { return {x+v.x, y+v.y}; }
Vec2D operator-(const Vec2D &v) const { return {x-v.x, y-v.y}; }
Vec2D operator*(double s) const { return {x*s, y*s}; }
// Prodotto scalare
double operator*(const Vec2D &v) const { return x*v.x + y*v.y; }
// Operatori di uguaglianza
bool operator==(const Vec2D &v) const { return x==v.x && y==v.y; }
bool operator!=(const Vec2D &v) const { return !(*this == v); }
// Lunghezza del vettore
double norma() const { return sqrt(x*x + y*y); }
// Operatore di output (friend per accedere a privati se necessario)
friend ostream& operator<<(ostream &os, const Vec2D &v) {
return os << "(" << v.x << ", " << v.y << ")";
}
};
int main() {
Vec2D a(3, 4), b(1, 2);
cout << "a = " << a << "\n";
cout << "b = " << b << "\n";
cout << "a + b = " << (a + b) << "\n";
cout << "a - b = " << (a - b) << "\n";
cout << "a * 2 = " << (a * 2) << "\n";
cout << "a · b = " << (a * b) << " (prodotto scalare)\n";
cout << "|a| = " << a.norma() << "\n";
cout << "a == b? " << (a == b ? "Si" : "No") << "\n";
return 0;
}
Membri static e const
#include <iostream>
using namespace std;
class Contatore {
private:
int id;
static int totale; // Condiviso da TUTTI gli oggetti della classe
public:
Contatore() {
totale++;
id = totale;
cout << "Oggetto #" << id << " creato. Totale: " << totale << "\n";
}
~Contatore() {
cout << "Oggetto #" << id << " distrutto.\n";
totale--;
}
int getId() const { return id; } // const: non modifica l'oggetto
static int getTotale() { return totale; } // static: non ha this
};
int Contatore::totale = 0; // Inizializzazione fuori dalla classe
int main() {
cout << "Inizio. Totale: " << Contatore::getTotale() << "\n";
{
Contatore a, b, c;
cout << "Dentro il blocco. Totale: " << Contatore::getTotale() << "\n";
}
cout << "Fine blocco. Totale: " << Contatore::getTotale() << "\n";
return 0;
}
Crea una classe Frazione con numeratore e denominatore. Implementa: costruttore con riduzione ai minimi termini (usando MCD), operatori +, -, *, /, ==, e operator<< per stamparla come "a/b".
#include <iostream>
using namespace std;
int mcd(int a, int b) { return b == 0 ? a : mcd(b, a % b); }
class Frazione {
int num, den;
void riduci() {
if (den < 0) { num = -num; den = -den; }
int g = mcd(abs(num), den);
num /= g; den /= g;
}
public:
Frazione(int n=0, int d=1) : num(n), den(d) { riduci(); }
Frazione operator+(const Frazione &f) const { return {num*f.den + f.num*den, den*f.den}; }
Frazione operator-(const Frazione &f) const { return {num*f.den - f.num*den, den*f.den}; }
Frazione operator*(const Frazione &f) const { return {num*f.num, den*f.den}; }
Frazione operator/(const Frazione &f) const { return {num*f.den, den*f.num}; }
bool operator==(const Frazione &f) const { return num==f.num && den==f.den; }
friend ostream& operator<<(ostream &os, const Frazione &f) {
return os << f.num << "/" << f.den;
}
};
int main() {
Frazione a(1, 2), b(1, 3);
cout << a << " + " << b << " = " << (a+b) << "\n";
cout << a << " - " << b << " = " << (a-b) << "\n";
cout << a << " * " << b << " = " << (a*b) << "\n";
cout << a << " / " << b << " = " << (a/b) << "\n";
cout << "1/2 == 2/4? " << (Frazione(1,2)==Frazione(2,4) ? "Si" : "No") << "\n";
}Crea una classe Stack<T> generica con: push(val), pop(), top(), isEmpty(), size(). Usala con int, double e string nel main. Lancia un'eccezione se si fa pop su uno stack vuoto.
#include <iostream>
#include <vector>
#include <stdexcept>
#include <string>
using namespace std;
template <typename T>
class Stack {
vector<T> dati;
public:
void push(const T &val) { dati.push_back(val); }
T pop() {
if (isEmpty()) throw runtime_error("Stack vuoto!");
T top = dati.back();
dati.pop_back();
return top;
}
T& top() {
if (isEmpty()) throw runtime_error("Stack vuoto!");
return dati.back();
}
bool isEmpty() const { return dati.empty(); }
int size() const { return dati.size(); }
};
int main() {
Stack<int> si;
si.push(10); si.push(20); si.push(30);
while (!si.isEmpty()) cout << si.pop() << " ";
cout << "\n";
Stack<string> ss;
ss.push("ciao"); ss.push("mondo");
cout << "Top: " << ss.top() << "\n";
try { Stack<int> vuoto; vuoto.pop(); }
catch (const exception &e) { cout << "Eccezione: " << e.what() << "\n"; }
}Crea la classe ContoBancario con: IBAN (generato automaticamente), titolare, saldo. Metodi: deposita, preleva (con eccezione se saldo insufficiente), trasferisci (da conto a conto), storico (vettore di stringhe con le ultime 10 operazioni). Usa static per il contatore IBAN.
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>
#include <iomanip>
using namespace std;
class ContoBancario {
static int contatore;
string iban, titolare;
double saldo;
vector<string> movimenti;
void aggiungi_movimento(const string &desc) {
if (movimenti.size() >= 10) movimenti.erase(movimenti.begin());
movimenti.push_back(desc);
}
public:
ContoBancario(const string &tit, double saldo_iniz = 0)
: titolare(tit), saldo(saldo_iniz) {
iban = "IT" + to_string(++contatore);
aggiungi_movimento("Apertura conto: +" + to_string(saldo_iniz));
}
void deposita(double importo) {
if (importo <= 0) throw invalid_argument("Importo non valido");
saldo += importo;
aggiungi_movimento("Deposito: +" + to_string(importo));
}
void preleva(double importo) {
if (importo <= 0) throw invalid_argument("Importo non valido");
if (importo > saldo) throw runtime_error("Fondi insufficienti");
saldo -= importo;
aggiungi_movimento("Prelievo: -" + to_string(importo));
}
void trasferisci(ContoBancario &dest, double importo) {
preleva(importo);
dest.deposita(importo);
aggiungi_movimento("Bonifico a " + dest.iban + ": -" + to_string(importo));
}
void stampa() const {
cout << "\n=== " << iban << " | " << titolare
<< " | Saldo: " << fixed << setprecision(2) << saldo << " EUR ===\n";
for (const auto &m : movimenti) cout << " " << m << "\n";
}
double getSaldo() const { return saldo; }
};
int ContoBancario::contatore = 0;
int main() {
ContoBancario c1("Mario Rossi", 1000.0);
ContoBancario c2("Anna Bianchi", 500.0);
c1.deposita(250.0);
c1.preleva(100.0);
c1.trasferisci(c2, 300.0);
c1.stampa();
c2.stampa();
try { c1.preleva(5000.0); }
catch (const exception &e) { cout << "\nErrore: " << e.what() << "\n"; }
}