Transcript Lezione introduttiva sulle classi in C++ - ICAR-CNR
Le classi
Definizione di classe Attributi e metodi di una classe Costruttori e distruttori Private e public Funzioni friend Il puntatore this 1
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:
Dato Dato Dato
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 chi 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
della memoria...
non ottimizza l’uso 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 non dall’implementazione ma 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
• 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 MyHeader_H #define MyHeader_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 3D,...
• 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++
Attributo Attributo Attributo
14
Classe Vector2D
• Un esempio: un vettore bidimensionale Vector2D.h
class Vector2D { public: private: } ; Vector2D(double x, double y); double x(); double y(); double r(); double phi(); double x_; double y_
Vector2D.cc
#include “Vector2D.h” #include
costruttore funzioni o metodi
double Vector2D::x() { y_ (y) { }
dati o attributi Punto e virgola!
double Vector2D::r() { return sqrt( x_ * x_ + y_ * y_ ); } ...
15
Interfaccia e implementazione
• Gli attributi privati non sono accessibili al di fuori della classe Vector2D.cc
• I metodi pubblici sono gli unici visibili
#include “Vector.h” Vector2D::Vector2D(double x, double y) : 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_; }; double Vector2D::x() { return x_; } double Vector2D::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
Vector2D::Vector2D() {. . . .} // costruttore di default #include “Vector2D.h” . . .
Vector2D 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)
Vector2D::Vector2D(const Vector2D& v) {. . . .} Vector2D v(v1); // dove v1 e` di tipo Vector2D
• 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
Vector2D::Vector2D(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
Vector2D::~Vector2D() {} // vuoto, in questo caso
20
Costruttori e distruttori (5)
• I costruttori con un solo parametro sono automaticamente trattati come operatori di conversione
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);
• Per evitare la conversione si puo` usare
explicit
explicit Vector2D(int); // solo costruttore
21
Classe Vector2D
• Come usare
Vector2D
: main.cc
#include
invoca il constructor
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
22
Classe Vector2D
• … oppure attraverso un puntatore...
main.cc
#include
Allocazione sullo heap
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;
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
Vector2D
sono
nascosti
(
private
) agli utilizzatori della classe.
• Gli utilizzatori
non dipendono
interna dei dati dalla struttura • Se la struttura interna cambia (es.:
r_
,
phi_
), il codice che usa
Vector2D
non deve essere modificato.
24
Classe Vector2D
• Protezione dell’accesso ai dati: main.cc
#include
<< “,” // << v.y_
<< “,” << endl; // non compila !
cout << “ r = “ << v.r(); cout << “ 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 Vector2D.cc
Vector2D.h
class Vector2D { public: Vector2D(double x, double y); double x() const ; double y() const ; double r() const ; double phi() private : const double x_, y_; ; void scale(double s); };
Selettori (
#include “Vector2D.h” void Vector2D::scale(double s) { x_ *= s; y_ *= s; } const
modificatore ) main.cc
#include “Vector2D.h” int main() { const Vector2D v(1, 0); double r = v.r() // OK v.scale( 1.1 ); // 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 aFunc(); 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 Vector2D.h
class Vector2D { public: Vector2D& copia(const Vector2D& ); // ...
private: double x_, y_; };
L’operatore copia ritorna una referenza a se stesso. Permette copie multiple Vector2D.cc
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)); }
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 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
Un membro statico deve essere inizializzato una e una sola volta nel codice eseguibile
int MyClass::counter=0;
Un metodo statico puo` essere
int main() { MyClass a,b,c; MyClass *p=new MyClass; cout<<“ How many? “<<
invocato cosi`...
MyClass::HowMany() <
… o cosi`...
31
Un singleton
• Un
class aSingleton { private: public:
singleton
è una classe di cui, in ogni momento nel corso del programma, non può esistere più di una copia (istanza)
static aSingleton *ptr; aSingleton () {}
Pattern utile per l’implementazione di classi “manager” di cui
static aSingleton *GetPointer(){ if (ptr==0) ptr=new aSingleton; return ptr;
deve esistere una sola istanza
#include “aSingleton.h” } }; aSingleton *aSingleton::ptr=0;
Attenzione a non farlo diventare l’equivalente di un common block!
int main() { aSingleton *mySing= aSingleton::GetPointer(); . . . Return 0; }
32