C++ - Lezione II Conversione di tipo I puntatori Arrays Funzioni 1 Operazioni Miste e Conversioni di Tipo Spesso si usano operandi di tipo diverso in una stessa espressione o si assegna ad una variabile un valore di tipo diverso della variabile stessa In ogni operazione mista è sempre necessaria una conversione di tipo che può essere implicita o esplicita Le conversioni implicite vengono effettuate dal compilatore: nell'ambito numerico, gli operandi sono convertiti al tipo di quello di dimensione maggiore Esempio: 3*2.6 viene calcolata come 3.0*2.6 e non come 3*2 nell’assegnazione un'espressione viene sempre convertita al tipo della variabile Esempi: float x = 3; //equivale a: x = 3.0 int y = 2*3.6; //equivale a: y = 7 Il programmatore può richiedere una conversione esplicita di un valore da un tipo ad un altro (casting) Esistono due notazioni: Esempio: int i = (int) 3.66; i=3 (troncamento!) Esempio: double f = double(3) 2 Cosa succede quando dichiariamo una variabile ? La dichiarazione di una variabile associa 3 attributi alla variabile stessa tipo, nome, indirizzo di memoria Es: int j = 24; tipo : int nome : j locazione di memoria in cui è custodito in valore della variabile gli indirizzi di memoria vengono rappresentati in esadecimale indirizzo del primo byte 0x7b03a928 j comunemente il tipo int occupa 4 bytes di memoria 24 3 I Puntatori Un puntatore è un riferimento ad una locazione di memoria memorizza l' indirizzo di un altra variabile #include <iostream.h> using namespace std; int main() { int j = 12; int * ptr = &j; ptr j cout << *ptr << endl; 24 12 j = 24; cout << *ptr << endl; sintassi T * t cout << ptr << endl; cout << &j << endl; cout << &ptr << endl; return 0; } L'operatore di indirizzo & restituisce l'inidirizzo delle variabili L'operatore di deferenziazione * mostra il contenuto della variabile puntata 4 Output #include <iostream.h> using namespace std; 12 24 int main() { int j = 12; int * ptr = &j; cout << *ptr << endl; 0x22efc4 indirizzi di memoria j = 24; cout << *ptr << endl; 0x22efc4 cout << ptr << endl; 0x22efc0 cout << &j << endl; cout << &ptr << endl; return 0; } 5 Puntatori - Altro Esempio int i, j; p &i i 3 q &i j 3 int *p; int *q; p=&i; // p=indirizzo di i *p=3; // equivale a i=3 j=*p; // equivale a j=i q=p; // equivale a q=indirizzo di i 6 Esercizio – Scambio di valore di 2 int int a = 10, b = 20, temp; temp = a; a = b; b = temp; Fare lo stesso utilizzando i puntatori ad int ... int a = 10, b = 20, temp; int *pa, *pb; ... 7 Gestione Dinamica della Memoria In C++ è possibile gestire la memoria anche dinamicamente, ovvero durante l’esecuzione del programma La gestione dinamica della memoria consente in un area di memoria esterna allo stack di esecuzione del programma L’accesso avviene tramite puntatori #include <iostream.h> using namespace std; int main() { int *ptr = new int; ptr *ptr = 12; cout << *ptr << endl; 12 delete ptr; return 0; } operatori di allocazione e deallocazione 8 Operatori new e delete L’operatore new alloca un’area di memoria atta a contenere un oggetto del tipo specificato e ritorna un puntatore a tale area di memoria Esempio: int * p = new int; L’operatore delete dealloca l’area di memoria dinamica puntata dal puntatore Esempio: int * p = new int; delete p; 9 Deallocazione Un oggetto creato dinamicamente resta allocato finchè: non viene esplicitamente deallocato con l'operatore delete il programma non termina Non usare delete fa accumulare locazioni di memoria inutilizzate (memory leak) La memoria non esplicitamente deallocata con l'operatore delete può risultare non più disponibile per altri programmi 10 Esempio di Memory Leak #include <iostream.h> using namespace std; si alloca memoria per un double int main() { while(1){ double x = 20; ora p_x punta all'area di memoria contenente x ma non si è liberata quella precedemente allocata!!! double * p_x = new double; delete p_x; p_x = &x; // Attenzione!! Memory leak!! cancelliamo la memoria precedentemente allocata per il double cout<< *p_x << endl; } return 0; } 11 Osserviamo come viene usata la memoria... Windows XP – Task Manager Linux - top 12 Altro errore ... puntatore a memoria non allocata int main() { int * new int; int; *p; *pp == new *p = 3; /* errore! p punta ad un’area non allocata */ delete p; return0;0; return } Proviamo ad compilare ed eseguire il programma... Ora è tutto OK ? 13 Puntatore Nullo #include <iostream.h> ptr j 12 using namespace std; int main() { int j = 12; int * ptr = 0; cout << *ptr << endl; // crash ! return 0; } Segmentation violation Esercizio : modificare il programma affinché non crashi... 14 Puntatore a Costante e Puntatori Costanti #include <iostream.h> Puntatore a costante: const T * t; const int * pc; Puntatore costante : T * const t; int * const pc; using namespace std; int main(){ const int c1 = 3; int c2 = 5; Puntatore costante a constante: const T * const t; const int * const pc; const int * pc1 = &c1; int * const pc2 = &c2; cout << "*pc1 " << *pc1 << endl; cout << "*pc2 " << *pc2 << endl; Solo una delle tre righe puo' essere decommentata: quale? // pc2=pc1; // (*pc1)++; // (*pc2)++; cout << "*pc1 " << *pc1 << endl; cout << "*pc2 " << *pc2 << endl; return 0; } 15 Arrays Un array è una sequenza finita di elementi dello stesso tipo Sintassi: tipo id[dim]; tipo id[dim]={lista_valori}; tipo id[]={lista_valori}; Esempi: double a[25]; //array di 25 double const int c=2; char b[2*c]={'a','e','i','o'}; char d[]={'a','e','i','o','u'}; Gli elementi di un array di dimensione N sono numerati da 0 a N-1 Esempio: a[3]=7 assegna il quarto elemento 16 Arrays Multidimensionali È possibile dichiarare array i cui elementi sono a sua volta degli array, generando degli array multidimensionali (matrici) Sintassi: tipo id[dim1][dim2]…[dimn]; tipo id[dim1][dim2]…[dimn]={lista_valori}; tipo id[][dim2]…[dimn]={lista_valori}; Quindi un array multidimensionale dim1 x dim2 x … x dimn può essere pensato come un array di dim1 array multidimensionali dim2 x … x dimn 17 Incremento e Decremento di Puntatori char * pc; char c = `a`; pc = &c; pc = pc++; // incrementa pc in modo che punti al char // immediatamente successivo //(ossia pc viene incrementato di 1) pc a char char il risultato non `e di incrementare di 1 il valore del puntatore, bensi di un numero di bytes equivalenti all’ampiezza di un char 18 Arrays e Puntatori #include <iostream> int main(){ float val[5]={ 3.1, 5.7, 6.4, 1.2, 0.5}; Il nome di un array è una costante puntatore al primo elemento dell'array stesso std::cout<< " val " << val <<std::endl; std::cout<< " *************************** " <<std::endl; int i=0; while(i<5){ std::cout<< "val[" <<i<<"] = " <<val[i] << ", val = " << val+i <<std::endl; ++i; } std::cout<< " *************************** " <<std::endl; val 3.1 val+1 5.7 val+2 6.4 val+3 1.2 val+4 0.5 i=0; while(i<5){ std::cout<< "*val+" <<i<<" = " << *val+i <<std::endl; ++i; } return 0; } 19 Arrays Statici & Dinamici float a[20]; \\ array statico float * p = new float[20]; \\ array dinamico Nella prima dichiarazione la memoria è allocata in maniera statica durante la compilazione del programma e la sua memoria e' allocata durante tutta l'esecuzione del programma Nella seconda dichiarazione la memoria è allocata dinamicamente, la memoria non è allocata fino a quando non e' eseguita la dichiarazione. La memoria viene liberata non appena si invoca l'operatore delete delete [] p; 20 I Riferimenti Il meccanismo dei riferimenti (reference) consente di dare nomi multipli a una variabile Esempio: int x=1; int &y=x; // y è di tipo reference y++ Attenzione : nelle dichiarazioni di variabili di tipo reference, l’inizializzazione è obbligatoria Esempio: int &y; // errore! Non è possibile ridefinire una variabile di tipo riferimento precedentemente definita: double x1,x2; double &y=x1; // ok double &y=x2; // errore! gia’ definita! 21 Esercizi... Scrivere un programma che usa un puntatore int * p per cercare un intero, dato dallo standard input, all'interno dell' array {11, 22, 33, 44, 55, 66, 77, 88, 99} Se l'intero viene trovato il programma stampa l'indirizzo altrimenti chiede un altro intero in input. Scrivere un programma che usa un puntatore float * p per stampare il puntatore del maggiore di un array di float passato dallo standard input 22 Funzioni Nella realizzazione di un programma il codice può diventare molto lungo spesso una stessa sequenza di operazioni viene ripetuta in più punti È quindi opportuno e conveniente strutturare il codice raggruppandone delle sue parti in moduli autonomi, detti funzioni, che vengono eseguiti in ogni punto in cui è richiesto L'organizzazione a funzioni consente di isolare parti di un programma che possono essere modificate in maniera indipendente 23 Sintassi per le Funzioni Dichiarazione: Tipo func(Tipo1 t1, …, TipoN tN); Definizione: Tipo func(Tipo1 t1, …, TipoN tN) {corpo } t1, …, tN sono i parametri della funzione Chiamata: func(exp1, …, expN) exp1, …, expN sono i parametri della chiamata e devono essere di tipo compatibile con quelli della dichiarazione 24 Esempio dalla Lezione I definiamo una funzione saluti.h #include <iostream> implementiamo la funzione saluti.cc #include ” s a l u t i . h ” using namespace std; void ciao(); g++ -c saluti.cc g++ -c test.cpp g++ -o runme saluti.o test.o ./runme oppure : g++ -o runme saluti.cc test.cpp •un altro po' di sintassi • void non ritorna nessun tipo • \n fine di linea void ciao ( ) { cout << ” ciao ! \ n ” ; } usiamo la funzione test.cpp #include ” s a l u t i . h ” int main() { ciao(); } return 0; 25 Altro Esempio di Funzione #include <iostream.h> using namespace std; /* funzione che calcola il massimo tra due numeri */ float max(float a, float b) { float max = a>b? a:b; return max; } • Le variabili dichiarate all'interno di una funzione sono locali a tale funzione e appartengono solo alla funzione in cui sono dichiarati (sono visibili solo nella funzione) /* programma che chiama la funzione max per il calcolo del massimo */ int main() { float f1, f2; cout << "Inserisci una coppia di numeri " << endl; cin >> f1 >> f2; cout << "Il max fra " << f1 << " e " << f2 << " vale " << max(f1,f2) << endl; return 0; } 26 Passaggio dei Parametri Esistono due modalità di associazione dei parametri attuali ai parametri formali: passaggio parametri per valore. Il parametro ha una sua propria cella di memoria in cui viene copiato il valore del parametro attuale corrispondente all’atto della chiamata. passaggio parametri per riferimento. La cella di memoria associata al parametro è quella della variabile corrispondente. Nell'esempio precedente veniva usato il passaggio per valore, ovvero veniva fatta una copia dei parametri 27 Passaggio per Riferimento #include <iostream.h> using namespace std; passaggio per riferimento /*funzione incremento e decremento*/ void incremento(int &ival) {ival=ival+1;} void decremento(int &ival) {ival=ival-1;} /* programma che chiama la funzione incremento decremento */ int main(){ supponiamo di voler implementare da noi la funzione per l'incremento ed il decremento di un intero int i1; cout << "Inserisci un numero intero " << endl; cin >> i1; cout << "Il numero inserito e` " << i1 << endl; incremento(i1); incremento(i1); cout << "Ora il numero inserito vale " << i1 << endl; decremento(i1); cout << "Ora il numero inserito vale " << i1 << endl; return 0; } 28 Puntatori e Passaggio per Riferimento I puntatori consentono di simulare il passaggio parametri per riferimento Esempio: void scambia(int* px, int* py) { int t; t = *px; *px = *py; *py = t; } void main() { int a, b; cin >> a >> b; scambia(&a, &b); cout << a << b << endl; } 29 Considerazioni sul Passaggio per Riferimento Quando l'oggetto da passare è grande minore carico di calcolo bisogna modificare il parametro passato alla funzione (da utilizzare esternamente) Tuttavia, se non si vuole che il parametro passato venga modificato conviene usare il passaggio per riferimento costante : void f(const Tipo &t) 30 Passaggio di un Array Quando viene passato un array ad una funzione, non vengono copiati i suoi elementi nel corrispondente parametro ma viene ricopiato solo l’indirizzo del primo elemento //funzione che somma i primi n elem. int somma(int v[], int n) { int i, s=0; indirizzo dell'array for (i=0; i<n; i++) s += v[i]; dimensione dell'array return s; } 31 Funzioni Ricorsive Una funzione può invocare non solo un'altra funzione, ma anche se stessa: si ha in questo caso una funzione ricorsiva Si rende agevole la programmazione in tutti i quei casi in cui risulta naturale formulare il problema da risolvere in maniera ricorsiva Esercizio: calcolare n! utilizzando una funzione ricorsivamente 32 Fattoriale : Soluzione #include <iostream.h> using namespace std; /*funzione fattoriale*/ int fatt(int n){ if (n==0 || n==1) return 1; return n*fatt(n-1); } ricorsione /* programma che chiama la funzione fattoriale*/ int main(){ int i1; cout << "Inserisci un numero intero " << endl; cin >> i1; cout << i1 <<"! = " << fatt(i1) << endl; return 0; } 33 Esercizi ... Implementare ed usare la funzione void computeCircle(float &area, float &perimetro, float raggio) per il calcolo dell'area e del perimetro di un cerchio Implementare ed usare una funzione che restituisce la deviazione standard di n numeri x1...xn 34 #include <iostream.h> using namespace std; void computeCircle(float& area, float& perimetro, float r) { const float PI = 3.1416; perimetro = 2*PI*r; area = PI*r*r; } int main(){ float r; cout << "Inserisci il raggio della circonferenza " << endl; cin >> r; float area, perimetro; computeCircle(area, perimetro, r); cout << "Il perimetro e` " << perimetro << ", l'area e` " << area << endl; return 0; } 35 #include <iostream.h> using namespace std; double deviazioneStandard(double x[], int dim) { double media=0; for(int kk=0;kk<dim;++kk) media+=x[kk]; media/=dim; double scarti=0; for(int kk=0;kk<dim;++kk) scarti+=(x[kk]-media)*(x[kk]-media); return sqrt(scarti/double(dim-1)); } int main(){ int n; cout << "Inserisci il numero di dati " << endl; cin >> n; double * x = new double[n]; cout << "Inserisci i dati " << endl; for(int kk=0;kk<n;++kk) cin>>x[kk]; cout << "La deviazione standard e` " << deviazioneStandard(x,n) << endl; delete [] x; return 0; } 36 Backup 37 Sistema Binario ed Esadecimale Sistema decimale : reppresentazione dei numeri in base 10 20 = 2x101+0x100 Sistema binario : reppresentazione dei numeri in base 2 20 = 1x24+0x23+1x22+0x21+0x20 = 10100 Esadecimale : sistema di numerazione in base 16 20 = 1x161+4x160 = 14 efficace perchè i numeri sono corti 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 A B C D E F 38 Scambio di interi coi puntatori int a = 10, b = 20, temp; int *pa, *pb; pa = &a; /* *pa diventa un alias per a */ pb = &b; /* *pb diventa un alias per b */ temp = *pa; *pa = *pb; *pb = temp; 39