• Non ci sono risultati.

Le funzioni friend

N/A
N/A
Protected

Academic year: 2021

Condividi "Le funzioni friend"

Copied!
30
0
0

Testo completo

(1)

Corso di Programmazione ad Oggetti

Funzioni e classi “friend”. Overloading di operatori

a.a. 2008/2009

Claudio De Stefano

(2)

Le funzioni friend

■ Nella definzione di una classe è possibile elencare quali funzioni, esterne alla classe, possono accedere ai memebri privati della classe. Queste funzioni sono dette friend (amiche).

■ Per dichiarare una funzione friend è necessario includere il prototipo nella classe, facendola precedere dalla parola chiave friend:

class Myclass { .

. .

friend Tipo funz(...);

};

■ Le funzioni friend sono particolarmente utili quando due o più classi contengono membri correlati con altre parti del programma.

(3)

Le funzioni friend: Esempio

class Myclass { int a,b;

public:

void set_ab(int i, int j) friend int sum(Myclass x);

};

Myclass::set_ab(int i, int j) {

a = i;

b = j;

}

// sum non è membro della classe int sum(Myclass x)

{

return x.a + x.b;

main() } {

Myclass n;

n.set_ab(2, 4);

cout<<sum(n);

}

La funzione Sum non è membro della classe, ma essendo friend può accedere ai suoi membri privati

(4)

Le classi friend

■ In C++ è anche possibile rendere un'intera classe friend di un'altra classe.

■ In tal caso tutte le funzioni della classe dichiarata friend avranno accesso ai membri privati della classe.

■ La dichiarazione di classe friend è del tipo:

class C1 { .

. .

friend class C2;

};

■ Osserviamo che le funzioni membro di C2 possono accedere ai membri di C1 e non viceversa.

(5)

Overloading degli operatori

■ Il meccanismo di overloading degli operatori consente di attribuire ulteriori significati agli operatori del linguaggio.

■ In C++ è possibile eseguire l'overloading della maggior parte degli operatori, per consentire loro di svolgere operazioni specifiche rispetto a determinate classi.

■ l'overlading di un operatore, estende l'insieme dei tipi al quale esso può essere applicato, lasciando invariato il suo uso originale.

■ L'overloading degli operatori consente di integrare meglio le nuove classi create dall'utente nell'ambiente di programmazione.

■ L'overloading degli operatori è alla base delle operazioni di I/O del C++.

(6)

Overloading degli operatori

■ L'overlaoding degli operatori viene realizzato per mezzo delle funzioni operator.

■ Un funzione operator definisce le specifiche operazioni che dovranno essere svolte dall'operatore sovraccaricato (overloaded) rispetto alla classe specificata.

■ Ci sono due modi per sovraccaricare un operatore.

– Tramite funzioni membro della classe;

– Tramite funzioni esterne che però devono esseredefinite friend per la classe.

(7)

■ Le funzioni operator membro di una classe hanno la seguente froma generale:

Tipo nome-classe::operator#(Tipo1 arg1, Tipo2 arg2, ...) {

// istruzioni .

. . }

dove # è il simbolo dell'operatore da sovraccaricare

■ Nella maggior parte dei casi le funzioni operator restituiscono un oggetto della classe su cui operano, ma in generale possono restituire qualsiasi tipo valido.

■ Quando si esegue l'overloading di un operatore binario, la funzione operator ha un solo argomento, mentre se l'operatore è unario la funzione non ha argomenti.

Funzioni operator membro

(8)

Overloading degli operatori: le regole da seguire

■ È possibile modificare il significato di un operatore esistente;

■ Non è possibile creare nuovi operatori e non e’ opportuno ridefinire la semantica di un operatore applicato a tipi predefiniti;

■ Non è possibile cambiare precedenza, associatività e “arity” (numero di operandi);

■ non è possibile usare argomenti di default.

(9)

Il comportamento del compilatore

■ L'overloading degli operatori viene realizzato dal compilatore.

■ Il compilatore, quando incontra un’espressione della forma x op y

verifica nell’ordine:

– se nella classe X dell’oggetto x vi è una funzione membro della forma operator op(Y), dove Y indica il tipo dell’oggetto y;

– se vi è una funzione non membro della forma “operator op(X, Y)”

se una delle due condizioni è verificata provvede ad invocare la funzione individuata, altrimenti segnala un errore.

(10)

Funzioni operator membro: esempio

class Complex { float re;

float im;

public:

Complex(float r=0.0, float i=0.0) {re=r; im=i;};

float getRe() const {return re; };

float getIm() const {return im; };

void setRe(float r) {re=r; };

void setIm(float i) {im=i; };

void show();

Complex operator+(Complex op2);

};

void Complex::show() {

cout<<endl<<”re: ”<<re<<” im: ”<<im;

}

Complex Complex::operator+(Complex op2) {

Complex tmp;

tmp.re = re + op2.re;

tmp.im = im + op2.im;

return tmp;

(11)

Funzioni operator membro: esempio

main() {

Complex c1, c2(1, 1), c3(4,5);

c1.show();

c2.show();

c3.show();

c1 = c2 + c3;

c1.show();

}

c1 = c2.operator+(c3)

>> re: 0 im: 0

>> re: 1 im: 1

>> re: 4 im: 5

>> re: 5 im: 6

output

NOTA

c1 = c2 + c3;

diventa

(12)

Funzioni operator: alcune osservazioni

■ quando si esegue l'oveloading di un operatore binario è l'oggetto di sinistra a generare la chiamata alla funzione operator.

■ l'operatore di assegnamento può essere usato solo perchè operator+ restituisce un oggetto della classe Complex.

■ La funzione operator+ NON modifica gli operandi. In generale, è opportuno definire sempre delle funzioni operator che non modificano gli operandi, in analogia con gli operatori standard.

(13)

Altri operatori

Complex Complex::operator-(Complex op2) {

Complex tmp;

tmp.re = re - op2.re;

tmp.im = im - op2.im;

return tmp;

}

Complex Complex::operator++() {

re++;

im++;

return *this }

poiché è l'oggetto di sinistra a generare la chiamata a

operator- i dati di op2 devono essere sottratti a quelli dell'oggetto chiamante, al fine di

conservare la semantica della sottrazione

In questo modo si definise

l'operatore prefisso di incremento l'operatore non ha parametri

perchè è un operatore unario.

In questo caso viene modificato l'operando.

(14)

Altri operatori: esempio d'uso

main() {

Complex c1(1,2), c2(3,5), c3(9,9);

c1.show();

c2.show();

++c1;

c1.show();

c2 = ++c1;

c1.show();

c2.show();

c1 = c2 – c3;

c1.show();

}

>> re: 1 im: 2

>> re: 3 im: 5

>> re: 2 im: 3

>> re: 3 im: 4

>> re: 3 im: 4

>> re: -6 im: -5

output

(15)

L'operatore di assegnamento

■ L'operatore di assegnamento viene usato in tutte le espressioni in cui è presente il segno ‘=‘.

■ L'assegnamento di default effettua un copia bit a bit. Pertanto l'overloading è necessario per le classi che hanno un'estensione dinamica.

■ Deve essere una funzione membro. Ha la forma:

const C& operator=(const C&)

■ L’operatore di assegnamento deve consentire assegnazioni multiple a=b=c….

(16)

L'operatore di assegnamento: esempio

Complex Complex::operator=(Complex op2) {

re = op2.re;

im = op2.im;

return *this;

}

NOTA

l'operatore restituisce *this ovvero l'oggetto che ha generato la chiamata. Questo accorgimento rende possibile assegnamenti multipli del tipo:

c1 = c2 = c3;

(17)

Operatori di incremento postfissi

■ Per definire operatori di incremento(decremento) postfissi bisogna usare la forma:

nome_classe nome_classe::operator++(int x) {

// istruzioni }

■ Nell'esempio precedente avremo:

Complex Complex::operator++(int x) {

re++;

im++;

return *this;

}

(18)

Overloading delle forme abbreviate

■ È possibile effettuare anche l'overloading delle forme abbreviate degli operatori, tipo: +=, *=, -= ecc.

Esempio

Complex Complex::operator+=(Complex op2) {

im += op2.im;

re += op2.re;

return *this;

}

main {

c1, c2(1,1);

. . .

c1 +=c2;

}

(19)

Overloading tramite funzioni friend

■ Come detto in precedenza l'ooveloading può essere eseguito anche per mezzo di funzioni non membro, ma definite friend per la classe in esame.

■ Nel caso delle funzioni friend il numero di argomenti coincide con il numero di operandi.

(20)

Overloading tramite funzioni friend: esempio

class Complex { .

. .

friend Complex operator+(Complex op1, Complex op2);

friend Complex operator++(Complex op);

};

Complex operator+(Complex op1, Complex op2) {

Complex tmp;

tmp.re = op1.re + op2.re;

tmp.im = op1.im + op2.im;

return tmp;

} Complex operator++(Complex &op)

{

++op.re;

++op.im;

return *this;

}

(21)

Confronto tra operatori membro e funzioni friend

■ Le funzioni friend offrono in generale maggiore flessibilità. Consideriamo il caso in cui alla classe Complex sia stata definita un'ulteriore funzione membro operator+ che prende come operando un intero:

Complex operator+(float val) {

Complex tmp;

tmp.re = re +val;

tmp.im = im +val;

return tmp;

} class Complex {

. . .

Complex operator +(float val);

};

Main() {

Complex c1, c2;

(22)

Confronto tra operatori membro e funzioni friend

■ È possibile rimediare al problema visto in precedenza in questo modo:

class Complex { .

. .

friend Complex operator +(Complex op, float val);

friend Complex operator +(float val, Complex op);

};

Complex operator+(Complex op, float val) {

Complex tmp;

tmp.re = op.re +val;

tmp.im = op.im +val;

return tmp;

}

Complex operator+(float val, Complex op) {

Complex tmp;

tmp.re = op.re +val;

tmp.im = op.im +val;

Main() {

Complex c1, c2;

c2 = c1 + 100; // OK c1 = 100 + c1; // OK }

(23)

Overloading degli operatori di stream

■ se si vuole permettere il flusso su stream della propria classe , e` necessario ricorrere ad una funzione ordinaria, perche` il primo argomento degli operatori di shift non e` una istanza della classe

class Complex { public:

. . .

private:

float Re, Im;

friend ostream& operator<<(ostream& os,Complex C);

friend istream& operator>>(istream& in,Complex& C);

};

(24)

Overloading degli operatori di stream

ostream& operator<<(ostream& os, Complex op) {

os << op.re;

if (op.im > 0) os << ” + ”;

else if (op.im < 0) o s<< ” - ”;

os << op.im;

return os;

}

istream& operator>>(istream& in, Complex &op) {

Complex tmp;

in >> temp.re;

in >> temp.im;

op = temp;

return in;

}

(25)

Overloading degli operatori di stream

void main() {

Complex c1, c2, c3;

cout << "\n inserisci il primo operando: ";

cin >> c1;

cout << "\n inserisci il secondo operando: ";

cin >> c2;

c3 = c1 + c2;

cout << c3;

cout << "\n";

}

cout << c3; operator<<(cout,c3);

cin >> c1;

operator>>(cin,c3);

Equivale

Equivale

(26)

Un esempio oveloading di operatori: la classe string

■ La libreria standard del C++ mette a disposizione la classe standard string per la gestione delle stringhe.

■ Proprio grazie all'oveloading degli operatori, questa classe rende molto semplice l'uso delle stringhe.

■ Un altro aspetto importante di questa classe è che la lunghezza della stringa non deve essere dichiarata a priori.

■ Ecco l'elenco degli operatori sovraccaricati delle corrispondenti funzioni C della libreria string.h

=

==, <, >, <=, >=, !=

<<, >>

+, +=

(27)

La classe string: esempi d'uso

#include <iostream>

#include <string>

using namespace std;

main() {

string s1(”alfa”), s2(“beta”), s3(“omega”), s4, s5;

char *C_str = “ciao”; // questa è una stringa stile C

s4 = s1; // assegnamento tra stringhe

s4 = s1 + s2 // assegna a s4 il concatenamento di s1 e s2 s4+= s3; // concatena s4 a s3

s4 = s1 + “-” + s2; //

(28)

operatori di concatenamento e assegnazione

■ Usando gli operatori sovraccariricati =, + e += le operazioni di assegnamento e concatenazione diventano immediate:

s4 = s1; // assegnamento tra stringhe

s4 = s1 + s2 // assegna a s4 il concatenamento di s1 e s2 s4+= s3; // concatena s4 a s3

s4 = s1 + “-” + s2; //

s1 = “questa è una stringa chiusa dal carattere nullo”;

s3 = s1 + “pippo”;

s3 = “pippo” + s1; // ERRORE!

s4 = C_str; // si possono usare stringhe classiche.

(29)

Operatori di confronto

■ Anche le operazioni di confronto sono immediate:

if (s1 > s2)

cout<<endl<<”s2 precede s1”;

if (s1 == “s2”)

cout<<endl<<”le stringhe s1 e s2 sono uguali”;

■ Così come le opreazioni di I/O.

cout<<endl<<”introdurre una stringa: ”;

cin>> s4;

cout<<endl<<s5;

}

(30)

Alcune osservazioni

■ Le dimensioni delle stringhe non sono specificate.

■ Gli oggetti di tipo string vengono dimensionati automaticamente per contenere la stringa fornita.

■ Pertanto quando si assegnano o concatenano stringhe le dimensioni della stringa destinazione cresceranno automaticamente per contenere la nuova stringa.

■ Questa gestione dinamica evita tutti gli errori delle gestione delle stringhe del C dovuti al superamento degli array.

Riferimenti

Documenti correlati

nell'edificio dopo la costruzione di quest'ultimo per iniziativa di parte dei condomini, non rientra nella proprietà comune di tutti i condomini, ma appartiene in proprietà a quelli

Era infatti considerato un temibile oppositore, simbolo della lotta di liberazione dei neri.. 46 Quale di questi trittici di personaggi visse durante il «secolo

Tutti i nomi di società e prodotti citati sono marchi registrati delle rispettive aziende.... L’ESPERIENZA AL SERVIZIO DI AGENZIE VIAGGI E TOUR

 if the destructor is freeing the memory pointed to by a pointer p , then make sure that every constructor initializes p to newly allocated. memory or to the NULL pointer

The present paper is divided into three sections, organized as follows: In sec- tion 2, we introduce some basic properties of the Lebesgue and Sobolev spaces with variable exponents

Perché ogni materia può essere composta da materiali

Il sistema operativo, quando c’è bisogno di eseguire un nuovo servizio (e quindi di mandare in esecuzione un nuovo processo) decide di mandarlo in esecuzione sul processore che

Ma per fare una somma occorre accedere ai campi di due oggetti distinti (anche se della stessa classe complex) e questo non può essere fatto con un metodo interno a una