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

Download Report

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 Vector2D::Vector2D(double x, double y): x_ (x), } return x_ ;

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 using namespace std; #include “Vector2D.h”

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 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

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 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;

• 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 using namespace std #include “MyClass.h”

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