Un esempio di struttura puo' essere un punto bidimensionale:
struct punto {
float x;
float y;
};
Dopo aver definito questa struttura, noi possiamo definire delle
variabili
di tipo punto ed accedere alla loro struttura interna, ad esempio:
struct punto XY1, *XY2; // dichiarazione una variabile e di
// un puntatore alla struttura
XY1.x=1.; // inizializzazione di XY1
XY1.y=2.;
XY2 = new struct punto;
// L'operatore new funziona anche sulle struture.
*XY2 = XY1;
// esiste l'operatore di assegnazione di strutture
// (copia membro a membro)
cout << "Le coordinate del punto 2 sono: " << XY2->x << XY2->y << endl;
// per i puntatori l'operatore per accedere alle variabili
// e' "->" invece di "."
Il tutto e' molto piu' snello se usiamo un typedef per
abbreviare
la definizione:
typedef struct punto PUNTO;In tal caso le istruzioni diventano semplicemente
PUNTO XY1, *XY2; // dichiarazione una variabile e di
// un puntatore alla struttura
XY1.x=1.; // inizializzazione di XY1
XY1.y=2.;
XY2 = new PUNTO;
// L'operatore new funziona anche sulle struture.
*XY2 = XY1;
// esiste l'operatore di assegnazione di strutture
// (copia membro a membro)
cout << "Le coordinate del punto 2 sono: " << XY2->x << XY2->y << endl;
// per i puntatori l'operatore per accedere alle variabili
// e' "->" invece di "."
Le strutture permettono solamente di raccogliere un insieme di dati, lasciando agli utenti la massima liberta' (ovvero, nessuna protezione) nella loro gestione. I linguaggi di programmazione piu' recenti (C++, Java...) tendono ad enfatizzare delle altre entita' dette oggetti. Mentre una struttura pone l'enfasi sui dati, gli oggetti pongono l'enfasi sul comportamento: un oggetto e' caratterizzato da un insieme di funzioni, dette metodi, attraverso le quali puo' operare. La sua struttura interna contiene dei dati che pero' non sono accessibili all'utente: quello che importa sono solo le azioni che l'oggetto puo' compiere. In C++ si possono definire delle classi, ovvero dei tipi di oggetti. Una volta definita una classe si possono istanziare oggetti di tale classe, sia usando dichiarazioni di tali oggetti oppure usando l'operatore new. Una volta dichiarato un oggetto si possono chiamare i sui metodi allo stesso modo con cui si accede alle componeni di una struttura, ovvero
Ad esempio il seguente programma scrive con precisione sempre crescente il valore di π sul file pigreco.dat:
#include <fstream>
#include <cmath>
// in questo caso il compilatore chiede di inserire la libreria matematica
using namespace std;
int main() {
ofstream f; // dichiarazione di f come oggetto
// di tipo output file stream
f.open("pigreco.dat"); // Apertura del file pigreco.dat
for (int i=3; i<20; i++) {
f.precision(i); // Cambio di precisione della stampa
f << 2.*acos(0.) << endl; // Utilizzo l'operatore di uscita <<
}
f.close(); // Al termine delle operazioni si chiude il file
return 0;
}
Una volta compilato ed eseguito, il file viene riempito con il seguente
contenuto:
3.14 3.142 3.1416 3.14159 3.141593 3.1415927 3.14159265 3.141592654 3.1415926536 3.14159265359 3.14159265359 3.1415926535898 3.14159265358979 3.141592653589793 3.1415926535897931 3.14159265358979312 3.141592653589793116
In questo esempio f e`stato prima dichiarato e poi inizializzato invocando il metodo open. L'inizializzazione puo` anche avvenire direttamente al momento della dichiarazione, invocando un costruttore. Una classe puo` avere anche piu` di un costruttore. Nel nostro caso, un costruttore per gli oggetti ifstream e ofstream e`una funzione che ha come argomento il nome del file da aprire. Il costruttore viene invocato al momento della dichiarazione usando la seguente sintassi:
ofstream f("pigreco.dat")
oppure, nel caso si usino puntatori a oggetti allocati dinamicamente:
ofstream *f= new ofstream("pigreco.dat")
Il programma precedente, puo` essere riscritto in maniera equivalente:
#include <fstream>
#include <cmath>
using namespace std;
int main() {
ofstream f("pigreco.dat"); // dichiarazione di f come oggetto
// di tipo output file stream
// ed invocazione del costruttore
for (int i=3; i<20; i++) {
f.precision(i); // Cambio di precisione della stampa
f << 2.*acos(0.) << endl; // Utilizzo l'operatore di uscita <<
}
f.close(); // Al termine delle operazioni si chiude il file
return 0;
}
Per accedere al programma bisogna configurare alcune variabili
d'ambiente, definento ROOTSYS ed aggiungendo la directory dove si trova
l'eseguibile di ROOT al PATH.
Probabilmente tali variabili saranno gia` state configurate a livello
di sistema, ma per sicurezza potete controllare il loro contenuto dando
i comandi:
echo $ROOTSYS
e
echo $PATH
e vedere se in entrambi compare la directory /tools/root.
Nel caso questa non apparisse, si possono definire tali variabili da riga
di comando:
export ROOTSYS=/tools/rootPer comodita` potete aggiungere anche questi comandi al file .zshrc.
export PATH=${PATH}:${ROOTSYS}/bin
Se le variabili sono configurate correttamente, sara` sufficiente dare da
riga di comando
root
Per invocare il programma.
ROOT interpreta tutto cio' che viene scritto sulla riga di comando attraverso CINT, un interprete C++ interattivo. Quindi dal prompt di ROOT e' possibile dare istruzioni in C++, creare variabili, effettuare operazioni con esse...
Per bypassare l'interprete C++, ci sono un insieme di comandi speciali che iniziano con un '.'.
Il piu' semplice e' quello per terminare la sessione di ROOT:
.q
Per definire un oggetto di tipo TRandom, basta dichiararlo:
TRandom nomeOggetto;
per usare i metodi ad esso associati, si usa una sintassi simile a
quella delle strutture:
nomeOggetto.nomeMetodo(argomenti);
Per fare un esempio, per stampare 100 numeri interi estratti da una
distribuzione di Poisson con valor medio 3, potremmo scrivere il
seguente frammento di codice in RooT:
TRandom gen(0); for (int i=0; i<100; i++) cout << gen.Poisson(3.) << endl;Come vedete noi possiamo utilizzare gen per fare quello che vogliamo (generare numeri casuali di diverso tipo) senza sapere niente della sua struttura interna (enfasi sul comportamento e non sui dati).
E' possibile anche costruire puntatori ad oggetti, ma la questione e' un po' sottile ed in ogni caso non saranno necessari per gli esercizi di oggi.
Potete trovare una tabella riassuntiva con la selezione dei tipi di oggetti e dei relativi metodi che saranno utili per questa lezione e per la parte di simulazione. Tali tipi di oggetti sono accessibili solo all'interno di ROOT (vedremo in seguito come utilizzarli all'interno dei nostri programmi).
Oltre alla tabella riassuntiva, potete trovare:
Una macro puo' anche contenere la definizione di una nuova funzione,
come la macro randread.C che
definisce
una funzione TH1F randread(char*). In tal caso non ha senso
eseguire
direttamente la macro (come facciamo a passare i parametri giusti alla
funzione?). Si puo' pero' caricarla in memoria con il comando:
.L nomefile
A questo punto la nuova funzione e' nota a ROOT e puo' essere
utilizzata
da linea di comando o da altre macro.
Se ci si accorge che la funzione ha degli errori e si vuole modificare la macro, nelle versioni piu' recenti di ROOT, si puo':
Poiche' ROOT esegue automaticamente l'inclusione di molti degli include file di sistema, spesso non c'e' bisogno delle dichiarazioni di #include nel file contentente la macro.
Per aggiungere testi, frecce, disegni... sul grafico, e' sufficiente selezionare Editor dal menu Edit. Per visualizzare le coordinate del puntatore del mouse, selezionare Options -> Event Status.
Per modificare la presentazione grafica di un oggetto e' possibile selezionarlo posizionandoci sopra il cursore e schiacciando il bottone destro del mouse. Cio' fa comparire un menu che dipende dal tipo di oggetto selezionato ed indica le opzioni possibili. Particolarmente utili saranno il FitPanel o il DrawPanel per cambiare la rappresentazione degli istogrammi. Per spostare o ridimenzionare un oggetto, basta selezionarlo con il bottone sinistro del mouse.
Per i grafici (ad esempio quelli creati con grafico.C) il DrawPanel non funziona, ma e' possibile cambiare interattivamente le caratteristiche delle linee e dei punti disegnati selezionando SetLineAttributes e SetMarkerAttributes.
Il puntatore del mouse cambia forma a seconda dell'oggetto che si sta selezionando, e questa caratteristica permette di facilitare molto la selezione. Puo' essere utile ricordare che il puntatore a croce di solito indica che si sta selezionando un'area di sfondo, la freccia un grafico o un istogramma.
Infine, se avete fatto un bel grafico e volete salvarlo,
dal menu File
selezionate Save As.... E` importante indicare esplicitamente
il suffisso del file quando si dichiara il nome.
Per rivedere i file salvati potete usare i seguenti programmi:
Tale teorema e' la base di tutto il trattamento statistico degli
errori
e grosso modo puo' essere formulato:
Siano x1, x2, x3, ...xN numeri casuali tutti estratti da una stessa
distribuzione con valor medio Xm e r.m.s. σ. Allora, per N tendente
all'infinito
la quantita' (x1+x2+...+xN)/N tendera' ad avere una distribuzione
gaussiana
con valor medio Xm e r.m.s. σ/sqrt(N).
Il teorema di fatto dice che, se si fanno tante misure, anche con errori non gaussiani, la media alla fine avra' una distribuzione gaussiana ed una precisione migliore delle singole misure.
Verifichiamo empiricamente che questo succeda.
Alla fine di tutto, compilare la relazione Relazione