class NumeriModulari { int n, m; int normalizza (int N) const { int n_normale; if (N >= 0) n_normale = N % m; else n_normale = m + (N % m); return n_normale; } public: NumeriModulari (int modulo) { m = modulo; n = 0; } void assegna (int N) { n = normalizza(N); } int valore () const { return n; } }; |
NumeriModulari
è stata
implementata utilizzando due interi: uno corrispondente al numero vero e
proprio e l'altro facente riferimento al suo periodo. La funzione
normalizza
è utilizzata per fare in modo che n
sia sempre un
numero compreso tra 0
e m
; è stata dichiarata private
perché essa non deve essere acceduta dal di fuori della classe: si tratta
cioè di una funzione utilizzata per i meccanismi interni di rappresentazione
dei dati della classe. La funzione valore
è una funzione pubblica di
accesso, la quale restituisce un intero corrispondente al valore di n
;
la funzione assegna, infine, è utilizzata per modificare n
, il quale
viene tuttavia preventivamente normalizzato.
Si supponga di dovere sommare due numeri modulari; il seguente codice tornerebbe un messaggio di errore 9.3:
NumeriModulari a(12), b(12); a + b; |
+
)
su due numeri modulari, non dobbiamo fare altro che sovrapporre tale
operatore, introducendo la seguente funzione membro pubblica:
int operator+ (const NumeriModulari& b) { if (this->m != b.m) // se i due numeri da sommare non hanno return 0; // lo stesso modulo, viene tornato 0 int somma = this->n + b.n; return normalizza(somma); } |
*
this.
Quando vorremo sovrapporre un operatore unario dovremo specificare la
funzione di sovrapposizione dell'operatore senza argomenti. Ad esempio
una possibile sovrapposizione dell'operatore di incremento unitario
(++
) è contenuta nel seguente esempio:
// ex9_5_1.cpp #include <iostream.h> class NumeriModulari { int n, m; int normalizza (int N) const { int n_normale; if (N >= 0) n_normale = N % m; else n_normale = m + (N % m); return n_normale; } public: NumeriModulari (int modulo) { m = modulo; n = 0; } void assegna (int N) { n = normalizza(N); } int valore () const { return n; } int operator+ (const NumeriModulari& b) { if (this->m != b.m) // se i due numeri da sommare non hanno return 0; // lo stesso modulo, viene tornato 0 int somma = this->n + b.n; return normalizza(somma); } int operator++ (int) { n = normalizza (n + 1); // this-> e` sottointeso return n; } }; void main() { NumeriModulari a(12), b(12); a.assegna (-11); b.assegna(7); cout << a.valore() << "\t" << b.valore() << "\n"; int somma = a + b; int c = a++; cout << "somma: " << somma << "\ta++ = " << c << "\n"; }
output:
1 | 7 | |
somma: 8 | a++ = 2 |
Si faccia bene attenzione che, per convenzione, gli operatori di incremento e
decremento unitario devono essere dichiarati come aventi un argomento intero
fittizio, il quale non ha alcun valore reale. Ovviamente, gli operatori
che non vengono definiti non possono essere desunti da quelli esistenti: dopo
avere definito gli operatori di somma e incremento unitario, non è possibile
utilizzare anche gli altri operatori aritmetici ``per analogia''. Se vogliamo
avere una classe completa per i numeri modulari dovremmo quindi sovrapporre
tutti gli operatori aritmetici; essi non sono tuttavia i soli a potere essere
sovrapposti: tutti gli operatori sono, in teoria, sovrapponibili. Comunque è
fortemente sconsigliato utilizzare la sovrapposizione per operatori come
->
, .
, ::
e gli altri comunemente utilizzati per scopi
particolati del linguaggio. Spesso è invece utile sovrapporre gli operatori
di confronto; nel nostro caso ad esempio potremmo aggiungere le seguenti due
funzioni membro:
bool operator< (const NumeriModulari& b) { if (n < b.n) return true; return false; } bool operator<= (const NumeriModulari& b) { if (n <= b.n) return true; return false; } |
<
o
<=
, dato che gli altri operatori di confronto (==
, >
,
>=
, !=
) non li abbiamo definiti. Si veda il seguente esempio:
// ex9_5_2.cpp #include <iostream.h> class NumeriModulari { \* definizione di NumeriModulari *\ }; void scambia (NumeriModulari& a, NumeriModulari& b) { NumeriModulari temp = a; a = b; b = temp; } void ordina (NumeriModulari* nm, int n) { for (int i = 0; i < n; i++) for (int j = i; j < n; j++) if (nm[i] < nm[j]) scambia (nm[i], nm[j]); } void main() { int n; // numeri da ordinare int m; // modulo dei numeri da ordinare cout << "quanti numeri? "; cin >> n; cout << "modulo? "; cin >> m; NumeriModulari* nm = new NumeriModulari[n](m); for (int i = 0; i < n; i++) { int numero; cout << "? "; cin >> numero; nm[i].assegna(numero); } ordina(nm, n); for (int i = 0; i < n; i++) cout << nm[i].valore() << "\n"; }
esempio di output:
quanti numeri? 5
modulo? 5
? 3
? 7
? -2
? 0
? 101
3
3
2
1
0
Come si vede, l'utilizzo di numeri modulari tramite la classe
NumeriModulari
è semplice ed intuitivo, come se si avesse a che fare
con un tipo di dati primitivo; i passaggi che mancano per completare i nostri
tipi di dati sono davvero pochi e li esamineremo nel capitolo successivo.
Vediamo invece subito un tipo di dati che potrà esserci utile, e lo
definiamo dunque in due files separati (intestazione e definizione): il tipo
di dati ArrayS
, ove la S
finale sta per ``statico'', ad indicare
che non sarà possibile modificare il numero degli elementi dell'array.
Implementeremo l'array con un array dinamico di double; dov'è
allora il vantaggio? Nel fatto che possiamo aggiungere all'array semplice un
gran numero di funzionalità accessorie, le quali risultano spesso utili
nella scrittura di un programma. Ad esempio sarà possibile ottenere con una
funzione membro di accesso il numero di elementi dell'array, il che ci evita
ad esempio di passare nelle funzioni gli array sempre insieme al numero degli
elementi di esso; scriveremo una funzione membro che ci ordini l'array e una
che ci restituisca la media aritmetica degli elementi di esso. Per potere
accedere ai singoli elementi dovremo, come è ovvio, sovrapporre l'operatore
parentesi quadre ([ ]
); aggiungeremo infine la possibilità di sommare
e moltiplicare tra di loro due array (elemento per elemento), tramite gli
operatori +=
e *=
.
// arrays.h // implementa un array di double tramite // l'utilizzo di un array dinamico #include <iostream.h> class ArrayS { int n; // numero di elementi double* array; // array public: ArrayS (int nElem); double& operator[] (int i); double media () const; void ordina (); void stampa (int nColonne = 0); ArrayS& operator+= (const ArrayS& b); // somma tra vettori ArrayS& operator*= (const ArrayS& b); // prodotto tra vettori ArrayS& operator*= (double x); // prodotto vettore-reale ~ArrayS (); }; // arrays.cpp #include "arrays.h" ArrayS::ArrayS (int nElem) { if (nElem >= 1) n = nElem; // se nElem e` negativo o nullo viene else n = 1; // creato un vettore di 1 elemento array = new double[n]; for (int i = 0; i < n; i++) // azzeriamo il vettore array[i] = 0; } double& ArrayS::operator[] (int i) { if (i > 0 && i < n) return array[i]; else // se l'indice e` errato viene tornato il primo return array[0]; // elemento del vettore } double ArrayS::media () const { double somma = 0; for (int i = 0; i < n; i++) somma += array[i]; return somma / double(n); } void ArrayS::ordina () { for (int i = 0; i < n; i++) for (int j = i; j < n; j++) if (array[i] < array[j]) { double temp = array[i]; array[i] = array[j]; array[j] = array[i]; } } void ArrayS::stampa (int nColonne) { int nc = 0; if (nColonne == 0) nc = n+1; else nc = nColonne; for (int i = 0; i < n; i++) if (i % nc == nc - 1) cout << array[i] << "\n"; else cout << array[i] << "\t"; } ArrayS& ArrayS::operator+= (const ArrayS& b) { int min = n; if (b.n < n) min = b.n; for (int i = 0; i < min; i++) array[i] += b.array[i]; return *this; } ArrayS& ArrayS::operator*= (const ArrayS& b) { int min = n; if (b.n < n) min = b.n; for (int i = 0; i < min; i++) array[i] *= b.array[i]; return *this; } ArrayS& ArrayS::operator*= (double x) { for (int i = 0; i < n; i++) array[i] *= x; return *this; } ArrayS::~ArrayS () { delete[] array; }
Un programma di prova per la classe ArrayS
è il seguente, il quale
genera due vettori ad elementi casuali ed effettua alcune operazioni su di
essi:
// ex9_5_3.cpp #include "arrays.h" #include <time.h> #include <stdlib.h> void main() { int i; ArrayS x(6), y(4); srand( time (NULL) ); for (i = 0; i < 6; i++) x[i] = rand() % 20 + 1; for (i = 0; i < 4; i++) y[i] = rand() % 20 + 1; cout << "i vettori sono:\n"; x.stampa(); cout << "\n"; y.stampa(); cout << "\n"; cout << "\nsommiamo il secondo al primo\n"; x += y; cout << "riduciamo ad un quarto gli elementi del secondo\n"; y *= .25; x.stampa(); cout << "\n"; y.stampa(); cout << "\n"; cout << "moltiplichiamo il primo per il secondo\n"; x *= y; x.stampa(); cout << "\n"; cout << "ordina i vettori:\n"; x.ordina(); y.ordina(); x.stampa(); cout << "\n"; y.stampa(); cout << "\n"; }
esempio di output:
i vettori sono:
17 | 18 | 5 | 8 | 18 | 18 | |
11 | 9 | 11 | 4 |
28 | 27 | 16 | 12 | 18 | 18 | |
2.75 | 2.25 | 2.75 | 1 |
77 | 60.75 | 44 | 12 | 18 | 18 |
77 | 60.75 | 44 | 18 | 18 | 12 | |
2.75 | 2.75 | 2.25 | 1 |
In definitiva, non c'è un limite agli operatori che è possibile
sovrapporre; si cerchi tuttavia di non eccedere: la sovrapposizione degli
operatori è stata utilizzata in C++ per rendere più intuitivo ed immediato
l'utilizzo di una classe, se si sovrappongono gli operatori senza che ci sia
il reale bisogno di tali operatori, allora è tutto inutile. Per esempio, è
poco intuitivo che oggetti di tipo Impiegato
possano essere sommati tra
di loro, o moltiplicati; si lasci allora perdere la sovrapposizione di tali
operati, al più usando delle funzione membro con nomi scelti ad hoc.
NumeriModulari
aggiungendo la
sovrapposizione degli operatori differenza (-
), divisione (\
),
maggiore (>
), maggiore o uguale(>=
), uguale (==
) e
diverso (!=
);
ArrayS
aggiungendo la sovrapposizione degli
operatori di differenza (tra gli elementi di due vettori) e divisione (tra
gli elementi di due vettori e tra un vettore e uno scalare); inoltre si
sovrappongano gli operatori di confronto: uguale (==
) e diverso
(!=
), i quali tornino true se tutti gli elementi di un
vettore sono uguali (diversi) da quelli di un altro vettore, a meno di
ArrayS
di numeri modulari casuali e lo si
ordini