Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
Puntatore a puntatore
Un tipo puntatore può puntare anche ad un altro puntatore. Per dichiarare tale puntatore si usano
due asterischi.
Ad es
int i=100;
int* ptr1=&i;
int** ptr2=&ptr1;
ptr1 e ptr2 sono due puntatori diversi, ma il primo è puntatore a interi e punta i, il secondo è
puntatore a puntatore di interi e punta ptr1. Per deferenzare il puntatore di puntatori si possono
usare i due asterischi: **ptr2=95 (assegna 95 a i)
Puntatori e array
In C++ gli array sono implementati mediante puntatori. Il nome di un vettore è esattamente un
puntatore al primo elemento dell'array. Prendendo ad esempio il vettore gradi[3],
cout << gradi[0] visualizzerà il primo elemento dell'array, ma lo farà anche cout << *gradi,
perché il nome gradi è un puntatore al primo elemento. In più se incrementiamo il puntatore a un
valore pari a un indice del vettore e poi lo referenziamo, andiamo a prendere l'elemento del
vettore corrispondente a quell'indice.
Pertanto per leggere o scrivere un elemento di un array si possono utilizzare indifferentemente
entrambe le notazioni, quella vettoriale e quella basata sui puntatori. Il nome di un array è però
una costante puntatore, non una variabile, non si può cambiarne il valore. Tuttavia, è possibile
cambiare i valori delle variabili di tipo puntatore per farle puntare valori differenti in memoria.
Array di puntatori
Se bisogna definire parecchi puntatori a uno stesso tipo si può utilizzare un array di puntatori. Un
array di puntatori è un vettore i cui elementi sono puntatori dello stesso tipo: int* ptr[10] definisce
10 puntatori ad interi. Ogni elemento contiene un indirizzo che punta ad altre variabili di tipo
intero in memoria e può essere riassegnato.
Queste due definizioni sono equivalenti:
char stringa[] = “Ciao vecchio mondo”;
char* stringa = “Ciao vecchio mondo”;
Puntatori a stringhe
Definisco una stringa di caratteri: char alfabeto[27]=”ABCD…..”;
Dichiaro p puntatore a chiar: char* p;
Faccio puntare p al primo carattere di alfabeto scrivendo: p=alfabeto; o p=&alfabeto[0];
Posso anche assegnare: p=alfabeto+15; o p=&alfabeto[15];
Non posso assegnare: p=&alfabeto; perchè p e alfabeto sono puntatori al tipo char e l’istruzione
tenterebbe di assegnare a p l’indirizzo di un puntatore a char e non l’indirizzo di una variabile di
tipo char.
Aritmetica dei puntatori
Se un puntatore a un tipo T viene incrementato (o decrementato) di 1, il suo valore viene in realtà
incrementato (o decrementato) di un numero pari alla dimensione del tipo T espressa in byte. In
generale, incrementare o decrementare di un intero n un puntatore significa incrementarlo o
decrementarlo di un numero di byte pari a n moltiplicato per la dimensione del tipo puntato.
Ad es: int gradi[5] = (10, 20, 30, 40, 50);
p=gradi;
p punta al primo intero 10 in gradi; ma dopo l’istruzione p++ p punterà al secondo intero 20.
Poichè ogni elemento occupa 4 byte, p è stato incrementato di 4 e non di 1.
Si può quindi usare tale tecnica per attraversare un vettore senza usare una variabile di indice.
Se il vettore da attraversare è una stringa, si può semplificare il ciclo considerando il fatto che
essa termina con il carattere nullo:
char messaggio[] = “Ciao vecchio mondo”;
p = messaggio;
while (*p) cout << p++ << endl;
Il ciclo while si ripete finchè *p ha un valore diverso da zero; si stampa il carattere e si
incrementa p per puntare al successivo carattere.
I puntatori possono anche essere sottratti, ma non sommati, moltiplicati o divisi.
Puntatori costanti e puntatori a costanti
Il nome di un array è un puntatore costante: il suo valore non può cambiare, ma possono essere
cambiati i valori della variabile puntata.
Un puntatore a costante potrà cambiare il suo valore ma non quello della variabile a cui punta.
Per creare un puntatore costante si utilizza il formato:
tipo_di_dato* const nome_punt = indirizzo_variabile
Si può scrivere: *p1=e ma non p1=&e
Per definire un puntatore a costante si usa il formato:
const tipo_di_dato* nome_puntatore = indirizzo_const_o_stringa;
Si può scrivere: p1=&e ma non *p1=15, qualunque tentativo di modificare il contenuto della
posizione di memoria puntata da p1 provocherà errore.
Nota: La definizione di un puntatore costante ha la parola riservata const davanti al nome del
puntatore, mentre il puntatore a una costante ha la parola riservata const davanti al tipo di dato.
Per definire puntatori costanti a costanti si usa il seguente formato:
const tipo_di_dato *const nome_puntatore = indirizzo_const_o_stringa
Se si sa che un puntatore punterà sempre la stessa posizione e non dovrà mai essere riallocato
lo si definisce come puntatore costante. Se si sa che il dato puntato dal puntatore non dovrà mai
essere modificato si definisce come puntatore a costante.
Lo spazio non è significativo nella dichiarazione di puntatori. Le dichiarazioni seguenti sono
equivalenti:
int* p;
int * p;
int *p;
Puntatori come argomenti di funzioni
Spesso si vuole che una funzione restituisca più di un valore oppure modifichi variabili passate
come argomenti. Si possono ottenere questi risultati passando puntatori, magari nomi di vettori,
come argomenti attuali.
Il passaggio per riferimento è più efficiente del passaggio per indirizzo
Puntatori a funzioni
E’ possibile creare anche puntatori a funzioni; anzichè indirizzare dati, i puntatori di funzioni
indirizzano codice eseguibile, dato che anche le istruzioni stanno in memoria in certi indirizzi. In
C++ si possono quindi assegnare gli indirizzi iniziali di funzioni a puntatori. Tali funzioni si
possono chiamare in modo indiretto tramite un puntatore il cui valore è uguale all’indirizzo iniziale
della funzione in questione. La sintassi generale per la dichiarazione di un puntatore a funzione
è:
tipo_di_ritorno (*puntatore_funzione) (argomenti);
Questo formato dice al compilatore che puntatore_funzione è un puntatore a una funzione che
restituisce il tipo_di_ritorno e una lista di parametri.
Un puntatore a una funzione è semplicemente un puntatore il cui valore è l’indirizzo del nome
della funzione. Dato che il nome di una funzione è un puntatore, come il nome di un vettore, un
puntatore a una funzione è un puntatore a un puntatore costante.
La sintassi generale per inizializzare un puntatore a funzione è:
puntatore_funzione = nome_di_una_funzione
La funzione assegnate deve avere il tipo del ritorno e dei parametri appropriati al tipo del
puntatore a funzione; in caso contrario, si produrrà un errore di compilazione.
I puntatori a funzioni permettono anche di passare una funzione come argomento a un’altra
funzione, specificandone il nome.
Funzione qsort() (Quicksort per ordinare vettori)
Presente nella libreria search. Ha come parametri:
(void*) vettore → Array da ordinare;
(size_t) 10 → Numero di elementi dell’array
sizeof(int) → Dimensione in byte di ciascun elemento dell’array
Array di puntatori di funzioni
Certe applicazioni richiedono di scegliere una funzione fra tante, in base alla valutazione a
runtime di determinare condizioni. Un metodo è quello di usare un’istruzione switch con
parecchi case. Un’altra soluzione è quella di utilizzare un array di puntatori di funzione. Si può
selezionare una funzione della lista e chiamarla.
Sintassi generale:
tipoRitorno(*PuntatoreFunz[LunghezzaArray]) (argomenti);
Si può assegnare l’indirizzo delle funzioni all’array, fornendo le funzioni che sono già state
dichiarate.
Puntatori a strutture
Un puntatore può anche puntare una strutture. Dati un tipo struct e una variabile di questo tipo, si
può definire un puntatore a questo tipo struct e inizializzarlo in maniera che punti alla variabile.
Per riferirsi a una variabile della struct tramite un puntatore è necessario mettere le parentesi
tonde del tipo (*p).nome; Per non utilizzare tale notazione è stata introdotta la notazione freccia a
destra; l’istruzione precedente è equivalente a p>nome;
Gestione dinamica della memoria
Con il termine heap si denota la parte della memoria principale che può essere allocata
dinamicamente durante l'esecuzione del programma. Per poter essere riallocara può essere
deallocata automaticamente mediante un garbage recollector oppure rimanere occupata fino a
che il programmatore la libera esplicitamente mediante opportune istruzioni. In C la gestione
della memoria dinamica avviene tramite le funzioni malloc, calloc e free. In C++ esistono gli
operatori new e delete.
Il sistema operativo assegna al programma specifici segmenti della memoria principale: il code
segment (ospita il codice), il data segment (ospita costanti e variabili globali), lo stack (contiene
le variabili locali, i parametri delle funzioni e le copie dei registri per restituire il controllo alle
funzioni chiamanti al termine delle funzioni chiamate). La memoria rimasta nello stack, lo heap,
viene usata per allocare dinamicamente le variabili.
Gli operatori new e delete sono più affidabili di quelli del C perché allocano memoria in funzione
del tipo di dato da memorizzare ed effettuano ogni volta i relativi controlli.
L'operatore new
Genera dinamicamente una variabile di un certo tipo, assegnando un blocco di memoria della
dimensione di quel tipo. L'operatore restituisce un puntatore che contiene l'indirizzo del blocco di
memoria allocato, cioè della variabile. La variabile sarà accessibile deferenziando il puntatore.
La sintassi è:
tipo* puntatore = new tipo
tipo* puntatore = new tipo[dimensione] (se array)
Dove puntatore sta per il nome a cui si assegna l'indirizzo dell'oggetto generato. Nel secondo
fattore al puntatore si assegna l'indirizzo di memoria di un blocco sufficientemente grande per
contenere un array con dimensione elementi di tipo.
Se il puntatore è già stato definito si può semplicemente usare new per assegnargli la variabile
dinamica:
int* pi;
pi = new int;
L'effetto è sempre quello di creare una variabile intera senza nome, accessibile deferenziando il
puntatore pi.
Se nell'heap esiste un blocco della dimensione richiesta, new restituisce un puntatore al primo
byte del blocco, altrimenti restituisce 0 o NULL.
La dimensione del blocco può anche essere allocata in runtime, richiedendola all'input.
L'heap non è infinito e se lo spazio finisce, new restituisce 0 o NULL e quindi non dà errore!
L