next up previous contents index
Next: Costruttore di copia e Up: Introduzione alle classi Previous: L'oggetto this e i   Indice   Indice analitico

Sovrapposizione degli operatori

Un oggetto classe ha la medesima dignità in C++ degli oggetti dei tipi primitivi: esso può essere allocato come variabile automatica o dinamica, passato ad una funzione (per riferimento o puntatore, se l'oggetto in questione occupa un gran quantitativo di memoria), derivato a sua volta. Tuttavia fino ad adesso non abbiamo definito come applicare i normali operatori del C++ agli oggetti classe; il compilatore sa esattamente come gestire gli operatori sugli oggetti primitivi, ma non può azzardare operazioni su oggetti che noi abbiamo definito: è compito del programmatore dunque fornire delle ``regole'' per il funzionamento degli operatori. È necessario cioè sovrapporre gli operatori del C++ in maniera tale che essi possano essere applicati anche ad oggetti classe. Ad esempio se abbiamo la classe di numeri modulari 9.2dobbiamo fornire le regole per le operazioni aritmetiche da effettuare su tali numeri.

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; }
};
Prima di tutto qualche osservazione; la classe 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;
Se vogliamo che sia possibile effettuare l'operazione somma (+) 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);
  }
Gli operatori binari hanno, per definizione, due argomenti; tuttavia nella nostra funzione compare un solo argomento, il quale viene passato per riferimento costante: si tratta del secondo argomento dell'operatore somma, il primo argomento è *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; }
In questa maniera possiamo, ad esempio, scrivere una routine di ordinamento di numeri modulari; siamo tuttavia costretti a sfruttare l'operatore < 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    

sommiamo il secondo al primo
riduciamo ad un quarto gli elementi del secondo
 28 27 16 12 18 18
 2.75 2.25 2.75 1    

moltiplichiamo il primo per il secondo
 77 60.75 44 12 18 18

ordina i vettori:
 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.

ex-2
si completi la classe NumeriModulari aggiungendo la sovrapposizione degli operatori differenza (-), divisione (\), maggiore (>), maggiore o uguale(>=), uguale (==) e diverso (!=);
ex-3
si completi la classe 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 $10^{-3}$;
ex-4
si costruisca un ArrayS di numeri modulari casuali e lo si ordini


next up previous contents index
Next: Costruttore di copia e Up: Introduzione alle classi Previous: L'oggetto this e i   Indice   Indice analitico
Claudio Cicconetti
2000-09-06