next up previous contents index
Next: Vettore di bit Up: Alcuni tipi di dati Previous: Coda   Indice   Indice analitico

Tabella

Il tipo tabella corrisponde essenzialmente ad un array bidimensionale, cioè ad una matrice. Mostreremo in questa sezione un esempio di implementazione di una classe template generica Matrice, che utilizzeremo successivamente per disegnare la classe specifica MatriceReale. Abbiamo spesso lavorato su matrici, per cui non ci dilungheremo su dettagli già chiariti riguardo il loro utilizzo.

// tabella.h
// classe template rappresentante una tabella

#ifndef _TABELLA_H_
#define _TABELLA_H_

template<class T>
class Tabella {
public:
  Tabella (unsigned int r, unsigned int c, bool p = true);
  Tabella (const Tabella&);
  const Tabella& operator= (const Tabella&);
  ~Tabella ();

  bool possesso () const { return possiede; }
  unsigned int nrighe () const { return righe; }
  unsigned int ncolonne () const { return colonne; }
  Tabella& ridimensiona (unsigned int r, unsigned int c);
  Tabella& inserisciRiga (unsigned int r);
  Tabella& inserisciColonna (unsigned int c);
  Tabella& eliminaRiga (unsigned int r);
  Tabella& eliminaColonna (unsigned int c);
  T* elemento (unsigned int i, unsigned int j) const {
    if (i > righe || j > colonne) return 0;
    return tabella[indice(i, j)]; }
  bool modifica (unsigned int i, unsigned int j, T* oggetto) {
    if (i > righe || j > colonne) return false;
    if (possiede == true) delete tabella[indice(i, j)];
    tabella[indice(i, j)] = oggetto; return true; }
private:
  T** tabella;
  unsigned int righe;
  unsigned int colonne;
  bool possiede;
  // funzioni interne per facilitare l'accesso alla tabella
  // restituisce l'indice (i,j) della tabella *this
  unsigned int indice (unsigned int i, unsigned int j) const {
    return ((i - 1) * colonne) + j - 1; }
  // restituisce l'indice (i,j) di una tabella
  // avente c colonne
  unsigned int indice (unsigned int i, unsigned int j,
                       unsigned int c) const {
    return ((i - 1) * c) + j - 1; }
};

template<class T>
Tabella<T>::Tabella (unsigned int r, unsigned int c, bool p) {
  righe = r; colonne = c;
  possiede = p;
  // crea l'array che conterra` la tabella ...
  tabella = new T*[righe * colonne];
  // ... e lo azzera
  for (int i = 0; i < righe * colonne; i++)
    tabella[i] = 0;
}

template<class T>
Tabella<T>::Tabella (const Tabella& t) {
  righe = t.righe;
  colonne = t.colonne;
  // ATTENZIONE le copie di oggetti esistenti NON
  // possiedono il loro contenuto di default
  tabella = new T*[righe * colonne];
  possiede = false;
  for (int i = 0; i < righe * colonne; i++)
    tabella[i] = t.tabella[i];
}

template<class T>
const Tabella<T>& Tabella<T>::operator= (const Tabella& t) {
  if (&t != this) {
    // ATTENZIONE le copie di oggetti esisteni NON
    // possiedono il loro contenuto di default
    possiede = false;
    if (possiede == true) {
      for (int i = 0; i < righe * colonne; i++)
        delete tabella[i];
    }
    if ((righe * colonne) != (t.righe * t.colonne)) {
      if (righe != t.righe) {  // colonne != t.colonne
        righe = t.righe;
        colonne = t.colonne;
      }
      // riallochiamo l'array
      delete[] tabella;
      righe = t.righe;
      colonne = t.colonne;
      tabella = new T*[righe * colonne];
    }
    for (int i = 0; i < righe * colonne; i++)
      tabella[i] = t.tabella[i];
  }
  return *this;
}

template<class T>
Tabella<T>::~Tabella () {
  if (possiede == true)
    for (int i = 0; i < righe * colonne; i++)
      delete tabella[i];
  delete[] tabella;
}

template<class T>
Tabella<T>& Tabella<T>::ridimensiona (unsigned int r,
                                      unsigned int c) {
  if (r != righe || c != colonne) {
    // allochiamo memoria per la nuova tabella
    T** nuova_tabella = new T*[r * c];
    for (int i = 1; i <= r; i++)
      for (int j = 1; j <= c; j++)
        if (i <= r && j <= c)
          if (i <= righe && j <= colonne)
            nuova_tabella[indice(i, j, c)] = tabella[indice(i,j)];
          else
            nuova_tabella[indice(i, j, c)] = 0;
    for (int i = 1; i <= r; i++)
      for (int j = 1; j <= c; j++)
        if (i > r || j > c)
          if (possiede == true)
            delete tabella[indice(i, j)];
          else
            tabella[indice(i, j)] = 0;
    delete[] tabella;
    tabella = nuova_tabella;
    righe = r;
    colonne = c;
  }
  return *this;
}

template<class T>
Tabella<T>& Tabella<T>::inserisciRiga (unsigned int r) {
  if (r > 0) {
    if (r > righe) r = righe + 1;
    T** nuova_tabella = new T*[(righe+1) * colonne];
    for (int i = 1; i <= r-1; i++)
      for (int j = 1; j <= colonne; j++)
        nuova_tabella[indice(i, j)] = tabella[indice(i, j)];
    for (int j = 1; j <= colonne; j++)
      nuova_tabella[indice(r, j)] = 0;
    for (int i = r+1; i <= righe+1; i++)
      for (int j = 1; j <= colonne; j++)
        nuova_tabella[indice(i, j)] = tabella[indice(i-1, j)];
    delete[] tabella;
    tabella = nuova_tabella;
    righe++;
  }
  return *this;
}

template<class T>
Tabella<T>& Tabella<T>::inserisciColonna (unsigned int c) {
  if (c > 0) {
    if (c > colonne) c = colonne +1;
    T** nuova_tabella = new T*[righe * (colonne+1)];
    for (int i = 1; i <= righe; i++)
      for (int j = 1; j <= c-1; j++)
        nuova_tabella[indice(i, j, colonne+1)] =
          tabella[indice(i, j)];
    for (int i = 1; i <= righe; i++)
      nuova_tabella[indice(i, c, colonne+1)] = 0;
    for (int i = 1; i <= righe; i++)
      for (int j = c+1; j <= colonne+1; j++)
        nuova_tabella[indice(i, j, colonne+1)] =
          tabella[indice(i, j-1)];
    delete[] tabella;
    tabella = nuova_tabella;
    colonne++;
  }
  return *this;
}

template<class T>
Tabella<T>& Tabella<T>::eliminaRiga (unsigned int r) {
  if (r > 0 && r <= righe) {
    T** nuova_tabella = new T*[(righe - 1) * colonne];
    for (int i = 1; i <= righe-1; i++)
      for (int j = 1; j <= colonne; j++)
        if (i <= r-1)
          nuova_tabella[indice(i, j)] = tabella[indice(i, j)];
        else
          nuova_tabella[indice(i, j)] = tabella[indice(i+1, j)];
    if (possiede == true)
      for (int j = 1; j <= colonne; j++)
        delete tabella[indice(r, j)];
    righe--;
    delete[] tabella;
    tabella = nuova_tabella;
  }
  return *this;
}

template<class T>
Tabella<T>& Tabella<T>::eliminaColonna (unsigned int c) {
  if (c > 0 && c <= colonne) {
    T** nuova_tabella = new T*[righe * (colonne-1)];
    for (int i = 1; i <= righe; i++)
      for (int j = 1; j <= colonne-1; j++)
        if (j <= c-1)
          nuova_tabella[indice(i, j, colonne-1)] =
            tabella[indice(i, j)];
        else
          nuova_tabella[indice(i, j, colonne-1)] =
            tabella[indice(i, j+1)];
    if (possiede == true)
      for (int i = 1; i <= righe; i++)
        delete tabella[indice(i, c)];
    colonne--;
    delete[] tabella;
    tabella = nuova_tabella;
  }
  return *this;
}

#endif // _TABELLA_H_

Le funzioni più interessanti sono quelle riguardanti l'inserimento o l'eliminazione di una riga o una colonna; il procedimento seguito è sempre il medesimo:

Si noti che la funzione indice(i,j) suppone che il numero di colonne sia il medesimo della tabella di *this, il che non è vero nel caso di eliminazione o inserimento di riga; all'interno di tali procedure abbiamo utilizzato dunque la funzione membro privata indice(i,j,c), alla quale passiamo come terzo argomento il numero di colonne della tabella della quale vogliamo ottenere un indice. Un semplice programma che testi la classe Tabella è il seguente:


// ex11_4_1.cpp

#include <iostream.h>
#include "stringa.h"
#include "tabella.h"

ostream& operator<< (ostream& os, Tabella<Stringa>& t) {
  for (int i = 1; i <= t.nrighe(); i++) {
    int j;
    for (j = 1; j < t.ncolonne(); j++)
      if (t.elemento(i, j) != 0)
        os << *t.elemento(i, j) << "\t";
      else
        os << "-\t";
    if (t.elemento(i, j) != 0)
      os << *t.elemento(i, j);
    else
      os << "-\t";
    cout << "\n";
  }
  return os;
}

void main() {
  const int r = 3;
  const int c = 4;
  Tabella<Stringa> t(r, c);
  t.modifica(1,1, new Stringa("(1,1)"));
  t.modifica(1,c, new Stringa("adx"));
  t.modifica(r,1, new Stringa("bsx"));
  t.modifica(r,c, new Stringa("ultimo"));
  cout << t << "\n";
  t.ridimensiona(2*r, c);
  t.modifica(2,1, new Stringa("riga2"));
  t.modifica(3,1, new Stringa("riga3"));
  t.modifica(1,2, new Stringa("col2"));
  t.modifica(1,3, new Stringa("col3"));
  cout << t << "\n";  
  t.inserisciRiga(3);
  t.inserisciColonna(3);
  cout << t << "\n";
  t.eliminaRiga(3);
  t.eliminaColonna(3);
  cout << t << "\n";
}

output:


(1,1)   -       -       adx
-       -       -       -
bsx     -       -       ultimo

(1,1)   col2    col3    adx
riga2   -       -       -
riga3   -       -       ultimo
-       -       -       -
-       -       -       -
-       -       -       -

(1,1)   col2    -       col3    adx
riga2   -       -       -       -
-       -       -       -       -
riga3   -       -       -       ultimo
-       -       -       -       -
-       -       -       -       -
-       -       -       -       -

(1,1)   col2    col3    adx
riga2   -       -       -
riga3   -       -       ultimo
-       -       -       -
-       -       -       -
-       -       -       -

Proviamo ora a programmare una classe che rappresenti una matrice di numeri reali, sfruttando la classe template Tabella che abbiamo appena completato. Il file di intestazione è il seguente:


// matrice.h
// implementa una matrice reale utilizzando
// la classe template Tabella

#ifndef _MATRICE_H_
#define _MATRICE_H_

#include <iostream.h>
#include "tabella.h"

class Matrice {
public:
  // costruttori, operatore=, distruttore
  Matrice (unsigned int r, unsigned int c,
           // valore iniziale elementi sulla diagonale
           double elem_diag = 0.0,
           // valore iniziale elementi non diagonali
           double elem_nondiag = 0.0);
  Matrice (const Matrice&);
  const Matrice& operator= (const Matrice&);
  ~Matrice ();

  // ridimensionamento matrice
  Matrice& ridimensiona (unsigned int r, unsigned int c) {
    matrice->ridimensiona (r, c); return *this; }
  Matrice& inserisciRiga (unsigned int r) {
    matrice->inserisciRiga (r); return *this; }
  Matrice& inserisciColonna (unsigned int c) {
    matrice->inserisciColonna (c); return *this; }
  Matrice& eliminaRiga (unsigned int r) {
    matrice->eliminaRiga (r); return *this; }
  Matrice& eliminaColonna (unsigned int c) {
    matrice->eliminaColonna (c); return *this; }
  
  // funzioni di accesso
  unsigned nrighe() const { return matrice->nrighe(); }
  unsigned ncolonne() const { return matrice->ncolonne(); }
  double elemento (unsigned int i, unsigned int j) const {
    if (i > 0 && i <= matrice->nrighe() &&
        j > 0 && j <= matrice->ncolonne())
      if (matrice->elemento(i,j) != 0)
        return *matrice->elemento (i, j); return 0; }
  bool modifica (unsigned int i, unsigned int j, double x) {
    if (i > 0 && i <= matrice->nrighe() &&
        j > 0 && j <= matrice->ncolonne()) {
      matrice->modifica(i, j, new double(x)); return true; }
    return false; }

  // funzioni algebriche sulle matrici
  Matrice operator+ (const Matrice& m) const;
  Matrice operator- (const Matrice& m) const;
  Matrice operator- () const;
  Matrice operator* (const Matrice& m) const;
  Matrice operator* (double x) const;
private:
  Tabella<double>* matrice;
};

ostream& operator<< (ostream& os, const Matrice& m);

#endif // _MATRICE_H_

Mentre le definizioni sono contenute nel file Matrice.cpp:


// matrice.cpp
// intestazione della classe in matrice.h

#include "matrice.h"
Matrice::Matrice (unsigned int r, unsigned int c,
                  double elem_diag, double elem_nondiag) {
  matrice = new Tabella<double>(r, c, true);
  for (int i = 1; i <= r; i++)
    for (int j = 1; j <= c; j++)
      if (i == j)
        matrice->modifica (i, j, new double(elem_diag));
      else
        matrice->modifica (i, j, new double(elem_nondiag));
}

Matrice::Matrice (const Matrice& m) {
  matrice = new Tabella<double>(*m.matrice);
}

const Matrice& Matrice::operator= (const Matrice& m) {
  if (&m != this) {
    delete matrice;
    matrice = new Tabella<double>(*m.matrice);
  }
  return *this;
}

Matrice::~Matrice () {
  delete matrice;
}

Matrice Matrice::operator+ (const Matrice& m) const {
  Matrice tmp(nrighe(), ncolonne());
  if (nrighe() == m.nrighe() && ncolonne() == m.ncolonne()) {
    for (int i = 1; i <= nrighe(); i++)
      for (int j = 1; j <= ncolonne(); j++)
        tmp.modifica (i, j, elemento(i ,j) + m.elemento(i ,j));
  }
  return tmp;
}

Matrice Matrice::operator- (const Matrice& m) const {
  Matrice tmp(nrighe(), ncolonne());
  if (nrighe() == m.nrighe() && ncolonne() == m.ncolonne()) {
    for (int i = 1; i <= nrighe(); i++)
      for (int j = 1; j <= ncolonne(); j++)
        tmp.modifica (i, j, elemento(i ,j) - m.elemento(i ,j));
  }
  return tmp;
}

Matrice Matrice::operator- () const {
  Matrice tmp(nrighe(), ncolonne());
  for (int i = 1; i <= nrighe(); i++)
    for (int j = 1; j <= ncolonne(); j++)
      tmp.modifica (i, j, -elemento(i ,j) );
  return tmp;
}

Matrice Matrice::operator* (double x) const {
  Matrice tmp(nrighe(), ncolonne());
  for (int i = 1; i <= nrighe(); i++)
    for (int j = 1; j <= ncolonne(); j++)
      tmp.modifica (i, j, elemento(i ,j) * x);
  return tmp;
}

Matrice Matrice::operator* (const Matrice& m) const {
  Matrice tmp(nrighe(), m.ncolonne());
  if (ncolonne() == m.nrighe()) {
    for (int i = 1; i <= nrighe(); i++)
      for (int j = 1; j <= ncolonne(); j++)
        tmp.modifica (i, j, elemento(i ,j) + m.elemento(i ,j));
  }
  return tmp;
}

ostream& operator<< (ostream& os, const Matrice& m) {
  for (int i = 1; i <= m.nrighe(); i++) {
    for (int j = 1; j <= m.ncolonne(); j++)
      if (j == m.ncolonne())
        os << m.elemento(i, j);
      else
        os << m.elemento(i, j) << "\t";
    os << "\n";
  }
  return os;
}

L'utilizzo di una classe del genere all'interno di un programma matematico-scientifico non è certo vantaggioso, in quanto risulta piuttosto macchinoso dovere allocare un array di puntatori a reali, piuttosto che direttamente un array di reali; tuttavia è utile mostrare come si possa sfruttare un contenitore di tipo Tabella per scopi particolari. Un esempio di utilizzo di Matrice è:


// ex11_4_2.cpp

#include "matrice.h"

void main() {
  Matrice m(3, 4, 1, 0);
  for (int i = 1; i <= m.nrighe(); i++)
    for (int j = 1; j <= m.ncolonne(); j++)
      m.modifica (i, j, double((i-1) * m.ncolonne() + j));
  cout << m << "\n";
  m.inserisciRiga(2);
  m.inserisciColonna(3);
  cout << m << "\n";
  m.eliminaRiga(1);
  m.eliminaColonna(1);
  cout << m << "\n";
  Matrice a(3, 4, 1, 0);
  cout << m+a << "\n";
  cout << m-a << "\n";
  cout << -m << "\n";
  cout << m * 3 << "\n";
}

output:


1       2       3       4
5       6       7       8
9       10      11      12

1       2       0       3       4
0       0       0       0       0
5       6       0       7       8
9       10      0       11      12

0       0       0       0
6       0       7       8
10      0       11      12

1       0       0       0
6       1       7       8
10      0       12      12

-1      0       0       0
6       -1      7       8
10      0       10      12

-0      0       -0      -0
-6      -0      -7      -8
-10     -0      -11     -12

0       0       0       0
18      0       21      24
30      0       33      36

ex-2
sfruttando la classe Tabella si programmi la classe IperArray costituita da una matrice di puntatori ad array unidimensionali di numeri interi; si abbiano le seguenti funzioni membro:


next up previous contents index
Next: Vettore di bit Up: Alcuni tipi di dati Previous: Coda   Indice   Indice analitico
Claudio Cicconetti
2000-09-06