Elementi di
programmazione
ad oggetti
a. a. 2009/2010
Corso di Laurea Magistrale in Ingegneria Elettronica
Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica,
Gestionale e dell’Automazione
Lezione 4
Introduzione alla progettazione e programmazione ad
oggetti: classi ed oggetti, notazione UML.
Natura del software

Il software è una soluzione temporanea ad
un problema



Dinamico
Complesso
Per affrontare la complessità, si suddivide il
software in componenti maneggevoli e
interagenti


Composizione: processo di costruzione a partire
da parti semplici
Astrazione: trattare le componenti senza
interessarsi dei dettagli di come siano costruite

Visione limitata alle poche proprietà essenziali,
ignorando i dettagli irrilevanti
Dati e funzionalità

I dati che un programma tratta si
distinguono
Le descrizioni dei dati sono fissate
 I valori individuali dei dati cambiano ogni
volta


Le funzionalità di un programma
determinano cosa si può fare con i
dati
Progettazione orientata agli
oggetti
Gli oggetti sono astrazioni usate per
creare un modello del mondo reale
 Le funzionalità sono distribuite sugli
oggetti


Ogni oggetto ha la responsabilità di
eseguire certe specifiche funzionalità
 L’allocazione
delle responsabilità sugli
oggetti è una parte critica di una buona
progettazione

Il sistema software è costituito da un
insieme di oggetti interagenti
Oggetti
La collaborazione tra gli oggetti
avviene tramite uno scambio di
messaggi
 Ogni oggetto ha

Un nome
 Delle proprietà

 Dati,
costanti e variabili, conosciuti
dall’oggetto, di cui rappresentano lo stato

Dei metodi
 Funzioni
e procedure eseguibili dall’oggetto
Messaggi
Ogni oggetto conosce delle
informazioni ed è in grado di svolgere
alcuni compiti
 Gli oggetti si scambiano messaggi,
corrispondenti a richieste di servizi


In risposta alle richieste, gli oggetti
eseguono le operazioni e forniscono i
risultati
Tipologie di messaggi

Esistono due tipologie di messaggi
Richieste di esecuzione di metodi
 Richieste di accesso alle proprietà
dell’oggetto

 Limitatamente
a ciò che l’oggetto espone
Dati e stato

I dati hanno una definizione ed un
valore associato
Le definizioni sono fisse, i valori associati
cambiano nel tempo
 Variabili di istanza

 Lo
spazio di memoria usato per memorizzare
tutte le variabili viene allocato al momento
della creazione dell’oggetto

Lo stato di un oggetto è contenuto
nei suoi dati
 Insieme
istanza
dei valori associati alle variabili di
Notazione UML

L’UML è il linguaggio di modellazione
standard per il progetto di sistemi software

Comprende diversi tipi di diagrammi usati per
modellare diversi aspetti












Casi d’uso
Attività
Classi
Sequenze
Comunicazione
Sincronizzazione
Interazione
Struttura composita
Componenti
Pacchetti
Stati
Installazione
Classi in UML
Notazione UML

Attributi: visibilità nome : tipo

Visibilità
+
public
 # protected
 ~ package
 - private

Operazioni: visibilità nome
(parametri) : tipo di ritorno
Definizione di una classe C++

La definizione di classe è composta da
Testa: class <nome_classe>
 Corpo (racchiuso tra parentesi graffe)
 Punto e virgola o dichiarazioni, es.:

 class
Class1 { /* … */ };
 class Class2 { /* … /* } var1, var2;

Il corpo della classe contiene i
membri
Dati
 Funzioni membro

Definizione di metodi e
operatore di scope

Dichiarazione e definizione nella
classe


Dichiarazione nella classe


<tipo di ritorno> <nome metodo>
(<parametri>) { <corpo> };
<tipo di ritorno> <nome metodo>
(<parametri>);
Definizione del metodo

<tipo di ritorno> <nome
classe>::<nome metodo>(<parametri>)
{ <corpo> }
Membri
I dati sono dichiarati nello stesso
modo in cui vengono dichiarate le
variabili
 I metodi sono funzioni dichiarate
dentro il corpo della classe


Detti anche funzioni membro
Instanziazione

L’operazione di creazione di un
oggetto di una certa classe è detta
instanziazione

Un oggetto è instanza di una classe
 Occupa


uno spazio in memoria
Due oggetti della stessa classe
differiscono per lo stato ma hanno gli
stessi metodi
Quando si dichiara una variabile del
tipo di una classe, viene istanziata
Inizializzazione di una classe

Per inizializzare lo stato di una classe,
si utilizzano speciali funzioni membro
di inizializzazione: i costruttori
Un costruttore è una funzione membro
con lo stesso nome della classe e senza
tipo di ritorno
 È frequente sovraccaricare il costruttore
 La maggior parte delle classi ha un
costruttore di default, senza parametri
 Un costruttore può anche non essere
public

Esempio: costruttori
class Counter
{
int count;
public:
Counter() {
count = 0;
}
Counter(int inizio) {
count = inizio;
}
};
Costruttore di default

Un costruttore di default è un costruttore che può essere
invocato senza parametri




Quando si scrive


Account::Account() { /* … */ }
Stack::Stack(int size = 0) { /* … */ }
Complex::Complex(double r = 0.0, double i = 0.0) { /* … */ }
Account a;
… viene chiamato il costruttore di default, se esiste ed è
pubblico



Se esiste un costruttore di default pubblico, OK
Se il costruttore di default non è pubblico, errore di
compilazione
Se non esiste alcun costruttore, OK


I membri non sono inizializzati, hanno il loro vaore di dafault
Se esistono solo costruttori non di default (necessitano di
parametri), errore di compilazione
Distruttore di una classe

Il distruttore è una speciale funzione
membro che viene invocata
automaticamente quando l’oggetto
non è più visibile o viene invocato
delete sull’oggetto
Il costruttore ha il nome della classe
preceduto da ~
 Il distruttore non restituisce alcun valore
e non può avere parametri

 Non
c’è sovraccaricamento
 Anche se ci sono più costruttori, c’è un solo
distruttore
Metodi e visibilità

Una classe definisce uno scope
Una funzione membro sovraccarica solo
le funzioni membro della classe
 Il nome del metodo non è visibile
all’esterno della classe

 Un



metodo si invoca come
oggetto.metodo( /* … */ );
puntatoreOggetto->metodo( /* … */ );
I metodi di una classe hanno accesso
completo ai membri di una classe
 Ma
non a quelli delle altre classi
Accesso ai membri



Il principio dell’information hiding o
incapsulamento dei dati consiste nell’impedire
l’accesso diretto all’implementazione interna di una
classe
Per default tutti gli elementi definiti in una classe
sono privati, ovverro visibili solo all’interno della
classe
La parola chiave public delimita le variabili e
metodi pubblici
class nomeClasse {
int d[300];
top = 0;
public:
void push(int);
int pop();
}
Controllo dell’accesso

Le parti della dichiarazione della
classe contenenti i membri con
diversa visibilità sono delimitate dalle
etichette

Public
 Un
membro pubblico è accessibile da
qualunque parte del programma

Private
 Un
membro privato è accessibile solo
all’interno della classe

Protected
Esempio: contatore
class Counter
{
int count;
public:
Counter(void) {
count = 0;
}
int getCount() {
return count;
}
void incrementCounter() {
count++;
}
void reset() {
count = 0;
}
};
int main()
{
Counter *c = new
Counter();
std::cout << c->getCount()
<< std::endl;
c->incrementCounter();
std::cout << c->getCount()
<< std::endl;
c->incrementCounter();
std::cout << c->getCount()
<< std::endl;
c->reset();
std::cout << c->getCount()
<< std::endl;
system("pause");
return 0;
}
Interfaccia e implementazione

I membri pubblici di una classe ne
costituiscono l’interfaccia


Definiscono dati e funzionalità visibili
dagli utilizzatori della classe
Le definizioni dei membri ed i membri
privati ne costituiscono
l’implementazione
Puntatore this

Le funzioni membro di una classe
hanno un parametro implicito
Puntatore this, punta all’istanza
dell’oggetto
 Il compilatore trasforma le funzioni
membro in sottoprogrammi a cui viene
passato un parametro in più

Membri statici

Un membro statico appartiene alla
classe, non ad una specifica istanza
È accessibile da tutti gli oggetti della
classe
 Ne esiste una sola istanza per tutta la
classe
 È comunque nel namespace della classe

 Rimane


l’information hiding
Un membro static può essere anche private
Un metodo static non ha come
parametro implicito in puntatore this
Esempio: contatore static
class StaticCounter
{
static int count;
public:
static int getCount() {
return count;
}
static void incrementCounter() {
count++;
}
static void reset() {
count = 0;
}
void f() {
count++;
}
};
Funzioni friend

La parola chiave friend identifica una
funzione che ha accesso ai membri
privati di una classe
Nella sezione public, si mette il prototipo
preceduto dalla parola chiave friend
 Una funzione friend non è una funzione
membro

 Non
ha il parametro implicito this
 Ha visibilità anche fuori dalla classe
 Una funzione può essere friend di più di una
classe
Esempio: friend
class Matrix
{
int m;
int n;
double *data;
public:
friend Vector *prod(Matrix*, Vector*);
}
class Vector
{
int n;
double *data;
public:
Vector(int n) : n(n) {
data = new double[n];
}
friend Vector *prod(Matrix*, Vector*);
};
Vector *prod(Matrix *m, Vector *v) {
Vector *result = new Vector(v->n);
for (int i = 0; i < m->m; i++) {
result->data[i] = 0;
for (int j = 0; j < m->n; j++)
result->data[i] += m->data[i, j] * v->data[j];
}
return result;
}
Metodi const

Un metodo dichiarato const non può
modificare lo stato dell’oggetto
int getCount() const { return
count; }
 Costruttori e distruttori non possono
essere const!
 Se un metodo non dovrebbe aver
bisogno di modificare lo stato, è buona
norma dichiararlo const

 salvo
rimuovere il const se consapevolmente
e a ragion veduta vogliamo che possa
modificare lo stato
Classi annidate

Si può inserire la dichiarazione di una
classe dentro la dichiarazione di
un’altra classe
Una classe annidata è membro della
classe
 La sua visibilità può essere public,
private, protected
 Il nome della classe è nel campo
d’azione della classe che la racchiude

Esempio: classi annidate
class Node {/*...*/};
class Tree {
public:
// Node è incapsulata nello scope di Tree
// nel campo di azione della classe, Tree::Node
// nasconde ::Node
class Node {/*...*/};
Node *tree;
};
//Tree::Node non è visibile nello scope globale
//Node si risolve nella dichiarazione globale di Node
Node *pNode;
class List {
public:
// Node è incapsulato nello scope di List
// nel campo di azione della classe, List::Node
// maschera ::Node
class Node {/*...*/};
Node *List;
};
Risoluzione dei nomi in classi
annidate

Per risolvere un nome usato in una
classe annidata, si considerano
Prima, i nomi dichiarati nella classe
annidata
 Poi, i nomi definiti nella classe che la
racchiude
 Infine, i nomi nel namespace che
racchiude la classe che la racchiude

 Una
classe può essere definita dentro
un’altra classe, in un namespace…
Classi locali

Una classe può essere definita anche
nel corpo di una funzione
Classe locale
 Visibile solamente nel campo d’azione
globale dove è definita

 Le
funzioni membro vanno definite
direttamente dentro la classe
 Una classe locale non può dichiarare membri
statici
Oggetti passati come parametro
Un oggetto può essere usato come
tipo di un parametro
 Gli oggetti vengono passati per valore

Le modifiche all’oggetto non sono visibili
fuori dalla funzione
 Possibili problemi a causa di effetti
collaterali

 Es.
un oggetto che alloca memoria dinamica
per poi rilasciarla; la sua copia rilascia la
medesima memoria, danneggiando l’oggetto
principale
Esempio: oggetto come
parametro
#include <iostream>
#include <stdlib>
using namespace std;
class myclass {
int *p;
public:
myclass (int i);
~myclass();
int getval() {return *p;}
};
myclass::myclass(int i) {
cout << “Allocazione di p\n”;
p = new int;
if (!p) {
cout << “Impossibile
allocare”;
exit(1);
}
*p=i;
}
myclass::~myclass()
{
cout << “Rilascio di p\n”;
delete p;
}
// questo provocherà un problema
void display(myclass ob)
{
cout << ob.getval() << ‘\n’;
}
main()
{
myclass a(10);
display(a);
return 0;
}
Oggetti passati come parametro

Gli oggetti passati come parametro
sono normalmente passati tramite
puntatori o riferimenti
// questo non provocherà
problemi
void display(myclass& ob) {
cout << ob.getval() << ‘\n’;
}
Oggetti come tipi di ritorno
Quando il tipo di ritorno di una
funzione è un oggetto, viene creato
automaticamente un oggetto
temporaneo
 Dopo che l’oggetto viene restituito,
viene distrutto


Altro caso in cui la distruzione può
provocare effetti collaterali indesiderati:
se rilascia le risorse, l’oggetto restituito
non è più utilizzabile
Esempio: funzione che
restituisce oggetto
class CharBuffer
{
char *s;
public:
CharBuffer(void) { s = '\0'; }
~CharBuffer(void) {
if (s)
delete(s);
std::cout << "rilascio di s" <<
std::endl;
}
void show() { std::cout << s <<
std::endl; }
};
void set(char *str) {
s = new char[strlen(str)+1];
if (!s) {
std::cout << "Impossibile allocare"
<< std::endl;
exit(1);
}
strcpy(s, str);
}
CharBuffer input()
{
char instr[80];
CharBuffer result;
std::cout << "Inserire una
stringa: ";
std::cin >> instr;
result.set(instr);
return result;
}
int main()
{
CharBuffer ob;
ob=input(); // questo causa
un errore
ob.show();
return 0;
}
Funzione che restituisce
oggetto: soluzioni
Restituire un puntatore o un
riferimento
 Usare un costruttore di copia
 Usare un operatore di assegnamento
soggetto a overloading

Costruttore di copia

È sempre possibile copiare un oggetto
CharBuffer c = d;
 La copia di un oggetto viene realizzata
copiando ogni membro

 Non

sempre la copia bit per bit è adeguata
Si può definire un tipo particolare di
costruttore (soggetto a overloading)
 CharBuffer::CharBuffer(const
CharBuffer& c)
Esempio: costruttore di copia
class CharBuffer
{
char *s;
bool copy;
public:
CharBuffer(void) {
s = 0;
copy = false;
}
CharBuffer(const CharBuffer& c) {
if (c.s) {
s = new char[strlen(c.s)+1];
if (!s) {
std::cout << "Impossibile
allocare" << std::endl;
exit(1);
}
strcpy(s, c.s);
} else
s = 0;
copy = true;
}
~CharBuffer(void) {
if (!copy) {
if (s)
delete(s);
std::cout << "rilascio di s" <<
std::endl;
} else
copy = false;
}
void show() {
std::cout << s << std::endl;
}
void set(char *str) {
s = new char[strlen(str)+1];
if (!s) {
std::cout << "Impossibile
allocare" << std::endl;
exit(1);
}
strcpy(s, str);
}
};
Union

Una union è un particolare tipo di
classe in cui i dati vengono
memorizzati sovrapposti
L’occupazione di memoria è pari a quella
del membro più grande
 È possibile assegnare un valore a solo un
membro alla volta

Esempio: union
union PackedNumber
{
int i;
double d;
public:
PackedNumber(int i) : i(i) { }
PackedNumber(double d) : d(d) { }
int getInt() {
return i;
}
double getDouble() {
return d;
}
};
Scarica

Lezione 4