Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


overloading nel c++, Appunti di Fondamenti di informatica

Descrizione dei fondamenti e delle nozioni relative all'overloading nel c++

Tipologia: Appunti

2020/2021

Caricato il 12/06/2021

antonio-filippelli
antonio-filippelli 🇮🇹

4

(1)

3 documenti

1 / 6

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Coding C++: OVERLOADING
Overloading delle funzioni
I cambiamenti nella definizione delle funzioni sono coerenti con una nuova funzionalità permessa dal C++
detta "sovraccaricamento" (overloading) di un nome di funzione. In C++ è lecito avere due (o più) funzioni
con lo stesso nome, ammesso però che la lista dei loro parametri sia differente (cosa che permette al
compilatore di distinguerle). Sovraccaricare un nome di funzione è facile: basta dichiarare e utilizzare le
funzioni con lo stesso nome come al solito.
Esempio:
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)
Ma possono sorgere dei problemi. Ad es. puoi definire due funzioni in questo modo:
int add (int a, int b);
float add (float a, float b);
e poi fare una chiamata ad add. Questo tipo di definizioni vanno evitate. I compilatori si confondono e
danno warning o errori a seconda dei casi, corrispondenti a perdite di informazioni o ambiguità della
situazione.
Ndt: vi invito a sperimentare la situazione col vostro compilatore. Provate a definire entrambe le funzioni in
modo che restituiscono a+b. Se si compila provate poi a chiamare add con due interi, due float, con un
intero e un float e viceversa. Potete stabilire quale funzione add viene chiamata tracciando il programma o
facendo stampare da ciascuna delle funzioni un messaggio indicativo con una printf.
Quella che abbiamo visto era una situazione di ambiguità pericolosa. Ma che ne dite di questa?
int add (int a, float b);
float add (float a, int b);
...
a = add (2, 2);
Fortunatamente, il compilatore rifiuterà sicuramente quel codice, spiegando che non può scegliere tra le
due funzioni funzioni add. Perciò fai attenzione!
OVERLOADING DEGLI OPERATORI
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.
pf3
pf4
pf5

Anteprima parziale del testo

Scarica overloading nel c++ e più Appunti in PDF di Fondamenti di informatica solo su Docsity!

Coding C++: OVERLOADING

Overloading delle funzioni

I cambiamenti nella definizione delle funzioni sono coerenti con una nuova funzionalità permessa dal C++

detta "sovraccaricamento" (overloading) di un nome di funzione. In C++ è lecito avere due (o più) funzioni

con lo stesso nome, ammesso però che la lista dei loro parametri sia differente (cosa che permette al

compilatore di distinguerle). Sovraccaricare un nome di funzione è facile: basta dichiarare e utilizzare le

funzioni con lo stesso nome come al solito.

Esempio:

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)

Ma possono sorgere dei problemi. Ad es. puoi definire due funzioni in questo modo:

int add (int a, int b); float add (float a, float b);

e poi fare una chiamata ad add. Questo tipo di definizioni vanno evitate. I compilatori si confondono e

danno warning o errori a seconda dei casi, corrispondenti a perdite di informazioni o ambiguità della

situazione.

Ndt: vi invito a sperimentare la situazione col vostro compilatore. Provate a definire entrambe le funzioni in

modo che restituiscono a+b. Se si compila provate poi a chiamare add con due interi, due float, con un

intero e un float e viceversa. Potete stabilire quale funzione add viene chiamata tracciando il programma o

facendo stampare da ciascuna delle funzioni un messaggio indicativo con una printf.

Quella che abbiamo visto era una situazione di ambiguità pericolosa. Ma che ne dite di questa?

int add (int a, float b); float add (float a, int b); ... a = add (2, 2);

Fortunatamente, il compilatore rifiuterà sicuramente quel codice, spiegando che non può scegliere tra le

due funzioni funzioni add. Perciò fai attenzione!

OVERLOADING DEGLI OPERATORI

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!

Funzioni friend oppure funzioni membro?

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.

dichiarare gli operatori come Funzioni Friend

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:

&= ^= |= <<=

Gli operatori () e []

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 }

Quando usi l'operatore [], puoi ritornare un riferimento all'oggetto. Così sarai in grado

non solo di leggere l'elemento, ma anche di scriverlo:

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 }

Conversioni con l'operatore =

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.