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 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 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
costruttore
class Vector2D
{
public:
Vector2D(double x, double y);
double x();
double y();
Vector2D.cc
double r();
double phi();
#include “Vector2D.h”
private:
#include <math.h>
double x_;
Vector2D::Vector2D(double x, double y): x_(x),
double y_
y_(y) {
};
}
double Vector2D::x() {
return x_;
}
funzioni o metodi
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
#include “Vector.h”
gli unici visibili
Vector2D.h
class Vector2D
{
public:
Vector2D(double x, double y);
double x();
double y();
double r();
double phi();
private:
double x_;
double y_;
};
Vector2D::Vector2D(double x,
double y) :
x_(x), y_(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 <iostream>
using namespace std;
#include “Vector2D.h”
invoca il constructor
int main()
{
Vector2D 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 Vector2D
• … oppure attraverso un puntatore...
main.cc
#include <iostream>
using namespace std
#include “Vector2D.h”
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 dalla struttura
interna dei dati
• 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 <iostream>
using namespace std
#include “Vector2D.h”
int main()
{
Vector2D v(1, 1);
cout <<
<<
<<
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
Vector2D.cc
Vector2D.h
class Vector2D
#include “Vector2D.h”
{
public:
void Vector2D::scale(double
Vector2D(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 “Vector2D.h”
double x_, y_;
modificatore
int main()
};
{
const Vector2D 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 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
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;
}
L’operatore copia ritorna una
referenza a se stesso.
Permette copie
multiple
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; }
};
Un membro statico deve essere
#include <iostream>
inizializzato una e una sola volta
using namespace std
#include “MyClass.h”
nel codice eseguibile
int MyClass::counter=0;
Un metodo statico puo` essere
invocato cosi`...
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;
}
… 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 aSingleton {
Pattern utile per
private:
static aSingleton *ptr;
l’implementazione di
aSingleton () {}
classi “manager” di cui
public:
static aSingleton *GetPointer(){
deve esistere una sola
if (ptr==0)
istanza
ptr=new aSingleton;
return ptr;
#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
Scarica

Lezione introduttiva sulle classi in C++ - ICAR-CNR