Puntatori e gestione dinamica della memoria Corso di Informatica 2 a.a. 2003/04 Lezione 4 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Vantaggi nell’uso dei vettori Sono legati all’accesso diretto agli elementi utilizzando gli indici. indirizzo v[i] = b + i d d = dimensione elemento v v[i] b = indirizzo base i = indice (spiazzamento) Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Indirizzamento della RAM 000 001 1 010 1 011 1 23 100 101 1 1 110 1 111 1 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Problemi nell’uso dei vettori La dimensione di un vettore deve essere fissata al momento della compilazione: int v[100]; // non int v[dim]; con dim variabile L’inserimento di un nuovo valore comporta lo slittamento di tutti quelli alla sua destra: for (j = indice_prox_loc_libera; j > i; j--) v[j] = v[j-1]; v[i] = nuovo_valore; Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
<tipo>* <variabile>; I puntatori Un puntatore è una variabile il cui dominio di definizione sono gli indirizzi di memoria <tipo>* <variabile>; int* p; // puntatore a intero int *p, *q; // breve per int* p; int* q; Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Dereferenziazione (*) L’operatore di dereferenziazione (* <puntatore>) legge/scrive alla locazione di memoria il cui indirizzo è il valore del suo operando p 025fe16 025fe16 2983 *p == 2983 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operatore indirizzo (&) Se x è una variabile, &x è l’indirizzo a cui sono memorizzati i suoi valori x 025fe16 2983 &x == 025fe16 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Esempio // esempio di dereferenziazione e // uso dell’oporatore & #include <iostream.h> int main() { int x = 7; int *p1, *p2; // oppure int* p; int* q; p1 = &x; p2 = p1; cout << “*p2 = “ << *p2 << endl; // stampa il valore di x cioe’ 7 cout << “p2 = “ << p2 << endl; // stampa il valore di p2 cioe’ // l’indirizzo di x } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Condivisione (sharing) I puntatori possono condividere l’area di memoria cui puntano: int *p, *q; int n = 44; p = q = &n; p q n 44 Ogni modifica del valore di n che avvenga per assegnazione su *p si riflette su n e su *q. Questa caratteristica viene sfruttata per ottonere effetti collaterali sui valori dei parametri attauli, passandoli cioè per indirizzo. Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
<tipo>& <nome rif> = <nome var> Riferimenti In C++ si può avere sharing anche senza i puntatori, usando riferimenti, ovvero sinonimi (alias): <tipo>& <nome rif> = <nome var> int main () { int n = 44; int& rn = n; // rn è sinonimo di n n--; cout << rn << endl; // stampa 43 } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Vettori e puntatori in C++ In C++ un vettore è una costante di tipo puntatore: int v[100]; int* p; p = v; // il valore di p è l’ind. di base di v // ossia p == &v[0] Si può usare la notazione con gli indici per i puntatori che si riferiscono a vettori: p[i] … // equivale a v[i] Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (1) Ad ogni tipo di dato corrisponde la dimensione in byte della memoria necessaria per contenere i suoi valori: int sizeof(<tipo>) // C: ritorna la dim. I puntatori sono tipati: ciò è essenziale per sapere cosa leggere/scrivere alle locazioni di memoria cui puntano Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (2) In C++ si possono sommare (o sottrarre) interi a puntatori: int *p, *q; q = p + 10; // il valore di q == // valore di p + 10*sizeof(int) Quindi, se p punta ad un vettore v: p+i == &v[i] // ovvero *(p+i) == v[i] == p[i] Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Aritmetica dei puntatori (3) incremento e decremento: int *p; p = &n; p++; /* p punta a &n + sizeof(int) */ somma e sottrazione di un intero: int n, m, *p; p = &n; p = p + m; /* p punta a &n + m * sizeof(int) */ differenze tra puntatori: int n, a, b, *p, *q; p = &a, q = &b; n = p - q; /* n è il numero degli interi allocabili tra gli indirizzi di a e di b */ confronto tra puntatori: int n, m, *p; p = &n; q = &m; if (p < q) … /* eseguito se l’indirizzo di n è minore di quello di m */ Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Esempio void Inverti (int v[]; int lun); // inverte l’ordine degli el. di v[lun] { int t, *p, *q; for(p = v, q = p + (lun-1); p < q; p++, q--) { t = *p; *p = *q; *q = t; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a n Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 1 n Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a n Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; 1 a n Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; 1 a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore void f(int* pn) { *(pn++); } int a = 0; f(&a) cout << a; a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore void f(int* pn) { *(pn++); } int a = 0; f(&a) cout << a; a pn Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore void f(int* pn) { *(pn++); } int a = 0; f(&a) cout << a; 1 a pn Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Passaggio di par. per puntatore void f(int* pn) { *(pn++); } int a = 0; f(&a) cout << a; 1 a Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica Allocazione = destinazione di una certa quantità di memoria per contenere valori di un dato tipo Tutte le variabili di un programma sono allocate quando sono in uso (puntatori inclusi) E’ possibile allocare memoria durante l’esecuzione del programma in una area specifica detta “memoria dinamica” Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025fe16 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025fe16 025fe16 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025fe16 025fe16 2983 Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica: new float* p; *p = 3.14159; // ERRORE: p non è allocato float x = 3.14159; float *p = &x // OK: p usa l’allocazione di x float* p = new float; *p = 3.14159; // OK: p è allocato Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica in C Tipo *p; p = (Tipo*) malloc (sizeof(Tipo)); Alloca una struttura dati la cui dimensione in byte dipende da Tipo ed è calcolata da sizeof; in caso di successo assegna il tipo Tipo* all’indirizzo di inizio del blocco, ed il valore è salvato in p. Se non c’è più memoria disponibile, malloc ritorna NULL, che sarà allora il valore di p. Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica di un vettore Per allocare dinamicamente un vettore occorre conoscere: Il tipo dei suoi elementi; il numero di questi elementi (che tuttavia potrà essere noto anche solo al momento dell’esecuzione). int *v, lun; v = new int[lun]; // in C++ v = (int*) malloc (sizeof(int)*lun); // in C Alloca un vettore di lun interi, dove però lun è una variabile. Comunque, una volta allocato, v punterà ad un vettore la cui lunghezza non è più modificabile. Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Le stringhe Le stringhe sono vettori di caratteri, contenenti un terminatore: ‘\0’ char s[] = “Salve mondo”; char s[MAXLUN]; char *s = “Salve mondo”; Esiste un tipo stringa, definito: typedef char* String; Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione di stringhe E’ opportuno costruire una funzione di allocazione (allocatore o generatore) per ogni struttura dati che si implementa: String StrAlloc(int len) { String *s; s = new String[len + 1]; // len + 1 per far posto a ‘\0’ return s; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operazioni sulle stringhe (1) int strlen (String s) { int n = 0; while (*s++ != ‘\0’) n++; return n; } int strcpy (String dest; String source) { while((*dest++ = *source++)!= ‘\0’); Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Operazione sulle stringhe (2) String LeggiStringa (void) { char buffer[MAXLUN]; String s; gets(buffer); s = Stralloc(strlen(buffer)); strcpy(s, buffer); return s; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
I record Un record è una tupla di valori di tipi possibilmente diversi acceduti attraverso etichette: struct <nome struttura> { <tipo1> <etichetta campo1>; ... <tipok> <etichetta campok>; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Allocazione dinamica di record (strutture) Come per i tipi di base e per i vettori, si possono allocare record: typedef struct Record {int field; …} *MyRecord; MyRecord = new Record; Data la frequenza dell’uso di puntatori a record, il C++ usa la sintassi: p->field invece di (*p).field Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (1) Un buon metodo per definire un vettore da allocare dinamicamente è usare un record con un campo lunghezza: typedef VecRec* Vettore; typedef struct vecrec { int lun; int* vec; } VecRec; Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (2) n – elementi del vettore Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un modo per definire il tipo vettore (3) Vettore VettAlloc (int lunghezza) { Vettore v; v = new VecRec; v->lun = lunghezza; v->vec = int[lunghezza]; return v; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un esempio più complesso: matrici (1) typedef MatrRec* Matrice; typedef struct matrmec { int righe, colonne; int **vecrighe; } MatrRec; n m m n Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Un esempio più complesso: matrici (2) Matrice NuovaMatrice (int r, int c) { Matrice m; int i; m = new MatrRec; m->righe = r; m->colonne = c; m->vecrighe = new int*[r]; for (i = 0; i < r; i++) m->vecrighe[i] = new int[c]; return m; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Deallocazione La memoria dinamica allocata può essere recuperata usando la funzione delete: delete <puntatore> Per deallocare un vettore non occorre ricordarne la dimensione: delete [] <nome vettore> Per deallocare una struttura complessa come matrice occorrono tante chiamate di delete quante sono state quelle di new Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4
Deallocazione di Matrice void DeallocaMatrice (Matrice m) { int i; for(i = 0; i < m->righe; i++) delete [] m->vecrighe[i]; delete [] m->vecrighe; delete m; } Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 4