trasparenti - INFN
Transcript
trasparenti - INFN
Oggetti e Classi (CAP 10) Alberto Garfagnini, Marco Mazzocco Università degli studi di Padova 30 Novembre 2011 La programmazione ad oggetti (OOP) • È un approccio concettuale alla programmazione (indipendente dal linguaggio utilizzato) • Il linguaggio C++ aggiunge nuovi elementi rispetto al C che permettono di implementare con facilità tale approccio. • Gli aspetti più importanti di OOP sono: 1. Abstrazione 2. Incapsulamento e Data Hiding 3. Polimorfismo 4. Ereditarietà 5. Riutilizzo del Codice • La Classe è lo strumento più importante del C++ per implementare la programmazione ad oggetti. Astrazione e Classi • È il passo importante per rappresentare l’informazione come interfaccia con l’utilizzatore: 1. si astraggono le operazioni essenziali di un problema; 2. si esprime la soluzione in quei termini. Es: Poligoni regolari Es: Vettore nel piano • Def. rappresentazione (dati): 1. numero di lati 2. lunghezza di un lato • Def. rappresentazione (dati): 1. coordinate (x, y) 2. coordinate (ρ,φ) 3. rappr (POLARE/CARTESIANA) • Def. interfaccia (azioni): 1. calcolare l’area 2. calcolare il perimetro • Def. interfaccia (azioni): 1. imposta coordinate 2. imposta rappresentazione 3. somma due Vettori 4. sottrai due Vettori 5. moltiplica Vettore per costante Astrazione e Classi (2) • La Classe è il veicolo per tradurre una astrazione in un tipo definito dal programmatore • Permette di combinare insieme la rappresentazione dei dati e i metodi per manipolarli • la scrittura di una classe comporta la specifica di 1. una class declaration, che descrive i dati che compongono l’oggetto come data members e l’interfaccia pubblica come member functions, chiamate anche metodi 2. una class method definition, che descrive come sono implementate le funzioni membri della classe Class Declaration Overview della Classe Methods Definition Implementazione Dettagliata Sviluppo di una Classe • Tipicamente, durante il disegno di una nuova classe (i.e. un nuovo tipo) si procede per passi successivi e si scrivono separatamente 1. un file header (es poligono.h) contiene l’interfaccia tra la classe e l’utilizzatore, sotto forma di definizione della classe. si compone di una parte pubblica accessibile dall’utilizzatore e privata disponibile soltanto ai metodi della classe 2. un file sorgente (es poligono.cxx) contiene il codice, vero e proprio, che implementa i metodi della classe. deve rispettare tutte le definizioni (prototipi) che sono stati definiti dall’interfaccia Es. Classe Poligono. File poligono.h // File: poligono.h #ifndef POLIGONO_H #define POLIGONO_H class Poligono { private : int n_lati; double lato; public : void double double double }; imposta_valori(double lung_lato, int nlati); calcola_area(); calcola_perimetro(); stampa(); // Importante il punto-e-virgola #endif Commenti alla classe Poligono • La parola chiave class definisce un nuovo tipo e permette di dichiarare variabili, chiamate oggetti o istanze della classe Poligono Poligono quad_1, pent_1; definiscono due oggetti quad_1 e pent_1 come istanze della classe Poligono • Le parole chiave private e public definiscono la modalità di accesso ai membri della classe (varibili e funzioni). • Un programma che utilizza un oggetto di una classe può accedere direttamente soltanto alla sezione pubblica della classe • Accedere ai membri privati della classe è possibile soltanto tramite funzioni pubbliche Per accedere alle variabili n_lati e lato bisogna passare attraverso il metodo pubblico imposta_valori • Questo meccanismo di isolamento dei dati della classe prende il nome di data hiding Es. Classe Poligono. File poligono.h parola class identifica la definizione della classe parola private identifica membri della classe ai quali si può avere accesso solo dalla parte pubblica (data hiding) il nome della classe diventa un nuovo tipo class Poligono { i membri della classe possono essere tipi o funzioni private : int n_lati; double lato; public : void imposta_valori(double llato, int nlati); double calcola_area(); double calcola_perimetro(); double stampa(); parola public identifica membri della classe che costituiscono l’interfaccia pubblica della classe (abstraction) }; Implementiamo le funzioni, metodi della classe • Per completare la scrittura delle della classe dobbiamo scrivere il codice per i metodi specificati nella dichiarazione della classe (file poligono.h). • le definizioni dei metodi sono simili alle definizioni delle funzioni regolari del C++: • ogni funzione ha un header e un corpo con il codice. • una funzione può avere argomenti e ritornare un tipo • ma soprattutto, • nel definire un metodo di una classe, utilizzare l’operatore :: per identificare la classe alla quale il metodo appartiene • i metodi della classe posso accedere alla componente privata della classe Es. Classe Poligono. File poligono.cxx (1) // File: poligono.cxx #include <iostream> #include "poligono.h" void Poligono::imposta_valori(double llato, int nlati) { lato = llato; n_lati = nlati; } double Poligono::calcola_perimetro() { return n_lati * lato; } double Poligono::calcola_area() { if (n_lati > 2 and n_lati <6) { double area = n_lati * lato * lato / 4.; return area/tan(M_PI/n_lati); } } Es. Classe Poligono. File poligono.cxx (2) void Poligono::stampa() { using namespace std; switch (n_lati) { case 3: cout << "Triangolo" << endl; break; case 4: cout << "Quadrato" << endl; break; case 5: cout << "Pentagono" << endl; break; default: cout << "Poligono sconosciuto. "; cout << "Numero lati: " << n_lati << endl; break; } return; } Usare le classi • Vogliamo ora scrivere un programma che utilizza la classe definita in precedenza • il C++ rende l’utilizzo delle classi, e la creazione di oggetti, il più simile possibile all’utilizzo dei tipi di base come int e char • si crea un istanza della classe dichiarando una variabile dei tipo classe, oppure utilizzando l’operatore new per allocare dinamicamente un oggetto del tipo classe • è possibile passare oggetti di una classe a funzioni definite dall’utente, ritornarli come valori di ritorno e assegnare un oggetto ad un altro • è persino possibile istruire cin e cout a riconoscere gli oggetti di una classe e persino fornire una conversione automatica tra oggetti di classi simili • vediamo un esempio di utilizzo // test_poligono.cxx - Prova la classe Poligono #include <iostream> #include "poligono.h" $ g++ -c poligono.cxx $ g++ test_poligono.cxx poligono.o int main( ) { using namespace std; Poligono tri; tri.imposta_valori(2.5, 3); Poligono qua; qua.imposta_valori(2.5, 4); ./a.out Poligono: Triangolo Area : 2.70633 Perimetro : 7.5 Poligono: Quadrato Area : 6.25 Perimetro : 10 cout << "Poligono: "; tri.stampa(); cout << "Area : " << tri.calcola_area() << " Perimetro : " << tri.calcola_perimetro() << endl; cout << "Poligono: "; qua.stampa(); cout << "Area : " << qua.calcola_area() << " Perimetro : " << qua.calcola_perimetro() << endl; return 0; } Costruttori e Distruttori di Classi • Riguardiamo la definizione della classe Poligono • subito dopo aver istanziato un oggetto della classe con l’istruzione Poligono tri; • è necessario invocare un metodo pubblico per inizializzare le variabili della classe (della sezione private) tri.imposta_valori(2.5, 3); • altrimenti le variabili n_lati e lato non sono correttamente inizializzate ai valori voluti • inoltre l’unico modo per impostare dei valori è tramite un metodo dell’interfaccia pubblica (perchè le variabili sono membri della parte private) • il C++ fornisce una maniera per inizializzare un oggetto definendo dei metodi speciali costruttori e distruttori invocati ogni volta che si istanzia un oggetto di una classe o che l’oggetto venga eliminato dalla memoria (e quindi non è più disponibile nel programma) Costruttori di Default • Tutte le volte che creiamo un oggetto Poligono tri; • viene invocato un metodo default constructor • se il metodo non esiste nell’interfaccia della classe (header file) viene fornito dal compilatore nella fase di compilazione • il metodo è vuoto (non contiene codice): // File: poligono.h class Poligono { ... public : Poligono(); ... }; // File: poligono.cxx ... Poligono::Poligono() { } ... Costruttori e overloading • Il default constructor viene invocato tutte le volte che si istanzia un oggetto senza passare valori di inzializzazione Poligono tri; • È possibile definire altri costruttori, tramite l’overloading // File: poligono.h class Poligono { ... public : Poligono(); Poligono(double llato, int nlati); ... }; // File: poligono.cxx ... Poligono::Poligono(double llato, int nlati) { lato = llato; n_lati = nlati; } Poligono::Poligono() { lato = 0.0; n_lati = 0; } Costruttori • Nel disegnare una classe, scrivere sempre un costruttore di default in modo da inzializzare in maniera implicita tutti i membri della classe Poligono tri; // chiama implicitamente // il default constructor Poligono qua = Poligono(); // lo chiama esplicitamente Poligono * apo = new Poligono; // lo chiama implicitamente Poligono * penta = new Poligono(12.5, 5); // chiama un altro // constructor • Tutte le volte che si invoca implicitamente il costruttore di default non si usano le parentesi Distruttori di Classi • Quando si crea un oggetto con un costruttore, il programma si impegna a tenere traccia dell’oggetto fino a quando scade • in quel momento il programma invoca un metodo speciale chiamato distruttore che ha lo scopo di liberare eventuale memoria dinamica allocata dai metodi costruttori. // File: poligono.h class Poligono { ... public : ~Poligono(); ... }; // File: poligono.cxx ... Poligono::~Poligono() { std::cout << "Distruttore"; std::cout << std::endl; } • Il distruttore viene invocato automaticamente se un oggetto va out-of-scope • non deve essere chiamato direttamente dal programma Es. Classe Poligono modificata. File poligono2.h // File: poligono.h #ifndef POLIGONO_H #define POLIGONO_H class Poligono { private : int n_lati; double lato; public : Poligono(double lung_lato, int nlati); // spec constructor Poligono(); // def constructor ~Poligono(); // destructor double calcola_area(); double calcola_perimetro(); double stampa(); }; #endif Es. Classe Poligono. File poligono2.cxx (1) // File: poligono.cxx #include <iostream> #include "poligono2.h" Poligono::Poligono() { cout << "Default constructor called"; lato = 0.0; n_lati = 0; } Poligono::Poligono(double llato, int nlati) { cout << "Specialized constructor called"; lato = llato; n_lati = nlati; } Poligono::~Poligono() { cout << "Destructor called"; } $ g++ -c poligono2.cxx $ g++ test_poligono2.cxx poligono2.o // test_poligono2.cxx - Prova la classe Poligono #include <iostream> ./a.out #include "poligono.h" Specialized constructor called Specialized constructor called int main( ) Default constructor called { Poligono: Triangolo using namespace std; Area : 2.70633 Perimetro : 7.5 Poligono tri = Poligono(2.5, 3); Poligono: Quadrato Poligono qua(2.5, 4); Area : 6.25 Perimetro : 10 Poligono pen; Destructor called Destructor called cout << "Poligono: "; Destructor called tri.stampa(); cout << "Area : " << tri.calcola_area() << " Perimetro : " << tri.calcola_perimetro() << endl; cout << "Poligono: "; qua.stampa(); cout << "Area : " << qua.calcola_area() << " Perimetro : " << qua.calcola_perimetro() << endl; return 0; }