



Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Descrizione dei fondamenti e delle nozioni relative all'overloading nel c++
Tipologia: Appunti
1 / 6
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!




int foo (int a, int b) { return a * b; } int foo (int a) { return a * 2; } ... { a = foo (5, 5) + foo (5); // ritorna 5 * 5 (risultato della prima foo) } // + 5 * 2 (risultato della seconda foo)
int add (int a, int b); float add (float a, float b);
int add (int a, float b); float add (float a, int b); ... a = add (2, 2);
In C++, gli operatori (come quello di addizione o di moltiplicazione) sono considerati come delle vere e proprie funzioni. Essi sono già definiti per i tipi di parametri standard (i tipi "nativi"), e possiamo farne l'overloading, cioè possiamo sovraccaricarne il nome di significati, come facciamo per le normali funzioni.
Riflettete che proprio questa è la conseguenza logica interessante del fatto che gli operatori non sono nient'altro che delle funzioni, che del resto è una banale particolarizzazione già nota a tutti i matematici. Ripeto: se gli operatori sono funzioni, allora possiamo fare l'overloading degli operatori per dargli nuovi ulteriori definizioni che si adattino alla nostre proprie classi, proprio come facciamo l'overloading di funzioni. Ma non dobbiamo dimenticarci che gli operatori hanno tutti un significato "naturale", e, anche se ovviamente possibile, non è una buona idea ad esempio definire l'operatore di addizione per moltiplicare vettori!
Si dichiara un operatore usando la keyword operator seguita dall' operatore stesso. Quando sovraccarichiamo un operatore, possiamo scegliere tra due strategie: possiamo dichiarare gli operatori come funzioni friend oppure come funzioni membro. Non c'è una soluzione migliore in assoluto, dipende dall'operatore.
Quando dichiari un operatore come una funzione friend, devi mettere nel prototipo della funzione i parametri che saranno utilizzati dall'operatore (quelli prima e dopo il segno + per esempio), e un valore di ritorno, cioè il risultato restituito dall'operatore. class Complex { float re, im; public: Complex (float r = 0, float i = 0) { re = r; im = i; } friend Complex operator+ (Complex &, Complex &); }; Complex operator+ (Complex &a, Complex &b) { return Complex (a.re + b.re, a.im + b.im); } dichiarare gli operatori come Funzioni Membro Per una funzione membro, il primo parametro è sempre la classe stessa (che è, come sappiamo, mai dichiarato e implicitamente trasmesso), e poi vanno indicati gli altri parametri. class Complex { float re, im; public: Complex (float r = 0, float i = 0) { re = r; im = i; } Complex operator+ (Complex &); }; Complex Complex::operator+ (Complex &a) { return Complex (re + a.re, im + a.im); } In entrambi i casi, gli operatori possono poi essere utilizzati nella stessa maniera con cui si usano con i tipi standard:
L'operatore () è speciale perchè il numero dei suoi argomenti può essere variabile: possiamo definire molti operatori (), ciascuno con parametri diversi. class Matrix { int array[10][10]; public: // ritorna un elemento della matrice (per riferimento, in modo da essere in grado // di modificarlo; ad esempio posso scrivere Matrix matrix; ... matrix(2, 3) = 5;) int &operator()(int x, int y) { return array[x][y]; } // ritorna la somma degli elementi di una colonna // Ndt: si fa la convenzione che il primo indice sia quello di colonna e quindi il secondo è quello di riga int operator()(int x) { int s=0; for (int i=0; i<10; i++) s+=array[x][i]; return s; } }; { Matrix matrix; ... int a = matrix (2, 2); // elemento (2,2) int b = matrix (2); // somma della seconda colonna }
class Vector { int array[10]; public: int & operator[](int x) { return array[x]; } }; { Vector v; int a = v[2]; // ottiene il valore v[5] = a; // imposta il valore }
L'operatore = deve essere definito per forza come una funzione membro. Quando i suoi argomenti sono di un tipo classe diverso o di un tipo predefinito, può essere usato per fare delle conversioni di oggetti da una classe all'altra o da un tipo nativo ad una classe, ma solo durante l'assegnamento. class Complex { float re, im; public: Complex (float r = 0, float i = 0) { re = r; im = i; } Complex & operator= (float f) { re = f; im = 0; return *this; } }; { Complex a (10, 12); a = 12; // Chiamata dell'operatore= } Infatti l'operatore = come convertitore di tipo ha queste limitazioni: non può essere usato ovunque, ad esempio non può essere usato per convertire argomenti prima della chiamata di una funzione o per convertire da tipo classe a tipo nativo, proprio perchè non abbiamo accesso alle classi astratte rappresentate dai tipi nativi per poterlo definire lì dentro! Un modo migliore per effettuare le conversioni di tipo da classe a classe e per poter fare anche quelle da classe a tipo nativo è usare gli operatori di cast descritti nel prossimo paragrafo. class test { public: // test(int) {} // togli il commento iniziale per vedere che succede test & operator= (int) { // ... return *this; } }; void foo(const test & c) // oppure test c { } int main() { foo(2); // qui una temporanea di tipo test viene creata // e subito distrutta dopo il ritorno return 0; } Questo esempio mostra anche come i costruttori possono essere usati (anche implicitamente) come convertitori di tipo (da tipo nativo a classe o anche da classe a classe). Notare che nonostante venga creata una variabile temporanea e la si inizializzi, l'operatore=(int) non viene usato, in quanto non viene effettuato un assegnamento della variabile temporanea, perciò avrete un errore se il costruttore test(int) non c'è. Insisto su questo punto: se ad es. scrivete test t=3; la variabile t non viene creata e poi assegnata ma bensì creata e inizializzata e viene chiamato il costruttore test(int) e non prima il costruttore vuoto e poi l'operator=(int). test t=3; infatti è lo stesso di test t(3);. Notate quindi che in C++ c'è differenza tra inizializzazione e assegnamento. Sono due concetti un po' diversi.