Puntatori gestione dinamica della memoria e stringhe Come













![Un esempio di uso dei puntatori void Inverti (int v[]; int lun); // inverte Un esempio di uso dei puntatori void Inverti (int v[]; int lun); // inverte](https://slidetodoc.com/presentation_image_h/0b3581dd3bfd924b1a8a3a0c4f00237e/image-14.jpg)

























![Una rappresentazione del tipo Vettore v n = v->lun w = v->vec W[0] v Una rappresentazione del tipo Vettore v n = v->lun w = v->vec W[0] v](https://slidetodoc.com/presentation_image_h/0b3581dd3bfd924b1a8a3a0c4f00237e/image-40.jpg)





- Slides: 45
Puntatori, gestione dinamica della memoria e stringhe Come gestire direttamente l’uso della memoria di un programma Informatica 2 Memoria dinamica
Vantaggi nell’uso dei vettori • I vettori consentono l’accesso diretto agli elementi utilizzando gli indici: per accedere a v[i] chiediamo alla RAM il contenuto dell’indirizzo b+i*d. 678 v indirizzo v[i] = b + i × d v[i] b = indirizzo base d = dimensione elemento i = indice (o “spiazzamento”) Informatica 2 Memoria dinamica
L’accesso a un byte in un insieme di N bytes è rapido 000 0 001 1 010 011 N= 23 100 101 110 111 0 0 1 1 La RAM fornisce il contenuto della memoria b + i × d in tempo log 2(N) Informatica 2 Memoria dinamica
Problemi nell’uso dei vettori • La dimensione di un vettore v viene fissata per sempre al momento della dichiarazione di v. Da evitare quindi: • int v[x]; /* PERICOLOSO: la dimensione di v e’ il valore corrente (magari “casuale”) di x. Inoltre cambiare x non cambia la dim. di v */ • Per inserire un nuovo elemento e nel posto i in V[0. . a-1] dobbiamo slittare di un posto tutti gli elementi dopo i: • for (j=a; j>i; j--)v[j]=v[j-1]; • v[i]=e; Informatica 2 Memoria dinamica
I puntatori • Un puntatore è una variabile il cui dominio di definizione sono gli indirizzi di memoria. La sua sintassi è: • • <tipo>* <variabile>; int* p; // puntatore a intero int *p, *q; /* abbreviazione di int* p; int* q; */ Informatica 2 Memoria dinamica
Dereferenziazione (*) • L’operatore *p o “dereferenziazione di p” legge/scrive dalla/sulla locazione di memoria il cui indirizzo è p p 025 fe 16 2983 *p e’ uguale a 2983 Informatica 2 Memoria dinamica
Operatore indirizzo (&) • Se x è una variabile, allora &x è l’indirizzo in cui è memorizzati il valore di x: x 025 fe 16 2983 &x vale 025 fe 16 *&x = 2983 L’operatore * è l’opposto di & Informatica 2 Memoria dinamica
Esempio di uso di *, & // esempio di dereferenziazione e // uso dell’operatore & #include <iostream. h> int main() {int x = 7; int *p 1, *p 2; // oppure int* p 1; int* p 2; p 1 = &x; p 2 = p 1; // p 1, p 2 sono l’indirizzo di x cout << “*p 2 = “ << *p 2 << endl; // stampa il valore di x cioe’ 7 cout << “p 2 = “ << p 2 << endl; // stampa il valore di p 2 cioe’ // l’indirizzo di x } Informatica 2 Memoria dinamica
Condivisione (o sharing) I puntatori possono condividere (in inglese: to share) l’area di memoria cui puntano: int *p, *q; p int n = 44; p = q = &n; q n 44 Ogni modifica del valore di n che avvenga per assegnazione su *p si riflette su n e su *q: p e q sono come dei “sinonimi” (in inglese: “alias”) di n. Questa caratteristica viene sfruttata per modificare i valori originali dei parametri attuali, simulando il passaggio per indirizzo. Informatica 2 Memoria dinamica
Sharing e alias • In C++ si possono definire “sharing” tra due aree di memoria anche senza i puntatori, usando una operazione che costruisce sinonimi (alias): int main () { int n = 44; int& rn = n; // rn è sinonimo di n n--; cout << rn << endl; // stampa 43 } Informatica 2 Memoria dinamica
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’indirizzo di base di v, ossia p = &v[0] */ • Si può usare la notazione con gli indici per i puntatori che rappresentano vettori: • p[i] // p[i] equivale a v[i] Informatica 2 Memoria dinamica
Aritmetica dei puntatori (1) • Ad ogni tipo di dato corrisponde la dimensione in byte della memoria necessaria per contenere i suoi valori: • int sizeof(double) • // ritorna la dim. di double • I puntatori sono tipati: int *p, bool *p, double *p, … : il tipo è essenziale per sapere cosa leggere/scrivere alle locazioni di memoria cui punta p. Informatica 2 Memoria dinamica
Aritmetica dei puntatori (2) • In C++ si possono sommare (o sottrarre) interi a un puntatore p: l’effetto e’ aggiungere o togliere multipli di sizeof(tipo di p) • int *p, *q; q = p + 10; • /* il valore di q e’ uguale al 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] Informatica 2 Memoria dinamica
Un esempio di uso dei puntatori 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--) //p, q puntano agli estremi opposti di v {t = *p; *p = *q; *q = t; } } Questo programma è più efficiente del programma di inversione che usa i vettori, dato che non usa la formula v[i] = b + i × d. Non e questo, però, l’uso più importante dei puntatori. Informatica 2 Memoria dinamica
Ripasso: il passaggio dei parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 La chiamata f(a) e’ per valore. Essa modifica la copia n di a da 0 ad 1. Non modifica il valore originale 0 di a. Informatica 2 Memoria dinamica
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 n 0 Informatica 2 Memoria dinamica
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 n 1 Informatica 2 Memoria dinamica
Passaggio di parametri per valore void f(int n){ n++; } int a = 0; f(a); cout << a; a 0 Informatica 2 Memoria dinamica
Ripasso: il passaggio di parametri per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 0 La chiamata f(a) ora e’ per riferimento. Essa crea un sinonimo n di a. Modificando n da 0 ad 1, essa modifica il valore originale di a. Informatica 2 Memoria dinamica
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 0 n Informatica 2 Memoria dinamica
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 1 n Informatica 2 Memoria dinamica
Passaggio di par. per riferimento void f(int& n) { n++; } int a = 0; f(a) cout << a; a 1 Informatica 2 Memoria dinamica
Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 0 Possiamo usare un puntatore pn nella chiamata f(a) per simulare una chiamata per riferimento. *pn e’ un sinonimo di a. Modificando *pn da 0 ad 1, essa modifica il valore originale di a. Informatica 2 Memoria dinamica
Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 0 pn Informatica 2 Memoria dinamica
Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 1 pn Informatica 2 Memoria dinamica
Passaggio di parametri usando puntatore void f(int* pn) { (*pn)++; } int a = 0; f(&a) cout << a; a 1 Informatica 2 Memoria dinamica
Allocazione dinamica della memoria e puntatori • Allocazione = destinazione di una certa quantità di memoria per contenere valori di un dato tipo • Tutte le variabili di un programma sono allocate dal programma stesso quando sono in uso (puntatori inclusi) • Usando i puntatori, possiamo noi stessi allocare memoria durante l’esecuzione del programma. La memoria allocata da noi si trova in una area specifica, detta “memoria dinamica”. Informatica 2 Memoria dinamica
Allocazione dinamica: l’istruzione new int *p; p = new int; *p = 2983; p 025 fe 16 L’istruzione new int “alloca”, cioe’ assegna al puntatore p, l’indirizzo di uno spazio di memoria libero e sufficente per contenere un intero.
Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025 fe 16 Informatica 2 Memoria dinamica
Allocazione dinamica: new int *p; p = new int; *p = 2983; p 025 fe 16 2983 Informatica 2 Memoria dinamica
Allocazione dinamica: new float* p; *p = 3. 14159; /* ERRORE: p non è allocato. Assegnare p significa scrivere su una memoria “casuale”, e questo ha effetti imprevedibili */ float x = 3. 14159; float *p = &x // OK: p usa l’allocazione già fatta per x float* p = new float; *p = 3. 14159; // OK: p è già allocato Informatica 2 Memoria dinamica
Allocazione dinamica di memoria in C: l’istruzione malloc Se conosciamo la dimensione in bytes dell’area di memoria di cui abbiamo bisogno, possiamo usare la funzione malloc Tipo *p; p = (Tipo*) malloc (sizeof(Tipo)); “malloc” alloca un’area di memoria con la dimensione in byte che gli chiediamo. In questo caso la dimensione dipende da Tipo ed è calcolata da sizeof. In caso di successo l’istruzione assegna il tipo Tipo all’area di memoria, e l’indirizzo dell’area al puntatore p. Se non c’è più memoria disponibile, malloc ritorna NULL, che sarà allora il valore di p. Informatica 2 Memoria dinamica
I puntatori e l’allocazione dinamica di un vettore Per “allocare dinamicamente un vettore v” intendiamo: posporre il momento in cui definiamo la lunghezza di v. Non è semplice: occorre conoscere: 1. Il tipo degli elementi di v; 2. il numero di questi elementi (almeno durante l’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. Una volta allocato, il vettore non è più modificabile. Informatica 2 Memoria dinamica
Un altro uso dei puntatori: stringhe • Le stringhe sono vettori di caratteri, contenenti un carattere speciale ‘ ’, non stampabile, detto terminatore. • char s[] = “Salve mondo”; • char s[MAXLUN]; • char *s = “Salve mondo”; • Esiste un tipo String, definito come char* : • typedef char* String; Informatica 2 Memoria dinamica
Allocazione di stringhe • E’ opportuno costruire una funzione di allocazione di memoria per le stringhe, e più in generale per ogni struttura dati che si implementa: • • • String Str. Alloc(int len) {String s = new char[len + 1]; // len + 1 per far posto a ‘ ’ return s; } Informatica 2 Memoria dinamica
Operazioni sulle stringhe int strlen (String s) {int n = 0; while (*s != ‘ ’) {++n; ++s; } return n; } int strcpy (String dest, String source) // PREC. lunghezza dest >= lunghezza source {while (*source != ‘ ’) {*dest=*source; ++dest; ++source}} Informatica 2 Memoria dinamica
I records Un record è una tupla di valori di tipi possibilmente diversi acceduti attraverso etichette: struct <nome struttura> {<tipo 1> <etichetta campo 1>; . . . <tipok> <etichetta campok>; } Un esempio: il tipo dei numeri razionali struct Rational {int num; int denum; } Se r ha tipo Rational, le due componenti di r si scrivono: r. num, r. denum Informatica 2 Memoria dinamica
Puntatori a record (strutture) • Come per i tipi di base e per i vettori, si possono definire dei puntatori a record e poi allocarli: • typedef struct Record • {int field; …} *Pointer; • Pointer r = new Record; • Data la frequenza dell’uso di puntatori a record, il C++ usa la seguente notazione abbreviata: • p->field invece di (*p). field Informatica 2 Memoria dinamica
Un modo per definire un tipo “Vettore” Possiamo definire un unico tipo Vettore per vettori di qualsiasi lunghezza. “Vettore” è il tipo Vec. Rec dei puntatori a un record di due campi, uno per la lunghezza del vettore, e l’altro per il vettore vero e proprio. typedef Vec. Rec* Vettore; typedef struct vecrec {int lun; int*vec; }Vec. Rec; //Vec. Rec = puntatori a vecrec Informatica 2 Memoria dinamica
Una rappresentazione del tipo Vettore v n = v->lun w = v->vec W[0] v è un puntatore a un record di due elementi: il primo elemento v->lun è la lunghezza di un vettore W, il secondo, v->vec è l’indirizzo del primo elemento di W. W[n-1] W = v->vec ha tipo int*, è un puntatore al primo elemento W[0] di un vettore W di n elementi Informatica 2 Memoria dinamica
Come allocare un nuovo oggetto di tipo Vettore Supponiamo di volere un nuovo vettore di lunghezza n. Dobbiamo predisporre uno spazio di memoria adatto a contenerlo: Vettore Vett. Alloc (int n) { Vettore v; v = new Vec. Rec; //alloco lo spazio per “lun”, “vec” v->lun = na; v->vec = new int[n]; //alloco lo spazio per n interi return v; } Informatica 2 Memoria dinamica
Un esempio più complesso: il tipo “Matrice” (matrici dim. qualsiasi) struct Matr. Rec{int righe, colonne; int **vecrighe; }; typedef Matr. Rec* Matrice; m: Matrice m m*: Matr. Rec r = M->righe c=M->colonne p=M->vecrighe int** p n righe di m elementi ciascuna W 0 =p[0] W 0[0] … W 0[m-1] Wn-1 =p[n-1] Wn-1[0] … Wn-1[m-1] n Informatica 2 Memoria dinamica
Come allocare un nuovo oggetto di tipo Matrice Nuova. Matrice (int r, int c) { Matrice m; int i; m = new Matr. Rec; 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; } /* POSTCOND. : valore di ritorno = indirizzo del record per una matrice r x c */ Informatica 2 Memoria dinamica
Deallocazione: come riciclare la memoria che non serve più • La memoria dinamica allocata ma non più in uso può essere riciclata usando la funzione delete: • delete <puntatore> • Per deallocare un vettore del C (statico) non occorre ricordarne la dimensione: • delete [] <nome vettore> • Per deallocare una struttura complessa definita da noi, per esempio di tipo Matrice, occorrono tante chiamate di delete quante sono state quelle di new Informatica 2 Memoria dinamica
Deallocazione di Matrice void Dealloca. Matrice (Matrice m) { int i; //(1) deallochiamo un vettore-riga dopo l’altro for(i = 0; i < m->righe; i++) delete [] m->vecrighe[i]; //(2) deallochiamo l’elenco dei vettori-riga delete [] m->vecrighe; // (3) deallochiamo l’indirizzo del record Matrice delete m; } Informatica 2 Memoria dinamica