Sei sulla pagina 1di 97

Agostino De Marco

Appunti di programmazione
a oggetti in C++ e Matlab

ver.2009.d Copyright © A. De Marco


DRAFT

Maggio 2009
ver. 2009.d
DRAFT ver.2009.d Copyright © A. De Marco
Indice

Introduzione 5

I Introduzione alla programmazione a oggetti 9


1 Tecniche di programmazione supportate dal C++ 11
1.1 Paradigmi di programmazione . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Programmazione procedurale . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.1 Procedure e funzioni . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2.2 Parametri di scambio e campi di visibilità . . . . . . . . . . . . . 13
1.2.3 Hello world! . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.2.4 Un semplice programma C++ con procedure . . . . . . . . . . . 17
1.3 Programmazione modulare . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.3.1 Creare moduli attraverso gli spazi dei nomi . . . . . . . . . . . . 19
1.3.2 Lo spazio dei nomi della libreria standard . . . . . . . . . . . . . 20
1.3.3 Compilazione separata . . . . . . . . . . . . . . . . . . . . . . . 21
1.3.4 Gestione delle eccezioni . . . . . . . . . . . . . . . . . . . . . . 23
1.4 Astrazione dei dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.4.1 Tipi definiti dall’utente . . . . . . . . . . . . . . . . . . . . . . . 25
1.4.2 Tipi concreti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.4.3 Tipi astratti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
1.4.4 Funzioni virtuali . . . . . . . . . . . . . . . . . . . . . . . . . . 40
1.5 Programmazione orientata agli oggetti . . . . . . . . . . . . . . . . . . . 41
1.5.1 Un esempio: l’aerodinamica di un velivolo completo . . . . . . . 41
1.5.2 Dalle classi concrete alle gerarchie di classi . . . . . . . . . . . . 46
ver.2009.d Copyright © A. De Marco

1.6 Cosa non abbiamo detto? . . . . . . . . . . . . . . . . . . . . . . . . . . 49

2 Ancora sulla programmazione in C++ 51


2.1 Elementi di base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2.1.1 Interfaccia dei programmi con il sistema operativo . . . . . . . . 52
2.1.2 Le funzioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.1.3 Tipi nativi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.1.4 Il preprocessore e le unità di traduzione dei programmi . . . . . . 55
2.2 Il mondo delle classi e degli oggetti . . . . . . . . . . . . . . . . . . . . 59
2.2.1 Tipi definiti dall’utente e terminologia . . . . . . . . . . . . . . . 59
DRAFT

2.2.2 Le strutture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.2.3 Dalle strutture alle classi . . . . . . . . . . . . . . . . . . . . . . 62
4 Indice

2.3 La libreria standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62


2.4 Le librerie Boost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.5 Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
2.5.1 Una classe completa per la lettura dati da file . . . . . . . . . . . 63
2.6 Come imparare il C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

3 Strumenti di sviluppo 69
3.1 Il Compilatore gcc in ambiente GNU/Linux o Cygwin . . . . . . . . . . 69
3.1.1 Il progetto GNU . . . . . . . . . . . . . . . . . . . . . . . . . . 69
3.1.2 I passi della compilazione . . . . . . . . . . . . . . . . . . . . . 70
3.1.3 L’utilità make . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.2 Ambienti integrati di sviluppo (IDE) . . . . . . . . . . . . . . . . . . . . 71
3.2.1 L’ambiente di sviluppo Microsoft Visual Studio . . . . . . . . . . 72
3.2.2 L’ambiente di sviluppo Code::Blocks . . . . . . . . . . . . . . . 72

II Programmazione a oggetti con Matlab 77


4 Una panoramica su Matlab 79
4.1 Elementi di base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.2 Toolboxes e Simulink . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.3 Sessioni di lavoro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.4 Usare l’help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.5 Nozioni e notazioni fondamentali . . . . . . . . . . . . . . . . . . . . . . 84
4.5.1 Le variabili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.5.2 Matrici e vettori . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.5.3 Altri tipi di dati . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.5.4 Le funzioni in Matlab . . . . . . . . . . . . . . . . . . . . . . . . 88
4.6 Operazioni di Input / Output . . . . . . . . . . . . . . . . . . . . . . . . 91

5 Programmazione a oggetti in Matlab 93


5.1 Strutture dati di Matlab . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.2 Classi e oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.3 Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
ver.2009.d Copyright © A. De Marco
DRAFT

A. De Marco
Introduzione

Questo non è un manuale di programmazione C++ o di Matlab, ci vorrebbe troppo tempo


e impegno per scriverne uno vero. Ho solo raccolto alcune esperienze maturate nel corso
degli ultimi anni di lavoro con questi linguaggi. Alcuni esempi sono molto semplici, ma
dovrebbero servire a mettere in luce alcuni degli aspetti più importanti che riguardano la
programmazione a oggetti.
Di sicuro questi appunti non sono una guida per il principiante, anche se ci sono tutte
le indicazioni fondamentali su come reperire informazioni sull’uso corretto dei comandi;
tuttavia anche chi è agli inizi potrebbe trovare qualche spunto interessante per avvicinarsi
al mondo delle classi, degli oggetti, dei puntatori e della programmazione generica.
La trattazione di alcune tecniche di programmazione orientata agli oggetti si serve dei
linguaggi C++ e Matlab. Ovviamente, esistono già numerosi manuali che si occupano
di C++, sia a livello introduttivo che a livello avanzato. Anche sull’ambiente Matlab e
sul suo linguaggio di programmazione esiste una vastissima possibilità di documentarsi,
dal potente sistema di help online ai numerosi manuali e libri di testo. Va osservato però
che i manuali di livello avanzato, in particolare per il C++, molto spesso sono destinati a
programmatori professionisti o, in generale, alla platea degli utenti informatici di media ed
elevata esperienza. Il lettore troverà una lista dei manuali più importanti nella bibliografia,
primo fra tutti il libro di Bjarne Stroustrup [1], il ricercatore di origine danese che ha
ideato il linguaggio.
È pur vero, d’altra parte, che per gli studenti di ingegneria dell’area industriale (mec-
canica, aerospaziale, navale, chimica, gestionale, eccetera) non è facile trovare manuali
che trattino la programmazione in C++ in maniera adeguata al loro percorso formativo.
Spesso i corsi di informatica impartiti ai primi anni delle facoltà di ingegneria finiscono
per utilizzare il linguaggio C++ come mero strumento di codifica, con il quale mostrare ver.2009.d Copyright © A. De Marco
gli aspetti di base del mondo della programmazione dei calcolatori elettronici. Ad esem-
pio, quasi sempre si propone l’esercizio di implementare algoritmi di ordinamento di un
vettore o di manipolazione di matrici. Per la soluzione di simili problemi nei corsi di
informatica si propone spesso il C++ perché è facile trovare strumenti di lavoro interattivi
gratuitamente scaricabili da internet — come, ad esempio, Dev-C++ [29], Code::Blocks
[28] o Microsoft Visual C++ (200X) Express [27] — che permettono di imparare a com-
pilare codice C++ in poco tempo e offrono la possibilità di mettere subito in pratica i
concetti generali della programmazione. Sia chiaro, ciò è cosa buona e giusta, anzi è
assolutamente necessario per i programmatori alle prime armi, e rientra negli scopi dei
corsi sui fondamenti dell’informatica. Ciò che gli studenti spesso non arrivano a com-
prendere, però, è la straordinaria potenza di un linguaggio come il C++, che supporta la
DRAFT

programmazione a oggetti e permette di esplorare delle tecniche di programmazione al-


lo stesso tempo avanzate ed eleganti. Non è raro ascoltare uno studente del terzo anno
6 Introduzione

di ingegneria aerospaziale che dichiara di aver studiato il C++ ma nega di conoscere il


concetto di classe. Per chi conosce la programmazione a oggetti ciò risulta quanto meno
sorprendente. Da questo punto di vista ho ritenuto necessario, dunque, proporre un’o-
perazione di “ampliamento” delle conoscenze informatiche del lettore meno esperto, ma
comunque in possesso delle nozioni di base sulla programmazione, introducendo con i
modi dell’ingegnere la programmazione a oggetti, prima in C++ e successivamente nel
linguaggio Matlab (che la maggioranza degli ingegneri conosce).
Come poter fare esperienza al riguardo? Ponendosi, ad esempio, un problema concre-
to e decidendo di risolverlo — se è il caso di farlo — con una tecnica di programmazione
orientata agli oggetti, attraverso un programma di calcolo in C++ o in Matlab.
La prima parte di questi appunti tratta della programmazione orientata agli oggetti
(Object-Oriented Programming, OOP) e si serve del linguaggio C++ sia per spiegarne i
vari aspetti sia per fornire direttamente degli esempi pratici.
La seconda parte, dando per noti i principi di base della programmazione Matlab, ne
presenta le caratteristiche (alquanto recenti) che permettono di costruire classi e svilup-
pare programmi “a oggetti”.
Ho cercato di essere il meno formale possibile e mi sono limitato a dare esempi con-
creti, con qualche omissione di parti di codice troppo lunghe da riportare integralmente e
non fondamentali ai fini della comprensione del particolare concetto. Spero che ciò non
porti a maggiore confusione. Non si troverà qui una descrizione formale della grammatica
dei linguaggi C++ e Matlab, per le quali si rimanda ai manuali specialistici.
* Le parti di testo in carattere ridotto trattano argomenti su cui è bene tornare solo dopo aver digerito il
resto, oppure contengono questioni meno importanti di quelle esposte in corpo normale.

Alcune spiegazioni potrebbero essere poco chiare: ne chiedo scusa e cercherò di


rimediare in future edizioni. Commenti e richieste possono essere inviate all’indirizzo

agostino dot demarco at unina dot it

Sicuramente ci saranno sviste e imprecisioni: sarò lieto di correggerle, sperando di non


causare problemi a chi leggerà queste note.
Un commento alla veste grafica. Questo documento è stato composto con PDFLATEX.
Si è usata, con poche modifiche, la classe book; i caratteri impiegati sono Times, Helvetica
e Bera Mono.
ver.2009.d Copyright © A. De Marco

Napoli
aprile 2009
DRAFT

A. De Marco
Appunti di programmazione
a oggetti in C++ e Matlab

ver.2009.d Copyright © A. De Marco


DRAFT
DRAFT ver.2009.d Copyright © A. De Marco
Parte I

Introduzione alla
programmazione a oggetti

ver.2009.d Copyright © A. De Marco


DRAFT
[: : :] non cè niente di più difficile da realizzare, né il cui successo sia meno
sicuro, né più difficile da maneggiare, che iniziare un nuovo ordine di cose.
Perché il riformatore si crea nemici tra tutti quelli che traggono vantaggio dal
vecchio ordine, e solo tiepidi sostenitori tra tutti quelli che trarrebbero
vantaggio dal nuovo ordine [: : :]
– Niccolò Macchiavelli
(da Il principe, §vi)
ver.2009.d Copyright © A. De Marco
DRAFT
Capitolo 1

Tecniche di programmazione
supportate dal C++

Programmare è capire.
– Kristen Nygaard

Indice
1.1 Paradigmi di programmazione . . . . . . . . . . . . . . . . . . . . . 11
1.2 Programmazione procedurale . . . . . . . . . . . . . . . . . . . . . 12
1.3 Programmazione modulare . . . . . . . . . . . . . . . . . . . . . . . 18
1.4 Astrazione dei dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.5 Programmazione orientata agli oggetti . . . . . . . . . . . . . . . . 41
1.6 Cosa non abbiamo detto? . . . . . . . . . . . . . . . . . . . . . . . . 49

1.1 Paradigmi di programmazione


In informatica, una tecnica di programmazione — un paradigma per scrivere “buoni”
programmi per un insieme di problemi — è uno stile fondamentale di programmazione,
ovvero un insieme di strumenti concettuali per la stesura di programmi.
Lo stile di programmazione definisce e determina il modo in cui il programmato-
ver.2009.d Copyright © A. De Marco

re concepisce e percepisce il programma. Per esempio, un programma scritto secondo


il paradigma della “programmazione a oggetti” (Object-Oriented Programming, OOP)
è costituito da oggetti che interagiscono fra loro. Diversamente, un programma svilup-
pato secondo il paradigma della “programmazione funzionale” è una composizione di
funzioni.
Ogni linguaggio di programmazione è generalmente ispirato (e riconducibile) a un
particolare paradigma di programmazione. Per esempio, il Pascal e il C sono basati sul
paradigma della programmazione strutturata, il linguaggio Lisp su quello della program-
mazione funzionale, il Prolog su quello della programmazione logica e così via. Alcu-
ni linguaggi di programmazione sono influenzati da molteplici paradigmi; un esempio
DRAFT

macroscopico è dato dal linguaggio Ada, che fu esplicitamente progettato per incorpora-
re diversi concetti provenienti dalla programmazione strutturata, dalla programmazione
12 Capitolo 1 Tecniche di programmazione supportate dal C++

modulare, dalla programmazione generica, dalla programmazione concorrente, e dalla


programmazione per tipi di dati astratti.
A questo punto occorre fare un’importante distinzione. Si dice che un linguaggio
supporta un dato stile di programmazione se fornisce funzionalità che rendono conve-
niente (ragionevolmente facile, sicuro ed efficiente) l’utilizzo di tale stile. Il linguaggio
C++ è stato progettato sia per consentire una efficiente programmazione di sistema (in-
globando praticamente le caratteristiche del linguaggio C) sia per fornire meccanismi che
ben supportassero la programmazione a oggetti (si dice che il C++ è un linguaggio di
programmazione “orientato agli oggetti”).
Al contrario, un linguaggio non supporta una data tecnica se per applicarla vengo-
no richiesti uno sforzo o un’abilità straordinari; un simile linguaggio non supporta, ma
semplicemente permette di utilizzare tale tecnica. Per esempio si possono scrivere pro-
grammi orientati agli oggetti in C, ma questo richiede uno sforzo improbo, in quanto tale
linguaggio non supporta direttamente questa tecnica. Supportare un paradigma non ha
solo il significato ovvio di mettere a disposizione funzionalità che permettano di usarlo
direttamente, ma anche quello, più sottile, di fornire meccanismi di controllo statico e/o
dinamico su eventuali violazioni del paradigma.
* Il controllo dei tipi rappresenta l’esempio più ovvio di quanto detto. L’individuazione di ambiguità e i
controlli dinamici sono anche utilizzati per estendere il supporto linguistico ai paradigmi di programmazio-
ne. Funzionalità extra-linguistiche, quali librerie e ambienti integrati di sviluppo, possono fornire ulteriore
supporto ai paradigmi.

Va ribadito che il C++ è stato progettato per supportare l’astrazione dei dati, la pro-
grammazione orientata agli oggetti e la programmazione generica, oltre alle tecniche di
programmazione C tradizionali. Non è stato pensato invece con l’intento di forzare tutti i
programmatori a seguire un particolare stile di programmazione.
I paragrafi seguenti prendono in considerazione alcuni stili di programmazione, dal
paradigma della programmazione procedurale al paradigma della programmazione a og-
getti. Un aspetto importante sarà quello di arrivare a distinguere due tra le tecniche più
usate per il design dei programmi: il cosiddetto approccio Top-Down, tipico della pro-
grammazione strutturata e procedurale, e l’approccio Bottom-Up, tipico della program-
mazione a oggetti.
Allo stesso tempo l’attenzione sarà rivolta, inizialmente, al C++ data l’importanza che
questo linguaggio riveste oggi nel campo della programmazione. Più avanti, nella seconda
parte verranno presentate le nuove caratteristiche del linguaggio Matlab che supportano
ver.2009.d Copyright © A. De Marco

la programmazione a oggetti.

1.2 Programmazione procedurale


Il paradigma della programmazione procedurale è il seguente:

Decidi quali procedure ti occorrano;


individua e utilizza i migliori algoritmi possibili.
DRAFT

Il punto focale di questa tecnica è l’elaborazione, cioè l’algoritmo necessario a svolgere


la computazione desiderata.

A. De Marco
1.2 Programmazione procedurale 13

1.2.1 Procedure e funzioni


Nella programmazione procedurale si organizzano i programmi come sequenze o com-
binazioni di sottoprogrammi — in generale funzioni — creando dei blocchi di codice
che interagiscono fra di loro opportunamente. Questi blocchi sono identificati da un no- Sottoprogrammi e
funzioni
me e sono racchiusi da dei delimitatori. Di fatto, i delimitatori definiscono un campo di
visibilità (detto anche scope) delle variabili definite nel blocco di codice.
I vari linguaggi supportano il paradigma di programmazione procedurale fornendo gli
strumenti per passare argomenti alle funzioni e ottenere dei valori di ritorno. A seconda
del linguaggio e dei ruoli che le funzioni e le procedure hanno all’interno del linguaggio
stesso, il nome del generico sottoprogramma sarà proprio il nome di una funzione o di
una procedura. Ad esempio nel linguaggio Fortran un sottoprogramma, denominato ad
esempio Task1, è delimitato dalle istruzioni SUBROUTINE Task1 ed END SUBROUTINE. In
Fortran esistono anche le funzioni, in altre parole dei sottoprogrammi che ritornano un
valore, definiti dalla parola chiave FUNCTION.
Il nome “programmazione procedurale” deriva dal linguaggio COBOL, che è stato Parametri di scambio

il primo ad utilizzare questo concetto. Le procedure possono essere definite in modo


da accettare argomenti, detti anche parametri di scambio. In fase di esecuzione di un
programma i parametri di scambio sono dati da una lista di variabili fornite alle procedure
attraverso le istruzioni di chiamata. Dal punto di vista dei sottoprogrammi, i parametri
di scambio sono delle variabili locali, i cui valori, oltre che ad essere forniti dall’esterno
del blocco di codice, possono essere eventualmente esportati. Ad esempio, in Fortran le
istruzioni
REAL x1, x2, x3, out
x1 = 1d-1; x2 = -0.5
CALL Task1(x1,x2,out)
x3 = out/2.

potrebbero voler dire che la chiamata alla procedura Task1 attraverso l’istruzione CALL
serve ad eseguire un insieme di calcoli che porteranno ad assegnare un certo valore alla
variabile out. Essa è dichiarata ed è visibile nella parte di codice su riportata ed ha senso Valori esportati
attraverso gli argomenti
utilizzarla dal punto immediatamente successivo alla chiamata a Task1. In questo caso di una procedura
out, che compare come terzo parametro di scabio di Task1, è stata utilizzata per contenere
un valore esportato dalla procedura.
Una procedura nel linguaggio C++ è semplicemente una funzione che non ritorna ver.2009.d Copyright © A. De Marco

valori. Essa assume la forma:


void hNomeProcedurai(hlista di parametrii)
{
hdichiarazioni e istruzionii
}

in cui si esplicita che il tipo di ritorno è void. In altri termini, questa funzione non ritorna
alcun valore.

1.2.2 Parametri di scambio e campi di visibilità


È pensabile concepire due tipi di parametri di scambio per le funzioni e le procedure: Passaggio dei parametri
DRAFT

“per valore” e
quelli scambiati per valore e quelli scambiati per riferimento. Nei primi vengono pas- “per riferimento”
sate delle copie di valori che, se modificati, non vengono comunque salvati al termine

Appunti di programmazione a oggetti in C++ e Matlab


14 Capitolo 1 Tecniche di programmazione supportate dal C++

del sottoprogramma. Nell’esempio precedente in linguaggio Fortran, se le variabili x1 ed


x2 fossero passate a Task1 per valore significherebbe che all’interno di questa sarebbero
visibili due copie dei loro rispettivi valori, 0.1 e -0.5. Una eventuale modifica di questi
ultimi all’interno della definizione della procedura verrebbe persa una volta che il control-
lo del flusso del programma passa dall’istruzione di terminazione di Task1 nuovamente al
codice chiamante (istruzione successiva alla istruzione CALL).
Con i parametri scambiati per riferimento, la procedura riceve l’indirizzo di una data
variabile in memoria, che può quindi essere modificata effettivamente e permanentemen-
te. In Fortran, ad esempio, il passaggio dei parametri di scambio per riferimento avviene
di default, salvo i casi in cui venga esplicitamente richiesto altrimenti nella definizione
Regole di passaggio dei delle SUBROUTINE e delle FUNCTION. In C ed in C++ lo scambio dei parametri avviene
parametri in C e C++
per valore. Quando è necessario scambiare parametri per riferimento si ricorre all’uso dei
puntatori. Nel frammento di codice che segue l’esempio precedente fornito in Fortran
In C++ gli argomenti di viene replicato in linguaggio C++:
tipo puntatore realizzano
lo scambio per float x1, x2, x3;
riferimanto float *out;
x1 = 0.1; x2 = -0.5;
Task1(x1,x2,out);
x3 = (*out)/2.0;

La variabile out di quest’ultimo esempio è di tipo puntatore a float perché dichiarata


attraverso l’operatore * (asterisco) che segue la parola chiave float e precede l’identifi-
catore out. In questo contesto si dice che * è un operatore unario di dichiarazione (da
ritenersi applicato al nome della variabile). Di fatto, la variabile puntatore out contiene
un indirizzo di memoria, in particolare punta ad una porzione di memoria adatta a conte-
nere un valore di tipo float. Perché la funzione Task1 possa esportare, attraverso out, un
valore verso il codice chiamante essa va definita nel modo seguente:
void Task1(float v1, float v2, float *output)
{
int j = 7;
v1 = -0.02; // modificano variabili scambiate
v2 = -j*v1; // per valore (x1 ed x2 non vengono modificate)
// ...
*output = huna qualsiasi espressione che dà un floati;
}
ver.2009.d Copyright © A. De Marco

Le parentesi graffe, { }, esprimono il raggruppamento in C++. In questo caso tali parente-


si indicano l’inizio e la fine del corpo della funzione. Si noti come nella definizione delle
funzioni i parametri di scambio possano essere nominati diversamente dalle corrispon-
denti variabili passate dal codice chiamante. In questo esempio l’istruzione di chiamata è
Task1(x1,x2,out) mentre nella definizione di Task1 si usano i nomi v1, v2 ed output.
Dereferenziazione delle Nell’ultima istruzione della definizione di Task1 l’operatore * che precede output è
variabili puntatore
usato come operatore unario di dereferenziazione (de-referencing): in pratica esso per-
mette di eseguire un’operazione di inserimento di un valore di tipo float in un’area di
memoria puntata da output. Al termine della funzione Task1, quando il controllo passa
al codice chiamante, quest’area di memoria conterrà il valore appena calcolato all’interno
di Task1 e ad esso punterà la variabile out. Ecco il senso di aver dichiarato out come
DRAFT

puntatore a float. Nell’istruzione immediatamente successiva del codice chiamante si


ha x3 = (*out)/2.0 in cui l’operatore * dereferenzia out. In questo caso si dice che con

A. De Marco
1.2 Programmazione procedurale 15

*out si esegue un’operazione di estrazione del valore float puntato dalla variabile out.
Tale valore viene convenientemente usato nell’istruzione di assegnamento della variabile
x3.
In C++ l’operazione opposta di quella di de-referencing è quella cosiddetta di estra- Operatore & di
estrazione dell’indirizzo
zione dell’indirizzo di una variabile. Ciò è realizzato dall’operatore & applicato al nome
di una variabile. Il risultato è un valore che è lecito assegnare ad una variabile di tipo
puntatore. Il seguente frammento di codice è perfettamente lecito:
double x = 3.5;
double *p = &x;

In linguaggi come il Pascal o il Fortran, ad esempio, le procedure sono distinte dalle


funzioni perché non restituiscono un valore associato al nome della procedura stessa, e
non possono quindi apparire a destra di una istruzione di assegnazione. Nel linguaggio
C, così come nel C++, esiste solo la funzione, che può o meno restituire valori; in pratica
una funzione che non restituisce valore è una procedura.
All’interno di un sottoprogramma possiamo generalmente definire delle variabili loca-
li, che vengono deallocate al termine del sottoprogramma stesso (variabili automatiche). Variabili locali alle
funzioni ed alle
Il loro contenuto viene quindi perso se non salvato o trasmesso altrimenti. La variabile procedure
j, dichiarata, definita ed usata nella definizione precedente di Task1, è una variabile auto-
matica. Essa ha campo di visibilità che si estende alla sola definizione della funzione in
cui è dichiarata.
Si osservi che secondo lo standard del linguaggio C++ si può limitare il ciclo di vita
di una qualsiasi variabile dichiarandola all’interno di una porzione di codice delimitata da
due parentesi graffe. Il seguente frammento di programma è perfettamente lecito:
double a, b, c;
a = 3.5;
{
double *p;
b = -1.2;
p = &a; // & estrae l’indirizzo di a (un possibile valore di p)
c = (*p)+b;
}
double d = c; // qui p è out-of-scope

In C++ è possibile dichiarare variabili in qualsiasi punto del codice sorgente. Nel punto Definire limiti di visibilità
in C++ attraverso le
in cui la variabile d è dichiarata ed assegnata il puntatore p non è più visibile. Le parentesi
ver.2009.d Copyright © A. De Marco

parentesi graffe
graffe che fissano il ciclo di vita della variabile p definiscono a tutti gli effetti un blocco
di codice. Questo blocco non ha il ruolo di una procedura ma soltanto quello di definire
l’inizio e la fine della visibilità delle variabili in esso definite. Va osservato che nell’in-
terno del blocco risultano visibili le variabili dichiarate esternamente (nello stesso file
sorgente). Alla variabile a viene applicato l’operatore unario & di estrazione di indirizzo
per poter assegnare un valore lecito alla variabile locale p, di tipo puntatore a double.
* È interessante esaminare una situazione che si sarebbe avuta se nell’esempio precedente avessimo
dichiarato all’esterno del blocco, prima delle parentesi graffe, il puntatore p e se, viceversa, avessimo di-
chiarato e assegnato la variabile a all’interno del blocco. In quel caso, l’assegnazione p = &a all’interno
del blocco sarebbe stata ancora lecita ma stavolta a sarebbe risultata out-of-scope dopo la chiusura della
seconda parentesi graffa. Al contrario, p sarebbe rimasto ancora in vita. In una situazione del genere si
DRAFT

parla di dangling reference (riferimento penzolante), cioè il valore del puntatore è quello di un indirizzo Dangling reference
non più corrispondente ad a ma ad un’area di memoria probabilmente destinata ad un uso diverso.

Appunti di programmazione a oggetti in C++ e Matlab


16 Capitolo 1 Tecniche di programmazione supportate dal C++

Limiti di scope per Il concetto di ambito visibilità limitato ad un blocco trova riscontro, ad esempio, nei
variabili definite
all’interno dei cicli
costrutti ciclici. Si esamini il seguente frammento di programma:
int k = 1;
float v = 1.5;
float *pf;
for(unsigned int i=0; i<10; i++)
{
float w;
w = (k++)*v - 1.0;
Task1(v, w, pf);
// stampa sul canale di output standard
cout << "Task1 restituisce: " << *pf << endl;
}
// qui i e w sono out-of-scope

Sia la variabile intera i, che serve da contatore delle iterazioni, che la variabile w che
viene passata a Task1 sono inaccessibili all’esterno delle parentesi che delimitano il ciclo
di for. Si dice che esse sono out-of-scope. Si noti come la variabile v, che prima viene
usata per assegnare w e poi viene passata a Task1, sia visibile all’interno del ciclo. Lo
stesso dicasi per la variabile pf.
Dal punto di vista dell’organizzazione del programma le funzioni vengono utilizzate
per creare ordine in una selva di algoritmi. Gli algoritmi stessi sono scritti utilizzan-
Design dei programmi do chiamate di funzioni e altri strumenti del linguaggio. Il progetto di un programma
secondo l’approccio
Top-Down
secondo il paradigma della programmazione procedurale segue un approccio di tipo Top-
Down: si individuano i passi fondamentali dell’algoritmo che il programma deve eseguire
e per ognuno si sviluppa un’apposita procedura; ciascuna procedura, a seconda della sua
complessità, viene a sua volta sviluppata secondo lo stesso criterio.

1.2.3 Hello world!


Funzione main Il programma più piccolo che si possa scrivere in C++ è:
int main() { }

Definisce una funzione che si chiama main, la quale non prende argomenti e non fa nulla.
Ogni programma C++ deve avere una funzione che si chiama main. Il programma inizia
eseguendo quella funzione. Il valore di tipo int restituito da main, se esiste, è il valore che
ver.2009.d Copyright © A. De Marco

il programma restituisce al “sistema”. Se non viene restituito alcun valore, il sistema ne


riceverà (secondo lo standard ISO) uno che indica una terminazione corretta. Un valore
diverso da zero restituito da main indica una terminazione non corretta. La funzione main
è una funzione speciale che, in C come in C++, si interfaccia con il sistema operativo ed
è detta, tecnicamente, entry point del programma eseguibile.
Tipicamente un programma produce qualche messaggio. Ecco un programma che
I/O standard con scrive il famoso tormentone “Hello world!” sul canale di output standard:
iostream
#include <iostream>
int main()
{
DRAFT

std::cout << "Hello world!\n" ;


}

A. De Marco
1.2 Programmazione procedurale 17

La riga #include <iostream> dice al compilatore di includere le dichiarazioni delle


funzioni standard per l’ingresso/uscita (I/O) su canali di caratteri (stream) che si trovano
nel file iostream. Senza queste dichiarazioni l’espressione std::cout << "Hello world
!\n" non avrebbe senso. L’operatore << (“metti su”) scrive il suo secondo argomento
(quello alla sua destra) sul primo. In questo caso si ha che la stringa letterale "Hello
world!\n" viene scritta sul canale di uscita predefinito std::cout, tipicamente, una fine-
stra di console. Una stringa letterale del C++ è una sequenza di caratteri delimitata da
doppie virgolette. In una stringa letterale il carattere \ (barra inversa) seguito da un altro
carattere denota un singolo carattere speciale. In questo caso \n è il carattere di fine riga
(newline) cosicché i caratteri stampati sono "Hello world!" seguiti da un fine riga.

1.2.4 Un semplice programma C++ con procedure


Rispetto al semplicissimo esempio precedente si può fare molto di più. Ecco un esempio
di semplice programma in C++ decomposto in procedure e funzioni.
// Forward declarations
bool options(const int, const char**);
void PrintHelp(void);
void Task1(void);
void Task2(void);

// Global data
const char *hostname = "localhost";
int port = 5501;
realtime = false;

int main(const int argc, const char *argv[])


{
bool success = false;

// *** PARSE COMMAND LINE OPTIONS *** //


success = options(argc, argv);
if (!success) {
PrintHelp();
exit(-1);
}
realtime = true; ver.2009.d Copyright © A. De Marco

// *** EXECUTE TASKS *** //


Task1();
Task2();

return 0;
}
// Define here functions: options, PrintHelp, Task1, Task2
// ...

Qui la main, oltre che a ritornare un valore al sistema, ne riceve due: argc e argv (i
nomi di questi due argomenti sono quelli usati dalla maggior parte dei programmi). Più
avanti si approfondirà quest’aspetto con un esempio più dettagliato. Qui basta sapere i due
DRAFT

argomenti di main, passati alla funzione dal sistema operativo, permettono al programma
di risalire alla riga di comando completa utilizzata dall’utente per lanciarne l’esecuzione.

Appunti di programmazione a oggetti in C++ e Matlab


18 Capitolo 1 Tecniche di programmazione supportate dal C++

Il programmatore ha predisposto in questo caso la funzione options che prende di-


rettamente in consegna gli argomenti di main e li elabora restituendo un valore booleano.
Un valore vero ritornato da options dovrebbe indicare una riga di comando ben scritta
(lecita). In caso contrario viene invocata la procedura PrintHelp, che non ha bisogno di
ritornare alcun valore perché probabilmente scriverà semplicemente una serie di messag-
gi sul canale di output standard. Per come è fatta qui la funzione main, ad una riga di
comando non lecita corrisponde la stampa di un testo di aiuto e l’interruzione immediata
dell’esecuzione tramite la funzione exit (il cui argomento -1 viene ritornato al sistema).
Se il controllo del flusso di operazioni del programma supera la fase di lettura della riga di
comando, allora verranno eseguite le procedure Task1 e Task2, e verrà restituito al sistema
un valore di ritorno nullo con l’istruzione return 0.
Il programmatore è evidentemente abbastanza sicuro che queste ultime non diano luo-
go a condizioni “eccezionali”, non preoccupandosi (apparentemente) di controllarne l’e-
sito. Le due funzioni probabilmente assolvono a compiti semplici e non c’è bisogno che
ritornino valori.
Questo programma, che può essere memorizzato, ad esempio, in un file main.cpp,
è ovviamente incompleto. Andrebbero definite nello stesso file le funzioni options,
PrintHelp, Task1, Task2, soltanto dichiarate prima della definizione della funzione main
(le cosiddette forward declarations). Tra queste solo la funzione options ritorna un va-
lore, che è di tipo bool; le altre sono a tutti gli effetti delle procedure: esse definiscono
delle variabili interne ed, al più, manipolano delle variabili globali come hostname, port
e realtime. In questo caso, le variabili globali sono quelle definite all’esterno di tutte
funzioni definite nel file main.cpp. Esse vengono assegnate tipicamente in sede di di-
chiarazione (quasi sempre all’inizio del file, prima della definizione di main) e risultano
visibili ovunque nel file sorgente (hanno global scope). In particolare le variabili globali
verranno usate nelle definizioni delle singole funzioni utilizzate da main.

1.3 Programmazione modulare


Nel corso degli anni, l’enfasi nella progettazione dei programmi si è spostata dal progetto
delle procedure all’organizzazione dei dati. Tra le altre cose, questo riflette un un aumento
della dimensione dei programmi. Un insieme di procedure correlate e dei dati da esse
manipolati è spesso chiamato un modulo. Il paradigma della programmazione modulare
ver.2009.d Copyright © A. De Marco

è dunque:

Decidi quali moduli ti occorrano;


suddividi il programma in modo che i dati
siano nascosti all’interno dei moduli.

Il principio di Questo paradigma è anche noto come il principio di occultamento dei dati (data hi-
occultamento dei dati
ding). Nei problemi la cui soluzione può essere modellata senza raggruppare necessaria-
mente le procedure ed i dati che esse manipolano lo stile di programmazione procedu-
rale risulta sufficiente. Quando viene applicato il paradigma modulare le tecniche per il
progetto di “buone procedure” sono da applicarsi per ogni procedura in un modulo.
DRAFT

Se i dati sono occultati nei moduli ma, allo stesso tempo, per la costruzione degli
algoritmi vi è necessità di far interagire i moduli di programma gli uni con gli altri —

A. De Marco
1.3 Programmazione modulare 19

scambiando dati fra di loro —, nasce l’esigenza di concepire le procedure e le funzioni di


ciascun modulo come delle interfacce con l’esterno (interfacce utente). Inoltre, è neces-
sario permettere di accedere alla rappresentazione dei dati di un modulo solo attraverso
l’interfaccia stabilita dal programmatore ed assicurare che i moduli inizializzino i loro
dati prima che questi vengano usati.

1.3.1 Creare moduli attraverso gli spazi dei nomi


Il C++ fornisce un meccanismo per raggruppare dati, funzioni ed altre entità che siano ad La parola chiave
namespace
essi correlate. Questo tipo di raggruppamento viene effettuato creando i cosiddetti spazi
dei nomi (o namespace) attraverso la parola chiave namespace.
Per comprendere il ruolo di un namespace si pensi ad un modulo con il quale il pro-
grammatore si prefigge di gestire e calcolare le caratteristiche aerodinamiche di un’ala.
In C++ si potrà scrivere:
namespace Wing // interfaccia
{
bool defGeometry(string);
void setAlpha(double);
void setMach(double);
void calculate(void);
double getCL(void);
double getCD();
}

Questa è l’intefaccia del modulo Wing. Si intuisce che la funzione defGeometry serve a Interfaccia di un modulo

configurare i dati geometrici dell’ala e che le funzioni setAlpha e setMach servono ad


assegnare delle grandezze che definiscono la corrente che investe il corpo aerodinamico
(˛ e M1 ). La funzione calculate è probabilmente destinata ad effettuare i calcoli più
importanti e ad assegnare valore ad alcune variabili interne al modulo Wing. Tra queste vi
compariranno, ad esempio quelle che contengono i valori dei coefficienti di portanza e di
resistenza (CL e CD ). Un frammento di codice che usa questo modulo potrebbe essere il Codice utente di un
modulo
seguente:
if ( Wing::defGeometry("wing_geometry_1.xml") )
printf("Errore nel file di configurazione della geometria!\n");
else {
Wing::setAlphaDeg(3.0); ver.2009.d Copyright © A. De Marco

Wing::setMach(0.5);
Wing::calculate();
printf(" CL = %f \n CD = %f \n", Wing::getCL(), Wing::getCD());
}

La dicitura Wing:: preposta ai nomi delle funzioni indica che queste appartengono
allo spazio dei nomi Wing. Altri usi di questi stessi nomi non interferiscono e non causano
confusione. Ad esempio l’uso del solo nome calculate potrebbe riferirsi ad un’altra
funzione, definita per uno scopo diverso da quello previsto dal programmatore per Wing
::calculate. Nel gergo del C++ si dice che qui i :: (doppio due punti) sono usati come
operatore di risoluzione di visibilità; la funzione calculate del namespace Wing risulta
visibile all’esterno del modulo come Wing::calculate.
DRAFT

La definizione del modulo Wing —cioè la sua “implementazione” — potrebbe essere Implementazione di un
modulo
fornita in una parte del programma compilata separatamente:

Appunti di programmazione a oggetti in C++ e Matlab


20 Capitolo 1 Tecniche di programmazione supportate dal C++

namespace Wing // implementazione


{
double referenceSurface, span, aspectRatio, taperRatio, leSweep,
dihedral, geomTwist, aeroTwist;
double alpha, Mach;
double CL, CD;
const int max_size = 200;
double y[max_size];
bool defGeometry(string filename)
{
hlegge il file di configurazione in formato XMLi
return hesito della lettura filei;
}
void setAlphaDeg(double val){ alpha = val/57.3; }
void setMach(double val){ mach = val; }
void calculate(void)
{
hesegue i calcoli aerodinamicii
// verranno assegnate e manipolate le y[i]
// verranno assegnati CL e CD
}
double getCL(void){ return CL; }
double getCD(void){ return CD; }
}

Il punto chiave riguardo al namespace Wing è che l’interfaccia esterna risulta isolata
dalla struttura dati sottostante. In questo caso i dati occultati all’interno del modulo so-
no le variabili double dichiarate all’inizio dell’implementazione ed assegnate nelle varie
funzioni appartenenti al namespace. I dati ed il codice delle funzioni del modulo rappre-
sentano Wing. L’utente non ha bisogno di conoscere i dettagli di come sono gestiti i dati
all’interno di Wing. L’implementazione del modulo può essere modificata — ad esempio,
potrebbe essere migliorata la funzione calculate — senza che il codice utente ne sia
influenzato.
Poiché i dati sono solo una delle cose che si può volere “nascondere”, la nozione
di occultamento dei dati viene estesa facilmente a quella, più generale, di occultamento
dell’informazione; ovvero, così come i nomi delle funzioni, anche i nomi dei tipi e di altri
elementi del linguaggio possono essere resi locali a un modulo. Di conseguenza, il C++
permette di collocare qualsiasi dichiarazione in un namespace.
ver.2009.d Copyright © A. De Marco

1.3.2 Lo spazio dei nomi della libreria standard


Il C++ è dotato di una libreria standard che mette a disposizione dei programmatori un
gran numero di funzionalità. La libreria standard è usabile in ogni implementazione
completa del C++, cioè con ogni sistema di compilazione che sia standard compliant,
e permette di scrivere codice portabile.
La libreria standard è definita in uno spazio dei nomi chiamato std. Per questo motivo
si scrive std::cout invece che cout, cioè per essere espliciti nell’usare il cout standard
invece che qualche altro cout. Anche il nome std::endl è definito nella libreria standard
e coincide con il carattere di newline ’\n’.
DRAFT

Ogni funzionalità della libreria è fornita tramite qualche file header simile al file
iostream. Per esempio:

A. De Marco
1.3 Programmazione modulare 21

#include <string>
#include <list>

rendono disponibili le classi standard string e list. Per usare queste classi al di fuori
del namespace std si dovrebbe usare il prefisso std::
std::string s = "A che ora si cena?";
std::list<std::string> phrases;

Per semplicità il prefisso std:: può essere omesso a patto di dichiarare l’uso globale
dello spazio dei nomi std così:
using namespace std ;

Questa dichiarazione può essere fatta, ad esempio, ad un livello globale subito dopo le
direttive #include. Si dice allora che il nome di namespace std è trasferito nel namespace
globale. Ad esempio:
#include <string>
using namespace std;
string s = "Perche’ sprecare tempo a imparare? L’ignoranza e’ immediata!";

È generalmente uno stile sconsigliato quello di trasferire nel namespace globale tutti i
nomi di un namespace. Spesso in questo documento, per abbreviare i pezzi di programmi
usati per illustrare le caratteristiche del linguaggio e della libreria, sono omesse le ripe-
tizioni delle direttive #include e delle specifiche di std::. Si capisce dal contesto che
alcune funzionalità hanno senso solo se vengono lette le opportune dichiarazioni da file
header di libreria e se vengono resi globali alcuni nomi del namespace std.

1.3.3 Compilazione separata


Il C++ supporta la nozione del C di compilazione separata. Questa può essere usata per
organizzare il programma in un insieme di frammenti semi-indipendenti.
Tipicamente, nella programmazione modulare si collocano le dichiarazioni che spe-
cificano l’interfaccia di un modulo in un file che un nome che indica l’uso a cui esso è
preposto. Ad esempio, le dichiarazioni di interfaccia di Wing sarebbero collocate nel file
Wing.h:

//----------------------------------------
// File: Wing.h ver.2009.d Copyright © A. De Marco
//----------------------------------------
namespace Wing // interfaccia
{
bool defGeometry(string);
void setAlpha(double);
void setMach(double);
void calculate(void);
double getCL(void);
double getCD();
}

e gli utenti dovrebbero includere (direttiva #include) tale file così:


//----------------------------------------
DRAFT

// File: user.cpp
//----------------------------------------

Appunti di programmazione a oggetti in C++ e Matlab


22 Capitolo 1 Tecniche di programmazione supportate dal C++

#include "Wing.h" // ottiene l’interfaccia

void f()
{
if ( Wing::defGeometry("wing_geometry_1.xml") )
printf("Errore nel file di configurazione della geometria!\n");
else {
Wing::setAlphaDeg(3.0);
Wing::setMach(0.5);
Wing::calculate();
printf(" CL = %f \n CD = %f \n", Wing::getCL(), Wing::getCD() );
}
}

Per aiutare il compilatore ad assicurare la consistenza, il file che fornisce l’implemen-


tazione del modulo Wing, Wing.cpp, includerà anche l’interfaccia:
//----------------------------------------
// File: Wing.cpp
//----------------------------------------
#include "Wing.h" // ottiene l’interfaccia

namespace Wing // rappresentazione


{
double referenceSurface, span, aspectRatio, taperRatio, leSweep,
dihedral, geomTwist, aeroTwist;
double alpha, Mach;
double CL, CD;
const int max_size = 200;
double y[max_size];
}

// implementazione dell’interfaccia
bool Wing::defGeometry(string filename){ /* ... */ }
void Wing::setAlphaDeg(double val){ alpha = val/57.3; }
void Wing::setMach(double val){ mach = val; }
void Wing::calculate(void){ /* ... */ }
double Wing::getCL(void){ return CL; }
double Wing::getCD(void){ return CD; }

Il codice utente va inserito in un terzo file, per esempio user.cpp. I codici in user.cpp
ver.2009.d Copyright © A. De Marco

e Wing.cpp condividono le informazioni relative all’interfaccia presentate in Wing.h, ma


i due file sono indipendenti sotto tutti gli altri punti di vista e possono essere compilati
separatamente.
La compilazione separata è una questione che riguarda tutti i programmi reali. Non
è semplicemente di pertinenza dei programmi che forniscono servizi, come Wing, sotto
forma di moduli. A rigor di termini, l’uso della compilazione separata non è tanto una
questione di linguaggio, quanto piuttosto di avvalersi al meglio di una particolare imple-
mentazione del linguaggio (cioè di un particolare sistema di compilazione, ad esempio
l’infrastruttura dei compilatori di sistema di Linux oppure un particolare ambiente di svi-
luppo integrato). Ciononostante, la compilazione separata è di grande importanza dal
punto di vista pratico. Il miglior approccio consiste nel massimizzare la modularità —
DRAFT

cioè creare per quanto è possibile, specialmente in programmi di una certa dimensione,
tanti moduli gerarchicamente organizzati —, rappresentare tale modularità dal punto di

A. De Marco
1.3 Programmazione modulare 23

vista logico per mezzo delle funzionalità del linguaggio e sfruttarla dal punto di vista
fisico per mezzo di file, per permettere un’efficace compilazione separata.

1.3.4 Gestione delle eccezioni


Un programma, spesso, dopo il rilevamento di un errore deve essere concluso, perché
non è possibile immaginare una maniera sensata di continuarlo. Ciò può essere ottenuto
chiamando exit(), che per prima cosa ripulisce le risorse in uso come gli stream di output
e poi conclude il programma restituendo il valore del suo argomento. Meccanismi di
gestione degli errori più sofisticati possono realizzarsi con l’uso delle eccezioni, ma per
un programma di meno di 150 righe è più che sufficiente l’uso di messaggi d’errore e
della funzione exit().
Quando un programma è progettato come un insieme di moduli, la gestione degli erro-
ri deve essere presa in considerazione alla luce dell’organizzazione modulare. Va stabilito
quale modulo è responsabile di un certo tipo di errore. Spesso il modulo che individua un
errore non sa quale azione intraprendere. L’azione di ripristino dipende dal modulo che ha
invocato l’operazione piuttosto che dal modulo che ha riscontrato l’errore mentre cercava
di eseguire tale operazione. Man mano che i programmi crescono, e specialmente quando
viene fatto uso estensivo di librerie scritte da altri, diventano importanti degli standard di
gestione dell’errore e, più in generale, delle “circostanze eccezionali”.
Si consideri ancora l’esempio di Wing. Cosa dovrebbe essere fatto quando cerchiamo
di caricare dal file wing_geometry_1.xml i dati geometrici dell’ala e si verifica un errore
dovuto ad un formato inatteso? Chi ha scritto il modulo Wing non sa cosa l’utente vorrebbe
fosse fatto in un caso simile. L’utente, d’altro canto, non può individuare il problema in
maniera coerente (se avesse potuto, avrebbe fatto in modo da evitare l’errore di lettura).
La soluzione è che chi ha implementato Wing individui l’eventualità di un errore di lettura
da file e quindi lo comunichi all’utente (sconosciuto). A questo punto il codice utente può
essere modellato in modo di intraprendere l’azione appropriata. Per esempio:
//----------------------------------------
// File: Wing.h
//----------------------------------------
namespace Wing // interfaccia
{
bool defGeometry(string);
ver.2009.d Copyright © A. De Marco

void setAlpha(double);
void setMach(double);
void calculate(void);
double getCL(void);
double getCD();

class BadFormatFile { /* ... */ }; // tipo che rappresenta le eccezioni


// relative agli errori di lettura
// dati da file
}

in cui compare per la prima volta la parola chiave class che in C++ serve in generale
DRAFT

a definire nuovi tipi. In questo caso si definisce il tipo badConfigFile all’interno del
namespace.

Appunti di programmazione a oggetti in C++ e Matlab


24 Capitolo 1 Tecniche di programmazione supportate dal C++

Quando riscontra un errore di lettura da file, Wing::defGeometry può invocare il co-


dice di gestione dell’errore, ovvero, si dice che essa “può sollervare un’eccezione di tipo
Wing::BadConfigFile”:
bool Wing::defGeometry(string filename)
{
bool goodFormat = false;
bool goodData = false;
hfunzione che apre e legge il file di input in formato XMLi;
// goodFormat e gooddata vengono opportunamente assegnate
if (!goodFormat) throw BadFormatFile();
// ...
return goodData;
}

Si noti che nell’ultimo frammento di codice si possono verificare due situazioni da ge-
stire come errori: una è la possibile incompletezza dei dati presenti nel file filename
(ad esempio chi ha composto il file XML ha dimenticato di inserire il dato dell’apertura
alare), l’altra è l’eventuale cattivo formato del file. Nel primo caso la funzione Wing::
defGeometry ritorna un valore falso e spetta al codice utente preoccuparsi di controllarlo.
Il secondo caso è quello di cui qui ci stiamo occupando come situazione eccezionale (in
realtà entrambe le situazioni sono di questo tipo ma qui ci piace far vedere come le due
eventualità possono essere trattate in modo separato).
L’istruzione throw trasferisce il controllo del programma da quel punto (e non all’u-
scita della funzione) ad un gestore delle eccezioni di tipo Wing::BadFormatFile definito
in qualche funzione che ha invocato, direttamente o indirettamente, Wing::defGeometry.
Per fare ciò il compilatore ha predisposto meccanismi che permettono al flusso del pro-
gramma di risalire la catena di invocazioni fino a tornare al contesto del chiamante. Quindi
si dice che “l’istruzione throw si comporta come un return multilivello”. Per esempio:
//----------------------------------------
// File: user.cpp
//----------------------------------------
#include "Wing.h" // ottiene l’interfaccia

void f() {
// ...
try { // qui le eventuali eccezioni sono gestite
// dal gestore definito sotto (catch)
ver.2009.d Copyright © A. De Marco

if ( Wing::defGeometry("wing_geometry_1.xml") )
printf("Dati mancanti nella configurazione geometrica!\n");
else {
Wing::setAlphaDeg(3.0);
Wing::setMach(0.5);
Wing::calculate();
printf(" CL = %f \n CD = %f \n", Wing::getCL(), Wing::getCD());
}
}
catch (Wing::BadFormatFile) { // gestore dell’eccezione
// oops: errore di formato
// intraprendere l’azione appropriata
// ...
DRAFT

}
}

A. De Marco
1.4 Astrazione dei dati 25

Se vi è un errore di formato nel file wing_geometry_1.xml l’istruzione


if ( Wing::defGeometry("wing_geometry_1.xml") )

prima ancora di pervenire ad un valore di ritorno nell’espressione condizionale, solleverà


l’eccezione. Pertanto, la clausola catch, che fornisce un gestore per l’eccezione di tipo
Wing::BadFormatFile, viene necessariamente eseguita dopo l’operazione di lettura da file
che ha causato un throw dall’interno della funzione Wing::defGeometry.
L’uso di meccanismi di gestione delle eccezioni può rendere la gestione degli errori
più regolare e leggibile e si presta in maniera appropriata alla programmazione modu-
lare. Quando un programma è composto da moduli separati, e soprattutto se i moduli
provengono da librerie sviluppate da altri programmatori, la gestione degli errori va ne-
cessariamente suddivisa in due parti: (i) la segnalazione di condizioni di errore che non
possono essere risolte localmente e (ii) la gestione degli errori identificati altrove. L’au-
tore di una libreria può identificare errori in fase di esecuzione, ma generalmente non sa
quale operazione intraprendere. l’utente della libreria, invece, sa come reagire all’errore,
ma non è in grado di identificarlo — altrimenti l’errore sarebbe stato gestito dal codice
utente e non lasciato alla libreria.
Le eccezioni sono il meccanismo che il C++ impiega per separare la gestione degli er-
rori dalla segnalazione degli errori. Se le istruzioni inserite in un blocco try, o le funzioni
ivi chiamate, sollevano un’eccezione, viene esaminata la parte di gestione dell’eccezione.
Se l’eccezione sollevata è di un tipo trattato da un gestore di eccezioni, tale gestore viene
eseguito. In caso contrario i gestori di eccezione vengono ignorati e il blocco try si com-
porta come un blocco ordinario. se viene sollevata un’eccezione e nessun blocco try la
tratta, il programma termina.
Fondamentalmente il trattamento delle eccezioni in C++ è un modo di trasferire in
controllo a una determinata parte della funzione chiamante. Quando serve, possono essere
passate al chiamante alcune informazioni sull’errore.

1.4 Astrazione dei dati


La modularità è un aspetto fondamentale di qualsiasi programma che non sia tanto sem-
plice da esaurirsi in meno di un centinaio di righe ed in un solo file. Quest’aspetto diventa
via via più importante al crescere delle dimensioni dei programmi e del numero di file che
li compongono. Nella progettazione delle applicazioni di successo la modularità diventa
ver.2009.d Copyright © A. De Marco

un fattore chiave. Ciononostante, la sola suddivisione del software in moduli può risultare
insufficiente ad esprimere in maniera pulita sistemi complessi.
Per fare ciò si deve passare alla nozione di tipi definiti dall’utente che rappresentano
la caratteristica fondamentale messa a disposizione dal C++. I tipi che un programmatore
C++ può costruire a piacimento (seppur rispettando un insieme di regole, se non vuole
combinare pasticci) permettono di definire variabili che hanno un ciclo di vita del tutto
simile a quello delle variabili dei tipi predefiniti.

1.4.1 Tipi definiti dall’utente


DRAFT

Il C++ consente di definire direttamente un nuovo tipo che si comporta (pressoché) allo
stesso modo di un tipo predefinito come int o double. Qualcuno chiama i tipi definiti

Appunti di programmazione a oggetti in C++ e Matlab


26 Capitolo 1 Tecniche di programmazione supportate dal C++

da utente “tipi di dati astratti”, ma c’è chi osserva che, probabilmente, è meno fuorviante
chiamarli tipi definiti dall’utente. Anzi, come vedremo poco più avanti, un nuovo tipo di
dato può anche essere confezionato in modo tale da essere chiamato tipo concreto.
Il paradigma di programmazione che utilizza i tipi definiti dall’utente diventa:

Decidi quali tipi ti occorrano;


fornisci un insieme completo di operazioni per ogni tipo.

Nei casi in cui non c’è necessità di usare più di un oggetto di un dato tipo, lo stile di
programmazione modulare basato sull’occultamento dei dati per mezzo dei moduli resta
sufficiente.
Un esempio immediati di possibile tipo definito dall’utente è quello che rappresenta
un numero complesso. Come è noto, i numeri complessi hanno una parte reale ed una
immaginaria ed hanno una loro aritmetica; e già questo permette di “astrarre” queste
caratteristiche peculiari in un nuovo tipo di dato a partire dal tipo predefinito double. In
Parola chiave class C++ si può definire il tipo complex per mezzo della parola chiave class scrivendo, per
esempio:
class complex {
double re, im;
public:
complex(double x, double y) { re=x; im=y; } // costruisce un complesso
// da due scalari
complex(double x) { re=x; im=0; } // costruisce un complesso
// da uno scalare
complex() { re=0; im=0; } // costruisce il complesso
// di default (0,0)

double real() const { return re; } // estrae parte reale


double imag() const { return im; } // estrae parte immaginaria
// ... eventuali altre operazioni possibili
};

Questa è la dichiarazione di una classe complex, cioè di un tipo di dato definito dall’utente,
che specifica la rappresentazione di un numero complesso e di alcune operazioni ad esso
applicabili (si noti il ; dopo la fine del blocco di dichiarazioni). La rappresentazione
Rappresentazione consiste semplicemente nei due dati double contenuti nelle variabili re ed im. Secondo
privata dei dati
le regole del C++ la posizione in cui compaiono le dichiarazioni di queste due variabili
ver.2009.d Copyright © A. De Marco

le rende di default dei dati privati, cioè accessibili solo dalle funzioni specificate dalla
Variabili membro classe complex. I dati che sono in dotazione ad una particolare classe si dicono proprietà
o variabili membro.
* In questa dichiarazione, oltre alla rappresentazione privata di complex dovrebbe colpire la specifica di
ben tre funzioni che hanno lo stesso nome della classe, senza valore di ritorno e ciascuna con una lista
differente di argomenti. Esse si dicono i costruttori della classe ed offrono all’utente la possibilità di creare
variabili di tipo complex in vari modi. Se ne parlerà tra poco.

In C++ si può dichiarare esplicitamente che delle variabili e delle funzioni sono private
con la clausola chiave private:. Ecco come un programmatore riscriverebbe la classe
complex:
DRAFT

class complex {
public:

A. De Marco
1.4 Astrazione dei dati 27

// costruttori
complex(double x, double y) { re=x; im=y; }
complex(double x) { re=x; im=0; }
complex() { re=0; im=0; }
// funzioni membro pubbliche
double real() const { return re; }
double imag() const { return im; }
// ...
private:
double re, im;
complex fmp(void);
// ...
};

In questo caso si ha anche la dichiarazione privata di una funzione, la fmp(), all’interno


della classe. le funzioni incorporate nei tipi definiti dall’utente vengono chiamate metodi Funzioni membro

o funzioni membro, possono operare sulle variabili membro ed eventualmente accettare


degli argomenti.
Essa è solo dichiarata nella parte privata della classe e può essere definta altrove (al
di fuori del blocco di dichiarazioni di complex o in un altro file). Potrebbe aversi, per
esempio:
// qui la classe complex e la funzione sqrt
// devono risultare dichiarate
// ...
complex fmp(void) {
double a = (re - im);
double b = sqrt(re*re - im*im);
return complex(a,b);
}

Si noti come le funzioni membro possano accedere alla rappresentazione della classe a
cui appartengono. Le variabili private re ed im sono visibili dall’interno della definizione
di fmp() (sarebbero addirittura modificabili, ma è assolutamente sconsigliabile in un caso
del genere). Inoltre, nell’ultima istruzione di fmp() si “costruisce al volo” un oggetto di
tipo complex a partire da due scalari e lo si consegna all’istruzione return.
Una funzione membro con lo stesso nome della classe di appartenenza viene chiamata Il costruttore di una
classe
costruttore. Un costruttore definisce un modo per inizializzare un oggetto della sua classe.
La classe complex fornisce tre costruttori. Significa che gli utenti hanno tre possibilità
ver.2009.d Copyright © A. De Marco

diverse per inizializzare un complex: lo si può creare a partire da una coppia di double, a
partire da un double oppure si può creare un complex con un valore di default. In pratica,
il costruttore è una funzione definita più volte con liste di argomenti diverse. Quando ciò Overloading delle
funzioni
avviene per le funzioni si dice che esse sono “sovraccaricate” (function overloading).
* L’overloading delle funzioni è una delle caratteristiche più potenti ed allo stesso tempo più delicate del
C++. Questa funzionalità è dovuta al controllo rigoroso dei tipi.

Come si è certamente notato, le due funzioni membro real() e imag() sono pubbli-
che e corrispondono alle operazioni di estrazione della parte reale e della parte immagi-
naria del numero complesso. I metodi pubblici sono espressione dell’interfaccia del tipo
complex verso i suoi utenti. Ad esempio:
DRAFT

void nice_printc(complex z) {
cout << "Parte reale: " << z.real() << ", "

Appunti di programmazione a oggetti in C++ e Matlab


28 Capitolo 1 Tecniche di programmazione supportate dal C++

<< "Parte immaginaria: " << z.imag() << "\n";


}

int main(void) {
double x = 0.0, y = 0.0;
for(int k=0; k<10; k++){ // stampa 10 numeri complessi
x = x + k*0.2;
y = x*x*x;
complex w = complex(x,y);
nice_printc(w);
}
}

Il codice della funzione nice_printc accede (in sola lettura, parola chiave const nelle
definizioni di real e di imag) alla rappresentazione dell’argomento z, una variabile di
tipo complex, attraverso le due funzioni membro pubbliche. L’invocazione di tali funzioni
viene fatta attraverso l’operatore . (punto) applicato alla variabile z.
Fino a questo punto l’utilità della classe complex sembra limitata. L’utente può co-
struire complessi ed al massimo usarne la parte reale e immaginaria. Anche una funzione
membro privata come fmp(), se non usata direttamente o indirettamente da qualche me-
todo pubblico sembra del tutto inutile. In effetti ha senso definire un nuovo tipo se, oltre
a raggruppare convenientemente dei dati, si riescono a definire anche delle operazioni su
di essi che permettano all’utente di costruire efficientemente dei sistemi più complessi.
Nel caso di complex ciò significa allargare lo spettro di funzioni, oltre a real() ed
imag(), che diano ai suoi utenti la possibilità di compiere delle operazioni complesse.
Non è detto, in realtà, che queste nuove funzioni debbano necessariamente far parte della
classe. Esse potranno semplicemente essere “accreditate” ad usare i suoi dati privati. In
C++ questa possibilità è data dalla parola chiave friend. Si può pensare, ad esempio, ad
una funzione sumc che permetta di sommare due complessi. Si dichiarerà all’interno della
classe:
class complex {
public:
// costruttori ....
// funzioni membro pubbliche
// ...
friend complex sumc(complex z1, complex z2);
// ...
ver.2009.d Copyright © A. De Marco

private:
// ...
};

ed altrove si definirà:
complex sumc(complex z1, complex z2) {
return complex( z1.re + z2.re, // sumc ha accesso alle
z1.im + z2.im ); // variabili private
}

Qui l’operatore . (punto) applicato agli argomenti z1 e z2 permette di accedere diretta-


mente ai valori delle variabili membro private. Questo meccanismo è permesso dal fatto
DRAFT

che sumc è stata dichiarata come funzione friend (amica) della classe. L’operazione di
somma di due complessi è usata da questo frammento di codice:

A. De Marco
1.4 Astrazione dei dati 29

complex u(1.0), w(0,1.0);


nice_printc( sumc(u,w) );

Il valore ritornato da sumc è di tipo complex e viene passato a nice_printc che stampa in
bella forma il risultato della somma.
Il C++ offre anche qualcosa di meglio rispetto a questa situazione. È possibile infatti
definire delle funzioni che vengono invocate in maniera appropriata quando in un’espres-
sione aritmetica compaiono variabili di tipo complex. Questo permetterebbe al program-
matore di scrivere del codice come quello seguente:
complex u(1.0), w(0,1.0);
complex z1, z2;
z1 = 3.5 * u - w; // prodotto per scalare e somma
z2 = complex(-1,-1) + z1*z1 + z1/z2; // prodotto e divisione
bool sure = ( z1 != z2 ); // confronto (!= significa: e’ diverso da?)

Per permettere un uso legale di queste operazioni bisogna estendere, al livello del lin-
guaggio, l’uso degli operatori aritmetici +, - (sia binario che unario), *, / e degli operatori
binari booleani == e != di uguaglianza e differenza. Tecnicamente si dice che bisogna
“sovraccaricare” questi operatori (operator overloading, così come avviene per le fun- Overloading degli
operatori
zioni) attraverso delle funzioni opportune. Il compilatore farà in modo di invocare que-
ste ultime se nelle espressioni aritmetiche troverà dei dati di tipo complex. In C++ le
funzioni che permettono l’overloading degli operatori devono avere un nome del tipo:
operatorhoperatorei.
Una più conveniente dichiarazione della classe complex sarà:
class complex {
public:
// costruttori
complex(double x, double y) { re=x; im=y; }
complex(double x) { re=x; im=0; }
complex() { re=0; im=0; }
// funzioni membro pubbliche
double real() const { return re; }
double imag() const { return im; }
// funzioni amiche
friend complex operator+(complex, complex);
friend complex operator-(complex, complex); // binario
friend complex operator-(complex); // unario ver.2009.d Copyright © A. De Marco

friend complex operator*(complex, complex);


friend complex operator/(complex, complex);
friend complex operator+(double, complex); // misto
friend complex operator+(complex, double); // misto
friend complex operator-(double, complex); // misto
friend complex operator-(complex, double); // misto
friend complex operator*(double, complex); // misto
friend complex operator*(complex, double); // misto
friend complex operator/(double, complex); // misto
friend complex operator/(complex, double); // misto

friend bool operator==(double, complex); // uguaglianza


friend bool operator!=(complex, double); // differenza
DRAFT

// ...
private:

Appunti di programmazione a oggetti in C++ e Matlab


30 Capitolo 1 Tecniche di programmazione supportate dal C++

double re, im;


// ...
};

dove sarà:
complex operator-(complex z1, complex z2) // es.: u-w
{
return complex( z1.re-z2.re, z1.im-z2.im );
}
complex operator-(complex z) // es.: -w
{
return complex( -z.re, -z.im );
}
complex operator-(double x, complex z2) // es.: 2.1-w
{
return complex( x-z2.re, -z2.im );
}
//... e cosi’ via

Siccome l’appetito vien mangiando, sembra naturale realizzare anche l’overloading di


un operatore come <<, per l’inserimento di un complex nell’oggetto predefinito cout. In
questo modo il programmatore può fare a meno della funzione nice_printc ed utilizzare
in un modo uniforme il meccanismo di output standard dei caratteri anche se c’è da stam-
pare un nuovo tipo di dato. L’operatore di inserimento vuole alla sua destra l’indirizzo di
memoria di un oggetto di tipo std::ostream messo a disposizione dalla libreria standard.
Si può scrivere allora:
#include <iostream>
using namespace std;

class complex; // Class forward declaration

ostream& operator<<(ostream& s, const complex& z)


{
// inserisce tipi predefiniti e poi ritorna s
return s << ’(’ << z.real() << ’,’ << z.imag() << ’)’;
}

int main()
{
ver.2009.d Copyright © A. De Marco

complex x(1,2), y(-2,0.5), z;


y += x; // aggiunge x ad y, poi riassegna ad y
z = 8.5*x-y/x;
cout << "x = " << x << ’\n’
<< "y = " << y << ’\n’
<< "z = " << z << ’\n’;
return 0; // ridondante in ISO C++
}

Il nome std::cout è il nome predefinito dell’indirizzo di un oggetto di tipo std::ostream.


Il compilatore converte gli operatori che coinvolgono numeri complex in opportune
invocazioni di funzioni. Per esempio, x!=y cignifica operator!=(x,y) e 1/z significa
DRAFT

operator/(1,z).
In C++ la dichiarazione

A. De Marco
1.4 Astrazione dei dati 31

class complex; // Class forward declaration

posta, ad esempio, all’inizio di un file, serve ad asserire (ad un livello di visibilità globale) Dichiarazione anticipata
di una classe
che nel resto del file, in una o più definizioni di funzione che seguiranno, sarà allocato un
oggetto di tipo complex. Qui la parola chiave class è usata in una dichiarazione.

1.4.2 Tipi concreti


I tipi definiti dall’utente possono essere progettati in modo da soddisfare un’ampia varietà
di esigenze. Il tipo complex visto in precedenza è solo un esempio e rappresenta un primo
esercizio di scrittura di una classe. La libreria standard definisce una classe std::complex
che può essere inclusa con la direttiva #include <complex>.
* La libreria standard definisce in realtà una classe “parametrica”, cioè un tipo cosiddetto generico, in
cui il tipo primitivo della parte reale ed immaginaria può essere scelto dal programmatore. Ad esempio, ci
si potrebbe accontentare che la rappresentazione di questi due dati si basi sul tipo primitivo float anziché
sul double. Alla programmazione generica, che è alla base delle Standard Template Libraries (STL) si
accennerà più avanti.

Consideriamo un esempio più realistico che trae spunto dall’esempio del namespace
Wing e ne presenta una versione migliore. Definiamo qui una classe Wing:

//----------------------------------------
// File: Wing.h
//----------------------------------------
class Wing {
public:
// costruttori
Wing(); // dichiarazione
Wing(string filename); // dichiarazione
// distruttore
~Wing(); // dichiarazione
// funzioni membro pubbliche
void SetAlpha(double); // dichiarazione
double GetAlpha(void){ return alpha; } // implementazione
void SetMach(double); // dichiarazione
double GetMach(void){ return mach; } // implementazione
// ...
bool Init(string filename); // dichiarazione
void Calculate(void); // dichiarazione ver.2009.d Copyright © A. De Marco

double GetCL(void){ return cL; } // implementazione


double GetCD(){ return cD; } // implementazione
// ...
class BadFormatFile { /* ... */ }; // una classe definita in una classe
// definisce le eccezioni di formato
private:
double referenceSurface, span, aspectRatio, taperRatio, leSweep,
dihedral, geomTwist, aeroTwist;
double alpha, mach;
double cL, cD;
double *y; // puntatore ad un vettore in heap
// ...
bool loadGeometry(string); // dichiarazione
DRAFT

// ...
};

Appunti di programmazione a oggetti in C++ e Matlab


32 Capitolo 1 Tecniche di programmazione supportate dal C++

Le stesse variabili utilizzate nell’esempio precedente del namespace Wing sono state
poste nella parte privata della classe. Questo risponde ancora al principio dell’occul-
tamento dei dati. Quando si ha a che fare con le classi si usa anche il termine incap-
sulamento. Grazie all’incapsulamento l’utente della classe Wing può accedere alla sua
rappresentazione — ai suoi dati — solo attraverso l’interfaccia della classe — l’insieme
delle funzioni pubbliche predisposto dal programmatore di Wing. Le seguenti istruzioni
mostrano esempi d’uso corretto e scorretto di questa classe:
Wing w; // definisce una variabile di tipo Wing
cout << "Apertura alare (m): "
<< w.GetSpanMT() << "\n"; // bene, se GetSpanMT è definita
w.span = 12.5 // errore: span e’ privata

In pratica, i programmatori che creano dei nuovi tipi di variabili — diversi da quelli
primitivi, cioè predefiniti dal linguaggio —, istruiscono il compilatore C++ ad organizzare
meccanismi di gestione della memoria e delle funzioni di manipolazione dei dati. Que-
sti meccanismi sono associati a delle singole entità, rappresentate appunto dalle variabili
Oggetti stesse. Le variabili di un qualsiasi tipo sono anche dette “oggetti”, anche le variabili di
tipi primitivi. Ma questa terminologia esprime bene le peculiarità delle variabili apparte-
nenti a tipi definiti dall’utente. Gli oggetti sono tipicamente delle variabili appartenenti a
determinate classi, le quali possono essere definite in modo tale da permettere agli oggetti
di interagire fra di loro attraverso le funzioni membro.
Nel gergo tecnico ogni oggetto è un’istanza di una data classe, cioè una variabile che
occupa la memoria necessaria a contenere le strutture dati e le funzioni previste dalla clas-
Il costruttore di una se. Il costruttore è la funzione invocata ogni volta che viene creato un oggetto della classe
classe
e si occupa dell’inizializzazione. Un costruttore come Wing(), che non ha argomenti, è
detto costruttore di default, invocato da un’istruzione come Wing w; ed effettua un’inizia-
lizzazione che il programmatore ritiene di default per oggetti di questa classe. Nel caso
Il distruttore di una sia necessario fare pulizia quando un oggetto di una classe non è più visibile, è possibile
classe
dichiarare il complementare di un costruttore, detto distruttore:
Wing::Wing() // costruttore di default
{
// inizializza tutte le variabili private
// ...
y = new double[100] // alloca gli elementi nella
// memoria dinamica (heap)
ver.2009.d Copyright © A. De Marco

Wing::~Wing() // definisce il distruttore


{
// le variabili private sono automatcamente distrutte
// tranne quelle indirizzate sullo heap

delete[] y // libera lo spazio occupato dagli elementi


// di y per permettere un futuro riuso
}

Il costruttore di default inizializza una nuova variabile di tipo Wing. Per fare ciò alloca
Memoria dinamica nella memoria dinamica (detta anche heap) utilizzando l’operatore new. La parola chiave
DRAFT

(heap) e operatore new


new del C++ permette di allocare memoria dinamica utile a contenere una variabile o una
sequenza di variabili di un determinato tipo (anche di tipi definiti dll’utente). L’operazione

A. De Marco
1.4 Astrazione dei dati 33

di allocazione di memoria dinamica restituisce (alla sinistra di new) un puntatore ad essa.


Nel nostro esempio y è un array di 100 elementi di tipo double. Come avverrebbe per il C,
anche nel linguaggio C++ la y non è altro che un puntatore al primo elemento dell’array,
ovvero y[0]. Conservare in una variabile di tipo puntatore il risultato di new è anche detto
indirizzare.
Quando un oggetto di classe Wing esce dal campo di visibilità, ad esempio il flusso di
controllo del programma passa al di fuori di un blocco, viene invocato auomatcamente il
suo distruttore ~Wing(). La parola chiave delete usata nel codice del distruttore serve a
istruire una liberarazione di memoria dinamica. Essa deve essere usata insieme ad un pun-
tatore all’area di memoria che si desidera rendere nuovamente disponibile. Nell’esempio
precedente y è il puntatore e l’istruzione delete[] y serve a dire al compilatore che la
memoria da liberare è quella occupata dall’intero array, dal primo all’ultimo elemento.
Tutto ciò si verifica senza l’intervento dell’utilizzatore di Wing. L’utente semplicemente
crea e usa degli oggetti Wing come se fossero variabili di tipi predefiniti. Per esempio:
Wing wvar1("wing_config_1.xml"); // oggetto globale, costruito con dati
// letti da file
void f(){
Wing wvar2("wing_config_2.xml"); // oggetto locale
wvar1.Calculate();
wvar2.Calculate();
cout << "CL ala 1: " << wvar1.GetCL() << "\n"
<< "CL ala 2: " << wvar2.GetCL() << "\n";
} // qui viene invocato il distruttore di wvar2
cout << "CL ala 1: " << wvar1.GetCL() << "\n" // bene, wvar1 è globale
<< "CL ala 2: " << wvar2.GetCL() << "\n"; // errore: wvar2 e’
// out-of-scope

Per le variabili di tipo Wing valgono le stesse regole di nomi, visibilità, allocazione
vita, copia, e così via, che valgono per i tipi predefiniti come int e char.
Si osservi che le funzioni membro come Init o loadGeometry, che sono solo dichiara- Classi dichiarate in file
header e funzioni
te nel costrutto class, devono anch’esse essere definite da qualche parte. La definizione membro definite in file di
qui è sinomino di implementazione. La dichiarazione di una (interfaccia di una) classe implementazione

come Wing — quella vista sopra — viene posta in un file apposito detto header, di esten-
sione .h (o anche .hpp e .hxx). Il nome dell’header (esclusa l’estensione) coincide in
genere con il nome della classe. La classe Wing avrà un header file di nome Wing.h ed un
file di implementazione di nome Wing.cpp. Guardiamo un frammento di possibile file di
ver.2009.d Copyright © A. De Marco

implementazione della classe Wing:


//----------------------------------------
// File: Wing.cpp
//----------------------------------------
// Headers
#include "Wing.h" // dichiara la classe Wing
// ...
//----------------------------------------
// costruttori
Wing::Wing() { /* ... */ }
Wing::Wing(string filename) { /* ... */ }
//----------------------------------------
DRAFT

// distruttore
Wing::~Wing() { /* ... */ }

Appunti di programmazione a oggetti in C++ e Matlab


34 Capitolo 1 Tecniche di programmazione supportate dal C++

//----------------------------------------
bool Wing::Init(string filename)
{
// effettua delle inizializzazioni preliminari
// ...
return loadGeometry(filename);
}
//----------------------------------------
bool Wing::loadGeometry(string filename)
{
// cerca di caricare i parametri geometrici leggendoli
// dalle direttive contenute in un file di configurazione
// in formato XML (eXtended Markup Language)
// ...
return hesito della lettura filei;
}
//----------------------------------------
Wing::Calculate() { /* ... */ }
// ...

Nella logica dei programmi organizzati in più file sorgente compilati separatamente,
anche il codice utente di Wing dovrà includere l’header che dichiara la classe. Ecco un
esempio:
// File: main.cpp
// Headers
#include "Wing.h" // dichiara la classe Wing
#include <iostream>
using namespace std;
// ...
int main()
{
Wing wing, htail;
if ( (!wing.Init("finite_wing_1.xml"))
||(!htail.Init("small_wing_2.xml")) ) {
cout << "Unable to configure wings from file." << endl;
PrintHelp();
exit(-1);
}
// da qui normale esecuzione
wing.SetAlphaDeg(3.0);
ver.2009.d Copyright © A. De Marco

wing.SetMach(0.5);
wing.Calculate();
cout << "CD = " << wing.GetCD() << endl
<< "CL = " << wing.GetCL() << endl
<< "Cm = " << wing.GetCm() << endl;
// ...
return 0;
}

All’interno della funzione main vengono dichiarati due oggetti, wing e htail, di classe
Wing, inizializzati ed usati.
Valori di ritorno di La funzione Init è un esempio di metodo pubblico che fornisce un valore di ritorno
DRAFT

funzioni membro
non void. Tale valore è previsto dal progettista della classe in modo che venga utile agli
utenti di Wing. Infatti Init ritorna un valore logico che informa il codice chiamante sul-

A. De Marco
1.4 Astrazione dei dati 35

l’esito dell’inizializzazione avvenuta tramite caricamento dei dati da file. Nella funzione
main su riportata, il significato della negazione !htail.Init("small_wing_2.xml") usata
nell’espressione logica del costrutto if potrebbe non essere immediato per i program-
matori meno esperti. Un codice più prolisso, ma non meno efficiente, potrebbe essere il
seguente:
Wing wing; // inizializzazione di default
Wing htail;
bool ok1 = wing.Init("finite_wing_1.xml"); // configura da file
bool ok2 = htail.Init("small_wing_2.xml"); // configura da file
if ( !ok1 || !ok2 ) { // OR logico
cout << "Unable to configure from file." << endl;
exit(-1);
}

Si assegnano alle variabili booleane ok1 ed ok2 i valori ritornati da Init invocate attraver-
so gli oggetti wing e htail. Queste variabili vengono usate per controllare l’esito delle
operazione di configurazione da file ed eventualmente interrompere il programma con un
comando exit.
Secondo una buona pratica della programmazione in C++, gli oggetti hanno nomi che Consuetudini nella
scelta dei nomi di classi
iniziano con una lettera minuscola — ad esempio “wvar”, o anche “wing” — mentre i e di oggetti
nomi delle classi di appartenenza hanno iniziali maiuscole. A volte, scorrendo il codice o
la documentazione di qualche libreria si possono trovare classi con nomi che iniziano per
“C”. Ad esempio può capitare di incontrare il tipo non predefinito “CString” che secondo
una consuetudine dei programmatori di intefacce grafiche in ambiente Windows serve a
segnalare che CString è una classe e non una semplice struttura dati (la struct del C) o
altro ancora. La consuetudine che è bene seguire resta comunque quella di dare alle classi
dei nomi con iniziali maiuscole.
Una ulteriore consuetudine, usata soprattutto per progetti software di grandi dimen- Nomi di classi definite in
progetti di grandi
sioni, è quella di anteporre al nome naturale di una classe una sigla che indichi inequi- dimensioni
vocabilmente il nome del progetto. Ad esempio, nel codice sorgente del software di
simulazione di volo FlightGear [12] si trova la definizione della classe FGColumnVector3.
Le iniziali “FG” fanno capire a chi ispeziona il codice che si tratta di una delle classi de-
finite nell’ambito del progetto e non una classe che appartiene ad una libreria da cui esso
eventualmente dipende. Il resto del nome “ColumnVector3” fa capire che si tratta di una
classe di utilità che serve a gestire i punti dello spazio tridimensionale, espressi come
matrici colonna (3  1). Analogamente si definiscono le classi FGMatrix33 per le matrici
ver.2009.d Copyright © A. De Marco

(3  3) ed FGQuaternion per la gestine del quaternione dell’orientamento. Si rimanda il


lettore alla consultazione del sito internet di ispezione interattiva del codice sorgente di
FlightGear Flight Simulator [13].
Tipi come Wing sono chiamati tipi concreti, in contrapposizione ai tipi astratti, nei
quali l’interfaccia isola in maniera ancora più completa l’utente dai dettagli implementa-
tivi.

1.4.3 Tipi astratti


Nei tipi concreti come la classe Wing la rappresentazione dei dati non è disaccoppiata
DRAFT

dall’interfaccia esterna della classe, ma piuttosto è parte di quanto verrebbe incluso in un


frammento di programma che facesse uso di Wing. Pur essendo privata, e quindi acces-

Appunti di programmazione a oggetti in C++ e Matlab


36 Capitolo 1 Tecniche di programmazione supportate dal C++

sibile solo attraverso le funzioni membro, la rappresentazione è comunque presente nella


dichiarazione della classe (ovvero nel file Wing.h incluso nel codice utente dalla direttiva
#include). Se tale rappresentazione cambiasse in maniera significativa (aggiunta di varia-
bili membro, manutenzione dell’implementazione, eccetera), l’utente sarebbe costretto a
ricompilare i file sorgenti della classe. Questo è il prezzo da pagare per avere tipi concreti
che si comportano esattamente come quelli predefiniti.
Per i tipi poco soggetti a modifiche, e per i quali l’utilizzo tramite variabili locali for-
nisce una maggior chiarezza ed efficienza, ciò è accettabile e spesso ideale. Tipicamente,
i programmatori non interessati a sviluppare librerie o progetti di ampio respiro possono
disegnare il loro software facendo uso per lo più di classi concrete, delle librerie standard
ed eventualmente di librerie esterne.
Tuttavia il C++ offre agli sviluppatori di software la possibilità di mantenere com-
pletamente isolati gli utilizzatori di una classe dalle eventuali modifiche della sua imple-
mentazione. Ma per fare questo una classe come Wing definita nel paragrafo precedente
risulta insufficiente. Per raggiungere lo scopo bisogna disaccoppiare l’interfaccia dal-
la rappresentazione e abbandonare l’uso di autentiche variabili locali (variabili membro
private).
Ridefiniamo una classe Wing come pura interfaccia (detta anche classe astratta):
class Wing {
public:
virtual bool Init(string filename) = 0;
virtual void Calculate(void) = 0;
virtual double GetCD(void) = 0;
virtual double GetCL(void) = 0;
virtual double GetCm(void) = 0;
class BadFormatFile { /* ... */ }; // usata come eccezione
};

Questa è una dichiarazione di classe molto semplice e molto particolare. La parola


virtual che precede una funzione membro significa “può essere ridefinita successiva-
Funzioni membro virtuali mente in una classe derivata da questa”. Una classe derivata da un’altra è praticamente
pure e classi interfaccia
un tipo utente identico al primo ed eventualmente con qualche variabile membro e fun-
zione membro in più. Una classe derivata da Wing dell’ultimo esempio è una classe che,
in pratica, fornisce l’implementazione per l’interfaccia Wing. La curiosa sintassi =0 indica
che una classe derivata da Wing deve necessariamente definire quella funzione (che si dice
ver.2009.d Copyright © A. De Marco

funzione virtuale pura). Di conseguenza, questo tipo Wing può fungere da interfaccia per
ogni classe che implementi le sue funzioni Init, Calculate, GetCD, GetCL e GetCm.
Un simile tipo Wing può essere usato come segue:
bool f(Wing *w, string fname)
{
if ( !w->Init(fname) ) return false;
w->Calculate();
return true;
};

L’operatore -> del C++ permette di accedere alle funzioni ed alle variabili membro di un
oggetto quando se ne ha a disposizione un puntatore (Wing *w dichiara w come variabile
DRAFT

di tipo puntatore a Wing). Si noti come la funzione f utilizzi l’interfaccia di Wing igno-
Tipi polimorfi randone completamente i dettagli implementativi. Una classe che fornisce l’interfaccia a

A. De Marco
1.4 Astrazione dei dati 37

una serie di altre classi è spesso chiamata tipo polimorfo. In questo caso Wing presuppone
necessariamente che debbano esistere classi derivate da essa poiché le sue funzioni mem-
bro sono funzioni virtuali pure. I programmatori C++ progettano classi come Wing con il
proposito di costruire una gerarchia di classi che derivano da questa.
Come è intuibile, l’implementazione in una classe derivata potrebbe consistere di tutto
ciò che apparteneva alla classe concreta del paragrafo precedente ed è stato invece omes-
so nell’ultima versione dell’interfaccia Wing. Se si ha in mente di risolvere problemi di
analisi aerodinamica allora la funzione Calculate dovrà rappresentare quasi certamente
l’aspetto più importante dell’implementazione. La funzione Init sarà altrettanto impor-
tante e provvederà a fornire all’utente di ciascuna classe derivata la possibilità di creare
file di dati e di configurazione iniziale del modello aerodinamico.
Si esamini la figura 1.1 nella quale sono richiamati schematicamente due diversi mo-
delli aerodinamici di un’ala finita. Quello rappresentato nella figura 1.1a è dovuto alla
famosa Teoria della linea portante (Lifting-Line Theory, LLT) di Prandtl [38] e permette
di calcolare, con un certo livello di approssimazione, le caratteristiche aerodinamiche di
un’ala — ad esempio, i coefficienti di resistenza indotta, CDi , di portanza, CL , e di mo-
mento di beccheggio, Cm — per una corrente di assegnato angolo d’attacco ˛ e numero
di Mach asintotico M1 .
A questo punto si potrebbe definire il nuovo tipo:

class Wing_LLT : public Wing { // Wing_LLT implementa Wing


public:
Wing_LLT();
~Wing_LLT();
bool Init(string filename);
void Calculate(void);
// double GetCD(void);
// double GetCL(void);
// double GetCm(void);
// ...
private:
// double referenceSurface, span, aspectRatio, taperRatio, leSweep,
// dihedral, geomTwist, aeroTwist;
// double alpha, mach;
// double cL, cD, cm;
// double *y; // span-wise stations ver.2009.d Copyright © A. De Marco
double *gamma // span-wise vorticity distribution
// ...
bool loadGeometry(string);
bool loadProfileData(string); // nella LLT è possibile usare dati
// ... // sperimentali dei profili
};
// ...
void Wing_LLT::Calculate(void)
{
// calcolo delle caratteristiche alari secondo la
// teoria della linea portante
// ...
}
DRAFT

// ...

Appunti di programmazione a oggetti in C++ e Matlab


38 Capitolo 1 Tecniche di programmazione supportate dal C++

y
vorticità
aderente vorticità
libera
centroidi
i C1
i
punti di
controllo

V1
(a) Modello basato sulla Teoria della linea portante di Prandtl.

y
vorticità
aderente

centroidi
vorticità
libera

i C1
i

V1
(b) Modello basato sulla Teoria del reticolo di vortici aderenti “ad anello” (Vortex Lattice Method, VLM).

Figura 1.1 Due possibili modelli per due diverse implementazioni di classi derivate da Wing.

Nella dichiarazione class Wing_LLT : public Wing { } il :public può essere letto
come “è derivato da”, “implementa” oppure “è un sottotipo di”. Perché una funzione
come la f definita in questo paragrafo possa usare un’istanza di Wing ignorando comple-
tamente i dettagli implementativi, qualche altra funzione deve farsi carico della creazione
di un oggetto sul quale f possa operare:
ver.2009.d Copyright © A. De Marco

void g() {
Wing_LLT wllt; // inizializzazione di default
wllt.SetAlpha(2.0); // potrebbero anche essere funzioni
wllt.SetMach(0.3); // membro di Wing anziche’ di Wing_LLT
try {
if ( !f( &wllt, // indirizzo di wllt
"wing_with_LLT_subdivisions.xml" ) )
{
cout << "Unable to configure from file." << endl;
exit(-1);
}
cout << "CL = " << wllt.GetCL() << endl;
DRAFT

}
catch (Wing::BadFormatFile) { // gestore dell’eccezione

A. De Marco
1.4 Astrazione dei dati 39

// oops: errore di formato


cout << "Bad format in Wing (LLT) configuration file" << endl;
}
}

Questa funzione è perfettamente lecita in C++ e mostra una delle caratteristiche più
importanti del linguaggio. La f non sa niente di Wing_LLT, ma conosce solo la classe
interfaccia Wing; opererà altrettanto bene per una diversa implementazione di Wing.
Quale potrebbe essere una implementazione alternativa? In Aerodinamica applicata
il modello della linea portante non è l’unico disponibile per effettuare studi ingegneristici
sulle proprietà delle ali. Esiste, ad esempio, un metodo di calcolo numerico dei coeffi-
cienti aerodinamici di un’ala chiamato Vortex-Lattice Method (VLM) che è basato sulla
Teoria dei vortici aderenti “ad anello” [39]. Una rappresentazione schematica di questo
modello alternativo è riportata nella figura 1.1b. Ecco un esempio di implementazione
alternativa:
class Wing_VLM : public Wing { // Wing_VLM implementa Wing
public:
Wing_VLM();
~Wing_VLM();
bool Init(string filename);
void Calculate(void);
// ...
private:
const int max_sw = 100; // span-wise
const int max_cw = 5; // chord-wise
_ _
double x[max sw][max cw]; // chord-wise divisions
double y[max_sw][max_cw]; // span-wise stations
double gamma[max_sw][max_cw] // bound vorticity
// ...
bool loadGeometry(string);
// ...
};
// ...
void Wing_VLM::Calculate(void)
{
// calcolo delle caratteristiche alari com
// il Vortex Lattice Method
// ...
}
ver.2009.d Copyright © A. De Marco

// ...

In questo caso la rappresentazione del tipo Wing_VLM è basata su un diverso insieme di dati:
le variabili private y e gamma — che nella classe Wing_LLT sono array monodimensionali e
rappresentano la discretizzazione dell’ala nel senso dell’apertura e le intensità dei vortici
aderenti ad essa associate (figura 1.1a) — sono adesso degli array bidimensionali poiché
è necessario gestire anche i nodi lungo la direzione della generica corda alare. Per questo
motivo a questa rappresentazione dei datisi è aggiunto l’array x. Per quanto riguarda
l’inizializzazione e la configurazione dall’esterno, per Wing_VLM sarà certamente diverso
il meccanismo di lettura da file della geometria del modello (funzione loadGeometry).
Questa classe derivata è una diversa implementazione dell’interfaccia Wing e l’indirizzo
DRAFT

di un oggetto di tipo Wing_VLM può essere lecitamente passato come primo argomento
della funzione f. Si dice che la classe derivata Wing_VLM, così come la Wing_LLT, “è

Appunti di programmazione a oggetti in C++ e Matlab


40 Capitolo 1 Tecniche di programmazione supportate dal C++

La classe derivata “è una” (“is a”) Wing. Quest’ultima è detta anche classe base o sopraclasse (base class o
una” (“is a”) classe base
super-class) mentre quelle da essa derivate si dicono sottoclassi (subclasses). L’esempio
seguente mostra un possibile uso di un oggetto di tipo Wing_VLM:

void h() {
Wing_VLM wvlm; // inizializzazione di default
wvlm.SetAlpha(2.0); // potrebbero anche essere funzioni
wvlm.SetMach(0.3); // membro di Wing anziche’ Wing_VLM
try {
if ( !f( &wvlm, // indirizzo di wvlm
"wing_with_VLM_subdivisions.xml" ) )
{
cout << "Unable to configure from file." << endl;
exit(-1);
}
cout << "CL = " << wvlm.GetCL() << endl;
}
catch (Wing::BadFormatFile) { // gestore dell’eccezione
// oops: errore di formato
cout << "Bad format in Wing (VLM) configuration file" << endl;
}
}

1.4.4 Funzioni virtuali

Come viene associata alla giusta definizione della funzione w->Init(fname), oppure del-
la funzione w->Calculate(), nella f? Quando f viene invocata dalla funzione h, devono
essere eseguite Wing_VLM::Init e Wing_VLM::Calculate. Per ottenere questo tipo di as-
sociazioni, un oggetto di tipo Wing deve contenere informazioni che indichino la funzione
che deve essere invocata durante l’esecuzione (at execution time, che è ben diverso da
at compilation time). Una tecnica che viene adottata dai compilatori moderni è quella di
convertire il nome di ciascuna funzione virtual in un indice all’interno di una tabella
ver.2009.d Copyright © A. De Marco

di puntatori a funzione. Questa tabella nel gergo tecnico degli sviluppatori di compila-
tori viene normalmente chiamata “tabella delle funzioni virtuali” o, semplicemente vtbl.
Ogni classe che possieda funzioni virtuali possiede una propria vtbl che identifica tali
funzioni.
Le funzioni nella vtbl permettono un corretto utilizzo dell’oggetto anche quando la
sua dimensione e la struttura dei suoi dati siano sconosciuti al chiamante. Tutto ciò che
il chiamante serve sapere è dove si trovi la vtbl in una Wing e l’indice utilizzato per cia-
scuna funzione virtuale. Questo meccanismo di chiamate virtuali viene implementato dai
compilatori in maniera così efficiente da essere paragonabile al meccanismo di “normale
chiamata di funzione”. Il suo costo aggiuntivo in termini di spazio occupato in memoria
DRAFT

è il puntatore (un intero) in ogni oggetto di una classe che contenga funzioni virtuali più
una vtbl per ognuna di tali classi.

A. De Marco
1.5 Programmazione orientata agli oggetti 41

1.5 Programmazione orientata agli oggetti

1.5.1 Un esempio: l’aerodinamica di un velivolo completo


Sebbene l’astrazione dei dati — cioè quella tecnica che porta alla progettazione e all’uso
di classi concrete — sia fondamentale per un buon progetto, si deve osservare che un tipo
concreto definisce una sorta di scatola nera. Una volta che la scatola nera è stata definita,
questa interagisce in maniera “rigida” con il resto del programma, nel senso che non è
possibile adattarla a nuovi usi, se non modificando la sua definizione. Questa situazione
può essere ideale ma può anche dimostrarsi poco flessibile in una serie di casi applicativi.
Nella progettazione delle classi Wing, Wing_LLT e Wing_VLM del paragrafo precedente,
visto il problema che si intende risolvere, si potrebbe pensare a cosa accomuna veramente
le implementazioni delle due classi derivate e a cosa, invece, le distingue. Nella formu-
lazione di molti problemi pratici l’astrazione dei concetti può portare effettivamente ad
individuare un insieme di proprietà comuni al gruppo dei possibili oggetti che il program-
matore intende mettere in gioco. Per ciascuno degli oggetti tipicamente si possono anche
distinguere delle proprietà specifiche, non possedute dagli altri. La capacità di di fatto-
rizzare le proprietà comuni e di distinguere tra le proprietà comuni e quelle specifiche
traendone vantaggio definisce la programmazione orientata agli oggetti. I linguaggi do-
tati di costrutti che permettono di esprimere e utilizzare questa distinzione supportano la
programmazione orientata agli oggetti, gli altri no. Il meccanismo dell’ereditarietà è uno Ereditarietà

degli elementi fondamentali di questo stile di programmazione.


Per approfondire questo concetto rivolgiamo l’attenzione ad un problema simile a Il calcolo aerodinamico
di un velivolo completo,
quello del calcolo aerodinamico delle ali dei velivoli ma, in un certo senso, più generale. possibile applicazione
Supponiamo che il programmatore debba progettare un sistema di calcolo delle forze e dei della tecnica di
programmazione a
momenti agenti sull’intera configurazione aerodinamica di un aeromobile. I progettisti dei oggetti
velivoli affrontano questo argomento integrando e sintetizzando le conoscenze provenienti
dall’Aerodinamica applicata ai profili alari, alle ali finite isolate, ai corpi affusolati, alle
combinazioni ala-fusoliera, e così via. Un esempio di configurazione aerodinamica di un
velivolo completo è mostrato nella figura 1.2f.
Come sanno bene gli ingegneri aerospaziali, per una data condizione di volo (quota,
velocità, angolo d’attacco, angolo di derapata, posizione effettiva dei comandi di volo, ec-
cetera), la stima delle forze e dei momenti aerodinamici agenti sul velivolo è un problema
tutt’altro che banale. Una schematizzazione ingegneristicamente accettabile dell’azione
ver.2009.d Copyright © A. De Marco

aerodinamica sulla configurazione del velivolo nella sua interezza è quella di conside-
rare quest’ultima come una sovrapposizione di effetti (aerodynamic build-up). L’azione
totale è la somma delle azioni aerodinamiche esercitate dai singoli elementi presi isolata-
mente (ala, fusoliera, piani di coda, gondole dei motori, eccetera); tipicamente, a questi
contributi isolati vanno aggiunti dei termini ulteriori: i cosiddetti effetti di interferenza
aerodinamica (dovuti al fatto che ciascun elemento aerodinamico è posto nella stessa cor-
rente aerodinamica ma in presenza degli altri). Le rappresentazioni schematiche mostrate
nella figura 1.2 danno un’idea di questo principio.
In particolare, nella figura 1.2a è evidenziato il ruolo prevalente dell’ala principale di
un velivolo di configurazione tradizionale nella genesi della portanza. La figura 1.2b sche-
DRAFT

matizza un’altra situazione di particolare importanza nel progetto di un velivolo che è data
dalla necessità di realizzare e mantenere stabilmente un equilibrio al beccheggio attraver-

Appunti di programmazione a oggetti in C++ e Matlab


42 Capitolo 1 Tecniche di programmazione supportate dal C++

(a) isolated wing (W) (b) wing-horizontal tail (WH)

(c) wing-body (WB) (d) wing-body-vertical tail (WBV)

(e) wing-body-horizontal tail (WBH) (f) Velivolo completo

Figura 1.2 Possibili sottoinsiemi della configurazione aerodinamica di un velivolo. Essa può es-
sere concepita come la composizione di un certo numero di “corpi aerodinamici”. Ciò può essere
tradotto opportunamente a livello di astrazione dati.

so un piano di coda orizzontale. Nelle figure 1.2c, 1.2d e 1.2e sono rappresentate le parti
di configurazione che devono essere necessariamente considerate, ad esempio, per una
ragionevole stima del coefficiente di resistenza del velivolo, o anche per un raffinamento
del calcolo delle caratteristiche aerodinamiche connesse alla portanza e al momento di
beccheggio.
Anche per il calcolo delle caratteristiche latero-direzionali dei velivoli valgono consi-
derazioni analoghe. In quel contesto è importante stimare la forza aerodinamica laterale
e i momenti di rollio e imbardata in condizioni di volo non simmetriche e risulta determi-
nante la successione di schematizzazioni: fusoliera o body (B), ala-fusoliera o wing-body
ver.2009.d Copyright © A. De Marco

(WB), ala-fusoliera-piano verticale di coda o wing-body-vertical tail (WBV). In tal caso,


il piano verticale di coda si comporta dal punto di vista aerodinamico esattamente come
un’ala, solo che è disposto diversamente nei confronti della corrente fluida.
Caratteristiche comuni Dalle considerazioni precedenti si può cominciare a pensare che nella configurazione
alle diverse componenti
di una configurazione
di un velivolo coesistono elementi aerodinamici che, dal punto di vista delle teorie ae-
aerodinamica rodinamiche, possiedono un certo numero di caratteristiche in comune. Ciò è vero sia
in termini dei parametri geometrici che ne permettono la definizione e la collocazione
in un dato sistema di riferimento globale sia in termini delle grandezze aerodinamiche
incognite che concorrono al calcolo dell’azione aerodinamica totale agente sul velivolo.
A tal proposito si esamini la figura 1.3. La posizione dell’ala rispetto ad un sistema di
DRAFT

riferimento globale del velivolo, detto riferimento costruttivo fOc ; xc ; yc ; zc g (con origine
Oc da qualche parte nella zona prodiera) è data dal punto AW (l’apice dell’ala) e dall’an-

A. De Marco
1.5 Programmazione orientata agli oggetti 43

yc

AW

xc bW

A?W
zc ?
cW
iW

xc

lW

Figura 1.3 Tipica schematizzazione della posizione di un’ala rispetto al sistema di riferimento
costruttivo di un velivolo fxc ; yc ; zc g. Il punto AW è l’apice dell’ala ed è un buon candidato per
rappresentare il refCenter di AerodynamicBody. L’angolo di calettamento iW coinciderà con il
valore della variabile membro incidenceSetting.

golo di calettamento iW . La definizione geometrica dell’ala potrà essere rappresentata da


?
alcuni parametri significativi; ad esempio: cW è la corda di radice, bW è l’apertura, e così
via.
Dal punto di vista dell’astrazione dei concetti, la figura 1.4 è simile a quella prece-
dente. Essa schematizza il posizionamento nel riferimento costruttivo dell’impennaggio
orizzontale (horizontal tail, H) ed il suo dimensionamento con dei parametri del tutto ana-
loghi a quelli utilizzati per l’ala. Ecco che si avranno, tra i possibili parametri geometrici
rappresentativi, la corda di radice cH? e l’apertura bH .
La posizione ed i parametri geometrici di questi corpi aerodinamici può essere sche-
matizzata in maniera del tutto generale in modo da comprendere anche gli altri elementi
della configurazione del velivolo come la fusoliera, il piano verticale di coda, eccetera.
ver.2009.d Copyright © A. De Marco

Basta definire:
 un riferimento locale al generico elemento, fO; ; ; g, la cui origine O è data nel
riferimento costruttivo,
 tre dimensioni caratteristiche di ciascun elemento, .` ; ` ; ` /, una per ogni dire-
zione coordinata,
 un orientamento rispetto alla terna costruttiva dato dalla terna di angoli di Eulero
. ; ; ˚ /.
I coefficienti aerodinamici di forza e di momento calcolati per ciascun corpo potranno
essere riferiti agli assi locali e trasformati, all’occorrenza, in modo da essere riferiti a
qualsiasi altra terna, ad esempio a quella degli assi costruttivi.
DRAFT

Nel caso dell’ala rappresentata nella figura 1.3, ad esempio, l’origine del riferimento
locale potrà coincidere con il punto AW , l’asse  sarà allineato con la corda di radice, l’asse

Appunti di programmazione a oggetti in C++ e Matlab


44 Capitolo 1 Tecniche di programmazione supportate dal C++

yc

AH

xc bH

A?H
zc cH?
iH

.< 0/
xc

lH

Figura 1.4 Posizione del piano di coda orizzontale rispetto al sistema di riferimento costruttivo di
un velivolo fxc ; yc ; zc g. Il punto AH è l’apice dell’impennaggio e l’angolo iH ne è il calettamento
intorno all’asse trasversale yc .

 sarà normale al piano di simmetria, orientato positivamente verso la destra del pilota,
e infine l’asse  sarà orientato in modo tale da rendere la terna levogira. Inoltre, l’unico
angolo di Eulero non nullo dell’orientamento di tale terna rispetto agli assi costruttivi è
  iW .
L’astrazione di questi concetti porta alla definizione di una classe base che potremmo
chiamare AerodynamicBody:
class AerodynamicBody {
public:
AerodynamicBody();
virtual bool Init(string filename) = 0;
virtual void Calculate(void) = 0;
ver.2009.d Copyright © A. De Marco

Point Where() { return refCenter; } // class Point defined elsewhere


virtual void Move(Point p, bool calc = true) {
refCenter = p;
/* ... */
if (calc) Calculate();
}
virtual void Orient(float a, float b, float c, bool calc = true) {
psi = a; theta = b; phi = c;
/* ... */
if (calc) Calculate();
}
virtual void SetAlphaDeg(float a, bool calc = true) {
alpha = a*degtorad;
DRAFT

/* ... */
if (calc) Calculate();

A. De Marco
1.5 Programmazione orientata agli oggetti 45

}
virtual void SetBetaDeg(float b, bool calc = true) {
beta = b*degtorad;
/* ... */
if (calc) Calculate();
}
// ...
float GetCFcsi() { return cFcsi; } // w.r.t. local ref.
float GetCFeta() { return cFeta; }
// ...
float GetCMzet() { return cMzet; }
float GetCFx(); // w.r.t. global ref
float GetCFy();
// ...
float GetCMz();
// ...
class BadFormatFile { /* ... */ }; // used as ecception class
// ...
protected:
static const double radtodeg; // these constants are unique
static const double degtorad; // for all objects
// ...
private:
Point refCenter; // origin of local ref.
float psi, theta, phi; // orientation w.r.t. global axes
float refLcsi, refLeta, refLzet; // reference quantities
float refSurface, refVolume;
float mach, reynolds;
float alpha, beta;
Point poleMoments; // moment ref. location
float cFcsi, cFeta, cFzet, cMcsi, cMeta, cMzet;
float cFx, cFy, cFz, cMx, cMy, cMz;
// ...
fromLocalToGlobal(); // transforming aerodynamic coefficients
// ...
};

Si dice che la classe AerodynamicBody raggruppa, “fattorizza”, mette a fattor comune,


le proprietà e le funzionalità che ci si aspetta da un generico corpo aerodinamico. Di un
corpo aerodinamico si vogliono conoscere, ad esempio, i coefficienti di forza e di mo-
ver.2009.d Copyright © A. De Marco


mento CF ; CF ; : : : ; CM rispetto al proprio riferimento locale oppure i corrispondenti

CFx ; CFy ; : : : ; CMz rispetto ad un riferimento globale.
Un utente potrebbe voler cambiare la posizione e l’orientamento del corpo aerodina-
mico rispetto al riferimento costruttivo. Per questo scopo la classe AerodynamicBody mette
a disposizione le funzioni: Where(), che ritorna un oggetto di classe Point contenente l’o-
rigine corrente del riferimento locale, Move(), che sposta l’origine in un punto desiderato,
e Orient() che ridefinisce l’orientamento. La chiamata di ciascuna di queste funzioni
presuppone una chiamata alla funzione virtuale Calculate(). Ciò sembra ragionevole,
dal momento che una qualunque variazione di posizionamento del corpo aerodinamico
rispetto alla corrente fluida e agli altri eventuali corpi aerodinamici compresenti dovrebbe
DRAFT

provocare una variazione dei coefficienti aerodinamici. Un ragionamento analogo vale


per SetAlphaDeg() e SetBetaDeg(), funzioni che consentono al programmatore di im-

Appunti di programmazione a oggetti in C++ e Matlab


46 Capitolo 1 Tecniche di programmazione supportate dal C++

postare nuovi angoli aerodinamici di funzionamento determinando una variazione delle


forze e dei momenti risultanti.
* Le definizioni di alcune funzioni membro di AerodynamicBody mostrano che in C++ è possibile as-
segnare un valore di default agli argomenti di una funzione. Ad esempio la definizione virtual void
Move(Point p, bool calc = true) prevede un secondo argomento, calc, che è di tipo bool. La sorta di
assegnazione che compare nella lista di argomenti vuol dire “se il codice chiamante non specifica questo
argomento, assegna ad esso un valore true”. La definizione di Move() consente all’utente di modificare
una caratteristica dell’oggetto e di scegliere se eseguire o non eseguire la funzione Calculate(). Questa
logica è valida anche per altre funzioni membro e potrebbe dimostrarsi molto utile per le classi derivate da
AerodynamicBody che ridefiniscono una funzione Calculate() particolarmente onerosa dal punto di vista
computazionale.

La rappresentazione della classe AerodynamicBody è data dalle variabili definite nella


parte privata, tra le quali si riconosceranno l’origine refCenter del riferimento locale
ed i suoi angoli di Eulero psi, theta e psi. Tra le altre variabili che costituiscono la
rappresentazione, vi sono le importanti grandezze cFcsi, cFeta, : : :, cMzeta, cFx, cFy,
: : :, cFz. Esse sono destinate a contenere i valori dei coefficienti aerodinamici.

1.5.2 Dalle classi concrete alle gerarchie di classi


La classe AerodynamicBody non è direttamente usabile dal programatore poiché contiene
le funzioni virtuali pure Calculate() e Init(). Questo significa che bisogna definire una
classe che erediti da AerodynamicBody sia la sua rappresentazione che la sua interfaccia,
cioè l’organizzazione dei dati e le funzioni previste per manipolarli.
Le funzioni dichiarate virtual nella classe base AerodynamicBody sono quelle che
possono avere una definizione specifica per ciascun tipo di corpo aerodinamico che il
programmatore sia disposto a concepire. Tra queste vi è certamente Calculate(), che
deve basarsi su tecniche di calcolo diverse a seconda se si tratti di un corpo affusolato,
come una fusoliera o una gondola motore, o di una superficie portante principale, come
l’ala. Analogamente, la funzione di interfaccia Init() troverà una definizione specifica
per ciascun tipo di forma aerodinamica, dovendo leggere da un file un insieme opportuno
di direttive di configurazione.
Si consideri ad esempio la fusoliera rappresentata nella figura 1.5. Il C++ permette di
definire una classe concreta Fuselage “derivandola” dalla classe astratta AerodynamicBody.
Quest’ultima è la classe base. Da essa la classe Fuselage, la classe derivata, eredita l’in-
terfaccia e la rappresentazione. In un apposito file di intestazione, ad esempio Fuselage.h,
ver.2009.d Copyright © A. De Marco

si potrà dichiarare:
class Fuselage : public AerodynamicBody {
public:
Fuselage();
bool Init(string filename);
void Calculate(void); // to be defined in the implementation
private:
vector<float> vCsiSectionC,
vZetSectionC,
vCrossSectionArea;
// ...
};
DRAFT

Altrove, ad esempio nel file di implementazione Fuselage.cpp, si potranno definire le


funzioni:

A. De Marco
1.5 Programmazione orientata agli oggetti 47

lB xc
zc bf .x/

bf,max  bB

hf .x/

yc

hf,max  hB
˛B
ˇB

V1

Figura 1.5 I principali parametri geometrici ed aerodinamici di una fusoliera (Body, B). Tipi-
camente, questo corpo aerodinamico ha un riferimento locale coincidente con quello globale:
.O; ; ; /  .Oc ; xc ; yc ; zc /.

void Fuselage::Init(string filename) {


/* ... */
}
void Fuselage::Calculate(void) {
/* ... */
}

La rappresentazione interna degli oggetti di tipo Fuselage è quella ereditata dalla


classe AerodynamicBody. In più, la classe derivata si specializza dotandosi nella parte pri-
vata di tre contenitori di tipo vector<float>, le variabili vCsiSectionC, vZetSectionC e
vCrossSectionArea (vettori della libreria standard), ed implementando le proprie versioni
di Init() e Calculate(). ver.2009.d Copyright © A. De Marco

Il tipo Fuselage può essere istanziato e ciascun oggetto potrà effettivamente interagire
con le risorse di sistema:
#include "Fuselage.h"
// ...
Fuselage fus;
if ( !fus.Init("a380_fuselage.xml") ) {
cout << "Error! Could not load config file for Fuselage object.\n"
exit(-1);
}
fus.SetMach(0.5,false); // Aerodynamic
fus.SetAlphaDeg(2,false); // settings
fus.SetBetaDeg(0,false);
DRAFT

fus.Calculate(); // Aerodynamic calculation


cout << "Cx = " << fus.GetCFx << endl;

Appunti di programmazione a oggetti in C++ e Matlab


48 Capitolo 1 Tecniche di programmazione supportate dal C++

Il passaggio dalla classe base AerodynamicBody alla classe concreta Fuselage è un


esempio molto semplice di gerarchia di classi. A seconda del problema che si intende
modellare, esiste la possibilità di articolare a piacimento la catena di derivazione di nuove
classi, a partire da superclassi a loro volta derivate da altre. Lo scopo è quello di costruire
dei meccanismi che consentano la massima flessibilità nell’uso degli oggetti, separando
per quanto sia possibile l’implementazione delle funzionalità e la rappresentazione degli
oggetti dalle loro interfacce. Questo è in effetti ciò che si intende per programmazione
orientata agli oggetti. Il paradigma diventa:

Decidi quali classi ti occorrano;


fornisci un insieme completo di operazioni per ogni classe;
rendi esplicite le parti comuni mediante l’ereditarietà.

La classe Fuselage può essere, a sua volta, la classe base di una nuova classe, che ere-
dita dalla prima la rappresentazione e l’interfaccia e si specializza per qualche motivo. Ad
esempio, per una particolare tecnica di calcolo dei coefficienti aerodinamici oppure per-
ché modella una particolare categoria di fusoliere che hanno delle caratteristiche peculiari
e non fattorizzabili negli oggetti aerodinamici generici modellati da AerodynamicBody o
da Fuselage.
A questo punto, dalla classe base AerodynamicBody si può pensare di derivare classi
analoghe a quella che modella fusoliere, ramificando la gerarchia. È naturale derivare
una classe Wing e da essa derivarne eventualmente delle altre, ad esempio HTail e Canard
che modellano tipi particolari di superfici portanti le cui caratteristiche aerodinamiche
vanno valutate in modo specifico. Questo discorso porta alla costruzione di una gerarchia
di tipi che permette di istanziare oggetti dalle proprietà adeguate agli oggetti fisici che
essi rappresentano. Gli oggetti interagiranno adeguatamente tra di loro e con le risorse di
sistema.
La programmazione a Un campo applicativo in cui la programmazione orientata agli oggetti si dimostra
oggetti è ideale per
sviluppare delle GUI
particolarmente adatta è quello dello sviluppo di applicazioni dotate di interfacce grafiche
(Graphical User Interfaces, GUI). Ma non è il solo. Esistono svariate classi di problemi
di tipo ingegneristico che ben si prestano alla modellazione per classi di oggetti. Come si
è visto, il calcolo aerodinamico dei velivoli ne è un esempio.
Come sostiene Herbert Schildt nella sua popolare Guida completa [4], il C++ è il
linguaggio perfetto per Windows ed il modo in cui supporta la programmazione a oggetti
ver.2009.d Copyright © A. De Marco

è completamente soddisfacente per sviluppare applicazioni in tale ambiente operativo (si


potrebbe aggiungere che ciò vale anche per Mac OS X e per i vari ambienti interattivi
di Linux). D’altra parte, i programmi per Windows sono, per loro stessa natura, estesi e
Complessità intrinseca complessi. La quantità di codice necessario per creare anche solo la semplice struttura
del codice sorgente di
applicazioni basate su
di un programma per Windows — dotato di una seppur minimale interfaccia grafica —
GUI occupa dalle 50 alle 70 righe. Per scrivere un programma per Windows che sia utile
per illustrare le funzionalità del C++ sono necessarie centinaia di righe di codice. In
altre parole, Windows non è l’ambiente più appropriato per descrivere le funzionalità
di un linguaggio di programmazione. Naturalmente è possibile utilizzare un compilatore
Windows per compilare semplici applicazioni che stampino messaggi sul canale di output
Programmi eseguiti da standard ed eventualmente accettino un input da tastiera. In questi casi il compilatore
DRAFT

console
creerà un’applicazione in grado di lanciare automaticamente una sessione a console nella
quale eseguire il programma.

A. De Marco
1.6 Cosa non abbiamo detto? 49

1.6 Cosa non abbiamo detto?


Non abbiamo parlato delle classi template del C++ e della programmazione generica, e
non ne parleremo approfonditamente. Questa tecnica di programmazione è alla base della Programmazione
generica e Standard
Standard Template Library (STL), una libreria software inclusa nelle implementazioni Template Library, STL
standard del linguaggio C++. La commissione dell’ente ISO (International Organization
for Standardization) che ha fissato ufficialmente le caratteristiche del C++ ha stabilito
che ogni compilatore che si dica standard compliant deve essere dotato di librerie che
implementano le funzionalità della STL, tra le quali la possibilità di usare strutture dati
generiche, iteratori e algoritmi generici. Un ottimo testo che approfondisce gli aspetti
progettuali e le modalità d’uso della STL è quello di Vandevoorde e Josuttis [8]. Non tutti
i programmatori sono tenuti a comprendere nei minimi dettagli le tecniche che consentono
di creare una libreria come la STL. Per molti è sufficiente capire quanto basta per poter
usare le classi che essa offre.
La STL costituisce uno strato software fondamentale per chiunque voglia lavorare
produttivamente con il C++. Essa fornisce un insieme precostituito di classi che il pro-
grammatore si aspetta di usare nella risoluzione della maggior parte dei problemi. Esempi
di classi del genere sono i container e gli array associativi, che nella STL sono definiti
in modo da poter operare con qualsiasi tipo di dato, sia primitivo che definito dall’uten-
te. Nelle classi container — a proposito di funzionalità che un programmatore si aspetta
di trovare già pronte all’uso — vi sono già implementate con criteri unificati le funzio-
ni standard di copia, assegnamento, inserimento/rimozione, iterazione tra gli elementi
(scorrimento).
Gli sviluppatori più avanzati possono anche derivare classi personalizzate da quelle
presenti nella STL purché essi rispettino pochi vincoli (ad esempio, l’implementazione di
operatori o funzioni di assegnamento o confronto). In tal senso la STL, unitamente alla
potenza espressiva di un linguaggio come il C++, offrono la possibilità di creare nuove
classi e librerie complete di tutte le funzioni e operazioni elementari.
Il container vector è una delle classi più importanti messe a disposizione dalla STL. Il contenitore vector
della STL
Un vector è un contenitore di elementi omogenei simile all’array, con la funzione di
permettere l’accesso ai suoi contenuti in modo rapido e ottimizzato, utilizzando indici o
tramite un iteratore. La definizione di questa classe si trova nel file header <vector> del
namespace std. Esso rappresenta in sostanza una versione evoluta dell’array del C. Infatti,
al contrario di un array tradizionale, un vector non ha una capacità massima prefissata in
ver.2009.d Copyright © A. De Marco

tempo di compilazione, ma si espande durante l’esecuzione a seconda delle necessità.


L’accesso agli elementi di un vector è di tipo casuale, e particolarmente efficaci sono
le operazioni di inserimento (push_back()) e rimozione in coda (pop_back()). Ecco un
esempio di costruzione e scorrimento di un vector di float:
std::vector <float> v;
v.push_back(1.0);
v.push_back(-2.5);
v.push_back(5.0);
for (int i = 0; i < v.size(); i++)
std::cout << v[i] << std::endl;
DRAFT

Sulla base degli esempi precedenti, si lascia al lettore l’esercizio di ragionare sul
seguente frammento di codice:

Appunti di programmazione a oggetti in C++ e Matlab


50 Capitolo 1 Tecniche di programmazione supportate dal C++

std::vector <AerodynamicBody *> v; // a vector of pointers

Fuselage * pF = new Fuselage(); // put in heap


pF->Init("fuselage.xml"); // define from file
v.push_back((AerodynamicBody *)pF); // cast and put in vector

Wing * pW = new Wing();


pW->Init("wing.xml");
v.push_back((AerodynamicBody *)pW); // cast and put in vector

HTail * pH = new HTail();


pH->Init("horizontal_tail.xml");
v.push_back((AerodynamicBody *)pH); // cast and put in vector

double Cx = 0.0;

for (int i = 0; i < v.size(); i++) {


v[i]->Calculate(); // call the correct member function
Cx += v[i]->GetCFx(); // cumulate the coefficient Cx
}
std::cout << "Aircraft total Cx = " << Cx << std::endl;

Nel prossimo capitolo continueremo ad approfondire le peculiarità della programma-


zione a oggetti attraverso ulteriori esempi in linguaggio C++. Questo servirà anche allo
scopo di introdurre altri dettagli sulle caratteristiche stesse del linguaggio. A tal proposito
vale la pena far precedere il prossimo argomento da un aforisma.

Il C++ sarebbe un liguaggio decente per insegnare la programmazione, se


potessimo insegnare la parte “++” senza dover spiegare anche la parte “C”.
– Michael B. Feldman
ver.2009.d Copyright © A. De Marco
DRAFT

A. De Marco
Capitolo 2

Ancora sulla programmazione in C++

If you think C++ is not overly complicated, just what is a protected


abstract virtual base pure virtual private destructor and when was the
last time you needed one?

– Tom Cargill

Indice
2.1 Elementi di base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2.2 Il mondo delle classi e degli oggetti . . . . . . . . . . . . . . . . . . . 59
2.3 La libreria standard . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.4 Le librerie Boost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.5 Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
2.6 Come imparare il C++ . . . . . . . . . . . . . . . . . . . . . . . . . . 66

[capitolo incompleto]
Il C++ fu “inventato” nel 1980 dal ricercatore informatico danese Bjarne Stroustrup,
che ricavò concetti già presenti in precedenti linguaggi (come il Simula67) per produrre
una versione modificata del C, che chiamò: “C con le classi”. Il nuovo linguaggio univa
la potenza e l’efficienza del C con la novità concettuale della programmazione a ogget-
ti, allora ancora in stato “embrionale” (c’erano già le classi e l’eredità, ma mancavano
l’overload, le funzioni virtuali, i riferimenti, i template, la libreria e moltre altre cose).
Il nome C++ fu introdotto per la prima volta nel 1983, per suggerire la sua natura
ver.2009.d Copyright © A. De Marco

evolutiva dal C, nel quale ++ è l’operatore di incremento (taluni volevano chiamarlo D,


ma C++ prevalse, per i motivi detti).
All’inizio, comunque, e per vari anni, il C++ restò un esercizio quasi privato del suo
autore e dei suoi collaboratori, progettato e portato avanti, come egli stesso disse, “per
rendere più facile e piacevole la scrittura di buoni programmi”.
Tuttavia, alla fine degli anni 80, risultò chiaro che sempre più persone apprezzavano ed
utilizzavano il linguaggio e che la sua standardizzazione formale era un obiettivo da per-
seguire. Nel 1990 si formò un comitato per la standardizzazione del C++, cui ovviamente
partecipò lo stesso Autore. Da allora in poi, il comitato, nelle sue varie articolazioni,
divenne il luogo deputato all’evoluzione e al raffinamento del linguaggio.
DRAFT

Finalmente l’approvazione formale dello standard si ebbe alla fine del 1997. In que-
sti ultimi anni il C++ si è ulteriormente evoluto, soprattutto per quello che riguarda
52 Capitolo 2 Ancora sulla programmazione in C++

l’implementazione di nuove classi nella libreria standard.


Per descriverlo in sintesi si può affermare che il C++ è un linguaggio di programma-
zione general-purpose, orientato alla realizzazione di sistemi che:
 può dirsi un C migliorato,
 supporta l’astrazione dei dati,
 supporta la programmazione agli oggetti,
 supporta la programmazione generica.

2.1 Elementi di base


Case sensitivity Il linguaggio C++ (come il C) distingue i caratteri maiuscoli da quelli minuscoli (è un
linguaggio case sensitive). Ad esempio, i nomi MiaVariabile e miavariabile indicano
due variabili diverse.
Moduli funzione In C++ (come in C) ogni modulo di programma è una funzione. Non esistono sub-
routines o altri tipi di sottoprogramma. Ogni funzione è identificata da un nome. Le
procedure possono essere simulate definendo funzioni che ritornano un void e gestendo
opportunamente gli argomenti attraverso variabili di tipo puntatore.

2.1.1 Interfaccia dei programmi con il sistema operativo


La funzione main Quando si manda in esecuzione un programma, questo inizia sempre dalla funzione iden-
tificata dalla parola chiave main. Questa funzione speciale viene detta in gergo “entry
point del programma” ed è invocata dal sistema operativo, che gli può passare dei para-
Passaggio di parametri metri; a sua volta main può restituire al sistema un numero intero. Questo valore coincide
da riga di comando e
codici di terminazione
con il valore di ritorno di main e di solito è destinato ad essere analizzato a valle dell’e-
dei programmi secuzione del programma come possibile codice di errore (detto in gergo return status o
exit code). Ad esempio, l’esecuzione da console del programma convert.exe può essere
invocata dal prompt dei comandi (>>) in questo modo:
>> convert.exe drawing.eps --output-format=PDF hinvioi
La console attraverso la quale viene eseguito questo comando può essere: il prompt dei
comandi di Windows (anche noto come prompt dei comandi DOS), una tipica finestra
shell di Linux oppure una finestra dei comandi Cygwin (che rappresenta il porting su
piattaforme Windows di gran parte delle funzionalità di Linux [24]). Se la console è
ver.2009.d Copyright © A. De Marco

dotata di interprete Bash [20], l’exit code è ottenibile attraverso il comando:


>> echo $? hinvioi
>> 0

La particella $? è, tecnicamente, una macro destinata ad essere “espansa” (cioè trasfor-


mata in una stringa) e serve ad interrogare l’interprete dei comandi sullo stato di ritorno
dell’ultimo comando. Questo valore è passato al comando echo di Bash che lo stampa sul
prompt. Il valore 0 restituito in questo caso segnala che la funzione main implementata nel
codice eseguibile di convert.exe ha fornito un valore di ritorno nullo. Il programmatore
che ha creato questo eseguibile ha probablimente seguito la regola per cui un valore di
ritorno 0 corrisponde ad una condizione di terminazione normale dei programmi.
DRAFT

La modalità d’invocazione del programma convert.exe fa luce, a sua volta, sul per-
ché la funzione main dei programmi C e C++ accetta degli argomenti. Dopo aver premuto

A. De Marco
2.1 Elementi di base 53

il tasto di invio, l’interprete dei comandi (sia esso quello di Bash o del DOS) esegue
l’analisi della stringa di caratteri costituita dall’intera riga di comando (command line
parsing). Esso riesce a separarla (interpretando gli spazi bianchi come separatori) in tre
stringhe: “convert.exe”, “drawing.eps” e “--output-format=PDF”; queste stringhe, in-
cluso il nome stesso del programma che si esegue, vengono dette parametri della riga di
comando (command line parameters). La funzione main è detta entry point del program-
ma convert.exe proprio perché, quando questo viene mandato in esecuzione, il sistema
tenterà di comunicarle sempre due valori: il numero di parametri della riga di comando
ed un puntatore ad un array di stringhe “in stile C” (sequenza di costanti di tipo carattere
terminate dal carattere di escape ’\0’, detto null terminator).
Ecco svelato il motivo per cui le funzioni main dei programmi C e C++ sono tipica-
mente definite in questo modo:
int main(int argc, char *argv[]){ hcorpo della funzionei }

La parte di riga su riportata che va dal primo carattere alla parentesi tonda chiusa —
int main( ... ) — viene detta in gergo function signature (la “firma” della funzione).
Essa dice che la funzione in esame ha per nome main, che ritorna un valore di tipo int
e che ha due argomenti: la variabile argc, di tipo int, e la variabile argv, di tipo array
di puntatori a caratteri (char*). Il valore di argc (che sta per argument count) è proprio
il numero degli argomenti della riga di comando che è servita ad invocare il programma.
Nel nostro esempio l’interprete dei comandi passa a main un argc pari a 3 e un vettore
argv pari a:

{"convert.exe\0","drawing.eps\0","--output-format=PDF\0"}

in cui l’elemento di posto 0, cioè argv[0], è sempre il nome dell’eseguibile. Nella fir-
ma di main il vettore argv ha dimensioni non specificate, cioè automaticamente determi-
nate a runtime (in fase di esecuzione) a seconda di come l’utente invoca il programma
dal prompt. Se lo stesso programma deve essere usato per convertire l’immagine vetto-
riale contenuta nel file drawing.eps in un’immagine bitmap, allora la riga di comando
dovrebbe essere un qualcosa di simile:
>> convert.exe drawing.eps --output-format=JPG --resolution-dpi=72 hinvioi

con un argument count pari a 4 ed un vettore argv:


{"convert.exe\0","drawing.eps\0",
ver.2009.d Copyright © A. De Marco

"--output-format=JPG\0","--resolution-dpi=72\0"}

È stato qui uno dei compiti del programmatore che ha implementato l’algoritmo di par-
sing della riga di comando quello di offrire all’utente di convert.exe la possibilità di
utilizzare l’applicazione per eseguire questo tipo di conversione e di specificare la riso-
luzione del risultato. Il buon senso dice che probabilmente l’ultimo comando produce in
output un file drawing.jpg della risoluzione voluta.
Come si può dedurre da questi esempi, il C++ si interfaccia al sistema operativo at-
traverso l’entry point dei programmi esattamente come ciò avverrebbe per applicazioni
scritte in linguaggio C. Ciò è dovuto al fatto che il C++ è stato progettato anche per poter
inglobare il C come una sorta di “sottolinguaggio”. Questa caratteristica permette di poter
DRAFT

compilare gran parte del codice C esistente con un compilatori C++ (standard compliant)
e di ereditare le efficienti librerie di funzioni di sistema sviluppate negli anni.

Appunti di programmazione a oggetti in C++ e Matlab


54 Capitolo 2 Ancora sulla programmazione in C++

2.1.2 Le funzioni
Le tre parti di una La funzione main è una funzione speciale ma, dal punto di vista formale, è una funzione
funzione
C++ come tutte le altre. Delle funzioni C++ si distinguono tre caratteristiche principali:
la lista degli argomenti, il blocco delle sue istruzioni ed il tipo del valore di ritorno.
Gli argomenti della lista di parametri di scambio passati dal programma chiamante
vanno indicati fra parentesi tonde dopo il nome della funzione; f(void) indica che la
funzione di nome f non ha argomenti.
Il blocco di delle funzioni C++ è l’ambito di azione, anche detto ambito di visibilità
o scope delle istruzioni della funzione; va racchiuso fra parentesi graffe. Ogni istruzione
deve terminare con “;” (può estendersi su più righe o vi possono essere più istruzioni
sulla stessa riga). Un’istruzione è costituita da una successione di token: un token è il più
piccolo elemento di codice individualmente riconosciuto dal compilatore. Sono token:
gli identificatori, le parole-chiave, le costanti letterali o numeriche, gli operatori e alcuni
caratteri di punteggiatura. Gli spazi bianchi e gli altri caratteri “separatori” (horizontal
or vertical tabs, new lines, formfeeds) fra un token e l’altro o fra un’istruzione e l’altra,
sono ignorati. In assenza di separatori il compilatore analizza l’istruzione da sinistra a
destra e tende, nei casi di ambiguità, a separare il token più lungo possibile. Ad esempio,
l’istruzione:
a = i+++j;

può essere interpretata come:


a = i + ++j;

oppure come:
a = i++ + j;

Secondo lo standard, il compilatore sceglie la seconda interpretazione.


Il tipo del valore di ritorno di una funzione al programma chiamante va indicato prima
del nome della funzione ed è obbligatorio; se è void indica che non c’è valore di ritorno.
Commenti I commenti sono brani di programma (che il compilatore ignora) inseriti al solo scopo
di documentazione, cioè per spiegare il significato delle istruzioni e così migliorare la
leggibilità del codice. Sono molto utili anche allo stesso autore, per ricordargli quello che
ha fatto, quando ha necessità di rivisitare il programma per esigenze di manutenzione o di
aggiornamento. Un buon programma si caratterizza anche per il fatto che fa abbondante
ver.2009.d Copyright © A. De Marco

uso di commenti. In C++ ci sono due modi possibili di inserire i commenti. Nel primo,
ereditato dal linguaggio C, l’area di commento è introdotta dal doppio carattere /* e ter-
mina con il doppio carattere */ (può anche estendersi su più righe). In alternativa, l’area
di commento inizia con il doppio carattere // e termina alla fine della riga.
Il programma seguente è un esempio minimale di programma in C++:
#include <cstdio>
void main(void)
{
printf("Hello world!\n");
}
DRAFT

Lo stesso programma è riportato con l’aggiunta di commenti che spiegano i diversi ele-
menti formali previsti dal linguaggio:

A. De Marco
2.1 Elementi di base 55

#include <cstdio> /* inserisce il file che dichiara printf,


<...> indicano che il file ”cstdio” va cercato
in una cartella di default del sistema
*/
void // nessun valore di ritorno
main(void) // nessun argomento accettato dall’entry point
// non sono previste parametri da riga di comando
{ // inizio blocco
// printf, funzione di libreria standard
printf("Hello world!\n"); // stampa una stringa sul canale
// di output standard
} // fine blocco

2.1.3 Tipi nativi


Come è noto, per tipo di una variabile si intende un termine di classificazione che rag- Il C++ effettua uno
stretto controllo sui tipi di
gruppa tutte quelle variabili che sono memorizzate nello stesso modo e a cui si applica lo variabile
stesso insieme di operazioni. Nel linguaggio C++ viene esercitato (dal compilatore) un
forte controllo sui tipi (strong type checking), nel senso che in fase di compilazione viene
regolata e limitata la conversione da un tipo all’altro (casting) e controllata l’interazione
fra variabili di tipo diverso.
In C++ esistono solo 5 tipi, detti intrinseci o nativi: Tipi nativi del C++

int numero intero di 2 o 4 byte,


char numero intero di 1 byte (interpretabile come codice ascii di un carattere),
float numero in virgola mobile con 6-7 cifre significative (4 byte),
double numero in virgola mobile con 15-16 cifre significative (8 byte),
bool valore booleano: true o false (1 byte).
In realtà il numero di tipi possibili è molto più grande, sia perché ogni tipo nativo può
essere specializzato mediante i qualificatori di tipo, sia perché il programma stesso può
creare propri tipi personalizzati (detti “tipi astratti”).

2.1.4 Il preprocessore e le unità di traduzione dei programmi


In C++ (come in C), prima che il compilatore inizi a lavorare, viene attivato un pro-
gramma, detto preprocessore, che ricerca nel file sorgente speciali istruzioni, chiamate
ver.2009.d Copyright © A. De Marco

direttive.
Una direttiva inizia sempre con il carattere # (nella colonna 1) e occupa una sola riga Le direttive di
compilazione
(non ha un terminatore, in quanto finisce alla fine della riga; riconosce però i commenti,
introdotti da // o da /*, e la continuazione alla riga successiva, definita da \).
Il preprocessore crea una copia del file sorgente (da far leggere al compilatore) e, ogni
volta che incontra una direttiva, la esegue sostituendola con il risultato dell’operazione.
Pertanto il preprocessore, eseguendo le direttive, non produce codice binario, ma codice
sorgente per il compilatore.
Ogni file sorgente, dopo la trasformazione operata dal preprocessore, prende il nome Le translation unit

di translation unit o unità di traduzione. Ogni translation unit viene poi compilata sepa-
DRAFT

ratamente, con la creazione del corrispondente file oggetto, in codice binario. Spetta al
linker, infine, collegare tutti i files oggetto, generando un unico programma eseguibile.

Appunti di programmazione a oggetti in C++ e Matlab


56 Capitolo 2 Ancora sulla programmazione in C++

Nel linguaggio C++ esistono molte direttive (alcune delle quali dipendono dal siste-
ma operativo). Le più usate sono le seguenti: #include, #define, #undef e direttive
condizionali.
La direttiva #include La direttiva #include è molto importante. Ad esempio la sequenza:
#include <filename1>
#include "filename2"

determina l’inserimento, nel punto in cui si trova la direttiva, dell’intero contenuto dei
file filename1 e filename2. L’uso delle parentesi angolari, intende che filename1 vada
cercato nel percorso (directory) di default del linguaggio (opportunamente preimpostato
o comunicato al compilatore al momento della compilazione). Se invece si usano le vir-
golette, il file, per essere incluso con seuccesso, deve trovarsi nella stessa directory in cui
risiede il sorgente del programma.
La direttiva #include viene usata quasi esclusivamente per inserire gli header-files
(.h) ed è particolarmente utile quando in uno stesso programma ci sono più file sorgenti
(implementation-files) che includono lo stesso header-file.
La direttiva #define di Quando il preprocessore incontra la seguente direttiva:
una costante
#define hidentificatorei hvalorei
dove, hidentificatorei è un nome simbolico (che segue le regole generali di specifica di
tutti gli altri identificatori) e hvalorei è un’espressione qualsiasi, delimitata a sinistra da
spazi bianchi (blank, ’ ’), caratteri di tabulazione (tab, \t) e a destra da dei blanks, dei
tab o dal carattere di andata a capo (new-line, ’\n’), l’hidentificatorei viene sostituito con
hvalorei in tutto il file (da quel punto in poi). In generale la direttiva #define serve per
assegnare un nome a una costante (che viene detta “costante predefinita”). Ad esempio,
la direttiva:
#define M_PI 3.14159265358979323846

sostituisce (da quel punto in poi) in tutto il file la parola M_PI con 3.14159265358979323846.
Ogni volta che il programma deve usare il valore numerico che approssima , si può
specificare in sua vece la costante M_PI.
Va osservato che che la sostituzione è assolutamente fedele e cieca, qualunque sia il
contenuto dell’espressione che viene sostituita all’identificatore. Il compito del prepro-
cessore è quello di effettuare sostituzioni di questo tipo, di includere file esterni e selezio-
nare le parti di codice sorgente da compilare (escludendone eventualmente delle altre). il
compito di “segnalare gli errori” viene lasciato al compilatore.
ver.2009.d Copyright © A. De Marco

Esistono principalmente due vantaggi nell’uso di #define. In primo luogo, se il pro-


grammatore decide di cambiare valore a una costante, è sufficiente che lo faccia in un
solo punto del programma. In secondo luogo, molto spesso i nomi sono più significativi e
mnemonici dei numeri (oppure più brevi delle stringhe, se rappresentano costanti stringa)
e perciò l’uso delle costanti predefinite permette una maggiore leggibilità del codice e una
maggiore efficienza nella programmazione.
In pratica la direttiva #define produce gli stessi risultati dello specificatore di tipo
const, che è una parola chiave peculiare del C++. Al posto della direttiva dell’esempio
precedente si sarebbe potuto scrivere la dichiarazione:
const float M_PI = 3.14159265358979323846 ;
DRAFT

I vantaggi nell’uso di const sono due: il tipo della costante è dichiarato ed un even-
tuale errore di dichiarazione viene segnalato immediatamente dal compilatore; inoltre, la

A. De Marco
2.1 Elementi di base 57

costante è riconosciuta, e quindi analizzabile, nelle operazioni di debug. D’altra parte, Confronto fra la direttiva
#define e lo
una una costante predefinita mediante l’uso di un #define a volte è più comoda e imme- specificatore const
diata (è una questione sostanzialmente “estetica”) e può essere usata anche per altri scopi
(per esempio per sostituire o mascherare nomi).
Il preprocessore dispone di un suo mini-linguaggio di controllo, che consiste nelle Le direttive condizionali

seguenti direttive condizionali:


#if hespressione 1i
hblocco di direttive e/o istruzionii
#elif hespressione 2i
hblocco di direttive e/o istruzionii
#else
hblocco di direttive e/o istruzionii
#endif

dove hespressionei è un’espressione logica che può contenere solo identificatori di co-
stanti predefinite o costanti letterali, ma non variabili e neppure variabili dichiarate const,
che il preprocessore non riconosce. In particolare hespressionei può essere del tipo:
defined(hidentificatorei)

che restituisce vero se hidentificatorei è definito (cioè se è stata eseguita la direttiva:


#define hidentificatorei
Al posto di
#if defined(hidentificatorei)

si può usare la forma:


#ifdef hidentificatorei
Esiste anche la possibilità di applicare l’operatore di negazione ! cosicché
!defined hidentificatorei
restituisce vero se hidentificatorei non è definito. Al posto di
#if !defined(hidentificatorei)

si può usare la forma:


#ifndef hidentificatorei
Nel costrutto di direttiva condizionale l’istruzione #elif sta per else if ed è opziona- ver.2009.d Copyright © A. De Marco
le (possono esserci più blocchi consecutivi, ciascuno introdotto da un #elif). L’istruzione
#else è opzionale (se esiste, deve introdurre l’ultimo blocco prima di #endif). L’istru-
zione #endif è obbligatoria e termina la sequenza iniziata con un #if. Non é necessario
racchiudere i blocchi fra parentesi graffe, perché ogni blocco è terminato da #elif, o da
#else, o da #endif. Il preprocessore identifica il blocco (se esiste) che corrisponde alla
prima condizione risultata vera, oppure il blocco relativo alla direttiva #else (se esiste)
nel caso che tutte le condizioni precedenti siano risultate false. Tale blocco può contenere
sia istruzioni di programma che altre direttive, comprese direttive condizionali (posso-
no esistere più blocchi #if “innestati”): il preprocessore esegue le direttive e presenta al
compilatore le istruzioni che si trovano nel blocco selezionato, scartando sia direttive che
istruzioni contenute negli altri blocchi della sequenza #if ... #endif.
DRAFT

Ecco un estratto di codice reale che mostra un esempio d’uso delle direttive di compi-
lazione:

Appunti di programmazione a oggetti in C++ e Matlab


58 Capitolo 2 Ancora sulla programmazione in C++

// Program JSBSim.cpp
// see: http://www.jsbsim.org
// http://jsbsim.cvs.sourceforge.net/viewvc/jsbsim/JSBSim/
// ...

#if !defined(__GNUC__) && !defined(sgi) && !defined(_MSC_VER)


# include <time>
#else
# include <time.h>
#endif

#if defined(__BORLANDC__) || defined(_MSC_VER) || defined(__MINGW32__)


# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <mmsystem.h>
# include <regstr.h>
# include <sys/types.h>
# include <sys/timeb.h>
#else
# include <sys/time.h>
#endif

// ...

#if defined(__BORLANDC__) || defined(_MSC_VER) || defined(__MINGW32__)


double getcurrentseconds(void)
{
struct timeb tm_ptr;
ftime(&tm_ptr);
return tm_ptr.time + tm_ptr.millitm*0.001;
}
#else
double getcurrentseconds(void)
{
struct timeval tval;
struct timezone tz;

gettimeofday(&tval, &tz);
return (tval.tv_sec + tval.tv_usec*1e-6);
}
ver.2009.d Copyright © A. De Marco

#endif

Questo frammento di codice deve stabilire quali header-file vanno inclusi al fine di acce-
dere a delle funzioni di sistema che restituiscono l’ora corrente e permettano di gestire
dei calcoli in realtime (cioè che avvengano rispettando una precisa scansione temporale).
Attraverso il preprocessore, nella prima direttiva condizionale viene verificata l’esistenza
della costante __GNUC__ e la non esistenza delle costanti sgi ed _MSC_VER. La prima vie-
ne definita di default dai compilatori gcc e g++, che fanno parte della distribuzione GNU
[21, 24]. Le altre due sono analoghe costanti definite, rispettivamente, dal compilatore
del sistema operativo Irix, sviluppato da Silicon Graphics (SGI) [32], e dall’ambiente di
sviluppo Microsoft Visual C++ [27] (compilatore cl.exe). Per questioni legate alle tecno-
DRAFT

logie dei sistemi di compilazione, è necessario in questo caso dover includere il file time
se __GNUC__ è definita; diversamente, ad esempio, se si sta compilando con Microsoft

A. De Marco
2.2 Il mondo delle classi e degli oggetti 59

Visual Studio sarà necessario leggere il file time.h.


Anche la successiva direttiva di compilazione condizionale verifica il tipo di com-
pilatore utilizzato e include gli header-file opportuni. La costante __BORLANDC__ è defi-
nita automaticamente dall’ambiente di sviluppo Borland C++Builder [26] (compilatore
bcc32.exe) e la costante __MINGW32__ è definita dall’ambiente di compilazione Mingw
[25] (compilatore mingw32.exe).
Infine, più avanti nel codice sorgente si rende necessaria la definizione della funzio-
ne getcurrentseconds che deve restituire il tempo di sistema (come numero in doppia
precisione, inclusa una parte decimale, che esprime i secondi trascorsi a partire dalla
mezzanotte dell’anno 1969). Come si vede ispezionando il codice, questa funzione deve
essere definita diversamente a seconda del compilatore in uso.
L’uso delle direttive di compilazione permette di architettare il codice sorgente in
maniere che possono diventare anche molto sofisticate. Uno dei vantaggi più evidenti
è che in questo modo è possibile sviluppare programmi in grado di essere compilati da
diversi sistemi di sviluppo e per diversi sistemi operativi. Ciò si esprime dicendo che il
software è portabile e multipiattaforma.

2.2 Il mondo delle classi e degli oggetti


Il termine “tipo astratto”, usato in contrapposizione ai tipi nativi del linguaggio, non è
molto appropriato: il C++ consente al programmatore di definire nuovi tipi, estendendo
così le capacità effettive del linguaggio; ma, una volta definiti, questi tipi sono molto
“concreti” e sono trattati esattamente come i tipi nativi. Per questo motivo, la tendenza
moderna è di identificare i tipi non nativi con il termine: “tipi definiti dall’utente” (user-
defined types) e di confinare l’aggettivo “astratto” a una precisa sottocategoria di questi
(di cui parleremo più avanti).
In questa sezione parleremo dei tipi astratti comuni sia al C che al C++, usando però
la nomenclatura del C++ (“oggetti”, “istanze”, eccetera).

2.2.1 Tipi definiti dall’utente e terminologia


Il termine “oggetto” è sostanzialmente sinonimo del termine “variabile”. Benché questo Oggetti

termine si usi soprattutto in relazione ai tipi definiti dall’utente (come strutture o classi), ver.2009.d Copyright © A. De Marco

si può generalizzare il concetto, definendo oggetto una variabile di qualunque tipo, non
solo formalmente definita, ma anche già creata e operante.
È noto infatti che l’istruzione di definizione di una variabile non si limita a dichiarare Costruire oggetti

il suo tipo, ma crea fisicamente la variabile stessa, allocando la memoria necessaria (nella
terminologia C++ si dice che la variabile viene “costruita” o “istanziata”): pertanto la
definizione di una variabile comporta la “costruzione” di un oggetto.
Il termine “istanza” è quasi simile al termine oggetto; se ne differenzia in quanto
sottolinea l’appartenenza dell’oggetto a un dato tipo (istanza di “qualcosa”). Per esempio,
la dichiarazione/definizione:
int ivar ;
DRAFT

costruisce l’oggetto ivar, istanza del tipo int. Dunque, “istanziare un certo tipo” significa Creare istanzianze di un
tipo di dato
creare un’istanza di quel tipo.

Appunti di programmazione a oggetti in C++ e Matlab


60 Capitolo 2 Ancora sulla programmazione in C++

La parola chiave L’istruzione introdotta dalla parola-chiave typedef definisce un sinonimo di un tipo
typedef
esistente, cioè non crea un nuovo tipo, ma un nuovo identificatore di un tipo (nativo o
astratto) precedentemente definito. Ad esempio:
typedef unsigned long int* Pul ;

definisce il nuovo identificatore di tipo Pul, che potrà essere usato, nelle successive
dichiarazioni (all’interno dello stesso ambito), per costruire oggetti di tipo puntatore a
unsigned long. Ecco alcuni esempi:

unsigned long a;
Pul ogg1 = &a;
Pul parray[100]; // eccetera

L’uso di typedef permette di semplificare dichiarazioni lunghe di variabili dello stesso


tipo. Per esempio, supponiamo di dover dichiarare molti array, tutti dello stesso tipo e
della stessa dimensione:
double a1[100];
double a2[100];
double a3[100]; // eccetera

Usando typedef la semplificazione è evidente:


typedef double MyArray[100];
MyArray a1, a2, a3;

2.2.2 Le strutture
Come gli array, in C++ (e in C) le strutture sono gruppi di dati. A differenza dagli array,
La parola chiave struct i singoli componenti di una struttura possono essere di tipo diverso. Ecco un esempio di
definizione di una struttura tramite la parola chiave struct (che il C++ eredita dal C):
struct Anagrafico
{
char nome[20];
int anni;
char indirizzo[30];
} ;

Dopo la parola-chiave struct segue l’identificatore della struttura, detto anche marcatore
ver.2009.d Copyright © A. De Marco

o tag, e, fra parentesi graffe, l’elenco dei componenti della struttura, detti membri o campi.
Ogni membro è dichiarato come una normale variabile (è una semplice dichiarazione, non
una definizione, e pertanto non comporta la creazione dell’oggetto corrispondente) e può
essere di qualunque tipo (anche array o puntatore o una stessa struttura). Dopo la parentesi
graffa di chiusura, è obbligatoria la presenza del punto e virgola (diversamente dai blocchi
delle funzioni).
In C++ (e non in C) la definizione di una struttura comporta la creazione di un nuovo
tipo, il cui nome coincide con il tag della struttura. Pertanto, nell’esempio appena fatto,
Anagrafico è a pieno titolo un tipo (come int o double), con la sola differenza che si
tratta di un tipo astratto, non nativo del linguaggio.
Per questo motivo l’enunciato di una struttura è una definizione e non una semplice
DRAFT

dichiarazione: crea un’entità (il nuovo tipo) e ne descrive il contenuto. Ma, diversamente
dalle definizioni delle variabili, non alloca memoria, cioè non crea oggetti. Perchè ciò

A. De Marco
2.2 Il mondo delle classi e degli oggetti 61

avvenga, il nuovo tipo deve essere istanziato, esattamente come succede per i tipi nativi.
Riprendendo l’esempio, l’istruzione di definizione:
Anagrafico davide, giovanni, cugini[30] ;

costruisce gli oggetti davide, giovanni e l’array cugini, istanze del tipo Anagrafico.
Solo adesso viene allocata memoria, per ogni oggetto in quantità pari alla somma delle
memorie che competono ai singoli membri della struttura
* L’operatore sizeof( ... ) può essere applicato sia al tipo Anagrafico sia ad una sua istanza (ad
esempio sizeof(Anagrafico) o sizeof(davide)) e restituisce il numero dei bytes allocati ad ogni istanza
di Anagrafico).

La collocazione ideale della definizione di una struttura è in un header-file: conviene Gli header-file

infatti separarla dalle sue istanze, in quanto la definizione deve essere (di solito) acces-
sibile dappertutto, mentre le istanze sono normalmente locali e quindi limitate dal loro
ambito di visibilità.
* Potrebbe però sorgere un problema: se un programma è suddiviso in più files sorgente e tutti includono
lo stesso header-file contenente la definizione di una struttura, dopo l’azione del preprocessore risulteranno
diverse translation unit con la stessa definizione e quindi sembrerebbe violata la “regola della definizio-
ne unica” (o one-definition-rule, ODR). Questo problema viene tipicamente risolto creando una costante
predefinita per ciascun header-file e facendo in modo che il preprocessore non lo legga più di una volta.

Ecco come un programmatore C++ scriverebbe il file Anagrafico.h:


#ifndef ANAGRAFICO_H
#define ANAGRAFICO_H
struct Anagrafico
{
hdefinizione dei campii
} ;
#endif

Se è necessario includere questo file in diversi file sorgente, il preprocessore controllerà


l’avvenuta definizione della macro ANAGRAFICO_H. La prima volta che il preprocessore si
trova a leggere Anagrafico.h sarà quella in cui resterà definita la macro ANAGRAFICO_H.
Nel resto del processo di precompilazione, se dovesse essere richiesta la rilettura del file
header, l’esito del controllo #ifndef ANAGRAFICO_H sarà falso e non ci sarà il rischio di
ridefinire il tipo Anagrafico più volte.
La grande utilità delle strutture consiste nel fatto che i nomi delle sue istanze posso- L’operatore . (punto)

no essere usati direttamente come operandi in molte operazioni o come argomenti nelle
chiamate di funzioni, consentendo un notevole risparmio, soprattutto quando il numero
ver.2009.d Copyright © A. De Marco

di membri è elevato. In alcune operazioni, tuttavia, è necessario accedere a un mem-


bro individualmente. Ciò è possibile grazie all’operatore binario . (punto) di accesso
al singolo membro: questo operatore ha come left-operand il nome dell’oggetto e come
right-operand quello del campo. Ad esempio: davide.indirizzo.
Come altri operatori che svolgono compiti analoghi (per esempio l’operatore [ ] di
accesso al singolo elemento di un array), anche l’operatore . può restituire sia la lettura di
un dato (un cosiddetto r-value) che l’inserimento di un dato (un cosiddetto l-value). Ecco
due esempi:
int a = davide.anni; // inizializza a con il valore del campo
// "anni" dell’oggetto davide
DRAFT

giovanni.anni = 1; // inserisce 1 nel campo anni dell’oggetto


// giovanni

Appunti di programmazione a oggetti in C++ e Matlab


62 Capitolo 2 Ancora sulla programmazione in C++

2.2.3 Dalle strutture alle classi

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit, vestibulum ut,
placerat ac, adipiscing vitae, felis. Curabitur dictum gravida mauris. Nam arcu libero,
nonummy eget, consectetuer id, vulputate a, magna. Donec vehicula augue eu neque.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis ege-
stas. Mauris ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna
fringilla ultrices. Phasellus eu tellus sit amet tortor gravida placerat. Integer sapien est,
iaculis in, pretium quis, viverra ac, nunc. Praesent eget sem vel leo ultrices bibendum.
Aenean faucibus. Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabi-
tur auctor semper nulla. Donec varius orci eget risus. Duis nibh mi, congue eu, accumsan
eleifend, sagittis quis, diam. Duis eget orci sit amet orci dignissim rutrum.
Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem
non justo. Nam lacus libero, pretium at, lobortis vitae, ultricies et, tellus. Donec aliquet,
tortor sed accumsan bibendum, erat ligula aliquet magna, vitae ornare odio metus a mi.
Morbi ac orci et nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque
a nulla. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
mus. Aliquam tincidunt urna. Nulla ullamcorper vestibulum turpis. Pellentesque cursus
luctus mauris.

2.3 La libreria standard


Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem
non justo. Nam lacus libero, pretium at, lobortis vitae, ultricies et, tellus. Donec aliquet,
tortor sed accumsan bibendum, erat ligula aliquet magna, vitae ornare odio metus a mi.
Morbi ac orci et nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque
a nulla. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
mus. Aliquam tincidunt urna. Nulla ullamcorper vestibulum turpis. Pellentesque cursus
luctus mauris.
Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at, tincidunt
tristique, libero. Vivamus viverra fermentum felis. Donec nonummy pellentesque ante.
Phasellus adipiscing semper elit. Proin fermentum massa ac quam. Sed diam turpis,
ver.2009.d Copyright © A. De Marco

molestie vitae, placerat a, molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend
at, accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend
consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt purus vel magna.
Integer non enim. Praesent euismod nunc eu purus. Donec bibendum quam in tellus.
Nullam cursus pulvinar lectus. Donec et mi. Nam vulputate metus eu enim. Vestibulum
pellentesque felis eu massa.

2.4 Le librerie Boost


DRAFT

Le librerie Boost per il C++ sono state create con lo scopo di estendere quelle Standard.
Sono, inoltre, concepite per essere ampiamente utili ed usabili in molte applicazioni.

A. De Marco
2.5 Esempi 63

2.5 Esempi

2.5.1 Una classe completa per la lettura dati da file


Si ha il problema di leggere delle storie temporali di alcune grandezze fisiche immagazzi-
nate in un file di testo nel formato csv (comma-separated values). Per ciascun parametro,
I dati sono contenuti in colonne. In ciascuna riga di testo i valori reali, di cui il primo è un
tempo, sono separati da virgole. Per facilitare la lettura e l’interpretazione delle colonne
di dati, la prima riga del file contiene dei nomi separati da virgole (stringhe di caratteri), in
numero pari al numero delle colonne di dati. Ecco un esempio di file di nome mydata.csv
con tre colonne di dati, oltre a quella dei tempi:
Time , P (deg/s), Q (deg/s), R (deg/s), P dot (deg/s^2)
0.0333332, -0.00015656, -4.292971189, 0.00002302, -0.005936929
0.0833330, -0.00062498, -7.185558850, 0.00010837, -0.010642481
0.1416661, 0.00000000, 0.000934767, -0.00000000, 0.000000434
0.1916659, 0.00000010, 0.001996633, -0.00000001, 0.000003059
0.2416657, 0.00000038, 0.000776778, -0.00000006, 0.000006922
0.2916655, 0.00000088, -0.003406548, -0.00000015, 0.000011341
0.3416653, 0.00000160, -0.010372289, -0.00000032, 0.000016014
0.3916651, 0.00000256, -0.019579002, -0.00000055, 0.000020764
0.4416649, 0.00000375, -0.030411132, -0.00000096, 0.000025527
0.4916647, 0.00000519, -0.042320402, -0.00000139, 0.000030247
0.5416645, 0.00000685, -0.054878723, -0.00000197, 0.000034896
0.5916643, 0.00000875, -0.067782347, -0.00000266, 0.000039467
0.6416641, 0.00001087, -0.080833323, -0.00000344, 0.000043918
0.6916639, 0.00001321, -0.093913868, -0.00000449, 0.000048246
...

Il file header della classe ADM::DataFile è il seguente:


//-------------------------------
// File: DataFile.h
// Author: Agostino De Marco
// Date: April 2009
//-------------------------------
#ifndef DATAFILE_H
#define DATAFILE_H
ver.2009.d Copyright © A. De Marco

#include <fstream>
#include <cstdio>
#include <string>
#include <vector>
#include <cmath>
#include <iostream>

using namespace std;

/* This class handles reading a data file containing


time histories in csv format (comma-separated values):
- data are stored in columns
- first row is a comma-separated list of names
DRAFT

- first colum represents time (sec)


*/

Appunti di programmazione a oggetti in C++ e Matlab


64 Capitolo 2 Ancora sulla programmazione in C++

namespace ADM {

class DataFile {
public:
DataFile();
~DataFile();
DataFile(string fname);

std::vector <string> names;


string data_str;
typedef std::vector <double> Row;
typedef std::vector <Row> DataType;
DataType Data;

int GetNumFields(void) {
if (Data.size() > 0) return(Data[0].size());
else return(0);
}
int GetNumRecords(void) {
return(Data.size());
}
double GetStartTime(void) {
if (Data.size() >= 2) return(Data[0][0]);
else return(0);
}
double GetEndTime(void) {
if (Data.size() >= 2) return(Data[Data.size()-1][0]);
else return(0);
}
double GetMax(int column) {return(Max[column]);}
double GetMin(int column) {return(Min[column]);}
double GetRange(int field) {
return (GetMax(field) - GetMin(field));
}
void SetStartIdx(int sidx) {StartIdx = sidx;}
void SetEndIdx(int eidx) {EndIdx = eidx;}
int GetStartIdx(void) {return StartIdx;}
int GetEndIdx(void) {return EndIdx;}
bool IsOk() {return fileGood;}
ver.2009.d Copyright © A. De Marco

void NicePrintNames(void);

private:
string buff_str;
ifstream f;
Row Max;
Row Min;
int StartIdx, EndIdx;
bool fileGood;

string stripSpaceLE(string str);


string stripSpaceTE(string str);
DRAFT

};
} // end namespace ADM

A. De Marco
2.5 Esempi 65

#endif

Ecco un esempio di possibile utilizzo della classe:


//-------------------------------
// File: test1.cpp
// Author: Agostino De Marco
// Date: April 2009
//-------------------------------
#include "DataFile.h"

#include <iostream>
#include <fstream>
#include <cstdio>
#include <cstdlib>

using namespace std;


using namespace ADM;

int main(int argc, char *argv[])


{
if (argc > 2 || argc == 1 ) {
cout << endl
<< "\tUsage: test1.exe <data_file_name.csv>" << endl;
exit(-1);
}

ADM::DataFile df(argv[1]);

df.NicePrintNames();

int numvars = df.GetNumFields();


int numpoints = df.GetNumRecords();
double starttime = df.GetStartTime();
double endtime = df.GetEndTime();

cout << endl;


cout << endl << "Data file contains "
<< numvars << " independent variables." << endl;
cout << "Number of data points: "
<< numpoints << endl;
ver.2009.d Copyright © A. De Marco

cout << "Time goes from "


<< starttime << " to " << endtime << " seconds." << endl;
return 0;
}

Se si lancia il programma test1.exe con la riga di comando:


>> test1.exe mydata.csv

si avrà in output:
File mydata.csv successfully opened.
Done parsing names. Reading data ...
Done Reading data.
DRAFT

Here are the available parameters:


0) Time 1) P (deg/s)

Appunti di programmazione a oggetti in C++ e Matlab


66 Capitolo 2 Ancora sulla programmazione in C++

2) Q (deg/s) 3) R (deg/s)
4) P dot (deg/s^2)

Data file contains 4 independent variables.


Number of data points: 320
Time goes from 0.0333332 to 15.9916 seconds.

2.6 Come imparare il C++


Come è noto, un linguaggio di programmazione ha due scopi correlati: (i) fornire i mezzi
perché il programmatore possa specificare le azioni da eseguire, e (ii) fornire un insieme
di concetti per pensare a quello che può essere fatto. Il primo scopo richiede che il lin-
guaggio sia vicino alla macchina, per permettere che tutti gli importanti aspetti legati alla
macchina siano trattati in una maniera ragionevolmente ovvia per il programmatore. Il
linguaggio C fu progettato con in mente questo scopo. Il secondo scopo richiede che il
linguaggio sia vicino al problema da risolvere, di modo che i concetti necessari alla so-
luzione siano direttamente e concisamente esprimibili. Le potenzialità aggiunte al C per
creare il C++ sono state progettate pensando a questo obiettivo.
Imparare direttamente il C++, per i programmatori che non conoscono il C, dovrebbe
essere la strada da seguire. Il C++ è più sicuro, più espressivo e riduce il bisogno di
concentrarsi su tecniche di basso livello. Per i programmatori che conoscono il C, il
vantaggio di imparare il C++ equivale al grande vantaggio che si ha nelle lingue naturali
di essere essere almeno bilingui.
Per imparare il C++, la cosa più importante è concentrarsi sui concetti, senza perdersi
nei dettagli tecnici del linguaggio. Come sostiene Bjarne Stroustrup nel suo testo C++
– Linguaggio, libreria standard, principi di programmazione [1]: si impara un nuovo
linguaggio di programmazione per diventare programmatori migliori; cioè per diventa-
re più bravi nel progettare e realizzare nuovi sistemi e nel mantenere quelli vecchi. Per
questo motivo, arrivare a padroneggiare le tecniche di programmazione e progetto do-
vrebbe essere molto più importante che comprendere subito i dettagli del linguaggio. La
comprensione, di solito, viene col tempo e la pratica.
Si deve tener presente che il C++ supporta molti stili di programmazione. Tutti sono
basati sul controllo rigoroso dei tipi (strong type checking), e hanno lo scopo di rag-
ver.2009.d Copyright © A. De Marco

giungere un livello di astrazione piuttosto alto e la diretta rappresentazione delle idee


del programmatore. Ogni tecnica di programmazione può raggiungere i suoi obiettivi
efficacemente, mantenendo efficiente l’uso delle risorse di tempo e memoria.
Chi è pratico di altri linguaggi (ad esempio il C, il Fortran o il Matlab) dovrebbe
convincersi che per sfruttare i benefici del C++ occorre spendere tempo per imparare e
assimilare gli stili di programmazione e le tecniche adatte al C++ (specialmente il para-
digma di programmazione a oggetti). Un programmatore abituato a lavorare col Fortran,
ad esempio, potrebbe certamente scrivere programmi in C++ adottando solamente il pa-
radigma di programmazione procedurale a cui è abituato. Egli deve però confrontarsi con
la realtà di fatto in cui l’applicazione non meditata a un linguaggio di tecniche efficaci
DRAFT

in un linguaggio differente porta generalmente a un codice difficile da capire, arduo da


mantenere e di scarsa efficienza.

A. De Marco
2.6 Come imparare il C++ 67

Il C++ può essere appreso gradualmente. Il modo con cui si impara un nuovo linguag-
gio dipende da ciò che già si conosce e dagli scopi che ci si prefigge. Probabilmente lo
spirito migliore con cui un programmatore può cimentarsi con il C++ non è tanto quello
di acquisire una nuova sintassi con cui realizzare le stesse cose di prima, ma, piuttosto,
quello di apprendere modi nuovi e migliori di costruire sistemi. Ciò equivale a cercare
di diventare un programmatore e un progettista migliore. Questo deve essere, per forza
di cose, un processo graduale, perché l’acquisizione di nuove capacità richiede tempo e
pratica: basti pensare quanto tempo occorre per apprendere bene una nuova lingua o im-
parare a suonare un nuovo strumento musicale. Migliorare come programmatore e come
progettista di programmi è senz’altro più semplice e veloce, ma non così semplice come
si vorrebbe.

ver.2009.d Copyright © A. De Marco


DRAFT

Appunti di programmazione a oggetti in C++ e Matlab


DRAFT ver.2009.d Copyright © A. De Marco
Capitolo 3

Strumenti di sviluppo

I quickly found out that C++ programmers are the


smartest programmers in the world, as each one I met
would quickly point out to me.

– Don Box

Indice
3.1 Il Compilatore gcc in ambiente GNU/Linux o Cygwin . . . . . . . . 69
3.2 Ambienti integrati di sviluppo (IDE) . . . . . . . . . . . . . . . . . . 71

[capitolo incompleto]

3.1 Il Compilatore gcc in ambiente GNU/Linux o Cygwin


Per GNU/Linux [22] e nelle finestre Cygwin [24] in ambiente Windows è disponibile un
compilatore integrato C/C++ che si invoca con i comandi gcc e g++, rispettivamente. In
realta g++ è uno script che chiama gcc con opzioni specifiche per riconoscere il C++.
La schermata di una finestra di Cygwin è mostrata nella figura 3.1, nella quale si vede
l’output del comando gcc -v che ottiene la versione del compilatore gcc in uso.

3.1.1 Il progetto GNU


ver.2009.d Copyright © A. De Marco

Il comando gcc, che sta per GNU Compiler Collection, fa parte del progetto GNU [21].
Il progetto GNU fu lanciato nel 1984 da Richard Stallman con lo scopo di sviluppare un
sistema operativo di tipo Unix che fosse un software completamente liberoi free software.
È ormai famoso il gioco di parole “Cosa è GNU? Gnu Non è Unix!”, che contiene una
sottile ricorsione. Riporto un estratto dal manifesto del progetto GNU http://www.gnu.
org/gnu/manifesto.html, scritto da Richard Stallman:

GNU, che sta per “Gnu’s Not Unix” (Gnu Non è Unix), è il nome del siste-
ma software completo e Unix-compatibile che sto scrivendo per distribuirlo
liberamente a chiunque lo possa utilizzare. Molti altri volontari mi stanno
DRAFT

aiutando. Abbiamo gran necessità di contributi in tempo, denaro, programmi


e macchine.
70 Capitolo 3 Strumenti di sviluppo

3.1.2 I passi della compilazione


Sia gcc che g++ processano file di input attraverso uno o più dei seguenti passi:
1. preprocessing, cioè
– la rimozione dei commenti, e
– l’interpretazione di speciali direttive per il preprocessore denotate da #, come:
#include, che include il contenuto di un determinato file, #define, che definisce
un nome simbolico o una variabile, eccetera;
2. compilation, cioè la traduzione del codice sorgente ricevuto dal preprocessore in
codice assembly;
3. assembly, cioè la creazione del codice oggetto;
4. linking, ovvero la combinazione delle funzioni definite in altri file sorgenti o definite
in librerie con la funzione main() per creare il file eseguibile.

Figura 3.1 Schermata di una finestra dei comandi Cygwin [24]. È mostrato l’output del comando
gcc -v che ottiene la versione del compilatore gcc in uso.

3.1.3 L’utilità make


Il programma make è una utility usata con i sistemi operativi della famiglia UNIX che
automatizza il processo di conversione dei file da una forma ad un’altra, risolvendo le
ver.2009.d Copyright © A. De Marco

dipendenze e invocando altri programmi per il lavoro necessario alle diverse operazioni.
Molto frequentemente make è usato per la compilazione di codice sorgente in codice
oggetto, unendo e poi collegando il codice oggetto in una forma finale corrispondente ad
un esegubile o ad una libreria. Il comando make usa file chiamati makefiles per deter-
minare il grafico delle dipendenze per un particolare output, e gli script necessari per la
compilazione da passare alla shell dei comandi (ad esempio una shell Bash). Il termine
“makefile” proviene dal nome di default makefile o Makefile che make va a cercare se
invocato senza parametri aggiuntivi.
Ecco un semplice esempio di Makefile che serve a creare l’eseguibile myfgfsclient.exe
a partire dalla compilazione dei file myfgfsclient.cpp e datafile.cpp
DRAFT

# Makefile for the program myfgfsclient


VERSION = 0.1

A. De Marco
3.2 Ambienti integrati di sviluppo (IDE) 71

CC = g++
OPTIMIZE = -g -Wall
CFLAGS = $(DEFINES) $(OPTIMIZE)
LFLAGS = -lstdc++ -lm
PROGS = myfgfsclient.exe
PROGS_O = myfgfsclient.o datafile.o
LIBS =
all: objs progs

progs:
$(CC) $(CFLAGS) $(LFLAGS) -o $(PROGS) $(PROGS_O) $(LIBS)

objs: $(PROGS_O)

.cpp.o:
$(CC) $(CFLAGS) -c -o $*.o $<

.o:
$(CC) $(CFLAGS) $(LFLAGS) -o $* $(PROGS_O) $(LIBS)

clean: cleanbin
rm -f *.o *~

cleanbin:
rm -f $(PROGS)

dep:
rm -f .depend
make .depend

tar:
tar czvf myfgfsclient.tgz Makefile \
myfgfsclient.cpp datadile.h datafile.cpp README TODO

Posizionandosi nella cartella in cui risiede il Makefile ed i sorgenti del programma


l’utente potrà lanciare il comando:
>> make

ottenendo la creazione del file mtfgfsclient.exe se questo era assente o possiede una
data di creazione precedente a uno qualsiasi dei file da cui dipende (sorgenti o oggetto).
ver.2009.d Copyright © A. De Marco

Per approfondimenti sulle funzionalità avanzate della utility make si rimanda il lettore
alla consultazione del manuale online di GNU make [23].

3.2 Ambienti integrati di sviluppo (IDE)


Un Integrated Development Environment (IDE) è un’applicazione interattiva che aiuta lo
sviluppatore di software (cioè il programmatore) a costruire un progetto. Un progetto è un
insieme di file, contenenti codice sorgente, che vengono letti ed elaborati dal compilatore
separatamente e poi collegati insieme (tramite il linker) per costruire un unico file in
DRAFT

codice binario, contenente il programma eseguibile, che può essere a sua volta lanciato
dallo stesso IDE o autonomamente da sistema operativo.

Appunti di programmazione a oggetti in C++ e Matlab


72 Capitolo 3 Strumenti di sviluppo

Figura 3.2 Schermata di una sessione di lavoro con Microsoft Visual C++ 2008 Express [27].

Lo sviluppatore interagisce con l’ambiente di svilutto tramite menu di tipo pop-up (a


tendina); in genere le voci di menu più significative sono selezionabili anche tramite tool-
bars (gruppi di icone) o tramite i cosiddetti acceleratori (tasti della keyboard che eseguono
la stessa funzione della corrispondente voce di menu).
Un IDE può aprire sullo schermo e usare parecchie finestre contemporaneamente,
contenenti i files sorgente (uno per ogni finestra), l’output del programma, le informazio-
ni acquisite in fase di debug, eccetera. Possono esistere anche finestre che contengono
l’elenco dei file, delle funzioni, o anche delle singole variabili utilizzate; “cliccando” su
queste voci si può raggiungere rapidamente la parte di programma che interessa esaminare
o modificare.

3.2.1 L’ambiente di sviluppo Microsoft Visual Studio


Qui illustreremo brevemente l’utilizzo di Microsoft Visual C++, disponibile per il siste-
ma operativo Windows (http://www.microsoft.com/express/vc/). Il Visual C++ non è
ver.2009.d Copyright © A. De Marco

soltanto un IDE, ma un linguaggio vero e proprio, essendo dotato di funzionalità e librerie


che vanno ben oltre lo standard C++. Ci limiteremo, però, ad illustrare il suo ambiente di
sviluppo, nella versione “ridotta” per applicazioni che utilizzano solo codice standard.

3.2.2 L’ambiente di sviluppo Code::Blocks


L’ambiente di sviluppo Code::Blocks è un IDE libero, open source e multipiattaforma
(http://www.codeblocks.org/). È scritto in C++ usando le librerie del toolkit grafico
wxWidgets [31]. L’ambiente utilizza un’architettura basata su plugin, le sue capacità
e caratteristiche sono estese proprio dai plugin installati dall’utente a seconda delle sue
DRAFT

esigenze. Attualmente, Code::Blocks è orientato verso il C/C++.


Code::Blocks è disponibile per Windows, GNU/Linux e Mac OS X.

A. De Marco
3.2 Ambienti integrati di sviluppo (IDE) 73

ver.2009.d Copyright © A. De Marco

Figura 3.3 La finestra di esplorazione della solution in Microsoft Visual C++ 2008 Express.
DRAFT

Appunti di programmazione a oggetti in C++ e Matlab


74 Capitolo 3 Strumenti di sviluppo

Figura 3.4 La finestra di configurazione delle proprietà di un progetto in Microsoft Visual C++
2008 Express (Menu Project, Properties; oppure clic con pulsante destro sulla voce del progetto
nella finestra di esplorazione, Properties dal menu a tendina, come nella figura 3.4). I parametri
del del processo di compilazione e collegamento possono essere impostati per tutte le possibili
configurazioni (Debug, Release) o resi specifici per ciascuna di esse.
ver.2009.d Copyright © A. De Marco

Figura 3.5 Particolare della configurazione delle proprietà di compilazione (C/C++, General). In
DRAFT

questo esempio l’utente ha impostato un percorso aggiuntivo, oltre a quello di default del compi-
latore Microsoft, per la ricerca degli header file (Additional Include Direcoties).

A. De Marco
3.2 Ambienti integrati di sviluppo (IDE) 75

Figura 3.6 Particolare della configurazione delle proprietà di collegamento (Linker, General). In
questo esempio l’utente ha impostato un percorso personalizzato per il file eseguibile. Nella casella
Output File si ha la macro $(ProjectDir)..\test\$(ProjectName).exe, che Visual Studio espanderà
sostituendo dei percorsi concreti ai simboli $(ProjectDir) e $(ProjectName).

ver.2009.d Copyright © A. De Marco

Figura 3.7 Finestra di dialogo attraverso la quale è possibile modificare il percorso dell’Output
DRAFT

File (figura 3.6). Visual Studio mostra i risultati dell’espansione di un nutrito numero di simboli
predefiniti, tra cui i percorsi di sistema $(FrameworkDir) e $(FrameworkSDKDir).

Appunti di programmazione a oggetti in C++ e Matlab


76 Capitolo 3 Strumenti di sviluppo

Figura 3.8 La finestra di output (o di log) della compilazione e del collegamento (Menu Project,
Build hnome progettoi).
ver.2009.d Copyright © A. De Marco

Figura 3.9 Schermata di una sessione di lavoro con Code::Blocks [28].


DRAFT

A. De Marco
Parte II

Programmazione a oggetti
con Matlab

ver.2009.d Copyright © A. De Marco


DRAFT
Acquisisci nuove conoscenze mentre rifletti sulle vecchie,
e forse potrai insegnare ad altri.
– Confucio
ver.2009.d Copyright © A. De Marco
DRAFT
Capitolo 4

Una panoramica su Matlab

Jesce sole, jesce sole, nun ce fa’ cchiù suspirà!


– Gatta Cenerentola

Indice
4.1 Elementi di base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.2 Toolboxes e Simulink . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.3 Sessioni di lavoro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.4 Usare l’help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.5 Nozioni e notazioni fondamentali . . . . . . . . . . . . . . . . . . . . 84
4.6 Operazioni di Input / Output . . . . . . . . . . . . . . . . . . . . . . 91

4.1 Elementi di base


Matlab è un linguaggio di programmazione di alto livello nato per il calcolo tecnico-
scientifico. Le funzionalità di Matlab in quanto applicazione rispecchiano la filosofia con
cui essa è stata progettata e sviluppata via via negli anni. L’ambiente di lavoro è struttu-
rato in modo da risultare particolarmente utile nei seguenti campi applicativi: (i) Analisi
numerica e calcolo, (ii) sviluppo di codice e scripting, (iii) modellistica, simulazione
e prototyping, (iv) analisi di dati, esplorazione e visualizzazione, (v) disegno industria-
ver.2009.d Copyright © A. De Marco

le e scientifico, (vi) sviluppo di applicazioni stand-alone corredate di interfaccia utente


(Graphical User Interface, GUI).
Matlab, il cui nome deriva da Matrix Laboratory, inteso come ambiente di sviluppo,
fornisce un linguaggio per il calcolo che ha come struttura di base la matrice. Gli scalari
sono matrici 1  1, i vettori riga sono matrici 1  n ed i vettori colonna sono matrici
m  1. Le due dimensioni principali di questi array vengono dedotte dal contesto delle
istruzioni di assegnamento. In momenti successivi è sempre possibile un reshaping, cioè
un adattamento di una data matrice m  n a delle nuove dimensioni m0  n0 attraverso il
riposizionamento, l’aggiunta o la sottrazione di elementi.
Esiste anche la possibilità, come accade per altri linguaggi di programmazione, di
DRAFT

definire ed utilizzare array multidimensionali. Questi sono delle generalizzazioni delle


matrici e costituiscono delle collezioni di dati accessibili mediante tre o più indici.
80 Capitolo 4 Una panoramica su Matlab

Matlab esegue tutti i calcoli numerici con numeri complessi in doppia precisione e la
grande maggioranza delle operazioni e delle funzioni predefinite sono implementate in
modo da lavorare nativamente su entità matriciali. Ciò è particolarmente vantaggioso nel-
la risoluzione di molti problemi di calcolo dell’ingegneria, in particolare quelli formulati
teoricamente nei testi con agevoli notazioni vettorali e matriciali. Tali problemi posso-
no essere implementati in linguaggio Matlab e risolti efficientemente attraverso comandi
semplici, con un minimo utilizzo di cicli. Ad esempio, per la soluzione dei tipici proble-
mi dell’algebra lineare è possibile, con poche righe di codice e senza un appesantimento
del carico di lavoro del programmatore, richiamare un numero di algoritmi ben collaudati
basati sulle funzionalità di librerie come LINPACK, LAPACK o EISPACK.
I codici di calcolo in linguaggio Matlab sono generalmente più snelli rispetto a quelli
che sarebbe necessario sviluppare con un linguaggio scalare come il C o il Fortran 77.
Anche se linguaggi di programmazione più evoluti come il C++, con il paradigma di pro-
grammazione a oggetti, e come il Fortran 90/95, con l’uso avanzato dei moduli, permetto-
no ormai una implementazione snella ed efficiente di una vasta categoria di algoritmi, la
popolarità di Matlab, con il suo linguaggio intuitivamente più semplice da imparare e con
la sua ben riuscita integrazione tra lo strumento di sviluppo codice, il debugger e l’output
grafico, rende il “laboratorio della matrice” uno strumento più attraente per chi si accosta
al mondo della programmazione o per chi ha in mente tempi di sviluppo ridotti.
L’ambiente Matlab si è evoluto durante gli anni grazie al feedback ed al contributo dei
molti utenti sparsi per il mondo. In ambienti universitari è ormai uno strumento didattico
standard per corsi introduttivi e corsi avanzati, nella matematica, nell’ingegneria e nelle
scienze.

4.2 Toolboxes e Simulink


L’ambiente possiede dei gruppi tematici di funzioni, denominate toolboxes (cassette degli
attrezzi), pensati ed implementati ad hoc per classi particolari di problemi. Essi risultano
molto utili per la maggior parte degli utenti di Matlab e sono pensati per fornire all’uten-
te degli strumenti di calcolo preconfezionati, efficienti e specializzati. I toolboxes sono
collezioni complete di funzioni Matlab che estendono l’ambiente di lavoro e permettono
di risolvere particolari categorie di problemi. Le aree scientifiche per le quali sono di-
sponibili dei toolboxes specifici sono numerose. Alcuni esempi sono: l’elaborazione dei
segnali, i sistemi di controllo, le reti neurali, le wavelets, la simulazione in generale, il
ver.2009.d Copyright © A. De Marco

calcolo simbolico, la realtà virtuale.


Di particolare importanza è il pacchetto software Simulink, utilizzabile come modulo
dell’ambiente Matlab, e concepito per la modellazione e l’analisi di sistemi dinamici.
Simulink supporta sia modelli lineari che non lineari, a tempo continuo e discreto. Si
possono modellare sistemi a vettore di stato di dimensione qualsiasi e di tipo multirate, in
cui diversi sottogruppi di variabili di stato sono aggiornate con frequenze differenti.
Per la modellazione Simulink fornisce un’interfaccia a finestre, che si avvia digitando
il comando simulink nella finestra dei comandi di Matlab, figura 4.4, o con l’apposito
tasto di avvio nell’interfaccia principale. L’uso della GUI di Simulink permette la co-
struzione di un modello a partire dal disegno, dalla collezione ed dalla interconnessione
DRAFT

di blocchi funzionali, proprio come si farebbe in una fase di analisi preliminare di un si-
stema (prototyping) con l’uso di carta e penna. Simulink è dotato di un’ampia libreria

A. De Marco
4.3 Sessioni di lavoro 81

di blocchi e di tipi di connessioni preconfezionati. L’utente può ovviamente personaliz-


zare delle entità già presenti nelle librerie di default o crearne delle proprie. Si rimanda
all’help di Matlab per approfondimenti sulle modalità di implementazione delle cosiddette
S-functions.
Un pacchetto di funzioni Simulink di notevole utilità nel campo aerospaziale ed in
particolare nella dinamica del volo è costituito dal toolbox Aerospace Blockset (nelle ver-
sioni di Matlab non inferiori alla 6.5) per l’analisi e l’integrazione di sistemi dinamici che
modellano il funzionamento di velivoli di vario genere e di sistemi propulsivi.

4.3 Sessioni di lavoro


Di solito Matlab viene lanciato selezionando l’applicazione da un ambiente a finestre co-
me Windows o fornendo il comando matlab in una delle shell di comandi Unix/Linux.
Matlab è a sua volta un programma con una riga dei commandi ed un suo prompt >>.
Appena lanciato compare per qualche istante, prima dell’avvio vero e proprio dell’appli-
cazione e durante il caricamento di tutti i moduli software necessari al suo funzionamento,
una finestra (splash screen) con il logo di Matlab, la versione ed il tipo di licenza. Se ciò
accade significa che tutto è stato configurato correttamente, in caso contrario è necessario
controllare la validità dell’installazione o della licenza.

ver.2009.d Copyright © A. De Marco

Figura 4.1 Il layout di default della finestra principale di Matlab: in alto a sinistra il workspace
con le variabili definite al momento dell’ultimo comando; in basso a sinistra la storia dei comandi
più recenti; a destra la finestra con il prompt dei comandi.

Una volta avviata l’applicazione, il layout di default della finestra principale di Matlab,
come si vede dalla figura 4.1, si presenta come l’insieme di più sotto-finestre: la finestra
DRAFT

con il prompt dei comandi, la finestra del workspace con le variabili definite al momento
dell’ultimo comando ed una finestra con la storia dei comandi più recenti. La command

Appunti di programmazione a oggetti in C++ e Matlab


82 Capitolo 4 Una panoramica su Matlab

Figura 4.2 L’editor di M-files di Matlab in grado di aprire più di un file di script alla volta e
corredato di tasti per l’avvio e la gestione del debug.

window svolge anche il ruolo di finestra di log dei messaggi all’utente. In questo senso si
presenta come estensione di una shell come la finestra DOS in Windows o della console
in Unix/Linux. Per accedere ad uno dei comandi della shell del sistema operativo o an-
che lanciare un eseguibile esterno basta digitare il comando stesso preceduto dal carattere
‘!’, detto shell escape command. Ad esempio per conoscere velocemente il contenuto
della directory di lavoro corrente basta dare il comando !dir in Windows o !ls -l in
Unix/Linux (o anche in Windows se è installato Cygwin).
Un numero di comandi di Matlab può essere collezionato in un file, di estensione
“.m”, ed interpretato dal kernel, che è il nucleo e motore numerico del programma. In
gergo informatico tali files, detti M-files, sono degli script di comandi perché eseguiti in
maniera sequenziale.
Un comando corrispondente al nome (estensione esclusa) di un M-file è noto all’am-
biente di lavoro se il file si trova nel percorso di ricerca predefinito. La ricerca da parte
dell’interprete dei comandi avviene in alcune sottocartelle della cartella in cui è instal-
ver.2009.d Copyright © A. De Marco

lato il programma e nella cartella di lavoro (current directory), che l’utente può definire
arbitrariamente a seconda delle sue esigenze.
Oltre alla finestra principale con il prompt dei comandi, fanno parte dell’ambiente
di lavoro: (i) l’editor degli script di comandi (M-file editor), si veda la figura 4.2, che si
avvia dal menu File!New!M-file, e (ii) le finestre dei grafici che l’utente produce nella
sua sessione di lavoro, ad esempio con il comando plot.
Se si digita plot(0:0.1:1) appare la finestra riportata nella figura 4.3. Essa contiene
un grafico in cui le ordinate corrispondono ai numeri ottenuti a partire da 0 con incrementi
di 0:1 fino ad arrivare a 1 e le ascisse corrispondono agli indici interi che vanno da 1 fino
ad 11. La sequenza di coppie di coordinate viene rappresentata per default unendo i punti
DRAFT

con una linea continua. Come vedremo tra breve, l’operatore “:” consente di ottenere
rapidamente un vettore di numeri.

A. De Marco
4.3 Sessioni di lavoro 83

Figura 4.3 Una finestra grafica di Matlab ottenuta con il semplice comando plot(0:0.1:1).

ver.2009.d Copyright © A. De Marco

Figura 4.4 L’interfaccia grafica di Simulink per la modellazione di sistemi dinamici.


DRAFT

Appunti di programmazione a oggetti in C++ e Matlab


84 Capitolo 4 Una panoramica su Matlab

4.4 Usare l’help


Se si vogliono avere informazioni su Matlab si può ovviamente ricorrere ai manuali op-
pure utilizzare Matlab stesso attraverso il suo sistema di help. Ad esempio, se si vuole
sapere a cosa serve il comando plot si può scrivere help plot nella stessa finestra dei
comandi. Comparirà una descrizione accurata di tale comando, sullo stile del comando
man della shell di Linux.
In generale, il comando help <nome-comando> restituisce (se il nome del comando
esiste) l’help del comando in questione. Un’altro comando che talvolta si dimostra utile
è lookfor <parola-chiave> che controlla se è disponibile l’help per la parola chiave
indicata tra tutti i comandi disponibili in Matlab.
In alternativa alla richiesta di help da riga di comando, utile per una consultazione
veloce ma a volte poco ricca di esempi, è possibile cercare aiuto nella preziosa finestra di
help dell’applicazione, figura 4.5.

Figura 4.5 La finestra di help di Matlab.


ver.2009.d Copyright © A. De Marco

4.5 Nozioni e notazioni fondamentali

4.5.1 Le variabili
Come in tutti gli ambienti di programmazione interattivi in Matlab è possibile definire
delle variabili, alle quali assegnare determinati valori, ed utilizzarle in calcoli successivi.
I nomi delle variabili Matlab possono essere qualsiasi, ma non devono cominciare con un
DRAFT

carattere che sia un numero o uno dei caratteri speciali (come quelli di punteggiatura).
Se una variabile ha lo stesso nome di un comando Matlab, quest’ultimo non sarà più

A. De Marco
4.5 Nozioni e notazioni fondamentali 85

disponibile a meno che la variabile non venga cancellata con il comando clear <nome-
variabile>. Per assegnare una variabile si usa l’operatore di assegnamento “=”. Il tipo
della variabile viene automaticamente costruito in base alla quantità che si assegna.
Negli esempi seguenti si riportano delle sequenze di comandi digitati al prompt di
Matlab, ma che potrebbero anche essere contenuti in un M-file. Le righe in cui il prompt
“>>” non compare costituiscono ciò che invece la command window restituisce all’utente,
detto in gergo message log. Ad esempio l’assegnazione di una data variabile non terminata
dal carattere “;” viene sempre seguita dal messaggio che mostra all’utente il valore della
variabile e le dimensioni della matrice. Ciò si verifica anche solo digitando il nome di
una variabile ed è utile per conoscerne velocemente il valore o sapere se ne esiste una o
se esiste una funzione con quel nome.
Maggiori dettagli sulla gestione delle variabili si evincono dagli esempi riportati in
quanto segue. Si osservi che il carattere “%” è quello che inizia un commento al codice
Matlab.
Per cominciare si prenda in esame la sequenza di comandi:
>> a=1 % assegna alla variabile a il valore 1
a =
1

>> b=’pippo’ % assegna alla variabile b la stringa pippo


b =
pippo

>> c=23; % con il carattere ; a fine comando non viene mostrato


% il valore di c

>> 12.5 % 12.5, viene assegnato alla variabile di default ans(wer)


ans =
12.5000

>> whos % interroga il sistema sulle variabili dichiarate


Name Size Bytes Class

a 1x1 8 double array


ans 1x1 8 double array
b 1x5 10 char array
c 1x1 8 double array

Grand total is 8 elements using 34 bytes

Il significato dei comandi e delle operazioni riportate è spiegato dai commenti di cui
sono corredati. Gli esempi di comandi che seguono illustrano ulteriori aspetti dell’am-
biente di programmazione interattivo.
ver.2009.d Copyright © A. De Marco

>> a=’pluto’; % se si riassegna una variabile questa viene riallocata


>> whos
Name Size Bytes Class

a 1x5 10 char array


ans 1x1 8 double array
b 1x5 10 char array
c 1x1 8 double array

>> clear a % si cancella a

>> who % le variabili dichiarate (senza tipo) ora sono


Your variables are:
ans b c
Grand total is 12 elements using 36 bytes
DRAFT

>> clear all % si cancellano tutte le variabili


>> who % nessuna risposta, nessuna variabile definita

Appunti di programmazione a oggetti in C++ e Matlab


86 Capitolo 4 Una panoramica su Matlab

4.5.2 Matrici e vettori


Una matrice viene delimitata da parantesi quadre. Ogni riga termina con un punto e
virgola e deve avere lo stesso numero di elementi (separati da spazi bianchi o da una
virgola). Ad esempio, i comandi seguenti:
>> A=[-1 2 3; 4 5 6]
A =
-1 2 3
4 5 6

>> A=[-1, 2, 3;
4, 5, 6] % il comando termina alla seconda riga con la ]
A =
-1 2 3
4 5 6

>> A=[-1 2 3; 4 ... % i 3 punti indicano continuazione su riga successiva


5 6];

rappresentano modi equivalenti di introdurre la stessa matrice 2  3. Un vettore riga di


dimensione n è una matrice 1  n, un vettore colonna di dimensione m è una matrice
m  1.
Matrici con dimensioni compatibili possono essere sommate o moltiplicate con gli
operatori “+”, “-” e “*”. Si osservi che il carattere “’” (apice) che segue il nome di una
variabile equivale all’operatore di trasposizione applicato alla matrice che tale variabile
rappresenta. A tal riguardo si prendano in esame i comandi che seguono:
>> A=[1 2 3; 4 5 6]; B=[7 8 9; 10 11 12];
>> A+B % somma di due matrici 2x3
ans =
8 10 12
14 16 18
>> A=[1 2 3; 4 5 6]; B=[7 8 9; 10 11 12]’;
>> A*B % prodotto di una matrice 2x3 con una 3x2
ans =
50 68
122 167

Se le matrici che compaiono in un’operazione di prodotto non hanno dimensioni


compatibili, viene segnalato un messaggio di errore. Ad esempio:

>> A=[1 2 3; 4 5 6]; B=[7 8 9; 10 11 12];


>> A*B % prodotto di una matrice 2x3 con una 2x3
??? Error using ==> *
Inner matrix dimensions must agree.
ver.2009.d Copyright © A. De Marco

Se invece si prova ad usare l’operatore “.*” al posto del solo “*” si ottiene:

>> A.*B
ans =
7 16 27
40 55 72

In questa circostanza Matlab non segnala alcun errore perché ora il prodotto è stato stato
eseguito “elemento per elemento” (element-wise). Facendo precedere il carattere “.” alle
operazioni elementari queste vengono eseguite elemento per elemento. Questa è una
proprietà importante ed utile, ad esempio, nella produzione di grafici di funzioni.
Quando si opera con matrici quadrate possono effettuarsi su di esse diverse operazioni
DRAFT

particolari che restituiscono le seguenti quantità:

A. De Marco
4.5 Nozioni e notazioni fondamentali 87

>> A=[1 2; 3 4];


>> inv(A) % l’inversa di A
ans =
-2.0000 1.0000
1.5000 -0.5000
>> det(A) % il determinante
ans =
-2
>> eig(A) % gli autovalori
ans =
-0.3723
5.3723
>> cond(A) % il numero di condizionamento in norma 2
ans =
14.9330
>> norm(A) % la norma 2
ans =
5.4650
>> norm(A,inf) % la norma infinito
ans =
7

A questo punto si sperimenti una prima operazione su un’intera matrice. Il comando


A+1 applicato alla matrice precedentemente definita fornisce:

>> A+1
ans =
2 3
4 5

cioè tutti gli elementi di A sono stati aumentati di 1. Quindi in questo senso si può
sommare un numero ed una matrice.
Per accedere all’elemento (i,j) di una matrice A, basta scrivere A(i,j), mentre per
accedere all’elemento i-mo di un vettore v (riga o colonna che sia) basta scrivere v(i).
Per accedere ad un’intera colonna o ad un’intera riga gli indici corrispondenti possono
essere rimpiazzati dai due punti “:” come negli esempi seguenti:

>> A(2,1)
ans =
4

>> A(:,2) % estrae la seconda colonna


ans =
3
5

>> A(1,:) % estrae la prima riga


ans =
ver.2009.d Copyright © A. De Marco

2 3

4.5.3 Altri tipi di dati


Una variabile può contenere anche valori non numerici. Un esempio comune è dato dai
caratteri e dalle stringhe di caratteri. Le costanti stringhe di caratteri come, ad esempio
’pippo’, vengono delimitate con degli apici, e con l’assegnazione seguente:

>> str = ’pippo’


str =
pippo
DRAFT

si definirà una variabile str da intendersi come un vettore i cui elementi sono i singoli
caratteri della stringa. Ad esempio il comando str(3) restituirà il carattere ‘p’.

Appunti di programmazione a oggetti in C++ e Matlab


88 Capitolo 4 Una panoramica su Matlab

Le recenti versioni di Matlab si sono arricchite di nuovi tipi di dati. Un esempio


è costituito dai cell arrays, cioè delle variabili costruite con una notazione basata sulle
parentesi graffe. Un primo esempio è dato dall’assegnazione: M = {a b c}. Un’assegna-
zione equivalente si ha mediante l’uso del costruttore cell. L’esempio precedente può
riscriversi come:

>> M = cell(1,3); % crea un cell array di valori nulli


M{1} = a; M{2} = b; M{3} = c; % riempie le celle

Le matrici a, b, e c possono essere oggetti qualunque. Ad esempio l’istruzione M = {A,


det(A), [det(A);det(B)]} è perfettamente valida.
È possibile inoltre definire delle strutture simili alle struct del linguaggio C, ai cui
campi si accede con l’operatore “.” (punto). Un esempio di definizione di una variabile
struttura S è il seguente:

>> S.name = ’Ed Plum’;


>> S.score = 83;
>> S
S =
name: ’Ed Plum’
score: 83

e naturalmente è possibile maneggiare interi array di strutture sfruttando i vantaggi offerti


dal linguaggio.

4.5.4 Le funzioni in Matlab


Matlab, è sia un linguaggio di programmazione che un ambiente computazionale interat-
tivo. Come già detto, i files che contengono comandi, cioè righe di codice Matlab, sono
chiamati M-files. Dopo aver creato un M-file usando un qualsiasi editor di testo, salvando-
lo in un appropriato percorso l’utente può disporre di un nuovo comando personalizzato
o di una nuova funzionalità, che arricchisce quelle predefinite dell’ambiente Matlab. In
particolare esistono due tipi di M-file:
 scripts, cioè comandi che non hanno argomenti di input o argomenti di output ed
operano su dati già presenti nel workspace e/o dati creati contestualmente,
 functions, che possono accettare anche un numero variabile di argomenti di input
(come le funzioni C++) ed hanno uno o più argomenti di output.
I programmatori in linguaggio Matlab creano in genere i propri M-files nella cartella
ver.2009.d Copyright © A. De Marco

di lavoro o in una delle sue sottocartelle. Eventualmente gli M-file potranno essere orga-
nizzati in toolboxes e salvati in cartelle che l’utente può aggiungere al percorso di ricerca
di Matlab. Se si duplicano nomi di funzioni, cioè si hanno nomi di files identici in più
punti del percorso di ricerca, Matlab esegue quello che trova per primo. Per editare i con-
tenuti di un M-file nella cartella di lavoro è possibile selezionare ed esplorare la finestra
“Current directory”, cioè una delle tabbed window mostrata nella figura 4.6, ed aprire
con un doppio click l’M-file desiderato. Si avvierà anche in questo caso l’M-file editor di
Matlab.
Quando si richiama uno script dalla riga di comando, Matlab esegue semplicemente
i comandi presenti nel file. Gli script possono operare su dati esistenti nel workspace,
DRAFT

o possono essi stessi contenere comandi che allocano ed assegnano dati su cui operare.
Sebbene gli script non forniscono dati di output, qualsiasi variabile da essi creata rimane

A. De Marco
4.5 Nozioni e notazioni fondamentali 89

Figura 4.6 La finestra “Current directory”


per l’esplorazione della cartella di lavoro.

nel workspace, per essere usata in calcoli successivi. Inoltre, gli script possono produrre
dei grafici, usando funzioni come plot.
Per esempio, se si crea un file chiamato myrmatrix.m contenente i seguenti comandi:
% Calcoli varii su matrici
r = zeros(1,32);
for n = 3:32 % determinanti di
r(n) = det(rand(7)); % matrici 7x7 a
end % elementi random
r
bar(r)

con il comando >> myrmatrix si chiede a Matlab di eseguire le istruzioni contenute nel file
una dopo l’altra: verrà calcolato il determinante di 30 matrici 7  7 con elementi random,
comando det(random(7)), e tracciato il grafico del risultato, bar(r). Dopo l’esecuzione
del comando, la variabile r rimane nel workspace.
I comandi eig, det, inv e così via, sono esempi di funzioni Matlab. Esse possono
essere richiamate in molti modi diversi. La sintassi più generale è la seguente:

>>[<out1 >, ... , <outn >] = <nome-funzione>(<in1 >, ... , <inm >);
ver.2009.d Copyright © A. De Marco
dove <out1 >, ... , <outn > sono i parametri di output, mentre <in1 >, ... , <inm > so-
no i parametri di input. Il loro numero può variare e per lanciare correttamente una fun-
zione è necessario consultare attentamente l’help. Se la funzione ha un solo parametro di
output, le parentesi quadre possono essere omesse.
Le funzioni possono accettare argomenti in input e forniscono argomenti in uscita.
Il nome dell’M-file e della funzione deve essere lo stesso e deve trovarsi nel percorso di
ricerca corrente. Le variabili definite all’interno di una funzione, dette variabili locali,
hanno visibilità (scope) locale alla funzione quando non sono dichiarate con attributo
global. È come se le variabili locali di una funzione fossero residenti in un workspace
proprio, separato dal workspace a cui si accede dal prompt dei comandi di Matlab, che
viene cancellato dopo la chiamata.
DRAFT

Un semplice esempio è fornito dalla funzione rank. L’M-file rank.m è disponibile


nella cartella <matlab root>/toolbox/matlab/matfun. La funzione può essere chiamata

Appunti di programmazione a oggetti in C++ e Matlab


90 Capitolo 4 Una panoramica su Matlab

dopo aver definito una matrice A, digitando al prompt dei comandi >> rank(A). L’M-file
in questione è qui di seguito riportato:

% File: rank.m
function r = rank(A,tol)
% RANK Matrix rank.
% RANK(A) provides an estimate of the number of linearly
% independent rows or columns of a matrix A.
% RANK(A,tol) is the number of singular values of A
% that are larger than tol.
% RANK(A) uses the default tol = max(size(A)) * norm(A) * eps.
s = svd(A);
if nargin==1
tol = max(size(A)) * max(s) * eps;
end
r = sum(s > tol);

La prima linea non commentata di un M-file contenente una funzione comincia con la
parola chiave function seguita dalla lista degli argomenti di output, in questo caso la sola
r, dal nome alla funzione, rank, e dalla lista di argomenti di input racchiusi tra parentesi
tonde, (A,tol). Le righe seguenti la definizione iniziano col carattere di commento “%”
e rappresentano dei commenti di aiuto che verranno visualizzati al prompt dei comandi
quando si digita >>help rank. La prima linea del testo di aiuto è la cosiddetta “H1 line”
che Matlab espone quando si ricerca aiuto da riga di comando. Il resto del file rappresenta
il codice interpretato da Matlab per rendere disponibile la funzione agli utenti.
Nell’esempio appena riportato la variabile s presente nel corpo della funzione, così
come le variabili che compaiono nella definizione, r, A e tol, sono locali alla funzio-
ne e rimangono indipendenti e separate da qualsiasi variabile nel workspace di Matlab.
Anche se in quest’ultimo fosse definita una variabile con lo stesso nome non vi sarebbe
possibilità di conflitto.
L’esempio illustra un aspetto delle funzioni di Matlab che si trova anche in altri lin-
guaggi di programmazione come il C++ o il Fortran 90/95 cioè la possibilità di definire
funzioni che possano essere chiamate con un numero variabile di argomenti. Il numero
di argomenti effettivamente passato alla funzione nel momento della chiamata viene me-
morizzato di volta in volta nella variabile riservata nargin. Una funzione come rank può
dunque essere usata in modi diversi come si vede dall’esempio seguente:

>> A = rand(10); % genera una matrice 10x10 ad elementi random


>> rank(A);
ver.2009.d Copyright © A. De Marco

>> r = rank(A);
>> r = rank(A,1.e-6);

Rispetto alla gestione del numero di parametri di input e di output, molte funzioni che
fanno parte del linguaggio Matlab sono definite in maniera simile. Se nessun argomento
di output compare nella chiamata, il risultato è assegnato alla variabile di default ans.
Tipicamente il primo argomento di input è sempre richiesto e non può essere omesso
nella chiamata. Se il secondo argomento di input non è presente, la funzione lo pone
uguale ad un valore di default. Tipicamente nel corpo di una funzione sono interrogate
le due variabili riservate nargin e nargout, che contengono il numero di argomenti di
input e di output effettivamente utilizzati nelle chiamate. L’effettivo comportamento della
DRAFT

funzione ed eventualmente l’incorrere in una condizione di errore di chiamata dipenderà


di volta in volta dal valore di queste due variabili.

A. De Marco
4.6 Operazioni di Input / Output 91

4.6 Operazioni di Input / Output


L’ambiente di lavoro offre la possibilità di analizzare dati raccolti eventualmente con altri
programmi ed immagazzinati in files di testo (formato ASCII). Ad esempio, si potrebbe
avere a disposizione l’insieme di dati seguente:
1 2.000 -5.0
2 0.2500 -9.1
3 0.0740 -23.0
4 0.0310 -53.2
5 0.0160 -105.1
6 0.0090 -185.5
7 0.0050 -299.4
8 0.0030 -453.7
9 0.0020 -653.0
10 0.0020 -905.3

Questi dati possono essere salvati su un file, per esempio ’aerodata.dat’, e caricati
in Matlab con il comando load. In questo esempio, in Matlab viene creata una matrice
aerodata di dimensioni 10  3:

>> load aerodata.dat


>> whos

Name Size Bytes Class


aerodata 10x3 30 double array

Grand total is 30 elements using 240 bytes

Il salvataggio di dati è possibile specificando un formato con il comando save. Un


file di dati come ’aerodata.dat’ potrebbe essere stato prodotto a valle di una sessione di
lavoro con la sequenza di comandi:

>> id = 1:10;
>> x = [2.000 0.2500 0.0740 0.0310 0.0160 ...
0.0090 0.0050 0.0030 0.0020 0.0020];
>> y = [-5.0 -9.1 -23.0 -53.2 -105.1 ...
-185.5 -299.4 -453.7 -653.0 -905.3];
>> dati = [id’ x’ y’];
>> save aerodata.dat dati -ascii % salva in forma ascii 8 bit

I possibili usi del comando di salvataggio sono i seguenti:


ver.2009.d Copyright © A. De Marco

>> save aerodata.dat dati % salva in forma binaria


>> save aerodata.dat x y % salva le matrici riga x e y
>> save aerodata.dat dati -ascii % salva in forma ascii 8 bit
>> save aerodata.dat dati -double % salva in forma ascii 16 bit
>> save aerodata.dat dati -ascii -tabs % salva in forma ascii
% con tabulatori

Se il nome del file è stdio l’output è inviato allo standard output device, generalmente
lo schermo.
Esistono comandi di input/output alternativi e di più basso livello rispetto a quelli ap-
pena richiamati. Ad esempio i comandi dlmread e dlmwrite lavorano su files ASCII in
DRAFT

dui i dati vengono separati da cartteri particolari detti delimiters. Non è raro dover ma-
nipolare ad esempio files contenenti comma separated values, valori separati da virgole,

Appunti di programmazione a oggetti in C++ e Matlab


92 Capitolo 4 Una panoramica su Matlab

tipicamente di estensione “.csv”. Un simile file potrà essere letto ed immagazzinato in


una matrice con il comando:

>> A = dlmread(’dati.dat’,’,’) % legge il file delimitato da ’,’

Utilizzatori esperti possono a volte preferire metodi di lettura e scrittura che utilizzano
tutta una serie di comandi derivati e simili a quelli del linguaggio C. Questi comandi sono
riportati nella Tabella 4.1 e si rimanda all’help per approfondimenti ed esempi pratici
sull’argomento.

Tabella 4.1 Comandi di Input/Output di basso livello. Si consulti l’help di Matlab per
maggiori dettagli sulla sintassi e per degli esempi d’uso.

fopen, fclose apertura e chiusura di un file


fread, fwrite I/O per dati non formattati
fscanf, fprintf I/O per dati formattati
(formattazione con i comandi simili al C)

fgetl, fgets I/O dati formattati


ferror, fseek, ftell, frewind Come i corrispondenti comandi in C
sprintf, sscanf Conversione di stringhe

Qui si riporta un semplice esempio in cui si scrivono in un file di testo una riga di
intestazione e due colonne di valori, una contenente quelli della variabile indipendente t
ed una contenente quelli della funzione y.t/ D 2 sin.t/ cos.t/:

t = linspace(0,2*pi,40); % 40 punti in Œ0; 2


y = 2*sin(t).*cos(t); % genera y.t/ D 2 sin.t/ cos.t/
data = [t ; y]; % matrice 2x40
fid = fopen(’valori-funzione.txt’,’w’); % "apre" il canale del file
% in scrittura
fprintf(fid,’Valori della funzione sin(t)*cos(t) tra 0 e 2*pi\n\n’);
fprintf(fid,’%4.2f %10.6f\n’,data); % tipica formattazione C
% N.B.: questa formattazione permette di ottenere
% un file con i dati su due colonne
fclose(fid) % "chiude" il canale del file
ver.2009.d Copyright © A. De Marco

Noto il metodo ed il formato con cui i dati sono stati scritti nel file valori-funzione.txt,
sarà possibile rileggerne il contenuto con comandi analoghi di e con l’uso di fscanf.
Per finire si ricorda che le Matlab dispone di uno strumento di alto livello per l’impor-
tazione dei dati chiamato Import Wizard al quale si accede dal menu File|Import Data.
Si tratta di una interfaccia grafica che permette di selezionare il file che contiene i dati, di
analizzarne il contenuto, selezionare il carattere di delimitazione dei dati, escludere even-
tualmente un certo numero de righe iniziali ed infine di stabilire in quante e quali variabili
caricare i dati nel workspace.
DRAFT

A. De Marco
Capitolo 5

Programmazione a oggetti in Matlab

I have always wished that my computer would be as easy to use as my telephone.


My wish has come true. I no longer know how to use my telephone!
– Bjarne Stroustrup

Indice
5.1 Strutture dati di Matlab . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.2 Classi e oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.3 Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

[capitolo incompleto]
Questa seconda parte di documento, seppur già concepita, è ancora in via di scrittura.
Lo scopo fondamentale è quello di presentare le caratteristiche del linguaggio introdot-
te a partire dalla versione “2008a” che permettono ai programmatori di definire nuove
classi e di derivare tipi da alcune classi predefinite. Il vantaggio di poter programmare a
oggetti in Matlab è ovvio: si possono progettare programmi, dai più semplici ai più com-
plessi, seguendo il paradigma di programmazione a oggetti servendosi, allo stesso tempo,
dell’enorme patrimonio di funzioni e toolbox disponibili.

5.1 Strutture dati di Matlab


Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit, vestibulum ut,
ver.2009.d Copyright © A. De Marco

placerat ac, adipiscing vitae, felis. Curabitur dictum gravida mauris. Nam arcu libero,
nonummy eget, consectetuer id, vulputate a, magna. Donec vehicula augue eu neque.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis ege-
stas. Mauris ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna
fringilla ultrices. Phasellus eu tellus sit amet tortor gravida placerat. Integer sapien est,
iaculis in, pretium quis, viverra ac, nunc. Praesent eget sem vel leo ultrices bibendum.
Aenean faucibus. Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabi-
tur auctor semper nulla. Donec varius orci eget risus. Duis nibh mi, congue eu, accumsan
eleifend, sagittis quis, diam. Duis eget orci sit amet orci dignissim rutrum.
Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem
DRAFT

non justo. Nam lacus libero, pretium at, lobortis vitae, ultricies et, tellus. Donec aliquet,
tortor sed accumsan bibendum, erat ligula aliquet magna, vitae ornare odio metus a mi.
94 Capitolo 5 Programmazione a oggetti in Matlab

Morbi ac orci et nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque
a nulla. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus
mus. Aliquam tincidunt urna. Nulla ullamcorper vestibulum turpis. Pellentesque cursus
luctus mauris.

5.2 Classi e oggetti (in Matlab  2008a )


Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at, tincidunt tri-
stique, libero. Vivamus viverra fermentum felis. Donec nonummy pellentesque ante.
Phasellus adipiscing semper elit. Proin fermentum massa ac quam. Sed diam turpis, mo-
lestie vitae, placerat a, molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend
at, accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend
consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt purus vel magna.
Integer non enim. Praesent euismod nunc eu purus. Donec bibendum quam in tellus.
Nullam cursus pulvinar lectus. Donec et mi. Nam vulputate metus eu enim. Vestibulum
pellentesque felis eu massa.
Quisque ullamcorper placerat ipsum. Cras nibh. Morbi vel justo vitae lacus tincidunt
ultrices. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. In hac habitasse platea
dictumst. Integer tempus convallis augue. Etiam facilisis. Nunc elementum fermentum
wisi. Aenean placerat. Ut imperdiet, enim sed gravida sollicitudin, felis odio placerat
quam, ac pulvinar elit purus eget enim. Nunc vitae tortor. Proin tempus nibh sit amet nisl.
Vivamus quis tortor vitae risus porta vehicula.

5.3 Esempi
Fusce mauris. Vestibulum luctus nibh at lectus. Sed bibendum, nulla a faucibus semper,
leo velit ultricies tellus, ac venenatis arcu wisi vel nisl. Vestibulum diam. Aliquam pellen-
tesque, augue quis sagittis posuere, turpis lacus congue quam, in hendrerit risus eros eget
felis. Maecenas eget erat in sapien mattis porttitor. Vestibulum porttitor. Nulla facilisi.
Sed a turpis eu lacus commodo facilisis. Morbi fringilla, wisi in dignissim interdum, justo
lectus sagittis dui, et vehicula libero dui cursus dui. Mauris tempor ligula sed lacus. Duis
cursus enim ut augue. Cras ac magna. Cras nulla. Nulla egestas. Curabitur a leo. Quisque
egestas wisi eget nunc. Nam feugiat lacus vel est. Curabitur consectetuer.
ver.2009.d Copyright © A. De Marco

Suspendisse vel felis. Ut lorem lorem, interdum eu, tincidunt sit amet, laoreet vitae,
arcu. Aenean faucibus pede eu ante. Praesent enim elit, rutrum at, molestie non, nonum-
my vel, nisl. Ut lectus eros, malesuada sit amet, fermentum eu, sodales cursus, magna.
Donec eu purus. Quisque vehicula, urna sed ultricies auctor, pede lorem egestas dui, et
convallis elit erat sed nulla. Donec luctus. Curabitur et nunc. Aliquam dolor odio, com-
modo pretium, ultricies non, pharetra in, velit. Integer arcu est, nonummy in, fermentum
faucibus, egestas vel, odio.
DRAFT

A. De Marco
Bibliografia e sitografia

[1] B. Stroustrup, C++. Linguaggio, libreria standard, principi di programmazione.


Addison-Wesley, Pearson Education Italia (collana Professionale), Terza edizione,
2000.

[2] B. Stroustrup, The C++ Programming Language . Addison-Wesley, Special 3rd


Edition, 2000.

[3] A. Koenig, B. E. Moo, Accelerated C++. Addison-Wesley, 2000.

[4] H. Schildt, C++. La guida completa. McGraw-Hill, 2003.

[5] H. Schildt, Herb Schildt’s C++ Programming Cookbook. McGraw-Hill, 2008.

[6] B. Stroustrup, Sito internet di Bjarne Stroustrup,


http://www.research.att.com/~bs/

[7] N. M. Josuttis, Object-Oriented Programming in C++. John Wiley, 2003.


Sito internet: http://www.josuttis.com/cppbook/cppbook.html

[8] D. Vandevoorde, N. M. Josuttis, C++ Templates. Addison-Wesley, 2003.

[9] N. M. Josuttis, The C++ Standard Library – A Tutorial and Reference, Self publi-
shed, 1999.
Sito internet: http://www.josuttis.com/libbook/

[10] Autori anonimi, Dal C al C++, Wikibook.


Sito internet: http://it.wikibooks.org/wiki/C%2B%2B

[11] A. Ficarra, M. Murgia, Il linguaggio C++ standard, Corso di formazione ISAC,


ver.2009.d Copyright © A. De Marco

CNR, Bologna 2003.


Sito internet:
http://www.bo.cnr.it/corsi-di-informatica/corsoCstandard/Lezioni/01Indice.html

[12] C. Olson, sito internet di FlightGear Flight Simulator,


http://www.flightgear.org

[13] C. Olson, sito internet di ispezione interattiva del codice sorgente di FlightGear
Flight Simulator (Interactive CVS log browser),
http://cvs.flightgear.org/viewvc/source/
DRAFT

[14] J. S. Berndt, sito internet di JSBSim, Flight Dynamics Model,


http://www.jsbsim.org
96 Capitolo 5 Bibliografia e sitografia

[15] J. S. Berndt, sito internet di ispezione interattiva del codice sorgente di JSBSim
Flight Dynamics Model (Interactive CVS log browser),
http://jsbsim.cvs.sourceforge.net/viewvc/jsbsim/JSBSim/

[16] J. S. Berndt, “JSBSim: An Open Source Flight Dynamics Model in C++.” AIAA
Modeling and Simulation Technology Conference, Providence, Rhode Island, 16-19
August 16, 2004.

[17] A. De Marco, E. L. Duke, J. S. Berndt, “A General Solution to the Aircraft Trim


Problem.” AIAA Paper 2007-6703, AIAA Modeling and Simulation Technology
Conference, Hilton Head, South Carolina, USA, 20-23 August 2007.

[18] D. P. Coiro, A. De Marco, F. Nicolosi, “A 6DOF Flight Simulation Environment for


General Aviation Aircraft with Control Loading Reproduction.” AIAA Paper 2007-
6364, AIAA Modeling and Simulation Technology Conference, Hilton Head, South
Carolina, USA, 20-23 August 2007.

[19] D. P. Coiro, A. De Marco, F. Nicolosi, “The Flight Simulation Environment of


The University of Naples.” Proceedings of ISC 2006 (International Simulation
Conference), July 2006, Palermo, Italy.

[20] M. Cooper, Advanced Bash-Scripting Guide. An in-depth exploration of the art of


shell scripting. Disponibile all’indirizzo: http://tldp.org/LDP/abs/html/

[21] Sito internet della distibuzione di software libero GNU: http://www.gnu.org

[22] Sito internet del progetto GNU/Linux:


http://www.gnu.org/gnu/linux-and-gnu.it.html

[23] Sito internet del manuale online di GNU make:


http://www.gnu.org/software/make/manual/

[24] Sito internet della distibuzione di software libero Cygwin: http://www.cygwin.com

[25] Sito internet del sistema MinGW (Minimalist GNU for Windows):
http://www.mingw.org

[26] Sito internet di Borland, produttore del sistema di sviluppo Borland C++Builder:
ver.2009.d Copyright © A. De Marco

http://www.borland.com

[27] Sito internet dell’ambiente di sviluppo integrato Microsoft Visual C++ Express:
http://www.microsoft.com/express/vc/

[28] Sito internet dell’ambiente di sviluppo integrato Code::Blocks (The open source,
cross platform, free C++ IDE): http://www.codeblocks.org

[29] Sito internet della Bloodshed Software e dell’ambiente di sviluppo integrato


Dev-C++: http://www.bloodshed.net/devcpp.html

[30] Sito internet dell’ambiente di sviluppo integrato wxDev-C++ (estensione di


DRAFT

Dev-C++ preconfigurata per lo sviluppo di applicazioni ad interfaccia grafica


basata sulla libreria wxWidgets): http://wxdsgn.sourceforge.net

A. De Marco
97

[31] Sito internet del toolkit grafico wxWidgets (The cross-platform GUI library):
http://wxwidgets.org

[32] Sito internet di Silicon Graphics Inc., produttore del sistema operativo Irix:
http://www.sgi.com

[33] Sito internet del sistema di compilazione multipiattaforma CMake:


http://www.cmake.org

[34] Sito internet dell’editore di testi gratuito Notepad++:


http://notepad-plus.sourceforge.net

[35] Sito internet dell’editore di testi gratuito Vim (Vi Improved): http://www.vim.org

[36] The Mathworks, Matlab online documentation: http:


//www.mathworks.com/access/helpdesk/help/techdoc/index.html?/access/
(versioni 2008b e 2009a).

[37] A. H. Register, A Guide to MATLAB Object-Oriented Programming. Chapman &


Hall/CRC, SciTech Publishing Inc., 2007.

[38] B. W. McCormick, Aerodynamics, Aeronautics, and Flight Mechanics. John Wiley


& Sons, New York, 1979.

[39] J. Katz, A. Plotkin, Low-Speed Aerodynamics. Cambridge University Press, 2nd


edition, Cambridge, England, U.K., 2001.

ver.2009.d Copyright © A. De Marco


DRAFT

Appunti di programmazione a oggetti in C++ e Matlab

Potrebbero piacerti anche