UNIVERSITA’ DEGLI STUDI ROMA TRE DIPARTIMENTO DI FISICA “E. AMALDI” laboratorio di calcolo II AA 2003/04 seconda settimana a cura di Domizia Orestano Dipartimento di Fisica Stanza 159 - tel. (06 5517) 7281 www.fis.uniroma3.it/~orestano [email protected] 1 Indice • • • • Puntatori Puntatori e vettori Vettori di caratteri e stringhe Allocazione dinamica della memoria • Strutture dati • Reference • Passaggio dei dati ad una funzione per “referenza” e per “valore” nella prossima lezione 2 Puntatori • Un puntatore è un oggetto che fa riferimento ad un altro oggetto • Lo si dichiara in uno dei due modi seguenti int* p; int *p; Dove p è il puntatore • Non gli si può assegnare un valore direttamente, ma solo un oggetto cui puntare int i=3; // l’oggetto salvato in memoria int *p=&i; // dove &i è l’indirizzo di memoria di i (questa istruzione equivale a int *p; p=&i;) • L’oggetto puntato si ottiene “dereferenziando” il puntatore: *p • Se p vale 0 (puntatore nullo) *p non è definito (il programma si ferma!) p: int * i: int 3 3 Esempio . . int numero = 82485; // un oggetto intero a 32 bit // che in binario si scrive // 00000000 00000001 0x09053 00110101 01000010 00000001 0x09050 00000000 numero // 01000010 00110101 int * puntatore; // puntatore ad un oggetto intero puntatore = № // puntatore a numero // (posto uguale all'indirizzo di numero) 01010000 10010000 puntatore . . 4 Puntatori e Vettori (Arrays) • float x[5]; // dichiarazione di un vettore di numeri reali con 5 elementi • x e &x[0] sono la stessa cosa: il puntatore al primo elemento dell’array • *x e x[0] sono la stessa cosa: il primo elemento dell’array x: x[0] x[1] x[2] x[3] float float float float x[4] float float * 5 Aritmetica dei puntatori float x[5]; float *y=&x[0]; // y è un puntatore a x[0] float *z=x; // z è un puntatore a x[0] (y+1) punta a x[1] *(y+1) , y[1] e x[1] accedono lo stesso oggetto sono consentite operazioni di somma e sottrazione ed è possibile confrontare tra loro due puntatori (si confrontano gli indirizzi di memoria, che sono numeri interi) 6 Esempi di uso dei puntatori somma degli elementi di un vettore float x[5]; // qui gli elementi vengono inizializzati… double sum=0; for(int i=0;i<5;i++) { sum+=x[i]; } oppure float x[5]; // qui gli elementi vengono inizializzati… float *y=x; double sum=0; for(int i=0;i<5;i++) { sum+=*y++; } 7 Esempi inversione dell’ordine degli elementi di un vettore float x[10]; // qui gli elementi vengono inizializzati… float *left = &x[0]; float *right = &x[9]; while(left < right) { float temp = *left; *left++ = *right; *right-- = temp; } azzeramento degli elementi di un vettore float x[10]; // qui gli elementi vengono inizializzati… float *p = &x[10]; //attenzione a non usare *p !!! while ( p != x ) *--p=0; 8 vettori di caratteri e stringhe Programma che usa char e stringhe C #include <iostream.h> #include <string.h> int main() { // Voglio scrivere il mio nome // Introduco un vettore di caratteri nomechar, // lungo 7 caratteri, ed introduco il mio // nome, 1 lettera alla volta. // L'output utilizza un loop, con o senza il // puntatore (piu' o meno raffinato) // input dei dati char nomechar[7]; nomechar[0] = ‘D'; nomechar[3] = nomechar[5] = ‘i'; nomechar[1] = ‘o'; nomechar[2] = ‘m'; nomechar[4] = ‘z'; nomechar[6] = ‘a'; 9 // // // // // // In realta' sarebbe anche possibile istanziare e inizializzare nomechar con una sola istruzione. La dimensione viene assegnata automaticamente dal compilatore. La linea di istruzione equivalente alle precedenti e': // char nomechar[] // = {‘D’,’o’,’m’,’i’,’z’,’i’,’a’ }; // Oppure l'input potrebbe essere gestito // tramite lettura da tastiera // cout << "Assegnare il nome( 7 caratteri): " // << endl; // for (int i=0; i<7; i++) { // cin >> nomechar[i]; // }; 10 // Passiamo all'output // Senza l'uso del puntatore cout << endl << " Output senza l'uso del puntatore: "; for(int m=0;m<=7;m++) { cout << nomechar[m]; }; //Ora passiamo all'uso delle stringhe di tipo C // Istanzio un puntatore alla stringa C che // contiene il nome char *nomeC; nomeC = “Domizia"; // Notare l'uso e la funzione del doppio apice cout << endl << " Stringa di tipo C: " << nomeC << endl; 11 // Da notare che si puo' effettuare l'output // anche pensando alla stringa C come una serie // di caratteri cout << "Output carattere per carattere: "; for(int j=0;j<7;j++) { cout << ' ' << nomeC[j] ; }; cout << endl; // oppure ... cout << " "; char *q=nomeC; // q e’ un puntatore ad un carattere for(int j=0;j<7;j++) { cout << ' ' << *q++; }; cout << endl << endl; return 0; } 12 Programma che usa le stringhe C++ #include <iostream.h> #include <string> int main() { string name; name = “Domizia"; cout << name << endl; string cognome=“Orestano"; string tutto=name+" "+cognome; cout << endl << tutto << endl; return 0; } 13 Nota al Programma che usa i caratteri e le stringhe C • Sarebbe meglio introdurre una variabile cmax const int cmax=7; • E poi sostituire tutte le ricorrenze di 7 con la variabile cmax. In questo modo il programma diventa più elegante ed eventuali variazioni di lunghezza non richiederebbero sostituzioni se non nel valore di cmax. • Il problema non si pone nell'uso di stringhe C++ (programmazione OO ed allocazione dinamica della memoria) 14 Dimensionamento dei vettori (1) // Un esempio da CorpoCeleste class CorpoCeleste { protected: char *Nome; …………….}; // Variante a dimensione fissa, // memoria allocata a compilation time class CorpoCeleste { protected: char Nome[20] ; ……………}; 15 // Tentativo di variante a dimensione variabile, // ma non compila! class CorpoCeleste { protected: int n; char Nome[n]; …………….}; vettori di dimensione variabile, fissata in fase di esecuzione, non possono essere inseriti tra gli attributi di una classe ! 16 Puntatori a variabili locali (1) ===File prova_fp.h class prova_fp { private: // ....... public : prova_fp() { } ; ~prova_fp() { } int * int_fp() { int a = 5 ; return &a ; }; } ; ===Main: #include <iostream.h> #include <string.h> #include "prova_fp.h" int main() { int * p; prova_fp myprova; p = myprova.int_fp(); cout << endl << endl; cout << " Puntatore : " << p << endl ; cout << " Valore : " << *p << endl ; return 0; }; Warning in compilazione e stampa di un Valore privo di senso ! 17 Allocazione dinamica della memoria • L’allocazione statica degli oggetti in memoria consente maggiore velocità di esecuzione perchè lo spazio usato e il tempo di utilizzo (“lifetime” dell’oggetto) sono determinati già in fase di compilazione. • due problemi: – vogliamo poter allocare di vettori di dimensione definita durante l’esecuzione del programma – vogliamo avere accesso agli oggetti definiti localmente (all’interno di uno { “scope”}) • una soluzione: allocazione dinamica della memoria. Gli oggetti vengono creati in un’area di memoria chiamata heap. Si utilizzano gli operatori – new – delete 18 Dimensionamento dei vettori (2) class CorpoCeleste { protected: char * Nome; …………….}; E poi nel costruttore inizializzato int n = strlen(nomeCorpo); Nome=new char[n]; Ma tutti gli oggetti creati con new devono essere distrutti esplicitamente con l’operatore delete, questi oggetti infatti non vengono cancellati automaticamente all’uscita dallo “scope”. In caso di vettori la sintassi è delete [ ] Nome; Dobbiamo modificare CorpoCeleste::~CorpoCeleste inserendovi questa istruzione! 19 #include #include #include #include CorpoCeleste.cc "CorpoCeleste.h" <string.h> <iostream.h> <iomanip.h> CorpoCeleste::CorpoCeleste() { Prima parte } CorpoCeleste::CorpoCeleste (const char *nomeCorpo, float mass, float xpos, float ypos, float vxi, float vyi) { Nome = new char[strlen(nomeCorpo)]; strcpy(Nome, nomeCorpo); m = mass; x = xpos; y = ypos; vx = vxi; vy = vyi; } void CorpoCeleste::calcolaPosizione( float fx, float fy, float t) { double ax = fx/m; double ay = fy/m; vx += ax*t; vy += ay*t; x += vx*t; y += vy*t; } 20 void CorpoCeleste::stampaPosizione() { cout.setf(ios::fixed); cout.setf(ios::showpos); cout << " " << setprecision(4) << setw(9) << x*1.e-11 << " " << setprecision(4) << setw(9) << y*1e-11 ; } CorpoCeleste.cc void CorpoCeleste::stampaVelocita() { cout.setf(ios::fixed); cout.setf(ios::showpos); cout << " " << vx << " " << vy ; } CorpoCeleste::~CorpoCeleste() Seconda parte {delete [] Nome;} const char* CorpoCeleste::nome() {return Nome; } double CorpoCeleste::M() { return m; } double CorpoCeleste::X() { return x; } double CorpoCeleste::Y() {return y; double CorpoCeleste::Vx() {return vx; } double CorpoCeleste::Vy() {return vy; } } 21 Puntatori a variabili locali (2) class prova_fp_n { private: // ....... public : prova_fp_n() { } ; ~prova_fp_n() { } ; int * int_fp() { int * p = new int(5); return p ; }; }; ===Main: #include <iostream.h> #include <string.h> #include "prova_fp_n.h" int main() { int * p; prova_fp_n myprova; p = myprova.int_fp(); cout << endl << endl; cout << " Puntatore : " << p << endl ; cout << " Valore : " << *p << endl ; delete p; return 0; }; Nessun warning in compilazione e stampa del Valore corretto: 5! 22 Esercitazione • Obiettivi: – Utilizzare la classe CorpoCeleste – Studiare il comportamento dei costruttori e del distruttore – Utilizzare puntatori, vettori, vettori di puntatori 1. Modificare il distruttore di CorpoCeleste includendo l’operazione di delete [ ] Nome 2. Aggiungere stampe nei costruttori e nel distruttore 3. Provare i seguenti programmi che usano CorpoCeleste: 23 Programmi • Es1_a.cc (costruttore, uso dei metodi “Get”, distruttore) • Es1_b.cc (allocazione dinamica, puntatori) • Es1_c.cc (vettore di oggetti, costruttore di default) • Es1_cbis.cc (vettore di puntatori ad oggetti) • Es1_d.cc (vettore con allocazione dinamica) • Es1_dbis.cc (vettore di puntatori con allocazione dinamica) 24 #include #include #include #include "CorpoCeleste.h" <string.h> <iostream.h> <iomanip.h> CorpoCeleste.cc Prima parte CorpoCeleste::CorpoCeleste() { cout << “invocato costruttore di default di CorpoCeleste”<<endl; } CorpoCeleste::CorpoCeleste (const char *nomeCorpo, float mass, float xpos, float ypos, float vxi, float vyi) { Nome = new char[strlen(nomeCorpo)]; strcpy(Nome, nomeCorpo); m = mass; x = xpos; y = ypos; vx = vxi; vy = vyi; cout << “invocato costruttore di CorpoCeleste ”<<Nome<<endl; } void CorpoCeleste::calcolaPosizione( float fx, float fy, float t) { double ax = fx/m; double ay = fy/m; vx += ax*t; vy += ay*t; x += vx*t; y += vy*t; } 25 void CorpoCeleste::stampaPosizione() { cout.setf(ios::fixed); cout.setf(ios::showpos); cout << " " << setprecision(4) << setw(9) << x*1.e-11 << " " << setprecision(4) << setw(9) << y*1e-11 ; } CorpoCeleste.cc void CorpoCeleste::stampaVelocita() { cout.setf(ios::fixed); cout.setf(ios::showpos); cout << " " << vx << " " << vy ; } Seconda parte CorpoCeleste::~CorpoCeleste() { cout << “invocato distruttore di CorpoCeleste ”<<Nome<<endl; delete [] Nome; } const char* CorpoCeleste::nome() {return Nome; } double CorpoCeleste::M() { return m; } double CorpoCeleste::X() { return x; } double CorpoCeleste::Y() {return y; } double CorpoCeleste::Vx() {return vx; } double CorpoCeleste::Vy() {return vy; } 26 #include <iostream.h> #include "CorpoCeleste.h” Es1_a.cc int main () { // istanzio una serie di oggetti di tipo CorpoCeleste CorpoCeleste sole("Sole", 2.e30,0.,0.,0.,0.); CorpoCeleste pietra("dolomite",1.,0.,0.,1.,1.); CorpoCeleste sasso("quarzo",2.,1.,0.,0.,1.); // Scrivo alcune caratteristiche degli oggetti che ho istanziato: // massa e posizione cout << endl << "Pianeta n. Massa Posizione iniziale " << endl << " x y " << endl << endl; cout << " " << "1" << " " << sole.M() << " " << sole.X() << " " << sole.Y() << endl; cout << " " << "2" << " " << pietra.M() << " " << pietra.X() << " " << pietra.Y() << endl; cout << " " << "3" << " " << sasso.M() << " " << sasso.X() << " " << sasso.Y() << endl; cout << endl; // Il programma e' finito. // Il compilatore provvede a distruggere gli oggetti di tipo CorpoCeleste return 0; } 27 Es1_b.cc #include <iostream.h> #include "CorpoCeleste.h" Prima parte int main () { // istanzio una serie di oggetti di tipo CorpoCeleste // Uso l'allocazione dinamica della memoria, tramite new CorpoCeleste * Psole = new CorpoCeleste("Sole", 2.e30,0.,0.,0.,0.); CorpoCeleste * Ppietra = new CorpoCeleste("dolomite",1.,0.,0.,1.,1.); CorpoCeleste * Psasso = new CorpoCeleste("quarzo",2.,1.,0.,0.,1.); // Scrivo alcune caratteristiche degli oggetti che ho istanziato; // Ad esempio la massa e la posizione cout << endl << "Pianeta n. Massa << " << endl << endl; cout << " " << "1" << " << " " << Psole->X() << " cout << " " << "2" << " << " " << Ppietra->X() << " cout << " " << "3" << " << " " << Psasso->X() << " cout << endl; Posizione iniziale " << endl x y " " " " << Psole->M() " << Psole->Y() << endl; << Ppietra->M() " << Ppietra->Y() << endl; << Psasso->M() " << Psasso->Y() << endl; 28 Es1_b.cc Seconda parte // Ora devo cancellare gli oggetti allocati dinamicamente tramite new cout << endl << "Prima delle chiamate a delete " << endl; delete Psole; delete Ppietra; delete Psasso; cout << endl << "Dopo le chiamate a delete " << endl; // // // // Il programma e' finito Notate (tramite il distruttore intelligente) che il compilatore ha provveduto a distruggere gli oggetti di tipo CorpoCeleste ai quali puntavano Psole, Ppietra, Psasso. return 0; } 29 Es1_c.cc #include <iostream.h> #include "CorpoCeleste.h" int main () { // uso un vettore di oggetti di tipo Corpoceleste const int np = 6; CorpoCeleste Pianeti[np]; // Con un loop scrivo gli elementi del vettore for (int i=0; i<np; i++) { CorpoCeleste Ausiliario(" ",i,i,i,i,i); Pianeti[i] = Ausiliario; } // Con un loop posso estrarre le informazioni cout << endl << "Pianeta n. Massa << " << endl << endl; for ( int i=0; i<np; i++) { cout << " " << i+1 << " " } cout << endl; return 0; } Posizione iniziale " << endl x y " << " " << Pianeti[i].M() << Pianeti[i].X() << " " << Pianeti[i].Y() << endl; 30 #include <iostream.h> #include "CorpoCeleste.h" Es1_cbis.cc int main () { Prima parte // Ora uso un vettore di puntatori a oggetti di // tipo CorpoCeleste const int npp = 8; CorpoCeleste* MieiPianeti[npp]; // Con un loop scrivo gli elementi del vettore for ( int i=0; i<npp; i++) { MieiPianeti[i] = new CorpoCeleste(" ",i,i,i,i,i); } // Con un loop posso estrarre le informazioni. Ad esempio la massa cout << endl << "Pianeta n. << " << endl << endl; Massa Posizione iniziale " << endl x y " for ( int i=0; i<npp; i++) { cout << " " << i+1 << " " << MieiPianeti[i]->M() << " " << MieiPianeti[i]->X() << " " << MieiPianeti[i]->Y() << endl; } cout << endl; 31 Es1_cbis.cc Seconda parte // devo ricordarmi di cancellare gli oggetti allocati dinamicamente for ( int i=0; i<npp; i++) { cout << i+1; delete MieiPianeti[i]; } return 0; } 32 #include <iostream.h> #include "CorpoCeleste.h" Es1_d.cc int main () { Prima parte // uso un vettore di oggetti di tipo Corpoceleste // Il vettore di oggetti e' allocato dinamicamente // tramite new, che fornisce il puntatore all'array const int np = 6; CorpoCeleste * Pianeti= new CorpoCeleste[np]; // Con un loop scrivo gli elementi del vettore for (int i=0; i<np; i++) { CorpoCeleste Ausiliario("",i,i,i,i,i); Pianeti[i] = Ausiliario; } // Con un loop posso estrarre le informazioni. Ad esempio la massa cout << endl << "Pianeta n. Massa Posizione iniziale " << endl << " x y " << endl << endl; for ( int i=0; i<np; i++) { cout << " " << (i+1) << " " << << " " << Pianeti[i].X() << " " << Pianeti[i].Y() << endl; } Pianeti[i].M() 33 Es1_d.cc Seconda parte // Ora devo cancellare il vettore allocato dinamicamente cout << endl << endl << " Prima di chiamare delete [] Pianeti " << endl; delete [] Pianeti; cout << endl << " Dopo aver chiamato delete [] Pianeti " << endl; return 0; } 34 #include <iostream.h> #include "CorpoCeleste.h" Es1_dbis.cc int main () { Prima parte // // // // // Ora uso un vettore di puntatori a oggetti di tipo CorpoCeleste Il vettore di puntatori e' a sua volta allocato dinamicamente, tramite new, che fornisce il puntatore all'array di puntatori. Per questo motivo la prima chiamata di new istanzia un oggetto di tipo CorpoCeleste ** , cioe' un puntatore ad un puntatore. const int npp = 8; CorpoCeleste** MieiPianeti = new CorpoCeleste*[npp]; // Con un loop scrivo gli elementi del vettore for (int i=0; i<npp; i++) { MieiPianeti[i] = new CorpoCeleste(" ",i,i,i,i,i); } // Con un loop posso estrarre le informazioni. Ad esempio la massa cout << endl << "Pianeta n. << " << endl << endl; Massa Posizione iniziale " << endl x y " 35 Es1_dbis.cc Seconda parte for (int i=0; i<npp; i++) { cout << " " << i+1 << " " << MieiPianeti[i]->M() << " " << MieiPianeti[i]->X() << " " << MieiPianeti[i]->Y() << endl; } cout << endl; // devo ricordarmi di cancellare gli oggetti allocati dinamicamente // e poiche' ho usato new a due livelli, chiamero' delete 2 volte // 1 - per I puntatori agli oggetti di tipo CorpoCeleste // (cioe' gli oggetti contenuti nell'array di puntatori cout << endl << " Prima del loop con delete MieiPianeti[i] " << endl; for (int i=0; i<npp; i++) { cout << i+1; delete MieiPianeti[i]; } cout << endl << " Dopo il loop con delete MieiPianeti[i] " << endl; // 2 - per l'array di puntatori delete [] MieiPianeti; return 0; } 36