Informatica 2 Liceo Scientifico Scienze Applicate/Puntatori
I Puntatori
Un puntatore è una variabile che contiene l'indirizzo di una cella di memoria dove, di solito, è registrata un'altra variabile. I puntatori non sono tutti uguali, ma possono essere di tipi diversi (il tipo deve corrispondere al tipo della variabile puntata), un puntatore di tipo intero serve allora solo per puntare a variabili di tipo intero. I puntatori possono essere di diversi tipi, il tipo corrisponde al tipo di variabile puntata. Per dichiarare un puntatore si scrive iltipodelpuntatore *nomedellavariabile per dichiarare un puntatore di tipo int allora si scrive
int *p;
p e' il nome del nostro puntatore di tipo intero, il comando poteva essere scritto anche come int* p che anche se di solito non utilizzato evidenzia che p e' una variabile di tipo int* cioe' un puntatore a intero. Per ottenere l'indirizzo di una variabile si usa l'operatore & prima del nome della variabile di cui si vuole ottenere l'indirizzo, allora
p=&a;
carica in p l'indirizzo della variabile a
#include <cstdlib> #include <iostream> using namespace std; int main(int argc, char *argv[]) { int a; int *p; a=5; p=&a; *p=7; cout<<p; cout<<*p; cout<<&p; system("PAUSE"); return EXIT_SUCCESS; }
Il programma stamperà:
1001
7
1004
int *p;
float z=10;
p=&z;
Questo è un esempio di scrittura sbagliata perché z è di tipo float mentre il puntatore è di tipo intero ma il puntatore può puntare solo variabili del suo tipo.
Quando si usano operazioni aritmetiche (hanno senso solo le operazioni di sottrazione e addizione) fra puntatori e costanti si deve fare attenzione perché i calcoli sono diversi da quelli svolti con le normali variabili. Se scrivo p=p+1; questo comando non aggiunge una unità a p bensì internamente fa questo calcolo p=p+1*sizeof(tipodatopuntatore) dove sizeof(tipodatopuntatore) corrisponde alla dimensione in byte del tipo di dato del puntatore, ad esempio se il puntatore è intero allora sizeof(int) vale 4, se p vale 1000 dopo il comando p=p+1; assume il valore 1004, se avessi scritto p=p+5; e p valeva 1000 avrei ottenuto dopo l'esecuzione del comando 1020, funziona in questo modo perché se si crea una variabile intera nella memoria ram questa occupa 4 byte (4 celle consecutive) e se questa variabile si trova partendo dalla cella di indirizzo 1000 (occupa quindi la 1000, 1001, 1002, 1003) una ipotetica variabile intera successiva si troverebbe a partire dalla cella 1004 (occupando le celle 1004, 1005, 1006, 1007), p=p+1; deve intendersi come incrementa la variabile p del numero di celle che servono per memorizzare un intero, p=p+2; deve intendersi come incrementa la variabile p del numero di celle che servono per memorizzare 2 interi e cosi via. Visto che normalmente sono questi gli spostamenti che vogliamo fare (a multipli della dimensione del dato puntato), l'aritmetica sui puntatori rispetta questo funzionamento.
L'uso degli operatori di confronto < <= > >= applicati a 2 puntatori pa e pb che puntino alla variabile a e alla variabile b non ha significato, perché quando viene eseguito il programma le posizioni dove vengono caricate le due variabili a e b sono arbitrarie (scelte dal sistema operativo) e quindi può capitare che per uno stesso programma nella prima esecuzione l'indirizzo di a sia 234 e quello di b 765 e nell'esecuzione successiva l'indirizzo di a sia 500 e quello di b 344, e allora domandarsi se pa<pb dà un risultato che può cambiare ad ogni esecuzione del programma.
Esistono anche puntatori generici (tipo void), a cui può essere associato l'indirizzo di una variabile qualsiasi, ma poi per usare tramite il puntatore il valore contenuto nella cella puntata bisogna fare una operazione di cast
int a=10; float b=3.12; void *p; p=&a; *( (int *) p) =12; // (int *) e' il cast che dice che p deve essere trattato come un puntatore a intero p=&b; *( (*float) p)=3.23;// (float *) e' il cast che questa volta dice di trattare p come un puntatore a intero
- Quando si scrive int *p; int * rappresenta il tipo del dato (un puntatore a intero)
- Quando scrivo z=a*5; il * e' l'operatore di moltiplicazione (pensando che a e b siano due variabili intere)
- Quando scrivo c= *p se p e' un puntatore *p rappresenta il valore della cella puntata e il simbolo * viene detto operatore di derefenziamento
Quindi attenzione lo stesso simbolo * ha significati diversi in base all'operazione e alle variabili coinvolte
E' possibile dichiarare anche puntatori a puntatori ad esempio
int a=10; int *b; //dichiaro un puntatore a intero b= &a; int **p; //dichiaro un puntatore a un puntatore di tipo intero p=&b; cout<<**p; //stampa 10, per ottenere il valore devo dereferenziare 2 volte cioe **
Quando un puntatore non punta a nulla (non ho ancora assegnato l'indirizzo una variabile) il suo valore viene posto a NULL, quindi se p==NULL vuol dire che il puntatore non punta a niente e non ha senso cercare il valore puntato tramite il comando *P
La funzione malloc() serve per creare delle strutture dati durante l'esecuzione del programma, ad esempio se voglio caricare una immagine di 120 bytes nella memoria RAM posso scrivere questo codice
int *p; p=(*int) malloc(120);
malloc alloca una struttura di 120 byte e ne restituisce un indirizzo , su questo indirizzo (tipo void) faccio il cast per dire che deve intendersi come l'indirizzo di tipo int, che viene salvato in p
La funzione sizeof() restituisce invece la dimensione in byte del tipo passato oppure della variabile passata quindi c= sizeof(int) carica 4 nella variabile c, posso anche scrivere c=sizeof(a) che carica in c la dimensione della variabile a in byte.
Esiste una forte analogia fra puntatori e vettori ( e piu' in generale con gli array)
Il nome di un vettore è un puntatore statico che contiene l’indirizzo dell’inizio del vettore. Statico significa che l'indirizzo associato al nome del vettore non si può modificare nel corso del programma, a differenza di un normale puntatore a cui invece possiamo modificare (se serve) il valore dell'indirizzo associato.
#include <iostream> using namespace std; int main(int argc, char** argv) { int vett[3]; vett[0]=6; cout<<vett; //stampo l'indirizzo di base del vettore, cioè l'indirizzo della cella vett[0] int *p; p=&vett[0]; // assegno a p l'indirizzo della cella di indice 0 del vettore *p=2; p=vett; // assegno a p l'indirizzo della cella 0 del vettore, fa la stessa cosa di p=&vett[0] *(p+1)=1; //carico nella cella vett[1] il valore 1 *(p+2)=7;//carico nella cella vett[2] il valore 7 return 0; }
Vett=1006
È sbagliato scrivere: vett=&a oppure vett=1234 perché vett è un puntatore costante. Per sottolineare l'analogia fra puntatori e vettori si ricorda che e' possibile usare la notazione dei vettori sui puntatori e viceversa
int vett[]= {2,4,5,6 }; int *p; p=vett; p[2]=23; // sintassi dei vettori su un puntatore
int vett[]= {2,4,5,6 }; *(vett+2)=23 //sintassi dei puntatori su un vettore
State attenti poi che *(p+1) e' diverso come significato da *p+1 , nel primo caso prendo l'indirizzo di p lo incremento (ipotizzo p un puntatore a intero) di 4 e accedo al valore puntato, mentre nel secondo caso accedo al valore puntato da p e gli aggiungo 1.