corso di fondamenti di informatica - unina stidueunina.stidue.net/fondamenti di...
Post on 25-Aug-2020
6 Views
Preview:
TRANSCRIPT
Corso di Fondamenti di Informatica
Puntatori e Allocazione Dinamica
I puntatori (Richiamo) Il C++ prevede puntatori a dati – di qualsiasi natura,
semplici o strutturati – e puntatori a funzione. In particolare il puntatore viene utilizzato:
per il riferimento a variabili dinamiche; per il riferimento a funzioni; per gli array,
in particolare per l'elaborazione di stringhe; per i parametri formali di funzione,
in alternativa allo scambio per riferimento; per il riferimento a locazioni specifiche della memoria,
associate a dispositivi hardware ad esempio per l’ingresso-uscita
I puntatori: notazioni in C++ (Richiamo)
• p è un variabile di tipo puntatore • p=&x al puntatore p viene assegnato l’indirizzo di x • *p denota la variabile puntata da p
&p=
&x=
Esempio: Sia x una variabile di indirizzo 127 e valore 0,3; p è un puntatore ad x;
I puntatori: operazioni (Richiamo) • char* pc; //pc è un puntatore a variabile di tipo carattere • int* pi; //pi è un puntatore a variabile di tipo intero • float* pf; //pf è un puntatore a variabile di tipo float
• double* pd; //pd è un puntatore a variabile di tipo doppia
precisione C++ tratta esplicitamente le espressioni di tipo puntatore. In particolare sono previsti i seguenti operatori:
• l’operatore di assegnazione tra puntatori che puntano allo stesso tipo T*; • gli operatori unari di incremento ++ e decremento -- unitari (in forma prefissa e postfissa ++p, p++, --p, p--); • l'operatore di somma o differenza tra un puntatore ed un intero (p+i punta all'elemento che segue di i posizioni quello puntato da p);
I puntatori: operazioni
T * p;
p=p+1; p=p+sizeof(T);
L’incremento a p è tale che p+1 punta all’area di memoria immediatamente successiva a quella impegnata dall’elemento puntato da p.
sizeof(T) è un operatore che applicato al nome di un tipo restituisce il numero di byte necessario alla rappresentazione in memoria di una variabile di quel tipo
Questa tecnica è particolarmente utile per i puntatori ad array
I puntatori a dati strutturati
Un puntatore può puntare a variabili strutturate (di tipo array o record). Esamineremo i seguenti casi:
• puntatore ad array;
• puntatore a record;
Tra i casi elencati, il puntatore ad array può utilmente essere sostituito dalla notazione a[i] tipica degli array
I puntatori ad array Un puntatore ad un array a è una variabile che punta alla prima locazione dell'array (a[0] in C++);
considerando che i successivi elementi dell'array sono allocati in posizioni contigue, esso consente di puntare anche a tutti gli altri elementi dell'array.
T a[dim]; // a è un array di dim elementi di tipo T T* p ; // p è un puntatore a T
p=a; p=&a[0];
Un puntatore ad array è dunque di per sé un puntatore al tipo T degli elementi dell'array
I puntatori ad array: esempio
const int dim=100; float a[dim]; // a è un array di dim elementi di tipo float float* p ; // p è un puntatore a float
//azzeramento di tutti gli elementi di un array di 100 elementi for (float* p = a, int i=0; i<dim; i++, p++)
*p= 0;
Stringhe e Puntatori
Una costante stringa viene memorizzata come array di caratteri con l'aggiunta di un carattere nullo alla fine della stringa.
Poiché un puntatore a char può contenere l'indirizzo di un char, è possibile definire: – char* stringa = "Hello World";
Che equivale a:
– char* stringa; – stringa = "Hello Word";
Esempio strlen (ver. 1)
int strlen(char *s) { char* start = s; while (*s++); return s-start; }
strcmp (ver. 1)
int strcmp(unsigned char *s1,unsigned char *s2) { while (*s1 && *s2 && *s1 == *s2) s1++, s2++; return (*s1 - *s2); }
Distanza tra stringhe
Strcpy (vers. 1)
void strcpy(char *dest,char *src) { while (*dest++ = *src++) ; }
Puntatori a record Un puntatore ad un record è una variabile che punta all'indirizzo di memoria ove il record è allocato; esso è molto utile nella realizzazione dei tipi dinamici
// Dichiarazioni di un tipo strutturato Ts struct Ts { // Ts è un tipo strutturato
D1; .....; Dn ; };
Ts r ; // r è variabile di tipo Ts
La funzione di accesso al singolo campo del record è l’operatore punto (.). Per accedere al campo D1 della variabile r la sintassi è: r.D1
// Dichiarazione di variabile di tipo puntatore a Ts Ts* p;
Puntatori a record Ts* p = &r; // p è una variabile puntatore inizializzata ad r
p può essere ridefinito nel corso del programma
p= &r1; //ora p punta alla variabile r1 di tipo Ts
(*p).Di; p->Di;
Accesso alla singola componente di un record attraverso il puntatore a record:
Puntatori a record: un esempio // Definizione di tipo struttura: struct Abbonati { // definisce struttura di nome Abbonati
char[10] nominativo; char[10] indirizzo; int numTel ; };
// Definizione di variabili Abbonati a; // a una variabile di tipo Abbonati Abbonati * p= &a; // p puntatore al tipo Abbonati, è
// inizializzato con l’indirizzo di a
// Accesso alla componente nominativo mediante puntatore p->nominativo;
Variabili e strutture dinamiche La gestione delle variabili dinamiche è resa possibile dalla disponibilità dei seguenti meccanismi linguistici:
variabili di tipo puntatore;
operatore new
operatore delete
Operatore new
In fase di esecuzione, new alloca un'area di memoria sufficiente ad ospitare un valore del tipo T e restituisce il puntatore a tale area;
il tipo T definisce implicitamente l'ampiezza dell'area di memoria occorrente.
Nel caso in cui l’allocazione dell’area richiesta non sia possibile, l’operatore new restituisce il valore NULL
T* p = new T; //alloca memoria sufficiente a contenere un // oggetto di tipo T e restituisce puntatore
T* p1 =new T[n]; //alloca memoria sufficiente a contenere n // elementi di tipo T e restituisce puntatore
Operatore delete
Produce la deallocazione dell'area di memoria puntata dalla variabile p, cioè annulla l'allocazione, rendendo nuovamente disponibile lo spazio di memoria prima occupato.
delete p; //dealloca area puntata da p
delete [] p1; //dealloca tutto l'array precedentemente allocato
Allocazione della memoria
Area progr.
Area dati statici
Area heap
Area stack
void main() {
}
int *p;
10
10 20
p=new int;
*p=20;
delete p;
Area codice
Area dati statici
Area heap
Area stack
100
100 34
p
Allocazione delle variabili dinamiche
void main() {
}
int *p; //var. autom.
p=new int;
*p=34;
delete p;
Il puntatore p è una variabile automatica, quindi allocata in area stack. Dopo l’istruzione new, p punta ad una locazione nello heap atta a contenere un intero
Esempio
Allocazione dinamica di un vettore Con l’allocazione dinamica di un vettore il puntatore restituito è quello all'indirizzo del primo elemento del vettore, pertanto è di tipo T* se T è il tipo degli elementi del vettore
float* a= new float[n];
Il riferimento agli elementi del vettore viene espresso a mezzo della consueta notazione con indice
a[i] = 10.0 *(a+i)=10.0
delete [] a; //cancella l'intero vettore precedentemente allocato
Esempio: // allocazione del vettore con deallocazione ed esempio di leak memory
#include <iostream> #include <math.h> #include <stdlib.h> using namespace std;
int main(void) {
int *vett; int n = 1000000;
while (true) { vett = new int[n]; cout << "allocati " << n*sizeof(int) << " byte" << endl;
delete [] vett; cout << "deallocati " << n*sizeof(int) << " byte" << endl; } return 0; }
Allocazione dinamica di una stringa Con l’allocazione dinamica di una stringa il puntatore
restituito è quello all’indirizzo del primo elemento della stringa. Pertanto è di tipo char *
L’istruzione precedente alloca spazio per una stringa di n caratteri più il terminatore
Valgono le stesse considerazioni fatte per i vettori
char* s= new char[n+1];
Esempio: #include <iostream> #include <stdlib.h>
using namespace std;
int main(int argc, char *argv[]) { char * stringa; int n;
cout << "\n quanto e' lunga la stringa che vuoi inserire?"; cin >> n;
stringa = new char[n+1];
cout << "\n inserisci la stringa: "; cin.ignore();
cin.getline(stringa,n+1); //inserisce il terminatore
cout << "\n hai inserito: "; cout << stringa; system("PAUSE"); return 0; }
Allocazione dinamica di un record
L'allocazione dinamica di un record avviene analogamente attraverso l'uso dell'operatore new
Indicato con R un tipo record e con r un puntatore ad R, si ha:
R* r = new R;
Esempio: #include <iostream> #include <math.h> #include <stdlib.h> using namespace std;
struct Punto { float X; float Y; };
int main(void) {
Punto P1; // allocazione statica P1.X=0.0; P1.Y=0.0;
Punto * PuntP; PuntP=new Punto; PuntP->X=3; // (*PuntP).X PuntP->Y=4; // (*PuntP).Y
cout << "\n le coordinate del punto P1: " << P1.X << "," << P1.Y; cout << "\n le coordinate del punto allocato dinamicamente: " << PuntP->X << "," << PuntP->Y;
system("PAUSE"); return 0; }
Errori comuni con puntatori e variabili dinamiche
Tipici errori quando si usa l’allocazione dinamica sono:
– Dimenticare di allocare un dato nello heap e usare il puntatore come se lo stesse riferendo (produce un effetto impredicibile)
– Dimenticare di “restituire” la memoria allocata quando non serve più (rischio di memory leak)
– Tentare di usare dati dinamici dopo che sono stati deallocati
– Invocare delete più di una volta sullo stesso dato
Riferimenti Da C++ a UML
– Capitolo 8; §1,§2, §8.3.1 (Richiamo) – Capitolo 8; §8.3.5, §8.4
Da Fondamenti di Informatica II – Parte II, Capitolo VII, §6 (Richiamo) – Parte II, Capitolo VIII, §2 (Richiamo)
top related