C++ - Lezione III Programmazione Orientata agli Oggetti Classi ed Oggetti Operatori 1 Paradigma di Programmazione Le due componenti principali dei programmi: Algoritmi: l’insieme delle istruzioni che svolgono un particolare compito Dati: ciò su cui gli algoritmi agiscono per produrre una soluzione unica La relazione fra queste componenti definisce il paradigma di programmazione Programmazione procedurale: problemi modellati dagli algoritmi. Dati immagazzinati in aree comuni o passate agli algoritmi Programmazione ad oggetti: problemi modellati dalle relazioni fra tipi di dati astratti (ADT, Abstract Data Types), chiamati generalmente oggetti 2 Programmazione per Oggetti Programmare per oggetti facilita la progettazione e il mantenimento di sistemi software molto complessi Un progetto software complesso deve evitare Rigidità e Fragilità non può essere cambiato con faciltà non può essere stimato l’impatto di una modifica una modifica singola causa una cascata di modifiche successive Non riusabilità esistono molte interdipendenze, quindi non è possibile estrarre parti che potrebbero essere comuni 3 Concetti della Programmazione Orientata agli Oggetti Classi ed oggetti Incapsulamento Ereditarietà Polimorfismo 4 Classi ed Oggetti Cosa è un oggetto? Un oggetto è un’entità che si possa immaginare dotata di determinate caratteristiche e funzionalità Lo stato di un oggetto è rappresentato da dati che ne descrivono le caratteristiche Le funzionalità di un oggetto sono le operazioni che può svolgere quando glie lo si richiede (cioè quando riceve un messaggio) La classe è un astrazione concettuale dell'oggetto e ne definisce l'interfaccia (ovvero un tipo) 5 Esempio Classi oggetti elefante Elefante Catapulta Soldato ... oggetti catapulta oggetti soldato Una classe rappresenta un tipo. L'oggetto è un istanza di quel tipo 6 Esempio (cont) Il codice client si riduce ad in insieme di messaggi (che costituisco l'interfaccia delle classi) inviati agli oggetti Classe Soldato attacca() muoviti() difendi() arrenditi() ... Codice client (main) Soldato s; //oggetto soldato s.attacca() s.muoviti() ... 7 Incapsulamento Netta divisione fra interfaccia e implementazione Da fuori si vede solo l’interfaccia che definisce i messaggi accettati dall’oggetto I dettagli dell’implementazione (dati e codice delle funzioni) sono invisibili dall’esterno Ogni oggetto ha in se tutto ciò che gli serve per rispondere alle chiamate (o deve sapere a chi chiedere…) Il confinamento di informazioni e funzionalità in oggetti permette livelli maggiori di astrazione e semplifica la gestione di sistemi complessi. Implementazione Interfaccia Classe client usa client client 8 Vantaggi Riduzione della dipendenza del codice di alto livello dalla rappresentazione dei dati Riutilizzo del codice Sviluppo moduli indipendenti l’uno dall’altro Il codice utente (client) dipende dalle interfacce ma non dall’implementazione 9 Classi C++ Object Orientation è implementata in C++ attraverso il concetto di classe Definizione di nuovi tipi (oltre a int, float, double) come: numeri complessi, vettori, matrici forme geometriche . . . Le classi permettono di modellare una problema che rappresenti la realtà Una classe C++ è formata da I dati (o attributi) di una classe definiscono lo stato dell’oggetto Le funzioni (o metodi) di una classe implementano la risposta ai messaggi 10 Esempio : Classe Numero Complesso class Complex { Definito nell' hedear file Complex.h public: Complex(); costruttori Complex(double init_real, double init_im); void setReal(double); void setIm(double); double GetReal(); double GetIm(); metodi Gli attributi privati non sono accessibili al di fuori della classe I metodi pubblici sono gli unici visibili private: double _real; attributi o dati membro double _im; }; Punto e virgola Definizione/Interfaccia della classe numero complesso 11 Classi C++ - Costruttori Un costruttore è un metodo il cui nome è quello della classe a cui appartiene costruisce gli oggetti della classe. Questo implica l’inizializzazione degli attributi e, frequentemente, allocazione di memoria dinamica Un costruttore la cui lista di argomenti è vuota o composta di argomenti di default viene normalmente chiamato costruttore di default Il costruttore di copia è costruttore che ha come argomento un riferimento ad un oggetto della stessa classe viene normalmente utilizzato: quando un oggetto è inizializzato per assegnazione quando un oggetto è passato come argomento ad una funzione quando un oggetto è ritornato da una funzione Se non viene fornito esplicitamente dall’utente, il compilatore ne genererà uno automaticamente Complex(const Complex& v) 12 Ancora Costruttori La maggior parte dei costruttori serve ad inizializzare la lista dei dati membro di un oggetto Questo puo' essere fatto anche attraverso la lista di inizializzazione Possono essere usati valori di default Il costruttore di copia, copia lo stato di uno oggetto in un altro oggetto Complex::Complex(double init_real,double init_im) { _real = init_real; _im=init_im; } Complex::Complex(double init_real,double init_im ): _real(init_real), _im(init_im) {} Complex::Complex(double init_real=0,double init_im=0 ): real(init_real), im(init_im) {} Complex::Complex(const Complex& c) : _real(c.real), _im(c.im) {} 13 Classi C++ - Il Distruttore Il distruttore è un metodo il cui nome è quello della classe a cui appartiene preceduto da una tilde (~) Il distruttore viene chiamato automaticamente quando un oggetto sta per essere distrutto (sia perchè delete è stato invocato sia perchè l’oggetto è finito fuori scope) Il compito del distruttore è di assicurarsi che l’oggetto per cui è invocato verrà distrutto senza conseguenze. In particolare, se memoria è stata allocata nel costruttore, il distruttore dovrà assicurarsi di liberarla Complex::~Complex() 14 Complex.cc #include “Complex.h” Complex::Complex(){ _real=0; _im=0; } Complex::Complex(double init_real,double init_im { _real = init_real; _im=init_im; } void Complex::setReal(double x){ _real=x; } void Complex::setIm(double y){ _im=y; } double Complex::GetReal(){ return _real; } double Complex::GetIm(){ return _im; } useComplex.cpp #include <iostream.h> #include “Complex.h” using namespace std; invoca il construttore int main(){ Complex c(1, 1); cout << “ c = “ << c.GetReal() << “+i” << c.GetIm() << endl; c.setIm(-1); cout << “ c = “ << c.GetReal() << “+i” << c.GetIm() << endl; return 0; } si accede ai metodi ed agli attributi public attraverso l'operatore . 15 Uso dei Puntatori useComplex.cpp #include <iostream.h> #include “Complex.h” using namespace std; Allocazione dinamica int main(){ Complex * c = new Complex(1, 1); cout << “ c = “ << c->real() << “+i” << c->im() << endl; } delete c; return 0; 16 Uso della Definizione di una Classe In generale un header file con la definizione di una classe puo' essere incluso (#include) in piu' di un sorgente Il file verra` effettivamente compilato piu' volte e ci saranno piu' volte le stesse dichiarazioni (e definizioni) che daranno luogo a errori di definizione ripetuta dello stesso oggetto. Si puo' ovviare al problema utilizzando la direttiva di preprocessore #ifndef COMPLEX_H #define COMPLEX_H /* contenuto file header */ #endif si verifica cioe` se un certo simbolo e` stato definito, se non lo e` (cioe` #ifndef e` verificata) si definisce il simbolo e poi si inserisce il codice C/C++, alla fine si inserisce l'#endif. 17 Forward Declaration B non ha bisogno di conoscere i dettagli della classe A poichè ne possiede solo un puntatore Nel file sorgente bisogna includere la classe con #include altrimenti non si puo' fare nessuna operazione con A Velocizza la compilazione class A { ... }; #include "A.h" class A; class B { class B { .... private: private: A * a; A * a; }; Dipendenze cicliche .... }; class B; class A; class A { class B { .... .... private: private: B * b; A * a; }; }; 18 Public & Private Dalle applicazioni client si puo' accedere solo ai metodi o attributi dichiarati public useComplex.cpp #include <iostream.h> #include “Complex.h” using namespace std; int main(){ Complex v(1, 1); cout << “ c = “ << c._real << “, ” << c._im << endl; //ERRORE!!!!!! return 0; } Generalmente gli attributi di una classe vengono dichiarati private mentre i metodi sono public. Tuttavia questa dicotomia non è obbligatoria: in alcuni casi puo' essere utile dichiarare un metodo private per funzioni di utilità locale 19 Esercizio: Tracciamo i Costruttori e Distruttori 1. 2. implemetiamo la classe Complex aggiungiamo dei print-out ai costruttori (e distruttori) del tipo : Complex::Complex(const Complex& c) : real(c.real), im(c.im){ cout<<"chiamata al costruttore di copia" << endl; } useComplex.cpp #include <iostream.h> #include “Complex.h” using namespace std; void print(Complex c) {cout << “ c = “ << c.real() << “, ” << c.im() << endl; } void print(Complex * c) {cout << “ c = “ << c->real() << “, ” << c->im() << endl; } int main(){ } Complex c(1, 1); Complex v(c); print(c); print(&c) return 0; Eseguiamo ed interpretiamo l'output20 Output... void print(Complex p) {cout << “ c = “ << c.real() << “+i” << c.im() << endl; } void print(Complex * p) {cout << “ c = “ << c->real() << “+i” << c->im() << endl; } int main(){ chiamata del costruttore Complex c(1, 1); chiamata del costruttore di copia Complex v(c); chiamata del costruttore di copia print(c); print(&c) return 0; } $ ./useComplex.exe c = 1, 1 chiamata del distruttore c = 1, 1 chiamata del distruttore chiamata del distruttore 21 Oggetti Costanti E' regola di buona programmazione dichiarare costante un oggetto che non deve venire modificato usando la parola chiave const const Complex c(1,1); In questo modo il compilatore limita l'accesso ai metodi dell'oggetto c.getreal() // errore!! I soli metodi che potrebbero essere chiamate per oggetti const sarebbero il costruttore ed il distruttore Per superare questa restrizione dobbiamo dichiarare come constanti quei metodi che vogliamo poter usare su oggetti const double getreal() const; 22 Metodi "Set" e "Get" Metodi di tipo get: metodo che non modifica lo stato (attributi) della classe. E’ dichiarato const Metodi di tipo set: metodo che può modificare lo stato della classe class Complex { public: Complex(); Complex(double init_real, double init_im); double setReal(double); double setIm(double); double getReal() const; modificatori selettori o metodi di accesso double getIm() const; private: double real; l'uso di const permette ai metodi di operare su oggetti constanti double im; }; 23 Attributi static Attributi dichiarati static in una classe sono condivisi da tutti gli oggetti di quella classe usaContatore.cpp Class Particella { public: Particella(){++count;} ~Particella(){--count;} int Contatore::count=0; Int Particella::count=0; static int count; int main(){ Contatored1, Particella d1,d2; d2; cout << "Adesso è " << Contatore::count d1.count << endl;<< endl; { Contatore d3, d4, d5; cout << "Adesso è " << Contatore::count d1.count << endl;<< endl; } cout << "Adesso è " << Contatore::count d1.count << endl;<< endl; Contatore d6; cout << "Adesso è " << Contatore::count d1.count << endl;<< endl; Private: Double _mass; OUTPUT... Int _charge; $ ./usaContatore.exe Double _px; }; #include <iostream.h> #include “Particella.h” “Contatore.h” using namespace std; Adesso e' 2 Adesso e' 5 Adesso e' 2 Adesso e' 3 } 24 Metodi static Funzione definita in una classe indipendende dagli oggetti della classe Metodi dichiarati static non possono accedere ad attributi non statici della classe usaContatore.cpp useDummy.cpp Attiributi statici possono #include <iostream.h> essere usati e modificati #include “Contatore.h” using namespace std; soltanto da metodi statici int Contatore::count=0; class Contatore { public: Contatore(){++count}; ~Contatore(){--count}; static int getCount(){return count;} private: static int count; }; int main(){ Contatore d1, d2; cout << "Adesso esistono " << Contatore.getCount() d1.count << " dummies <<" " << dummies endl; "{ << endl; { Contatore d3, d4, d5; cout Contatore << "Adesso d3, d4,esistono d5; " << d1.count << " dummies " << endl; }cout << "Adesso esistono " << Contatore.getCount() << " dummies " cout << endl; << "Adesso esistono " << d1.count << " dummies " << endl; }Contatore d6; .... cout << "Adesso esistono " << d1.count << " dummies " << endl; return 0; } 25 } this La keyword this rappresenta un puntatore costante all'oggetto corrente Complex::Complex(double init_real,double init_im) { this->_real = init_real; this->_im=init_im; } 26 Esercizi... Completare la classe Complex Cosa manca per modellare il problema "numero complesso"? Progettare e usare la classe Vector3D #include <iostream> #include <time.h> Progettare e usare la classe Timer (Cronometro) using namespace std; int main(){ cout << time(0) << endl; time_t rawtime Utilizzare la funzione time(int) della libreria std time.h time(&rawtime); cout << ctime(&rawtime) << endl; return 0; } $ ./time.exe 1117146427 27 Fri May 27 16:27:07 2005 Numero Complesso - Definizione 28 Numero Complesso Implementazione 29 Stack di Interi Content val Stack next top Content ... Content val val next next class Content { public: Content( int, Content* ); int getVal (); Content * getNext(); private: Content * next; int val; }; Stack top Lo stack vuoto push pop class Stack { public: Stack(); ~Stack(); void push(int); int pop (); private: Content * top; }; 30