1Alessandro De Falco, INFN Cagliari 3/4/14
Rudimenti di programmazione in C++Rudimenti di programmazione in C++
Manuali: ce ne sono molti in commercio. L'importante è che siano chiari
Sul C:
BW Kernigan, DM Ritchie- Linguaggio C (per consultazione)manca completamente la parte sulle classi (è C, non C++!)
Sul C++:
S. Prata- C++ primer plus
Si trova moltissimo anche on-line, p.es. su http://www.cplusplus.com
http://www.cplusplus.com/doc/tutorial/
2Alessandro De Falco, INFN Cagliari 3/4/14
Disclaimer & outlineDisclaimer & outlineQuesto non è un corso di programmazione in C++
Verranno dunque dati gli elementi fondamentali, ma verranno trascurati tantissimi dettagli anche importanti
Outline
I/O: printf, scanf, fprintf, fscanf, sprintf....
variabili: tipi ed operazioni di base. Array
Operatori logici e blocchi if
Loop
funzioni
puntatori e reference
classi
ereditarietà e polimorfismo
3Alessandro De Falco, INFN Cagliari 3/4/14
Hello WorldHello WorldFile HelloWorld.C
include le librerie per lo standard I/O (printf fa parte di questa libreria) la funzione main
rende un intero
Punto di ingresso del programmatutti i programmi in C devono avereuna funzione main()E' possibile (per alcuni compilatoriobbligatorio) specificare degli argomenti di ingresso: int main(int argc=0, char *argv=””)
un commento, preceduto da // finisce a fine rigaalternativamente i commenti possono essere compresi tra /* e */
funzione printf. Scrive quello che c'è tra gli apici. \n manda a capo
Le parentesi graffe indicano l'inizio e la fine del blocco main
ogni istruzionetermina con ; (esclusi i commenti)
la funzione rende 0. Dopo return eventuali altre istruzioni non saranno eseguite
“indentare” il programmaFondamentale per la leggibilità
4Alessandro De Falco, INFN Cagliari 3/4/14
Hello World versione alternativaHello World versione alternativainvece di printf uso cout (contenuto in iostream); questione di gusti. Userò (quasi) sempre printf
se voglio evitare di mettere sempre std:: davanti a cout, dovrò scrivere using namespace std
5Alessandro De Falco, INFN Cagliari 3/4/14
Compilazione ed esecuzioneCompilazione ed esecuzioneCon gcc il compilatore per il c++ è g++
Il comando per la compilazione è
Se tutto va bene, il risultato sarà HelloWorld, che eseguo scrivendo
...se tutto va beneIn realtà la gran parte del lavoro di un programmatore sarà assorbita dalla ricerca dei bachi del codice
In presenza di un baco la compilazione potrebbe fallire o -peggio- il programma potrebbe non fare quello che il programmatore voleva
Andare avanti a piccoli passi. Verificare (quasi) ogni singola istruzione
g++ -o HelloWorld HelloWorld.C
./HelloWorld
6Alessandro De Falco, INFN Cagliari 3/4/14
Variabili e tipiVariabili e tipiLista dipendente dalla macchina e dal compilatore
Per sapere il numero di byte usare la funzione sizeof() P.es. sizeof(int) nella macchina virtuale Centos x86_64 con ggc rende 4
Tipo dimensione Valori
unsigned short int 2 bytes da 0 a 65535
short int 2 bytes da -32768 a 32768
unsigned long int 8 bytes
long int 8 bytes da -4294967295 a 4294967295
int 4 bytes da -2147483648 a 2147483648
unsigned int 4 bytes
float 4 bytes
double 8 bytes
bool 1 byte true o false
char 1 byte 256 caratteri
Nota: in C++ si possono definire tipi più sofisticati (p.es. particella). Questi sono gli oggetti
7Alessandro De Falco, INFN Cagliari 3/4/14
posso inizializzare le variabili quando le definisco
non devo necessariamente definire tutte le variabili all'inizio
posso usare sprintf per scrivere su una stringa
quando uso (s)printf posso scrivere interi (campo %d) eventualmente con una lunghezza stabilita (%4d), float e double specificando quante cifre e quante dopo il . (%10.4f) o senza specificarlo (%g) e stringhe (%s)
programma PrintVar.C
compilazione ed esecuzione
8Alessandro De Falco, INFN Cagliari 3/4/14
Scrivere e leggere su/da un file di testoScrivere e leggere su/da un file di testoprogramma IO.C
si usano fopen,fprintf, fscanf, fclose Altre possibilitànon vengono esaminate in questo contesto
Normalmente la lettura avviene su tabelle di numeri (o stringhe) con una struttura regolarefscanf rende un numero intero che è pari al numero di argomenti letti. Se questo numero è 0, significa che la lettura del file è terminata
9Alessandro De Falco, INFN Cagliari 3/4/14
Compilazione ed esecuzione di IO.C
10Alessandro De Falco, INFN Cagliari 3/4/14
Ancora sui tipi delle variabiliAncora sui tipi delle variabiliSe una variabile sfora il suo valore massimo, avremo un baco
Posso definire dei parametri costanti. Dovrò assegnare il loro valore al momento dell'inizializzazione e non potrò più cambiarle
Posso usare per comodità delle costanti enumerative, p.es:
in questo caso RED=0, GREEN=1,....
Posso definire delle variabili personalizzate col comando typedef. p.es.
const int i=10;
typedef unsigned short int USHORT
(sto usando root come interprete c++)
enum COLOR {RED, GREEN, BLUE, YELLOW}
11Alessandro De Falco, INFN Cagliari 3/4/14
ArrayArray
Posso definire degli array di variabili. La loro lunghezza è fissataGli array possono essere inizializzati al momento della definizione
Nota: l'indice parte da zero ed arriva fino ad n-1in questo esempio il primo elemento y[0]=1 mentre y[9]=10 y[10] non fa parte dell'array
attenzione a non sforare la dimensione dell'array Se cambiate y[10], in realtà andrete a modificare un'altra variabile, quella che si trova nella locazione di memoria successivaPotenziali danni incontrollabili
float y[10]={1,2,3,4,5,6,7,8,9,10};
12Alessandro De Falco, INFN Cagliari 3/4/14
Operazioni tra variabiliOperazioni tra variabili
13Alessandro De Falco, INFN Cagliari 3/4/14
Operatori relazionali e logici; blocchi ifOperatori relazionali e logici; blocchi if
inserisco un valore dalla tastiera
se la condizione è soddisfatta, vengono eseguite le istruzioni tra le { }
operatori and ( && ) e or ( || )
Attenzione: per il confronto usare == e non = !!!Se avessi scritto if (i=j) avrei ridefinito i assegnandogli il valore di j classico errore da novizi del c++
14Alessandro De Falco, INFN Cagliari 3/4/14
15Alessandro De Falco, INFN Cagliari 3/4/14
LoopLoop
Esercizio: calcolare n! con n inserito da tastiera. Mettere un if che interrompe il loop (mediantel'istruzione break;) se n!>100000 Calcolare la somma dei soli numeri dispari <=n.
blocchi nidificati e indentazione
formato standard del for
while: esegue un loop che termina quando la condizione tra ( ) non è piùvalida. Potenziale pericolo di loop infiniti
in questi punti, queste variabili sono 'fuori dallo scope' (fuori ambito)
16Alessandro De Falco, INFN Cagliari 3/4/14
break e continuebreak e continue
Di norma è meglio evitare le interruzioni quando possibile, dato che queste possono rendere il codice oscuro e dunque soggetto a malfunzionamenti
Assolutamente da evitare il goto (che infatti non spiego!)
17Alessandro De Falco, INFN Cagliari 3/4/14
switchswitchPer completezza aggiungo l'istruzione switch, abbastanza usata
18Alessandro De Falco, INFN Cagliari 3/4/14
FunzioniFunzioniUna funzione è in sostanza un pezzo di programma che può prende in ingresso degli argomenti di vario tipo e rende una variabile di un tipo
se la funzione rende un void significa che non rende una variabile
la funzione deve essere definita prima della prima chiamata
si può anche definire il prototipo all'inizio, e mettere l'implementazione alla fine
main è un tipo particolare di funzione
19Alessandro De Falco, INFN Cagliari 3/4/14
20Alessandro De Falco, INFN Cagliari 3/4/14
Valori di defaultValori di defaultIl C++ permette di dare dei valori di default ai parametri in ingressoQuando ci sono sia il prototipo che l'implementazione, i parametri vanno specificati nel prototipo e non nell'implementazioneModifichiamo la funzione gaussiana in modo che di default =0, =1
pi e gaus esistono solo entro le { }poi vengono dimenticate
valori di default per e
21Alessandro De Falco, INFN Cagliari 3/4/14
Function overload Function overload Possiamo definire più funzioni con lo stesso nome, purchè cambi il tipo e/o il numero di variabili d'ingresso
La cosa può essere vantaggiosa, in particolare nella programmazione ad oggetti
Es.: prodotto scalare tra quadrivettori di Lorentz oppure tra vettori nello spazio a tre componenti
22Alessandro De Falco, INFN Cagliari 3/4/14
overloadoverload
23Alessandro De Falco, INFN Cagliari 3/4/14
Manipolazione dei parametri di ingressoManipolazione dei parametri di ingressoNel C++, quando viene chiamata una funzione, viene fatta una copia temporanea delle variabili in ingresso in un'altra locazione di memoria
All'uscita dalla funzione, questa copia viene dimenticata
In generale questo è un bene:
In questo modo la variabile di ingresso viene protetta da modifiche all'interno della funzione, che potrebbero essere indesiderate
Qualche altra volta vorrei poter cambiare i valori delle variabili in ingresso in modo che questo cambiamento sia persistente anche nel main (o più genericamente nel punto dove è stata chiamata la funzione)
Questo è vero in particolare quando ho bisogno di più di una variabile in uscita
Un esempio: supponiamo di voler scrivere una funzione che scambia i valori di due variabili in ingresso (swap)
24Alessandro De Falco, INFN Cagliari 3/4/14
La funzione swapLa funzione swap
aggiunge tab
la modifica di x e y in swap non è persistente!
25Alessandro De Falco, INFN Cagliari 3/4/14
PuntatoriPuntatoriIl problema nell'esempio precedente consiste nel fatto che swap non agisce sull'area di memoria che contiene le variabili x e y, ma ne fa una copia su un'altra area, che viene poi abbandonata all'uscita da swap
E' però possibile agire direttamente sull'area di memoria
Invece di passare a swap il valore di x e y, fornisco l'indirizzo dell'area di memoria
Questa operazione è possibile con i puntatori
Un puntatore è una variabile che contiene un indirizzo di memoria
I puntatori sono difficili da comprendere e da usare per i principianti del C
Gli errori possono essere difficili da rilevare perchè danno spesso dei malfunzionamenti imprevedibili
D'altra parte i puntatori sono potentie assolutamente indispensabili
Costituiranno dunque un incubo per chi è alle prime armi
26Alessandro De Falco, INFN Cagliari 3/4/14
Variabili e memoriaVariabili e memoriaQuando definisco una variabile, viene occupata un'area di memoria opportunamente grande (p.es 4 byte per un intero nel mio sistema)
A quest'area corrisponde un indirizzo
Il valore dell'indirizzo non ha in sè un significato particolare: se eseguite due volte lo stesso programma, con ogni probabilità il valore dell'indirizzo associato alla stessa variabile cambierà
Nonostante ciò è utile conoscere questo indirizzo
Nel mio esempio (reale) ho definito una variabile
questa ha occupato l'area di memoria che ha indirizzo (esadecimale)
Ho ottenuto l'indirizzo in questa maniera: 0xf52770
0xf52774
0xf52778
0xf52782
0xf52786
0xf52790
0xf52794
0xf52798
0xf52802
0xf52806
indirizzo di memoria in byte (a passi di 4 se le variabili sono intere)
10
contenuto
area dove viene memorizzato x
int x=10;
0xf52782
int *px=&x;
puntatore a x indirizzo di x
27Alessandro De Falco, INFN Cagliari 3/4/14
L'operatore & seguito da una variabile fornisce l'indirizzo di quella variabile
int * indica che la variabile è di tipo puntatore ad un intero (con ovvia generalizzazione per qualunque tipo di variabile)
Dunque int *px significa che sto definendo una variabile px di tipo “puntatore ad un intero”. Questa variabile conterrà un indirizzo di memoria
D'altra parte &x fornisce l'indirizzo di x
Dunque ho creato una variabile di tipo puntatore ad un intero e l'ho inizializzata con l'indirizzo di una variabile intera
Per accedere al contenuto di px, userò *px
28Alessandro De Falco, INFN Cagliari 3/4/14
Funzione swap coi puntatoriFunzione swap coi puntatori
ora la modifica di x e y persiste anche nel main
29Alessandro De Falco, INFN Cagliari 3/4/14
Utilità dei puntatoriUtilità dei puntatoriAbbiamo dunque già visto un caso in cui i puntatori sono utili La sintassi non è proprio immediata e i novizi tenderanno a fare confusione tra valore della variabile, indirizzo della variabile, valore del puntatore (che è un indirizzo) e contenuto del puntatore
I puntatori sono molto potenti quando si deve passare a una funzione un oggetto con molte variabili: basterà passare l'indirizzo senza dover copiare tutti gli elementi, rendendo l'esecuzione più veloce
Inoltre permettono di ripartire [allocare in gergo] la memoria in maniera dinamica (vedremo come)
Per queste e altre ragioni sono onnipresenti nei programmi in C e C++
int x = 10;int *px = &x;
tipo valore
x variabile 10
&x indirizzo della variabile 0x[qualcosa]
px puntatore 0x[qualcosa]
*px contenuto del puntatore 10
30Alessandro De Falco, INFN Cagliari 3/4/14
Inizializzazione dei puntatoriInizializzazione dei puntatoriNon è obbligatorio assegnare ad un puntatore un valore non appena questo viene creato
Nonostante ciò, è buona pratica inizializzare SEMPRE i puntatori. Nel caso in cui al momento della creazione non sia ancora definito chiaramente il valore che gli deve essere assegnato, è bene inizializzarlo a zero
Dunque vanno bene istruzioni come
mentre non va bene scrivere
Un puntatore che punta ad un indirizzo di memoria non ben definito o sbagliato può causare danni imprevedibili all'interno di un programma
float *px = 0;
float *py = &[indirizzo di qualche variabile float];
float *px;
Sì
No
31Alessandro De Falco, INFN Cagliari 3/4/14
Puntatori ed arrayPuntatori ed arrayUn array di dimensione n occupa n celle contigue
Definisco p.es.
La differenza tra gli indirizzi di x[0] e x[1] sarà 1 int (4 byte)
x corrisponde all'indirizzo del primo elemento ...
x[2]
x[1]
x[0]
0xf52770
0xf52774
0xf52778
0xf52782
0xf52786
0xf52790
0xf52794
0xf52798
0xf52802
0xf52806
int x[5] = {10,20,30,40,50};
Attenzione: se int *px=&x[0], *px+1=11 (agisce sul contenuto), mentre px+1=0xf52786 (agisce sull'indirizzo)
cambia ad ogni esecuzione del programma
32Alessandro De Falco, INFN Cagliari 3/4/14
riprendo il codice della slide precedente con lo screenshot della sua esecuzione
33Alessandro De Falco, INFN Cagliari 3/4/14
Memoria volatile e permanenteMemoria volatile e permanenteLa memoria usata da un programma è tipicamente divisa in quattro aree:
L'area del codice, dove risiede il programma compilato
L'area delle variabili globali
Lo stack, dove sono allocati i parametri e le variabili locali
p.es. le copie delle variabili che vengono create automaticamente quando viene chiamata una funzione
Quando termina l'esecuzione della funzione lo stack viene ripulito e reso nuovamente disponibile. Le variabili vengono perse
Lo heap, dove vengono allocate le variabili dinamiche
L’area heap non é allocata automaticamente, ma solo su esplicita richiesta del programma (allocazione dinamica della memoria)
L’area allocata è accessibile solo tramite puntatori
stack e heap costituiscono la memoria dinamica, gestita in run-time da S.O. e programma
34Alessandro De Falco, INFN Cagliari 3/4/14
Operatori new e deleteOperatori new e delete
L'area heap viene utilizzata mediante l'operatore new eliberata mediante l'operatore delete
Con new e delete posso creare/distruggere anche array
L'istruzione vale per variabili di qualunque tipo e per oggetti
35Alessandro De Falco, INFN Cagliari 3/4/14
new e deletenew e deleteL'operatore new alloca memoria dinamica in modo persistente
Questa viene liberata solo in due casi:
quando viene chiamato l'operatore delete
alla fine dell'esecuzione del programma
Se un programma crea un puntatore con new e poi non libera l'area heap con delete quando quest'area non serve più, si ha uno spreco di memoria (memory leak) che alla lunga può deteriorare significativamente le prestazioni del programma
Regola aurea: per ogni new che si inserisce in un programma, deve esserci anche il corrispondente delete (dove opportuno)
36Alessandro De Falco, INFN Cagliari 3/4/14
Esempio di memory leakEsempio di memory leakQuesto programma causa un memory leak di 1000 float ogni volta che viene chiamata la funzione
Quanti cicli deve compiere il programma per occupare una memoria di 4 GB?
37Alessandro De Falco, INFN Cagliari 3/4/14
ReferenceReference
Se ho una variabile di un certo tipo (p.es. intera) posso creare una reference a questa variabile in questo modo:
La reference refToi è un alias della variabile i:
punta alla stessa cella di memoria
qualunque modifica sia fatta ad i sarà fatta anche a refToi, e viceversa
Nota: le reference devono essere necessariamente inizializzate al momento della loro creazione
int i=10; // creo una variabile i int &refToi = i; // creo una reference a i
38Alessandro De Falco, INFN Cagliari 3/4/14
39Alessandro De Falco, INFN Cagliari 3/4/14
Come uso le referenceCome uso le referenceLe reference semplificano la notazione per il passaggio di argomenti modificabili ad una funzione
Riprendiamo l'esempio della funzione swap, stavolta fatta con le reference
Sintassi molto più semplice ed immediata!
40Alessandro De Falco, INFN Cagliari 3/4/14
Output di Swap scritto con le reference
Non si devono usare reference ad oggetti fuori dallo scope! (cioè fuori dal loro dominio di esistenza)
41Alessandro De Falco, INFN Cagliari 3/4/14
Classi ed oggettiClassi ed oggetti
42Alessandro De Falco, INFN Cagliari 3/4/14
Livelli di astrazioneLivelli di astrazioneNell'affrontare un problema scientifico, non ragioniamo mai nei termini di numeri interi, razionali, reali, etc...
Utilizziamo dei concetti più astratti che solo dopo un cammino più o meno lungo ci porteranno infine alle variabili più semplici
Quando consideriamo un esperimento in fisica delle particelle, ragioniamo in termini di
particelle
✔ carica, spin, quadrimpulso✗ energia e impulso
✗ componenti dell'impulsorivelatori
✔ tracce✗ punti
✔ magneti✔ .....
43Alessandro De Falco, INFN Cagliari 3/4/14
La programmazione ad oggettiLa programmazione ad oggetti
Riuscire a portare il problema ad un livello più elevato di astrazione ci aiuta ad avere contatto immediato con gli elementi realmente importanti
In altre parole, per capire qualcosa dobbiamo focalizzare senza perderci nei dettagli
L'idea nella programmazione ad oggetti è la stessa: astrarre per comprendere meglio
Un esempio: Se anzichè avere variabili semplici (double, float, int, char...) avessimo delle variabili di tipo Vettore, quanto sarebbe più semplice la trattazione di problemi che richiedono il calcolo vettoriale?
I dati del vettore (numero di componenti, loro valore) così come le operazioni che lo riguardano (somma, prodotto, modulo...) saranno incapsulate in un'unica entità che chiamiamo classe.
La classe Vettore definisce un nuovo tipo di variabile
Un membro della classe vettore si chiama oggetto (di tipo vettore)
44Alessandro De Falco, INFN Cagliari 3/4/14
Programmazione ad oggetti in C++Programmazione ad oggetti in C++La vera novità del C++ rispetto al C sta proprio nella possibilità di definire classi (ovverosia di programmare ad oggetti)
Quando si programma ad oggetti è molto importante pensare prima di agire:
quali sono gli oggetti che caratterizzano il mio problema?
come devo strutturarli?
Imparare a ragionare ad oggetti richiede un certo periodo di apprendistato
Oggetti complessi richiederanno procedure complesse per la loro definizione
In generale si scriveranno più linee di codice, ma questo sarà molto più comprensibile e dunque più solido
La programmazione ad oggetti va a vantaggio del programmatore: il programma non va più veloce
45Alessandro De Falco, INFN Cagliari 3/4/14
Costruzione di una classe: Vettore3Costruzione di una classe: Vettore3
Così come le funzioni, anche le classi hanno un prototipo
Questo di solito viene messo in un file a parte, normalmente con estensione .h (header file)
Così come le variabili, anche gli oggetti delle classi andranno inizializzati
Inizializzare un oggetto è in generale un'operazione più complicata che inizializzare una variabile, dato che la classe a cui appartiene l'oggetto può essere costituita da diverse variabili e altri oggetti
Alcuni elementi della classe saranno accessibili dall'esterno (public) altri saranno solo per uso interno (private o protected)
Vediamo un esempio: la classe vettore
Quali sono i dati della classe?
Quali sono i metodi?
46Alessandro De Falco, INFN Cagliari 3/4/14
Classe Vettore3 (v1.0): header Classe Vettore3 (v1.0): header
elementi pubbliciaccessibili dall'esterno(normalmente i metodi)
elementi protettiper uso interno (i dati dellaclasse, qualche metodo
costruttore di defaultcostruttore overloaded
costruttore di copia
inizializzazione
distruttore
definisco la classe
notare il ; dopo la }. E' necessario (caso unico)
Vettore3.h
47Alessandro De Falco, INFN Cagliari 3/4/14
ConvenzioniConvenzioniFaccio mie alcune convenzioni che vengono usate anche in root:
i dati della classe saranno indicati da una f minuscola seguita dal nome della variabile scritti in minuscolo
fX è un dato della classe
se definissi il modulo come dato della classe, scriverei fModulo
x, modulo, etc potranno essere variabili usate internamente, che muoiono appena viene lasciato il metodo
I dati della classe sono sempre protected o private (non trattiamo la differenza tra i due: usiamo protected e private in modo equivalente)
Il nome dei metodi inizia sempre con una maiuscola
Se il nome è costituito da più parole, queste vengono scritte attaccate e l'iniziale di ciascuna parola viene scritta maiuscola
ModuloQuadro() è un buon nome
moduloquadro() non lo è
48Alessandro De Falco, INFN Cagliari 3/4/14
CostruttoreCostruttoreIl costruttore è un metodo che inizializza i dati della classe
Viene chiamato automaticamente quando viene creato un oggetto
Il costruttore non ha tipo: non scriviamo
void Vettore3() {....;}
ma semplicemente
Vettore3() {....;}
Possiamo avere più costruttori overloaded
Un tipo particolare di costruttore è il costruttore di copia:
Vettore3(Vettore3 &v) {....;}
Il costruttore di copia fa appunto una copia dell'oggetto Serve p.es. quando si passa un oggetto ad una funzione: in questo caso viene creata una copia dell'oggetto, e questa copia viene manipolata dalla funzione
49Alessandro De Falco, INFN Cagliari 3/4/14
DistruttoreDistruttoreAnalogamente al costruttore, il distruttore viene chiamato automaticamente quando l'oggetto viene distrutto
serve p.es per liberare la memoria allocata
Ricordiamo la regola: ad ogni new deve corrispondere un delete
Se abbiamo un new nel costruttore (o altrove) nel distruttore deve essere presente il delete corrispondente
Il distruttore non ha tipo e non accetta argomenti. Ne possiamo avere solo uno
~Vettore3() {....;}
Costruttore, costruttore di copia e distruttore DEVONO ESISTERE in ogni classe
Se non li creiamo noi, ci penserà il compilatore a crearliE non è affatto detto che sia una buona idea...
50Alessandro De Falco, INFN Cagliari 3/4/14
Metodi, header e implementazioneMetodi, header e implementazioneIl file .h contiene il prototipo
L'implementazione dei metodi è di solito contenuta in un altro file (di solito l'estensione è .cxx o .cpp)
Qualche funzione particolarmente semplice, come SetX(), GetX()... viene definita inline, direttamente nell'header (in pratica il compilatore inserirà il codice della funzione)
Quando i metodi sono più lunghi (bastano 2-3 istruzioni o più) vanno scritti a parte
Nel nostro esempio i costruttori non sono inline
51Alessandro De Falco, INFN Cagliari 3/4/14
Vettore3 (v1.0): implementazioneVettore3 (v1.0): implementazioneVettore3.cxx
Nel file .cxx sto includendo il .h
un modo per inizializzare i dati della classe Avrei anche potuto scrivere dentro le {} le istruzioni fX=0; ...
Scrive un messaggio, utile solo per debug e apprendimento
Con la sintassi Vettore3::XYZdiciamo che il metodo XYZ appartiene alla classe Vettore3
52Alessandro De Falco, INFN Cagliari 3/4/14
Vettore3.h (v1.1)Vettore3.h (v1.1)La classe Vettore3 per ora non fa niente. Aggiungiamo i metodi Modulo e Dump
53Alessandro De Falco, INFN Cagliari 3/4/14
Vettore3.cxx (v1.1)Vettore3.cxx (v1.1)
aggiungo al .cxx queste righe di codice
54Alessandro De Falco, INFN Cagliari 3/4/14
Uso di Vettore3 nel main programUso di Vettore3 nel main program
creo l'oggetto v1 col costruttore di default
chiamo i metodi di v1 con v1.[nome metodo]
creo un puntatore v3
chiamo i metodi di v3 con v1->[nome metodo]
Devo creare un file che contenga la funzione main(). Lo chiamo Vettore3.CDentro main uso Vettore3 (ma potrei usarlo anche in altre funzioni)
Compilazione: g++ -o Vettore3 Vettore3.cxx Vettore3.C
55Alessandro De Falco, INFN Cagliari 3/4/14
Generalizzazione della classe VettoreGeneralizzazione della classe VettoreE se volessimo scrivere una classe Vettore ad n elementi, con n da decidersi al momento dell'esecuzione?
Possiamo farlo:
Dovremo avere n, che verrà definito all'inizializzazione
invece di avere fX, fY, fZ, dovrò avere un array di n elementi
Questo array dovrà essere allocato dinamicamente
Possiamo allocare dinamicamente un array utilizzando i puntatori e l'operatore new
56Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.h (v1.0)Vettore.h (v1.0)
57Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.cxx (v1.0)Vettore.cxx (v1.0)
58Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.CVettore.C
59Alessandro De Falco, INFN Cagliari 3/4/14
Definizione di operatoriDefinizione di operatoriPossiamo ridefinire gli operatori =,+,-,*,[ ],... in modo che svolgano le operazioni opportune (overloading operators)
P.es., se ridefinisco = e +, posso scrivere nel main:
Sintassi elegante e potente. Purtroppo è piuttosto difficile definire correttamente gli operatori overloading
Vettore v1(3); Vettore v2(3); // fisso con SetX i valori delle componenti di v1 e v2// .... omissis ....Vettore v3 = v1 + v2; // comodo e chiaro!
60Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.h (v2.0) con overloaded operatorsVettore.h (v2.0) con overloaded operators
due SetX overloaded
vettore per costanteprodotto scalare
questi comandi del preprocessore servono per evitare di includere più volte lo stesso listato in un programma Questo tipo di istruzioni ci dovrebbe essere sempre nel .h#ifdef viene chiuso da un endif alla fine
ho definito tutti i metodi virtualimportante per il polimorfismovedere in seguito
costruttore di default
61Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.cxx (v2.0) Vettore.cxx (v2.0) (riporto solo la parte aggiunta rispetto alla 1.0)
62Alessandro De Falco, INFN Cagliari 3/4/14
Un dettaglio molto tecnico e non banale:
l'operatore di assegnazione overloaded (o sovraccarico)= è ridefinito con questa sintassi:
L'’operator=() deve rendere un riferimento a *this perché così diventa possibile concatenare gli assegnamenti, p.es.:
v1=v2=v3;
Vettore & operator= (const Vettore &v)
Questi aspetti sono un po' troppo avanzati per lo scopo dei nostri rudimentiPer ulteriori delucidazioni, si veda p.es.http://en.cppreference.com/w/cpp/language/operators
63Alessandro De Falco, INFN Cagliari 3/4/14
Vettore.C (contiene il main)
Vettore.C (contiene il main)
uso gli operatori overloaded = e +
uso gli operatori overloaded = e * (che prende un Vettore e rende un double)
uso gli operatori overloaded = e * (che prende un double e rende un Vettore)
Nota: v4 = 10 * v3 non funzionerebbePerchè?
64Alessandro De Falco, INFN Cagliari 3/4/14
65Alessandro De Falco, INFN Cagliari 3/4/14
EreditarietàEreditarietàNegli esempi precedenti ho scritto prima una classe Vettore3, poi la generalizzazione a un Vettore ad n componenti
Avrei potuto ottenere Vettore3 come caso particolare di Vettore
Però Vettore3 potrebbe avere delle specificità (p.es. il prodotto vettoriale)
Potrei anche volere scrivere la classe quadrivettore di Lorentz
Devo riscrivere tutto il codice e poi sviluppare le parti specifiche?
Cattiva idea: qualunque modifica alla classe madre (correzioni di bachi, aggiunta di funzionalità) dovrebbe essere ricopiata nelle classi figlie
Il C++ permette di creare delle classi che ereditano da una classe madre tutti i dati e le funzionalità e ne aggiungono o sovrascrivono alcune
P.es VettoreLorentz sovrascriverà il costruttore (fN=4, fissato), il prodotto scalare e il modulo e implementerà la metrica g=
1 0 0 00 −1 0 00 0 −1 00 0 0 −1
66Alessandro De Falco, INFN Cagliari 3/4/14
Struttura gerarchica dell'ereditarietàStruttura gerarchica dell'ereditarietà
Essenza dell'ereditarietà: un VettoreLorentz è anche un Vettore, così come un adrone è anche una particella
E' possibile anche l'ereditarietà multipla
Se aggiungo tra le sottoclassi che ereditano da Particella le classi Fermione e Bosone (parallele ad Adrone, Leptone, Bosone di gauge), allora Barione erediterà sia da Adrone che da Fermione
Vettore
Vettore3 VettoreLorentz
Particella
Adrone Leptone Bosone di gauge
Barione Mesone
.................................
67Alessandro De Falco, INFN Cagliari 3/4/14
VettoreLorentz.hVettoreLorentz.h
con questa sintassi diciamo che VettoreLorentz eredita da Vettore`
68Alessandro De Falco, INFN Cagliari 3/4/14
VettoreLorentz.cxx (schermata 1)
VettoreLorentz.cxx (schermata 1)
chiamo il costruttoredella classe madre
69Alessandro De Falco, INFN Cagliari 3/4/14
VettoreLorentz.cxx (schermata 2)VettoreLorentz.cxx (schermata 2)
70Alessandro De Falco, INFN Cagliari 3/4/14
Main program (lo chiamo VettoreLorentz.C)Main program (lo chiamo VettoreLorentz.C)
71Alessandro De Falco, INFN Cagliari 3/4/14
Il comando per la compilazione diventa sempre più complicatoAl crescere del numero di classi diventa utile usare i makefile(non spiegati in queste lezioni)
72Alessandro De Falco, INFN Cagliari 3/4/14
PolimorfismoPolimorfismoIl polimorfismo è il meccanismo grazie a cui un metodo con lo stesso nome può avere implementazioni diverse in classi diverse
Nel nostro esempio, questo è il caso di Modulo e operator* in VettoreLorentz e in Vettore
In un programma potremmo definire un puntatore ad un vettore e decidere solo in seguito, a seconda dell'uso, di specializzarlo
Se abbiamo un metodo overridden (p.es. modulo), quale versione verrà utilizzata? L'implementazione in Vettore o quella in VettoreLorentz?
La risposta sta nell'uso della keyword virtual
Vettore *v = 0; // puntatore alla classe madre...v = new VettoreLorentz(); // specializzazione
73Alessandro De Falco, INFN Cagliari 3/4/14
virtual methodsvirtual methodsNell'header Vettore.h abbiamo introdotto la keyword virtual davanti a tutti i metodi (tranne i costruttori ed il distruttore)
Scrivo Polimorfismo.C che usa il polimorfismo
74Alessandro De Falco, INFN Cagliari 3/4/14
Output di Polimorfismo.C (1)Output di Polimorfismo.C (1)Nella prima esecuzione definisco virtual il metodo Vettore::Modulo()
(in Vettore.h)...virtual double Modulo(); //specificare solo nell'header, non nell'implementazione
Le componenti sono 4,3,2,1 -->
Dunque quando nella classe madre il metodo [Vettore::Modulo()] è virtual, la formula usata è quella implementata nella classe figlia [VettoreLorentz]
42−32−22−12=2=1.41421
compilo: g++ -o Polimorfismo Polimorfismo.C Vettore.cxx VettoreLorentz.cxxeseguo:
75Alessandro De Falco, INFN Cagliari 3/4/14
Output di Polimorfismo.C (2)Output di Polimorfismo.C (2)Ora tolgo la keyword virtual da Vettore::Modulo() e ricompilo
Quasi sempre è consigliabile definire i metodi come virtual
Se il metodo è definito virtual posso sempre richiamare quello della classe madre. Nel nostro esempio:
(in Vettore.h)...double Modulo(); //non è più definito virtual
42322212=30=5.47723
ricompilo: g++ -o Polimorfismo Polimorfismo.C Vettore.cxx VettoreLorentz.cxxed eseguo:
Dunque quando nella classe madre il metodo [Vettore::Modulo()] non è virtual, la formula usata è quella implementata nella classe madre [Vettore]
double mod2 = v->Vettore::Modulo();