next up previous contents index
Next: Cenni di polimorfismo Up: Classi derivate. Polimorfismo. Streams Previous: Upcasting   Indice   Indice analitico

Un esempio di stack

Vedremo all'interno della presente sezione come la derivazione può fondersi con la tecnica di progettazione di classi template; si è scelto di utilizzare un contenitore già noto per evitare che l'attenzione si focalizzasse sulla struttura del dato piuttosto che sulle problematiche della derivazione.

Il nostro miniprogetto comprende tre classi, le prime due delle quali sono template e che derivano l'una dall'altra. Si suppone che tutte siano copiate nel file ex12_4_1.h. La classe base è la seguente:


template <class Tipo>
class Array {
public:
  Array () { array = 0; nelem = 0; }
  ~Array () { delete[] array; }
protected:
  bool alloca (unsigned n) {
    if (n <= nelem) return false;
    Tipo* tmp = new Tipo[n];
    for (int i = 0; i < nelem; i++)
      tmp[i] = array[i];
    delete[] array;
    array = tmp;
    nelem = n; }
  Tipo& operator[] (unsigned i) { return array[i]; }
  unsigned numElementi () const { return nelem; }
  unsigned memoria () const { return nelem * sizeof(Tipo); }
private:
  Tipo* array;
  unsigned nelem;  // numero di elementi
};

che rappresenta semplicemente un array di elementi di un certo Tipo; tale array è allocato nello heap e può essere ridimensionato a piacimento dall'utilizzatore di tale classe. Si noti tuttavia che tutte le funzioni membro, tranne il costruttore e il distruttore, sono protected, per cui non è possibile agire direttamente sulla memoria dell'array a meno che non si derivi una classe da essa. Infatti la classe successiva è:


template <class Tipo>
class Stack : public Array<Tipo> {
public:
  Stack () { passo = PASSO_DEFAULT; nelem = 0; }
  ~Stack () { }
  void setPasso (unsigned nuovoPasso) {
    if (passo >= 1) passo = nuovoPasso; }
  void getPasso () const { return passo; }
  unsigned numElementi () const { return nelem; }
  bool  push (const Tipo& nuovoOggetto);
  Tipo& pop  () { return operator[](--nelem); }
  Tipo& peek () { return operator[](nelem - 1); }
  unsigned memoria () const { return Array<Tipo>::memoria(); }
private:
  enum { PASSO_DEFAULT = 5 };
  unsigned passo;
  unsigned nelem;
};

template <class Tipo>
bool Stack<Tipo>::push (const Tipo& nuovoOggetto) {
  bool riallocazione = false;
  if (numElementi() >= Array<Tipo>::numElementi()) {
    alloca (numElementi() + passo);
    riallocazione = true;
  }
  operator[](nelem++) = nuovoOggetto;
  return riallocazione;
}

Stack è una classe template la quale è in realtà un array, ma che possiede l'interfaccia di uno stack; essa si appoggia alla struttura interna della classe Array, mentre si occupa di riallocare, tramite la funzione alloca() di Array, memoria quando ciò si renda necessario. Con questo spirito l'utente non deve mai avere a che fare con la riallocazione di memoria, ma sarà la classe Stack a pensarci al suo posto. Andiamo ancora oltre; nel caso l'utente abbia necessità di uno stack di int, la strada più semplice da seguire è la seguente: si deriva una classe da Stack<int>, eventualmente aggiungendo funzioni membro adattate ai propri scopi. Vediamo in pratica un semplice esempio di tale comune tecnica:


class StackInt : public Stack<int> {
public:
  StackInt () { }
  ~StackInt () { }
  int somma () const {
    int s = 0;
    for (int i = 0; i < numElementi(); i++)
      s += operator[](i);
    return s; }
  double media () const {
    return (double)somma() / (double)numElementi(); }
};

Come è evidente l'utente non deve fare altro che derivare la propria classe specifica, senza che egli tocchi neanche minimamente la struttura interna del dato che sta utilizzando. A questo punto disponiamo di una classe StackInt che abbiamo personalizzato per le nostre necessità e che possiamo utilizzare dimenticando sia che si tratti di una classe derivata, sia che essa derivi da una classe template. Vediamo un semplice esempio:


// ex12_4_1.cpp
#include <iostream.h>
#include "ex12_4_1.h"
void main() {
  // sizeof(int);  // 4
  StackInt s;
  s.setPasso (10);
  for (int i = 0; i < 21; i++)
    cout << s.push(i+1);
  // s.peek();     // 21
  cout << "\nsomma:  " << s.somma() << "\n";
  cout << "n.elem: " << s.numElementi() << "\n";
  cout << "media:  " << s.media() << "\n";
  cout << "mem:    " << s.memoria() << "\n";
  cout << "primi cinque elementi: " << "\n";
  for (int i = 0; i < 5; i++)
    cout << "\t" << s.pop() << "\n";
}

esempio di output:


100000000010000000001
somma:  231
n.elem: 21
media:  11
mem:    120
primi cinque elementi:
        21
        20
        19
        18
        17

ex-2
si derivi da Stack la classe StackDouble, la quale sia costituita da uno stack di double e che abbia un nome (immagazzinato in un char*); essa possegga dunque i seguenti membri pubblici:


next up previous contents index
Next: Cenni di polimorfismo Up: Classi derivate. Polimorfismo. Streams Previous: Upcasting   Indice   Indice analitico
Claudio Cicconetti
2000-09-06