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
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:
unsigned max()
: restituisce l'indice dell'elemento di massimo valore;void estraiSpeciale()
: estrae gli elementi dall'ultimo inserito fino a quello di massimo valore;void negativo()
: rende tutti gli elementi dello stack negativi, eventualmente sostituendo con il loro opposto quelli positivi;void azzera()
: elimina tutti gli elementi dello stack;void setNome(char* s)
: imposta il nome dello stack a s
;char* getNome ()
: restituisce il nome dello stack;