PROGRAMMAZIONE AD OGGETTI 2001-2002

15-1-2002

OPERATORI
OVERLOADING DI OPERATORI
ASSEGNAZIONE E COSTRUTTORI
CLASSI PER I NUMERI COMPLESSI
OVERLOADING DI NEW E DELETE
ALGORITMI DI RICERCA E ORDINAMENTO

 

OPERATORI

In C++ ci sono parecchi operatori ( che si possono raggruppare  in operatori binari +,-,/,*,>>,<<,...) e operatori unari (++,--,...): gli operatori binari sono quelli che operano con due parametri mentre gli operatori unari operano su un solo parametro. Un operatore può essere visto come una funzione: più precisamente un operatore binario è come una funzione che prende due parametri in ingresso, mentre un operatore unario è come una funzione che prende in ingresso un solo parametro. Ad esempio l'operatore "+" è un operatore binario perché ha bisogno di due parametri:

a + b       3 + 5

e può essere visto come una funzione del tipo:

+(x,y)

che prende due valori in ingresso e ne fa la somma ("+" è il nome della funzione).

HOME


OVERLOADING DI OPERATORI

In C++ è possibile ridefinire il significato degli operatori (overloading) all'interno delle classi, con delle funzioni del tipo:

operator#(...);
/* operatore unario "#" (++,--,...) */

operator#(...,...);
/* operatore binario "#" (+,-,*,...) */

La funzione operatore può ritornare un valore che dipende dall'uso che si vuol fare dell'operatore: tipicamente il valore di return è un oggetto costante (viene ritornato cioè il valore contenuto negli attributi, come per la somma di due variabili int viene ritornato un valore costante), un puntatore ad un oggetto costante oppure, nel caso di operatori di confronto (<,>,==,...) il valore ritornato può essere di tipo booleano o intero (TRUE, FALSE, 0, 1). Se un operatore ritorna un oggetto costante, allora il valore ritornato può essere utilizzato all'interno di un espressione (ad esempio una somma tra tre oggetti). Nel caso di operatori binari, definiti come metodo della classe, il primo parametro passato all'operatore è l'oggetto che viene scritto a sinistra (che è anche quello che chiama l'operatore, quindi l'oggetto puntato da "this"), e quindi nella dichiarazione dell'operatore è sufficiente specificare solo il parametro di destra; per operatori binari definiti invece come friend è necessario specificare nella dichiarazione entrambi i parametri.Ecco un esempio:

Class X
{

int valore;

public:

X (int val): valore(val){}
friend const X operator-(X sx, X dx);

};

...

const X operator-(X sx, X dx)
{

return X(sx.valore-dx.valore); // ritorna un oggetto costante di classe X

}

...

X a(5), b(2), c(1);

X d = a - b - c;
/* l'espressione ritorna un oggetto costante che ha valore = 2. d viene inizializzato con il costruttore di copia (quello che fa la copia fisica della memoria)*/

X e = d - c;
/* il primo argomento dell'operatore è quello a sinistra (cioè d) */

Quindi l'operatore "-" è stato ridefinito per essere utilizzato con gli oggetti della classe X. La definizione di operatori per le classi è molto utile per semplificare la sintassi e per migliorare la comprensibilità del codice. E' possibile ridefinire due operatori con lo stesso nome che però differiscono per il tipo di parametri: ad esempio per l'operatore dell'esempio precedente;

const X operator-(X sx, int dx)
{

return X(sx.valore-dx);

}

...

X f = a - 2;
/* f.valore = 3 */

Così è stato effettuato l'overloading dell'operatore "-".

  vedi esempi:  
    OperatorOverloadingSyntax.cpp  
    Fint.cpp  
    FalseAdd.cpp  
    OverloadindUnaryOperators.cpp  
    IostreamOperatorOverloading.cpp  
    Integer.h  
    Integer.cpp  

 

HOME


ASSEGNAZIONE E COSTRUTTORI

E' facile confondere la sintassi dei costruttori di copia con la sintassi di un operatore di assegnazione ridefinito (l'operatore "="). Ecco degli esempi:

X x(3);

In questo caso (la classe X è quella dell'esempio precedente) l'oggetto x viene istanziato chiamando il costruttore della classe che inizializza x.valore a 3.

X y = x;

Stavolta l'oggetto y viene creato con il costruttore di copia. Poiché la classe X non ha definito un costruttore di copia, il compilatore agisce di default facendo una copia fisica della memoria: in questo modo y.valore viene inizializzato con il valore contenuto in x.valore, cioè 3.

X z(5);

y = z;

Qui non viene chiamato il costruttore di copia poiché y è già stato inizializzato, ma viene utilizzato un operatore di assegnazione "=" per le classi (che sarà stato definito nella classe). L'operatore sarà di questo tipo:

X& operator=(X&);

E' un operatore binario (è definito con un solo parametro perchè è metodo di una classe, e quindi il parametro di sinistra è l'oggetto puntato da this), che nel caso precedente viene chiamato dall'oggetto y, e che prende in ingresso l'indirizzo della variabile z. All'interno dell'operatore il valore ci saranno delle istruzioni che assegnano a y.valore il valore di z.valore, cioè 5.

Scrivendo:

y = 7;

si effettua una chiamata ad un altro operatore di assegnazione (eventualmente definito all'interno della classe X) che differisce da quello precedente solo per i parametri di ingresso:

X& operator=(int);

questo operatore unario non farà altro che assegnare a y.valore l'intero (o la variabile di tipo int) che viene preso come parametro, cioè 7.

HOME


CLASSI PER I NUMERI COMPLESSI

In C++ non esistono librerie in grado di lavorare con i numeri complessi. Per questo può essere molto utile definire una classe che risolva questo problema e ridefinire, all'interno della classe, degli operatori che permettano di svolgere le normali operazioni algebriche tra numeri complessi, e magari anche la conversione dalla rappresentazione cartesiana a quella polare con modulo e fase. In questo modo è possibile lavorare con i numeri complessi con la stessa facilità con cui si usano gli interi.

HOME


OVERLOADING DI NEW E DELETE

Per allocare dello spazio in memoria in modo dinamico si utilizza l'operatore dei new, che ritorna un puntatore alla zone di memoria allocata. Nel caso si voglia ridefinire un operatore di new per una classe, questo si può fare come per gli operatori visti in precedenza. Il compilatore, nel caso di una new di un oggetto, alloca la memoria necessaria e chiama automaticamente il costruttore della classe. Un operatore new ridefinito deve fare gli stessi passi; al limite può fare qualcosa di più (come stampare una stringa ecc...). L'operatore ridefinito ritornerà un puntatore all'oggetto allocato. Per quanto riguarda la delete non c'è molta differenza, dato che il compilatore nel caso di delete di un oggetto, oltre a liberare la memoria chiama anche il distruttore. Quindi la ridefinizione degli operatori di new e di delete non è molto utile dato che la definizione standard è già completa anche per le classi.

HOME


ALGORITMI DI RICERCA E ORDINAMENTO

Per gestire un insieme di dati (database) in modo efficiente, è necessario l'uso di un algoritmo adatto al problema. Ad esempio la ricerca di un elemento all'interno di un vettore può essere più o meno veloce a seconda del metodo che si usa per cercarlo o a seconda della struttura del vettore (che può essere ordinato o meno).

E' molto semplice utilizzare una lista per ordinare un insieme di dati e il risultato in termini di tempo è molto buono. Il problema della lista é la ricerca di un elemento, che può essere fatta solo in modo sequenziale: gli elementi devono essere scorsi uno alla volta in successione dall'inizio alla fine della lista. Nel caso peggiore la lista deve essere scorsa fino ala fine.

La ricerca in un vettore può essere fatta in vari modi:

In modo sequenziale (come per la lista)

Ricerca binaria (tipo vocabolario): può essere effettuata solo su un vettore ordinato. Si guarda l'elemento in mezzo al vettore e poi si divide una della due metà ecc... fino a quando non si individua l'elemento desiderato (se questo è presente).

E' molto più efficiente la ricerca binaria in un vettore ordinato della ricerca in una lista. Ma l'ordinamento di un vettore è un'operazione in genere molto più costosa dell'ordinamento di una lista.

E' possibile creare una struttura dati dizionario utilizzando una tabella (vettore ordinato) che permette di scandire una lista ordinata (o più liste ordinate) in un tempo breve. E' una sorta di rubrica telefonica: per trovare un cognome che inizia con la "C" si può ridurre il numero di cognomi da leggere aprendo la rubrica alla pagina con la lettera "C".

Per simulare una rubrica si può creare un vettore di dimensione 21 i cui elementi (che corrispondono alle lettere dell'alfabeto) sono puntatori ad altrettante liste ordinate che contengono tutti i nomi con la stessa iniziale. Così, una volta trovata la lista interessata si esegue la ricerca su un numero di nomi molto più ristretto dell'insieme totale dei nomi presenti nella rubrica.

HOME