Laboratorio di Algoritmi e Strutture Dati Esercizi - LACAM
Transcript
Laboratorio di Algoritmi e Strutture Dati Esercizi - LACAM
Laboratorio di Algoritmi e Strutture Dati Esercizi Teresa M.A. Basile – [email protected] – Dipartimento di Informatica Università degli Studi di Bari “Aldo Moro” 1 Operatori Indispensabili ● Ogni Classe che utilizza della memoria allocata dinamicamente dovrebbe solitamente essere provvista del gruppo di funzioni membro costituito da: ● ● ● ● Costruttore: Distruttore Costruttore di copia Operatore ridefinito di assegnamento (overloading operator=) Ogni classe che possiede un distruttore che esegue compiti non banali, quali deallocare memoria dinamica, dovrebbe essere completamente corredata di tutte le funzioni che controllano la costruzione, la distruzione e la copia 2 Costruttore ● ● ● ● ● Utilizzato automaticamente dal compilatore quando una istanza della classe viene creata, in modo che tale istanza sia sin dall'inizio in uno stato consistente. Un costruttore altro non è che un metodo il cui nome è lo stesso di quello della classe. Un costruttore può avere un qualsiasi numero di parametri, ma non restituisce mai alcun tipo (neanche void); il suo scopo è quello di inizializzare le istanze della classe Una classe può possedere più costruttori, cioè i costruttori possono essere overloaded, in modo da offrire diversi modi per inizializzare una istanza Il costruttore di default è particolare, in quanto è quello che il compilatore chiama quando il programmatore non utilizza esplicitamente un costruttore nella dichiarazione di un oggetto 3 Costruttore di copia ● ● ● ● ● Le funzioni possono ricevere un oggetto come argomento e possono restituirne uno. Per default entrambe le operazioni sono effettuate per valore, cioè la funzione riceve o restituisce una copia dell'oggetto In tali casi il C++ crea una copia dell'oggetto da passare e chiama il costruttore di copia per copiare I valori dell'oggetto originale nell'oggetto copia In generale il costruttore di copia di una classe D viene invocato ● in una inizializzazione di variabile (Matrice M1=M2) ; ● nel passaggio di parametri per valore ● per i valori di ritorno ● per la gestione di eccezioni Per ogni classe il compilatore crea un costruttore di copia di default, che copia ogni dat membro dell'oggetto originale nel corrispondente dato membro del nuovo oggetto. Il costruttore di copia fornito dal compilatore esegue una copia degli attributi. In generale questo è sufficiente, ma quando una classe contiene puntatori è necessario definirlo esplicitamente onde evitare problemi di condivisione di aree di memoria 4 Operatore di assegnamento ● ● Viene invocato in tutte le operazioni che assegnano all'oggetto dichiarato un altro oggetto Come nel caso caso del costruttore di copia anche in questo caso il compilatore fornisce automaticamente un operatore di assegnamento 5 inizializzazione != assegnamento operazioni sintatticamente simili operazioni semanticamente profondamente diverse ● ● ● ● l'inizializzazione viene compiuta una volta sola, quando l'oggetto viene creato un assegnamento invece si esegue su un oggetto precedentemente creato Il costruttore di copia viene utilizzato quando si dichiara un nuovo oggetto e si inizializza il suo valore con quello di un altro l'operatore di assegnamento viene invocato successivamente in tutte le operazioni che assegnano all'oggetto dichiarato un altro oggetto 6 Overloading Operatori < ReturnType > operator@( < ArgumentList > ) { < Body > } ● ● Operatori unari class String { public: bool operator!() const; ... }; Operatori binari class String { public: const String &operator+=(const String & ); ... }; ● y +=z è equivalente a y.operator+=(z) 7 Distruttore ● ● ● ● ● ● Poichè ogni oggetto ha una propria durata (lifetime) è necessario disporre anche di un metodo che permetta una corretta distruzione dell'oggetto stesso, un distruttore. Un distruttore è un metodo che non riceve parametri, non ritorna alcun tipo (neanche void) ed ha lo stesso nome della classe preceduto da una ~ (tilde) Invocato automaticamente alla fine del blocco applicativo (le istruzioni racchiuse tra { } ) in cui la variabile è stata dichiarata (alla fine del programma per variabili globali e statiche). Il compito del distruttore è quello di assicurarsi della corretta deallocazione delle risorse e se non ne viene esplicitamente definito uno, il compilatore genera per ogni classe un distruttore di default che chiama alla fine della lifetime di una variabile Poichè il distruttore fornito dal compilatore non tiene conto di aree di memoria allocate tramite membri puntatore, è sempre necessario definirlo esplicitamente ogni qual volta esistono membri puntatori. In tutti gli altri casi, spesso il distruttore di default è più che sufficiente e non occorre scriverlo. Non è possibile eseguire l'overloading dei distruttori: ogni classe cioè possiede sempre e solo un unico distruttore. 8 Revisione Classe Matrice 9 Classe Matrice: allocazione statica vs dinamica // const int DIMMAX = 5; const double ELEMDEFAULT=0.0; typedef double tipoelem; typedef int indice; class Matrice { public: // Costruttore Distruttore Matrice(indice, indice); ~Matrice(); indice getnrighe(); indice getncolonne(); tipoelem leggimatrice(indice,indice); void scrivimatrice(indice,indice,tipoelem); Matrice somma(Matrice); Matrice prodotto(Matrice); private: indice righe; indice colonne; // dimensione massima costante - allocazione massima con stesso numero di righe e colonne // tipoelem elem[DIMMAX][DIMMAX]; tipoelem **elem; bool controllaindici(indice,indice); // controllo sugli indici bool controllalimite(indice);// controllo sulla dimensione void tipomatrice(); // verifica tipo di matrice (interi o reali) }; 10 Classe Matrice: allocazione statica vs dinamica COSTRUTTORE // Costruttore – caso A_S Matrice::Matrice(indice nrighe, indice ncolonne) { if (controllalimite(nrighe) && controllalimite(ncolonne)) { righe=nrighe; colonne=ncolonne; // inizializzazione degli elementi for (indice i=0; i<righe;i++) for (indice j=0; j<colonne;j++) { elem[i][j]=ELEMDEFAULT; } } else cout << "Almeno uno degli indici fuori limite" << endl; } 11 Classe Matrice: allocazione statica vs dinamica COSTRUTTORE // Costruttore – caso A_D Matrice::Matrice(indice nrighe, indice ncolonne) { righe=nrighe; colonne=ncolonne; // allocazione dinamica della matrice elementi = new tipoelem* [righe]; for (indice i=0; i<righe;i++) elementi[i] = new tipoelem[colonne]; // inizializzazione degli elementi for (indice i=0; i<righe;i++) for (indice j=0; j<colonne;j++) { elem[i][j]=ELEMDEFAULT; } } 12 Classe Matrice: allocazione statica vs dinamica DISTRUTTORE // Distruttore – caso A_S Matrice::~Matrice() {} // Distruttore – caso A_D Matrice::~Matrice(){ for (indice i=0; i<righe;i++){ // dealloco la memoria per gli array delete(elem[i]); } // dealloco la memoria per il puntatore a puntatori delete(elem); } 13 Classe Matrice: allocazione statica vs dinamica COSTRUTTORE DI COPIA (.h) Matrice(const Matrice &); (.cpp) // Costruttore di copia – caso A_D Matrice::Matrice(const Matrice & origin) { righe = origin.getnrighe(); colonne = origin.getncolonne(); // allocazione dinamica della matrice elem = new tipoelem* [righe]; for (indice i=0; i<righe;i++) elem[i] = new tipoelem[colonne]; // inizializzazione degli elementi for (indice i=0; i<righe;i++) for (indice j=0; j<colonne;j++) { this->elem[i][j]=origin.elem[i][j]; } } 14 Classe Matrice: allocazione statica vs dinamica Operatore di Assegnamento (.h) Matrice & operator=(const Matrice &); (.cpp) // Operatore di Assegnamento – caso A_D Matrice & Matrice::operator=(const Matrice & B){ righe = B.getnrighe(); colonne = B.getncolonne(); elem = new tipoelem*[righe]; for(int i=0;i<righe;i++) { elem[i] = new tipoelem[colonne]; } } for(int i = 0; i<B.getnrighe(); i++) { for (int j = 0; j < B.getncolonne(); j++) { elem[i][j] = B.elem[i][j]; } } return *this; 15 Classe Matrice: overloading operatori const int DIMMAX = 5; const double ELEMDEFAULT=0.0; typedef int indice; class Matrice { public: // Costruttore Distruttore Matrice(indice, indice); ~Matrice(); indice getnrighe(); indice getncolonne(); tipoelem leggimatrice(indice,indice); void scrivimatrice(indice,indice,tipoelem); Matrice somma(Matrice); // operator+ Matrice prodotto(Matrice); //operator* private: indice righe; indice colonne; tipoelem elem[DIMMAX][DIMMAX]; bool controllaindici(indice,indice); // controllo sugli indici bool controllalimite(indice);// controllo sulla dimensione void tipomatrice(); // verifica tipo di matrice (interi o reali) }; 16 Classe Matrice: overloading operatori (.h) Matrice operator+(const Matrice & B) const; (.cpp) Matrice Matrice::operator+(const Matrice & matrix) //Matrice Matrice::somma(Matrice matrix) const { if (righe==matrix.getnrighe() && colonne==matrix.getncolonne()) { Matrice risultato(righe,colonne); for (indice i=1; i<=righe;i++) for (indice j=1; j<=colonne;j++) { risultato.scrivimatrice(i,j,leggimatrice(i,j) +matrix.leggimatrice(i,j)); } return risultato; } else cout << "Somma non possibile!" << endl; } 17