Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Appunti di programmazione
a oggetti in C++ e Matlab
Maggio 2009
ver. 2009.d
DRAFT ver.2009.d Copyright © A. De Marco
Indice
Introduzione 5
2.2.2 Le strutture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.2.3 Dalle strutture alle classi . . . . . . . . . . . . . . . . . . . . . . 62
4 Indice
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
A. De Marco
Introduzione
Napoli
aprile 2009
DRAFT
A. De Marco
Appunti di programmazione
a oggetti in C++ e Matlab
Introduzione alla
programmazione a oggetti
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
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++
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.
A. De Marco
1.2 Programmazione procedurale 13
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
in cui si esplicita che il tipo di ritorno è void. In altri termini, questa funzione non ritorna
alcun valore.
“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
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 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.
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.
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
A. De Marco
1.2 Programmazione procedurale 17
// Global data
const char *hostname = "localhost";
int port = 5501;
realtime = false;
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.
è dunque:
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
Questa è l’intefaccia del modulo Wing. Si intuisce che la funzione defGeometry serve a Interfaccia di un modulo
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:
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
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.
//----------------------------------------
// 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();
}
// File: user.cpp
//----------------------------------------
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() );
}
}
// 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
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.
void setAlpha(double);
void setMach(double);
void calculate(void);
double getCL(void);
double getCD();
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.
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
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.
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
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:
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)
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);
// ...
};
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() << ", "
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
}
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
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
// ...
private:
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
int main()
{
ver.2009.d Copyright © A. De Marco
operator/(1,z).
In C++ la dichiarazione
A. De Marco
1.4 Astrazione dei dati 31
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.
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
// ...
};
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
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
A. De Marco
1.4 Astrazione dei dati 33
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
// distruttore
Wing::~Wing() { /* ... */ }
//----------------------------------------
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
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:
// ...
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
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, “è
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;
}
}
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
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-
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
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.
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
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
/* ... */
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
// ...
};
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
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
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 /.
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
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
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
Sulla base degli esempi precedenti, si lascia al lettore l’esercizio di ragionare sul
seguente frammento di codice:
double Cx = 0.0;
A. De Marco
Capitolo 2
– 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
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++
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
"--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.
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;
oppure come:
a = i++ + j;
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
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.
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
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
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)
Ecco un estratto di codice reale che mostra un esempio d’uso delle direttive di compi-
lazione:
// Program JSBSim.cpp
// see: http://www.jsbsim.org
// http://jsbsim.cvs.sourceforge.net/viewvc/jsbsim/JSBSim/
// ...
// ...
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
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.
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
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.
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
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.
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.
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
#include <fstream>
#include <cstdio>
#include <string>
#include <vector>
#include <cmath>
#include <iostream>
namespace ADM {
class DataFile {
public:
DataFile();
~DataFile();
DataFile(string fname);
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;
};
} // end namespace ADM
A. De Marco
2.5 Esempi 65
#endif
#include <iostream>
#include <fstream>
#include <cstdio>
#include <cstdlib>
ADM::DataFile df(argv[1]);
df.NicePrintNames();
si avrà in output:
File mydata.csv successfully opened.
Done parsing names. Reading data ...
Done Reading data.
DRAFT
2) Q (deg/s) 3) R (deg/s)
4) P dot (deg/s^2)
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.
Strumenti di sviluppo
– 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]
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
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.
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
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
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].
codice binario, contenente il programma eseguibile, che può essere a sua volta lanciato
dallo stesso IDE o autonomamente da sistema operativo.
Figura 3.2 Schermata di una sessione di lavoro con Microsoft Visual C++ 2008 Express [27].
A. De Marco
3.2 Ambienti integrati di sviluppo (IDE) 73
Figura 3.3 La finestra di esplorazione della solution in Microsoft Visual C++ 2008 Express.
DRAFT
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).
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).
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
A. De Marco
Parte II
Programmazione a oggetti
con Matlab
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
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.
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
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
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).
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
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=[-1, 2, 3;
4, 5, 6] % il comando termina alla seconda riga con la ]
A =
-1 2 3
4 5 6
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
A. De Marco
4.5 Nozioni e notazioni fondamentali 87
>> 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
2 3
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’.
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
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
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:
>> 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
A. De Marco
4.6 Operazioni di Input / Output 91
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:
>> 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
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,
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.
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/:
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
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.
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.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
[9] N. M. Josuttis, The C++ Standard Library – A Tutorial and Reference, Self publi-
shed, 1999.
Sito internet: http://www.josuttis.com/libbook/
[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
[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.
[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
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
[35] Sito internet dell’editore di testi gratuito Vim (Vi Improved): http://www.vim.org