Dipartimento di fisica “E


Implementazione della classe CorpoCeleste: l’implementation file CorpoCeleste.cc



Scaricare 1.8 Mb.
Pagina6/24
12.12.2017
Dimensione del file1.8 Mb.
1   2   3   4   5   6   7   8   9   ...   24

Implementazione della classe CorpoCeleste: l’implementation file CorpoCeleste.cc


Una volta definita l’interfaccia occorre specificare in dettaglio il funzionamento dei metodi. Ad esempio, occorrerà indicare esattamente quali operazioni compiere in seguito all’invocazione del metodo calcolaPosizione(fx,fy,dT) oppure del metodo M().

Questo si fa scrivendo il codice per la manipolazione dei dati in un file separato che usualmente ha una estensione del tipo cxx, C, cpp, cc a seconda del compilatore usato. Nel nostro caso utilizzeremo sempre l'estensione .cc, mentre riserveremo l'estensione .cpp al programma main. Nel caso del CorpoCeleste, l’implementazione dei metodi si scrive quindi in un implementation file di nome CorpoCeleste.cc.

Indichiamo nel seguito il file di implementazione CorpoCeleste.cc nella sua forma completa, e poi commenteremo le varie parti che lo compongono. In alcuni casi ci accorgeremo che per capirne sino in fondo la struttura sara' necessario introdurre alcuni concetti nuovi.
#include “CorpoCeleste.h”
#include

#include

#include
CorpoCeleste::CorpoCeleste() { } ;

CorpoCeleste::CorpoCeleste(const char *nomeCorpo, float mass,

float xi, float yi, float vxi, float vyi) {

Nome = new char[strlen(nomeCorpo)+1];

strcpy(Nome, nomeCorpo);

m = mass ;

x = xi ;

y = yi ;


vx = vxi ;

vy = vyi ;

}
CorpoCeleste::~CorpoCeleste() {delete [] Nome; }

void CorpoCeleste::calcolaPosizione(float fx, float fy, float dt) {

double ax = fx/m ;

double ay = fy/m ;

vx += ax*dt ;

vy += ay*dt ;

x += vx*dt ;

y += vy*dt ;

}

void CorpoCeleste::stampaPosizione() {



cout.setf(ios::fixed) ;

cout.setf(ios::showpos) ;

cout << " " << setprecision(4) << setw(9) << x*1e-11 << " "

<< setprecision(4) << setw(9) << y*1e-11 ;

}
void CorpoCeleste::stampaVelocita() {

cout.setf(ios::fixed) ;

cout.setf(ios::showpos) ;

cout << " " << vx << " " << vy ;

}
const char* CorpoCeleste::nome() { return Nome; } ;

float CorpoCeleste::M() { return m ; } ;

double CorpoCeleste::X() { return x ; } ;

double CorpoCeleste::Y() { return y ; } ;

double CorpoCeleste::Vx() { return vx ; } ;

double CorpoCeleste::Vy() { return vy ; } ;
Il file di implementazione inizia includendo il file che contiene la definizione della Classe, cioe' l'header file della classe che si sta implementando, nel nostro caso il file CorpoCeleste.h: questo e' necessario per poter essere in grado di manipolare coerentemente gli attributi e i metodi della classe. Al fine di includere l’header file nel file di implementazione si usa una direttiva di compilazione: si tratta di un comando che viene dato al compilatore affinchè esegua una certa operazione (in questo caso l’inclusione di un file). Non si tratta cioè di una istruzione di programma, ma di una istruzione necessaria al compilatore per la produzione del programma. Le direttive di compilazione si distinguono dalle istruzioni del linguaggio essendo precedute da un cancelletto ( # ). Se il file che si vuole includere, come nel nostro caso, e' in una directory indicata dall'utente o nella directory nella quale si sta lavorando, il nome del file deve essere racchiuso tra virgolette ( " ). Altri header files sono poi di fatto parte integrante del liguaggio ed occorre includerli nel caso si utilizzino classi di libreria o particolari funzioni predefinite. In questo caso i files si trovano in una directory predefinita che non deve essere specificata se, al posto delle virgolette, si usano le parentesi acute ( < > ). Nel nostro caso abbiamo incluso un header file necessario per l'uso delle stringhe ( string.h ), un altro per permettere l'input / output su schermo ( iostream.h ) ed un terzo per la manipolazione del formato di input / output, impostando ad esempio il tipo di notazione utilizzata, il numero di cifre significative da utilizzarsi, etc. ( iomanip.h )

Nel file di implementazione devono comparire tutti metodi che compaiono nell'header file (salvo una possibilita' alternativa che discuteremo al termine di questo paragrafo), caratterizzati dal loro nome e dal numero di argomenti e dal tipo di argomenti. L'implementazione deve essere compresa tra parentesi graffe ( { } ) ed il nome del metodo deve essere preceduto dal nome della classe, utilizzando i doppi due punti ( :: ) come segno di separazione (ad esempio: Corpoceleste::stampaPosizione() ): il compilatore infatti deve poter conoscere a quale classe si riferisce il metodo che viene implementato.

Commentiamo brevemente l'implementazione di ciascun metodo, richiamando anche la loro funzione che pero', a livello di principio, dovrebbe essere ormai chiara al momento dell'implementazione: la funzione, cioe' i compiti, di ciascun metodo devono essere definiti in sede di progettazione dell'intero schema del Programma.

Il primo metodo che incontriamo e' il costruttore senza parametri: questo costruttore istanzia un oggetto della classe CorpoCeleste, riservando uno spazio di memoria ad esso assegnato, senza pero' inizializzare gli attributi dell'oggetto istanziato. Il metodo successivo e' un altro costruttore, che permette non solo di istanziare un oggetto della classe CorpoCeleste, riservando uno spazio di memoria ad esso assegnato, ma anche di inizializzare tutti gli attributi. Nell’intestazione: notiamo che il primo parametro è definito come const char *nomeCorpo, che indica una stringa il cui nome è nomeCorpo. La parola chiave const viene utilizzata per indicare esplicitamente che la variabile non può essere modificata all’interno del metodo, si tratta cioè di una variabile costante. Questa parola chiave non è strettamente necessaria, ma l’abbiamo utilizzata per introdurne l’uso. L’uso di const, inoltre, rende più veloce l’esecuzione del programma grazie al modo diverso in cui le variabili vengono passate all’interno dei metodi in fase di esecuzione. L'assegnazione della massa e delle coordinate cinematiche, posizione e velocita', avviene in maniera immediata, mentre l'inizializzazione del nome del CorpoCeleste richiede una procedura un poco piu' macchinosa, per la quale si rimanda alla sezione di sintassi dedicata alle stringhe di tipo C: si tratta comunque di creare un nuovo vettore di caratteri, tramite l'operatore new, che discuteremo in dettaglio in seguito, e di copiare su di esso il nome del corpo celeste, tramite la funzione di libreria strcpy(stringa1,stringa2). La lunghezza della stringa di caratteri sulla quale vogliamo scrivere deve essere pari al numero di caratteri del nome del corpo celeste, per ottenere il quale utilizziamo la funzione di libreria strlen(stringa), aumentata di 1 per ospitare un carattere di controllo che indica che la stringa e' terminata.

Il metodo successivo e' il distruttore, che in questo caso deve occuparsi solo di cancellare le variabili create con l'operatore new: anche di questo ci occuperemo in seguito, e per ora si possono trascurare i dettagli dell'implementazione. Comunque possiamo anticipare che l'uso delle parentesi [] indica che Nome è una variabile che può avere una lunghezza maggiore di un singolo carattere e che tale lunghezza può essere qualsiasi: quando questa viene distrutta occorre calcolarne la lunghezza e cancellare il contenuto di ciascuno dei byte ad essa allocati. E' necessario ricordare che il distruttore deve essere presente in ogni classe e che deve essere implementato, anche se l'implementazione puo essere nulla ( { } ).

Il metodo calcolaPosizione(float fx, float fy, float dt) deve calcolare le nuove coordinate cinematiche dell'oggetto CorpoCeleste quando e' soggetto ad una forza (fx, fy) per un tempo dt. Quando viene invocato il metodo calcolaPosizione i valori di fx e fy (passati attraverso i parametri del metodo e quindi esterni all’oggetto) vengono utilizzati per calcolare le accelerazioni nelle due direzioni utilizzando la variabile m che invece è un attributo dell’oggetto: m è un attributo privato dell’oggetto e quindi non può essere utilizzato all’esterno di esso, ma all’interno dei metodi di CorpoCeleste esso si comporta come una variabile qualsiasi. Si noti che tutte le variabili che vengono utilizzate in un programma C++ devono essere preventivamente dichiarate: deve cioè essere specificato il tipo per ciascuna di esse. La specifica del tipo si può fare in un qualunque punto del programma purchè la variabile non venga utilizzata prima della sua dichiarazione. Le variabili ax e ay rappresentano le accelerazioni nelle due direzioni x e y e sono state dichiarate double. In C++ è possibile dichiarare il tipo di una variabile ed assegnarne il valore sulla stessa riga di programma, ma sarebbe stato possibile utilizzare anche due istruzioni, una per istanziare l'oggetto ( double ax ) ed una per assegnarne il valore ( ax = fx/m ). Il nuovo valore della velocita' viene poi calcolato aggiungendo alle velocita' (vx, vy) le variazioni (ax*dt, ay*dt) dovute alla applicazione della forza (fx, fy). Un algoritmo simile viene utilizzato per determinare la nuova posizione (x,y). I nuovi valori della posizione (x, y) e della velocita' (vx, vy) restano quindi memorizzati negli attributi dell'oggetto. Si noti, anche se questo riguarda l'aspetto fisico del modello e non l'implementazione in C++, che la relazione con cui si calcola la nuova velocita' e' esatta (poiche' la forza e' supposta costante), mentre la relazione con cui si calcola la nuova posizione e' corretta solo al primo ordine in dt. E' necessario comunque osservare che, nello spirito della logica della simulazione, dt e' piccolo rispetto ai tempi caratteristici in gioco. Inoltre va rilevato che anche il calcolo della nuova velocita' e' approssimato, poiche' la forza non e' costante: in questo caso pero' l'approssimazione non e' legata all'implementazione di qualche metodo di CorpoCeleste, ma a come viene utilizzata nell'insieme della simulazione.

Il metodo stampaPosizione() deve consentire la visualizzazione delle coordinate del corpo. Attraverso la libreria standard del C++ iostream.h, si può utilizzare l’operatore << per scrivere dati su un dispositivo opportuno. Lo schermo è un dispositivo predefinito che si indica con l’identificatore cout. Gli operatori << possono essere concatenati: possono cioè essere utilizzati uno dietro l’altro come in una catena al fine di eseguire l’output di dati diversi sullo stesso dispositivo. L’identificatore cout è un oggetto della classe iostream.

Il formato dei dati (cioè il modo in cui i dati appaiono, il tipo di notazione utilizzata, il numero di cifre significative da utilizzarsi, etc.) può essere definito attraverso un metodo particolare di cout (cioe’ un metodo della classe iostream applicato all’oggetto cout), che si chiama setf. setf è un metodo che richiede, in ingresso, un valore che indica quale caratteristica dell’oggetto occorre modificare. Per comodità dell’utente questi valori sono definiti attraverso degli identificatori del tipo ios (un oggetto definito nella libreria standard del C++ iomanip.h). Supponiamo di voler visualizzare questo dato utilizzando un certo numero di cifre significative ed utilizzando comunque un numero fisso di cifre totali in modo che, nella stampa di un numero consistente di coordinate il tutto appaia come una tabella ben ordinata. Nell’implementazione di CorpoCeleste::stampaPosizione(), la prima riga invoca il metodo setf di cout per far sì che i numeri vengano visualizzati con un numero fissato di cifre (ios::fixed). La seconda riga invece serve a fare in modo che, per i numeri positivi, venga visualizzato il segno + davanti al numero (ios::showpos). La riga successiva è il comando che produce il risultato: cout << “ “ indica che si sta richiedendo al programma di stampare uno spazio sullo schermo. A seguire si istruisce cout a scrivere il valore di x (che abbiamo moltiplicato per 1*10-11 per comodità). Si noti che l’operazione può essere eseguita direttamente nella riga di comando per l’output, senza bisogno di definire una variabile apposita. Il numero corrispondente al valore della variabile x verrà visualizzato utilizzando 4 cifre significative e un numero di cifre totali pari a 9 (setprecision(4) e setw(9)). Analogamente si può procedere per y. Un’altra caratteristica da notare è che, in questo caso, la riga per l’esecuzione dell’output è in realtà scritta su due righe diverse. Questo è solo un modo per facilitare la lettura del codice da parte del programmatore. L’istruzione in realtà è una sola che comincia con cout e finisce con il punto e virgola.

Infine sono implementati i metodi (di tipo get) per accedere ai valori degli attributi della classe. ad esempio, per conoscere la posizione x del corpo sarà sufficiente invocare il metodo X(), la cui implementazione si ottiene utilizzando l’istruzione return, che produce l’uscita dal metodo. Il modo in cui questo genere di metodi viene usato sarà chiaro non appena cominceremo a scrivere i metodi della classe SistemaSolare.
A conclusione del paragrafo, possiamo pero’ osservare che, per i metodi la cui implementazione è estremamente semplice come Mass(), si usa definirne l’implementazione direttamente nell’header file. Nel nostro caso, essi vanno omessi dal file di implementazione CorpoCeleste.cc, e compaiono nell’header file CorpoCeleste.h, che per completezza riportiamo nuovamente per intero
class CorpoCeleste {

private:


char *Nome ;

float m ;

double x ;

double y ;

double vx ;

double vy ;

public:

CorpoCeleste();

CorpoCeleste(const char *nomeCorpo, float mass, float xi,

float yi, float vxi, float vyi);

~CorpoCeleste();

void calcolaPosizione(float fx, float fy, float dt);

void stampaPosizione() ;

const char* nome() { return Nome ; } ;

float M() { return m ; } ;

double X() { return x ; } ;

double Y() { return y ; } ;

double Vx() { return vx ; } ;

double Vy() { return vy ; } ;

};
A questo punto la definizione della classe CorpoCeleste è terminata.





Condividi con i tuoi amici:
1   2   3   4   5   6   7   8   9   ...   24


©astratto.info 2019
invia messaggio

    Pagina principale