Programmazione a oggetti 2 - Overloading
Overloading
L’overloading (sovraccarico) permette di definire più funzioni o metodi con lo stesso nome, purché abbiano parametri diversi (per tipo o per numero).
Non ha nulla a che fare con l’ereditarietà: avviene all’interno della stessa classe ed è risolto a compile-time.
Perché è utile?
Permette di mantenere un nome coerente per operazioni simili, cambiando solo cosa accettano in input.
Esempio
class Math {
public:
int somma(int a, int b) {
return a + b;
}
double somma(double a, double b) {
return a + b;
}
int somma(int a, int b, int c) {
return a + b + c;
}
};
Tutte queste funzioni si chiamano somma, ma il compilatore sceglie quella corretta in base ai parametri.
Idee chiave
- stesso nome
- parametri diversi
- scelta a compile-time
- nessun polimorfismo dinamico
Overloading degli operatori
Oltre a funzioni e metodi, in C++ è possibile ridefinire il comportamento degli operatori (come +, -, ==, <<…), permettendo di usarli con gli oggetti e avere un comportamento personalizzato.
Questo rende il codice più naturale e leggibile, perché è possibile scrivere:
v1 + v2;
anche se v1 e v2 sono oggetti.
Esempio 1: somma tra oggetti
Immaginiamo una classe Punto che rappresenta un punto nel piano.
class Punto {
public:
int x, y;
Punto(int x, int y) : x(x), y(y) {}
};
int main() {
Punto a(2, 3);
Punto b(4, 1);
Punto c = a + b; //somma tra due punti: comportamento indefinito -> errore
}
Aggiungendo all’interno della classe la seguente funzione, viene ridefinito l’operatore +.
class Punto {
public:
int x, y;
Punto(int x, int y) : x(x), y(y) {}
Punto operator+(const Punto &altroPunto) {
return Punto(x + altroPunto.x, y + altroPunto.y);
}
};
Qui operator+ è una funzione speciale che dice al compilatore: quando vedi oggetto1 + oggetto2, chiama il metodo operator+.
Questa istruzione quindi:
Punto c = a + b;
viene tradotta concettualmente in:
Punto c = a.operator+(b);
Esempio 2: ridefinire la stampa con cout
Nello stesso modo in cui possiamo ridefinire l’operatore +, possiamo ridefinire anche la stampa di un oggetto quando utilizziamo cout <<.
Questo significa che possiamo scrivere:
cout << a;
e decidere noi come deve apparire l’oggetto a schermo.
Esempio con la classe Punto:
class Punto {
public:
int x, y;
Punto(int x, int y) : x(x), y(y) {}
friend ostream& operator<<(ostream &os, const Punto &p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
Ora possiamo fare:
Punto p(3, 5);
cout << p;
e verrà stampato:
(3, 5)
Non c’è nessuna magia nella programmazione: stiamo solo dicendo al compilatore cosa fare quando incontra cout << oggetto.
Stiamo nascondendo la complessità dietro una sintassi più leggibile, ma il comportamento è sempre definito in modo esplicito da noi.