320 likes | 416 Views
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…
E N D
Le classi Definizione di classe Attributi e metodi di una classeCostruttori e distruttori Private e public Funzioni friendIl 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!
Funzione Codice funzione Funzione Codice funzione Dato Dato Dato Codice funzione Funzione … cos’è un oggetto: Un insieme di dati e funzioni:
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 chi chiedere…) • Il confinamento di informazioni e funzionalità in oggetti permette livelli maggiori di astrazione e semplifica la gestione di sistemi complessi.
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
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!
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
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
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 • 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 #include <iostream> #ifndef MyHeader_H #define MyHeader_H // dichiarazioni ….. #endif
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
C++ e Object Orientation • Definizione di nuovi tipi (oltre a int, float, double) come: • numeri complessi, • vettori, • matrici, . . . • ma anche: • curve, • superfici, • Modelli 3D,... • Gli oggetti permettono di modellare una problema che rappresenti la realtà
…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
Messaggio Metodo Messaggio Metodo Attributo Attributo Attributo Metodo Messaggio Una classe C++
Vector2D.h class Vector2D { public: Vector2D(double x, double y); double x(); double y(); double r(); double phi(); private: double x_; double y_ }; Vector2D.cc #include “Vector2D.h” #include <math.h> Vector2D::Vector2D(double x, double y): x_(x), y_(y) { } double Vector2D::x() { return x_; } double Vector2D::r() { return sqrt(x_*x_ + y_*y_); } ... Classe Vector2D • Un esempio: un vettore bidimensionale costruttore funzioni o metodi dati o attributi Punto e virgola!
Vector2D.cc #include “Vector.h” Vector2D::Vector2D(double x, double y) : x_(x), y_(y){} double Vector2D::x() { return x_; } double Vector2D::r() { return sqrt(x_*x_ + y_*y_); } Vector2D.h class Vector2D { public: Vector2D(double x, double y); double x(); double y(); double r(); double phi(); private: double x_; double y_; }; Interfaccia e implementazione • Gli attributi privati non sono accessibili al di fuori della classe • I metodi pubblici sono gli unici visibili
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 Vector2D::Vector2D() {. . . .} // costruttore di default #include “Vector2D.h” . . . Vector2D v; // oggetto costruito con il // costruttore di default
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) • 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 Vector2D::Vector2D(const Vector2D& v) {. . . .} Vector2D v(v1); // dove v1 e` di tipo Vector2D
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 • 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 Vector2D::Vector2D(double x, double y) : x_(x), y_(y) { . . . }
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 Vector2D::~Vector2D() {} // vuoto, in questo caso
Costruttori e distruttori (5) • I costruttori con un solo parametro sono automaticamente trattati come operatori di conversione • Per evitare la conversione si puo` usare explicit Vector2D::Vector2D(int i) {. . .} // costruisce un vettore a partire da un intero, ma puo` // essere usato per convertire un intero in vettore v=Vector2D(i); explicit Vector2D(int); // solo costruttore
main.cc #include <iostream> using namespace std; #include “Vector2D.h” int main() { Vector2D v(1, 1); cout << “ v = (“ << v.x() << “,” << v.y() << “)” << endl; cout << “ r = “ << v.r(); cout << “ phi = “ << v.phi() << endl; return 0; } Output: v = (1, 1) r = 1.4141 phi = 0.7854 Classe Vector2D • Come usare Vector2D: invoca il constructor
main.cc #include <iostream> using namespace std #include “Vector2D.h” int main() { Vector2D *v = new Vector2D(1, 1); cout << “ v = (“ << v->x() << “,” << v->y() << “)” << endl; cout << “ r = “ << v->r(); cout << “ phi = “ << v->phi() << endl; delete v; return 0; } Output: v = (1, 1) r = 1.4141 phi = 0.7854 Classe Vector2D • … oppure attraverso un puntatore... Allocazione sullo heap Attenzione!
Interfaccia e implementazione • La struttura interna dei dati (x_, y_) che rappresentano l’oggetto della classe Vector2D 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 Vector2D non deve essere modificato.
main.cc #include <iostream> using namespace std #include “Vector2D.h” int main() { Vector2D v(1, 1); cout << “ V = (“ << v.x_ << “,” // << v.y_ << “,” << endl; // non compila ! cout << “ r = “ << v.r(); cout << “ phi = “ << v.phi() << endl; } Classe Vector2D • Protezione dell’accesso ai dati: • I metodi di una classe hanno libero accesso ai dati privati e protetti di quella classe
Vector2D.cc Vector2D.h #include “Vector2D.h” void Vector2D::scale(double s) { x_ *= s; y_ *= s; } class Vector2D { public: Vector2D(double x, double y); double x() const; double y() const; double r() const; double phi() const; void scale(double s); private: double x_, y_; }; main.cc #include “Vector2D.h” int main() { const Vector2D v(1, 0); double r = v.r() // OK v.scale( 1.1 ); // errore! } 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 Selettori (const) modificatore
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 aFunc(); friend void C::f(int); }; class B { … friend class C; }; class C { . . . };
friend (2) • friend (nonostante il nome) e` nemico dell’incapsulamentoe 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”
Vector2D.h Vector2D.cc class Vector2D { public: Vector2D& copia(const Vector2D& ); // ... private: double x_, y_; }; Vector2D& copia(const Vector2D& v){ x_=v.x(); y_=v.y(); return *this; } main.cc #include “Vector2D.h” int main() { Vector2D null(0, 0); Vector2D a, b; a.copia(b.copia(null)); } L’operatore copia ritorna una referenza a se stesso. Permette copie multiple 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
static • Attributi dichiarati staticin 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)
Un membro statico deve essere inizializzato una e una sola volta nel codice eseguibile Un metodo statico puo` essere invocato cosi`... … o cosi`... Un contatore Class MyClass { private: static int counter; static void increment_counter() { counter++; } static void decrement_counter() { counter--; } public: MyClass() { increment_counter(); } ~MyClass() { decrement_counter(); } static int HowMany() { return counter; } }; #include <iostream> using namespace std #include “MyClass.h” int MyClass::counter=0; int main() { MyClass a,b,c; MyClass *p=new MyClass; cout<<“ How many? “<< MyClass::HowMany() <<endl; delete p; cout<<“ and now? “<< a.HowMany() <<endl; return 0; }
Un singleton • Un singleton è una classe di cui, in ogni momento nel corso del programma, non può esistere più di una copia (istanza) class aSingleton { private: static aSingleton *ptr; aSingleton () {} public: static aSingleton *GetPointer(){ if (ptr==0) ptr=new aSingleton; return ptr; } }; Pattern utile per l’implementazione di classi “manager” di cui deve esistere una sola istanza #include “aSingleton.h” aSingleton *aSingleton::ptr=0; int main() { aSingleton *mySing= aSingleton::GetPointer(); . . . Return 0; } Attenzione a non farlo diventare l’equivalente di un common block!