Le classi Definizione di classe Attributi e metodi
Le classi Definizione di classe Attributi e metodi di una classe Costruttori e distruttori Private e public Funzioni friend Il puntatore this
Cos’è un oggetto? • Né più né meno di quello che potreste trovare scritto in un vocabolario… – 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 in un certo istante • Le funzionalità di un oggetto sono le operazioni che può svolgere quando glielo si richiede (cioè quando riceve un messaggio) • Nella nostra vita quotidiana siamo molto più abituati a ragionare per oggetti che non in modo strutturato! 2
Un esempio. . . 3
… cos’è un oggetto: Un insieme di dati e funzioni: Fun zio Dato Fu ne nz io i nz on Fu e Co ce d di Co ione funz ice i nz on fu e Co fun dice zio ne ne 4
Incapsulazione • 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 chiedere…) • Il confinamento di informazioni e funzionalità in oggetti permette livelli maggiori di astrazione e semplifica la gestione di sistemi complessi. 5
Approccio OO • Sono le strutture di dati che svolgono le azioni, non le subroutines • Il lavoro è svolto dal server, non dal client • “Cos’ è? ” “Com’ è fatto? ” Data Oriented • “Cosa può fare per me? ” Object Oriented 6
Perché programmare per oggetti? • Programmare per oggetti non velocizza l’esecuzione dei programmi. . . • Programmare per oggetti non ottimizza l’uso della memoria. . . E allora perchè programmare per oggetti? • Programmare per oggetti facilita la progettazione e il mantenimento di sistemi software molto complessi! 7
Caratteristiche del software non mantenibile • Rigidità – non può essere cambiato con faciltà – non può essere stimato l’impatto di una modifica • Fragilità – una modifica singola causa una cascata di modifiche successive – i bachi sorgono in aree concettualmente separate dalle aree dove sono avvenute le modifiche • Non riusabilità – esistono molte interdipendenze, quindi non è possibile estrarre parti che potrebbero essere comuni 8
Programmazione ad oggetti • La programmazione ad oggetti, attraverso l’incapsulazione, consente di: – ridurre la dipendenza del codice di alto livello dalla rappresentazione dei dati – riutilizzare del codice di alto livello – sviluppare moduli indipendenti l’uno dall’altro – avere codice utente che dipende dalle interfacce ma non dall’implementazione 9
Organizzazione dei files • Normalmente, le dichiarazioni delle interfacce e le specifiche sono separate dall’implementazione – header files (. h o. hh) • inclusi nei file sorgente utilizzando direttive del precompilatore #include <iostream> • non contengono codice eseguibile (con l’eccezione delle definizioni delle funzioni inline) • non devono essere inclusi piu` di una volta, per evitare problemi con il linker #ifndef My. Header_H #define My. Header_H // dichiarazioni …. . #endif 10
Organizzazione dei files (2) – Files sorgente (. C, . cxx, . cpp, . cc) • contengono l’implementazione di funzioni e metodi • codice eseguibile • includono gli header files utilizzando le direttive del preprocessore • vengono compilati 11
C++ e Object Orientation • Definizione di nuovi tipi (oltre a int, float, double) come: • numeri complessi, • vettori, • matrici, . . . • ma anche: • curve, • superfici, • Modelli 3 D, . . . • Gli oggetti permettono di modellare una problema che rappresenti la realtà 12
…C++ e Object Orientation • Object Orientation implementata in C++ attraverso il concetto di classe: • I dati privati (o attributi) di una classe definiscono lo stato dell’oggetto • Le funzioni (o metodi) di una classe implementano la risposta ai messaggi 13
Una classe C++ Mes sag Met gio odo o gi ag ss Me o d to Me Attributo Me ss a gg io M e to do Attributo 14
Classe Vector 2 D • Un esempio: un vettore bidimensionale Vector 2 D. h costruttore class Vector 2 D { public: Vector 2 D(double x, double y); double x(); double y(); Vector 2 D. cc double r(); double phi(); #include “Vector 2 D. h” private: #include <math. h> double x_; Vector 2 D: : Vector 2 D(double x, double y): x_(x), double y_ y_(y) { }; } double Vector 2 D: : x() { return x_; } funzioni o metodi dati o attributi Punto e virgola! double Vector 2 D: : r() { return sqrt( x_*x_ + y_*y_); }. . . 15
Interfaccia e implementazione • Gli attributi privati non sono accessibili al di fuori della classe Vector 2 D. cc • I metodi pubblici sono #include “Vector. h” gli unici visibili Vector 2 D. h class Vector 2 D { public: Vector 2 D(double x, double y); double x(); double y(); double r(); double phi(); private: double x_; double y_; }; Vector 2 D: : Vector 2 D(double x, double y) : x_(x), y_(y) {} double Vector 2 D: : x() { return x_; } double Vector 2 D: : r() { return sqrt(x_*x_ + y_*y_); } 16
Costruttori e distruttori • Un costruttore è un metodo il cui nome è quello della classe a cui appartiene • Lo scopo di un costruttore è quello di costruire oggetti del tipo della classe. Questo implica l’inizializzazione degli attributi e, frequentemente, l’allocazione della memoria necessaria • Un costruttore la cui lista di argomenti è vuota o composta di argomenti di default viene normalmente chiamato costruttore di default Vector 2 D: : Vector 2 D() {. . } // costruttore di default #include “Vector 2 D. h”. . . Vector 2 D v; // oggetto costruito con il // costruttore di default 17
Costruttori e distruttori (2) • Un costruttore del tipo che ha come argomento un riferimento ad un oggetto della stessa classe viene chiamato copy constructor (costruttore per copia) Vector 2 D: : Vector 2 D(const Vector 2 D& v) {. . } Vector 2 D v(v 1); // dove v 1 e` di tipo Vector 2 D • Il copy constructor 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 18
Costruttori e distruttori (3) • Gli attributi di una classe possono essere inizializzati nel costruttore per mezzo di una lista di inizializzatori, che precede il corpo della funzione Vector 2 D: : Vector 2 D(double x, double y) : x_(x), y_(y) {. . . } • Quando uno degli attributi è esso stesso una classe, il costruttore appropriato viene scelto sulla base dei parametri forniti nell’inizializzazione • E` obbligatorio inizializzare gli attributi (non statici) che siano o riferimenti o const 19
Costruttori e distruttori (4) • 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 restituirla allo heap Vector 2 D: : ~Vector 2 D() {} // vuoto, in questo caso 20
Costruttori e distruttori (5) • I costruttori con un solo parametro sono automaticamente trattati come operatori di conversione Vector 2 D: : Vector 2 D(int i) {. . . } // costruisce un vettore a partire da un intero, ma puo` // essere usato per convertire un intero in vettore v=Vector 2 D(i); • Per evitare la conversione si puo` usare explicit Vector 2 D(int); // solo costruttore 21
Classe Vector 2 D • Come usare Vector 2 D: main. cc #include <iostream> using namespace std; #include “Vector 2 D. h” invoca il constructor int main() { Vector 2 D v(1, 1); cout << “ v = << v. x() << v. y() cout << “ r = cout << “ phi return 0; } (“ << “, ” << “)” << endl; “ << v. r(); = “ << v. phi() << endl; Output: v = (1, 1) r = 1. 4141 phi = 0. 7854 22
Classe Vector 2 D • … oppure attraverso un puntatore. . . main. cc #include <iostream> using namespace std #include “Vector 2 D. h” Allocazione sullo heap int main() { Vector 2 D *v = new Vector 2 D(1, 1); cout << “ v = (“ << v->x() << “, ” << v->y() << “)” << endl; cout << “ r = “ << v->r(); cout << “ phi = “ << v->phi() << endl; delete v; return 0; } Attenzione! Output: v = (1, 1) r = 1. 4141 phi = 0. 7854 23
Interfaccia e implementazione • La struttura interna dei dati (x_, y_) che rappresentano l’oggetto della classe Vector 2 D sono nascosti (private) agli utilizzatori della classe. • Gli utilizzatori non dipendono dalla struttura interna dei dati • Se la struttura interna cambia (es. : r_, phi_), il codice che usa Vector 2 D non deve essere modificato. 24
Classe Vector 2 D • Protezione dell’accesso ai dati: main. cc #include <iostream> using namespace std #include “Vector 2 D. h” int main() { Vector 2 D v(1, 1); cout << << << cout << “ V = (“ v. x_ << “, ” // v. y_ << “, ” << endl; // non compila ! “ r = “ << v. r(); “ phi = “ << v. phi() << endl; } • I metodi di una classe hanno libero accesso ai dati privati e protetti di quella classe 25
Selettori e modificatori • Selettore: metodo che non modifica lo stato (attributi) della classe. E’ dichiarato const • Modificatore: metodo che può modificare lo stato della classe Vector 2 D. cc Vector 2 D. h class Vector 2 D #include “Vector 2 D. h” { public: void Vector 2 D: : scale(double Vector 2 D(double x, double y); { double x() const; x_ *= s; y_ *= s; double y() const; } double r() const; Selettori (const) double phi() const; main. cc void scale(double s); private: #include “Vector 2 D. h” double x_, y_; modificatore int main() }; { const Vector 2 D v(1, double r = v. r() // v. scale( 1. 1 ); // } s) 0); OK errore! 26
friend • La keyword friend puo` essere usata perche` una funzione (o una classe) abbia libero accesso ai dati privati di un’altra classe class A {. . . friend int a. Func(); friend void C: : f(int); }; class B { … friend class C; }; class C {. . . }; 27
friend (2) • friend (nonostante il nome) e` nemico dell’incapsulamento e quindi dell’Object Orientation • Un uso eccessivo di friend è quasi sempre sintomo di un cattivo disegno • Esistono anche situazioni in cui un friend può essere accettabile – Overloading di operatori binari – Considerazioni di efficienza – Relazione speciale fra due classi “A programmer must confer with an architect before making friend declarations” 28
this • In una classe è automaticamente definito un attributo particolare: this – this è un puntatore all’oggetto di cui fa parte – E’ particolarmente utile quando una funzione deve restituire l’oggetto tramite il quale è stata invocata Vector 2 D. h Vector 2 D. cc class Vector 2 D { public: Vector 2 D& copia(const Vector 2 D& ); //. . . private: double x_, y_; }; Vector 2 D& copia(const Vector 2 D& v){ x_=v. x(); y_=v. y(); return *this; } L’operatore copia ritorna una referenza a se stesso. Permette copie multiple main. cc #include “Vector 2 D. h” int main() { Vector 2 D null(0, 0); Vector 2 D a, b; a. copia(b. copia(null)); } 29
static • Attributi dichiarati static in una classe sono condivisi da tutti gli oggetti di quella classe • Metodi dichiarati static non possono accedere ad attributo non statici della classe • Attiributi statici possono essere usati e modificati soltanto da metodi statici • Nonostante l’utilizzo di static sembri imporre condizioni troppo restrittive, esso risulta utile nell’implementazione di: – contatori – singleton (vedi oltre) 30
Un contatore Class My. Class { private: static int counter; static void increment_counter() { counter++; } static void decrement_counter() { counter--; } public: My. Class() { increment_counter(); } ~My. Class() { decrement_counter(); } static int How. Many() { return counter; } }; Un membro statico deve essere #include <iostream> inizializzato una e una sola volta using namespace std #include “My. Class. h” nel codice eseguibile int My. Class: : counter=0; Un metodo statico puo` essere invocato cosi`. . . int main() { My. Class a, b, c; My. Class *p=new My. Class; cout<<“ How many? “<< My. Class: : How. Many() <<endl; delete p; cout<<“ and now? “<< a. How. Many() <<endl; return 0; } … o cosi`. . . 31
Un singleton • Un singleton è una classe di cui, in ogni momento nel corso del programma, non può esistere più di una copia (istanza) class a. Singleton { Pattern utile per private: static a. Singleton *ptr; l’implementazione di a. Singleton () {} classi “manager” di cui public: static a. Singleton *Get. Pointer(){ deve esistere una sola if (ptr==0) istanza ptr=new a. Singleton; return ptr; #include “a. Singleton. h” } }; a. Singleton *a. Singleton: : ptr=0; Attenzione a non farlo diventare l’equivalente di un common block! int main() { a. Singleton *my. Sing= a. Singleton: : Get. Pointer(); . . . Return 0; } 32
- Slides: 32