Vai al contenuto

Informatica 2 Liceo Scientifico Scienze Applicate/Puntatori

Wikibooks, manuali e libri di testo liberi.
Indice del libro

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;
 }     

1DISEG~1

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

2DISEG~1

È 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.