Il puntatore è una variabile il cui contenuto è l’indirizzo di un oggetto (variabile, funzione); l’operatore & permette di ottenere l’indirizzo di un oggetto, mentre l’operatore unario * di indirezione permette a un puntatore di accedere al valore contenuto nella locazione di memoria, il cui indirizzo si trova nel puntatore stesso (locazione di memoria “puntata” dal puntatore).
Definizione, in C, di variabili puntatore:
tipo *identificatore;
Esempi:
int a,*p; // dichiarazione di una variabile di tipo intero (a) e di un puntatore ad interi (p) ( può contenere indirizzi di variabili intere).
char *q; // q è dichiarata come puntatore a caratteri (può contenere indirizzi di variabili di tipo carattere).
float *n; // n è dichiarata come puntatore a float (può contenere indirizzi di // variabili di tipo float).
# include <stdio.h>
main()
{ int a,b, *x,*y; // a e b variabili intere; x e y puntatori ad interi.
scanf (“%d”,&a); // &a: indirizzo della variabile a.
scanf (“%d),&b); // &b: indirizzo della variabile b.
x = &a; // x conterrà l’indirizzo della variabile a.
y = &b; // y conterrà l’indirizzo della variabile b.
printf (“%d”,*x); // è equivalente a printf (“%d”,a): la variabile puntatore x
// contiene l’indirizzo della variabile a, quindi *x indica il contenuto.
// della locazione di memoria il cui indirizzo si trova in x.
printf (“%d”,*y); // è equivalente a printf (“%d”,b): vale quanto detto per x.
}
int a,*q; // a variabile intera; q puntatore ad interi.
&a = 10; // è errata perché non si può modificare l’indirizzo di una variabile.
*q = 10; // è corretta, perché viene assegnato un valore nella locazione di memoria puntata da q, ma i risultati sono imprevedibili perché la ella di memoria indirizzata da q potrebbe essere qualsiasi.
const int *i; // puntatore a un valore costante di tipo int; il puntatore può essere modificato perché punti a un altro valore, ma il valore a cui punta, adesso, non può essere modificato.
int *const i; // puntatore costante a un valore di tipo int; il valore puntato può essere modificato il puntatore no.
C’è una stretta relazione tra array e puntatori; infatti in C il nome dell’array coincide con l’indirizzo del primo elemento dell’array stesso cioè, fatte le dichiarazioni:
int vett[10],*p;
si ha
vett ≡ &vett[0]
pertanto è lecito scrivere:
p = vett; // è equivalente a p = &vett[0];
e si ha che:
*p ≡ vett[0]
*(p+i) ≡ vett[i]
p[i] ≡ *(vett+i)
da notare che *(p+i) è diverso da *p+i (in questo caso viene sommato i al contenuto della cella puntata da p, infatti l’operatore di indirezione ha precedenza più alta dell’addizione).
Nella manipolazione di puntatori e nomi di array bisogna tener presente che mentre i puntatori sono delle variabili i nomi di array sono delle costanti, perciò, se p è un puntatore e vett il nome di un array, mentre è lecito scrivere:
p = vett; o p++;
è errato scrivere:
vett = p; o vett++;
perché si sta cercando di modificare l’indirizzo di una variabile.
L’uso dei puntatori permette di scrivere programmi molto concisi, qualche volta però a scapito della comprensione; il seguente segmento di programma permette, ad esempio, di inizializzare a 0 gli elementi di un array:
.................
int dati[10],*p;
for (p = dati; p < &dati[10]; *p++ = 0);
.................
(p=dati): p punta inizialmente al primo elemento dell’ array;
(p < &dati[10]): il ciclo ha termine quando p = &dati[10];
(*p++ = 0): a ogni passaggio, nell’elemento puntato da p (*p) viene messo il valore 0, quindi p viene incrementato (incremento postfisso), per puntare all’elemento successivo.
Le funzioni per la manipolazione di stringhe (array di char) fanno largo uso dei puntatori; le più utilizzate di queste funzioni sono:
strcpy(stringa1, stringa2): copia stringa2 in stringa1.
strcmp(stringa1,stringa2): confronta stringa1 con stringa2; restituisce 0 se stringa1 = stringa2; un valore < 0 se stringa1 < stringa2; un valore > 0 se stringa1 > stringa2.
strcat(stringa1,stringa2): concatena stringa2 a stringa1.
strlen(stringa): restituisce il numero di caratteri di stringa (‘\0’ è escluso dal conteggio).
atoi(stringa), atol(stringa), atof(stringa): convertono una stringa rispettivamente in un int, un long, un double.
È possibile anche definire un puntatore ad una struttura (struct o union), esempio:
typedef struct
{ char nome[20];
int eta;
} DATI;
DATI a, *p; // a è una variabile del tipo DATI e p un puntatore a una variabile di tipo DATI
per accedere ad un campo di una struttura attraverso un suo puntatore, piuttosto che attraverso il nome di una variabile, basta sostituire all’ operatore . l’operatore ->, cosicché
a.nome ≡ p->nome
Un uso più complesso dei puntatori si ha nei seguenti casi:
int (*p)[10]; // p è un puntatore ad un array di 10 interi
int *p[10]; // p è un array di 10 puntatori ad interi
char **p; // p è un puntatore ad un puntatore a char
int (*p) (int); // p è un puntatore ad una funzione che restituisce un intero e che ha // come parametro di input un intero
Si può “modificare” un identificatore con questi modificatori
* puntatore (a sinistra dell'identificatore)
( ) funzione (a destra dell'identificatore)
[ ] array (a destra dell'identificatore)
Le ( ) [ ] hanno la stessa precedenza con associazione da sinistra a destra e hanno la precedenza su *
Interpretazione dei dichiaratori complessi
1) si parte dall' identificatore
2) guardare se a destra ci sono ( ) o [ ] e quindi a sinistra *
3) se a destra c'è una ")" tornare indietro e applicare 1) e 2) a ogni cosa entro parentesi
4) applicare lo specificatore di tipo
Esempio 1:
3bis
char *(*(*Var) () )[10];
5 2 1 3 4
1 – Var: identificatore; è seguito da una “)”, guardare se a destra e a sinistra sono presenti ( ) [ ] o *
2 – *: è un puntatore (*Var)
3 – (): a una funzione (3) che restituisce un puntatore (3bis) ( * (*Var) () )
4 – [10] : a un array di 10 elementi, che sono
5 – char *: puntatori a char.
Nota al punto 3: la coppia () (funzione senza parametri) ha precedenza rispetto all' *
Esempio 2:
int *var[5]; //array di 5 puntatori a int; [ ] ha la precedenza su * che è applicato al tipo degli elementi dell’array.
Esempio 3:
int (*var)[5]; //puntatore ad un array di 5 interi; la precedenza è cambiata dalle parentesi.
Esempio 4:
long *var (long, long); //funzione che ha 2 param di tipo long e restituisce un puntatore a long; ( ) ha la precedenza su * che è applicato al valore restituito dalla funzione.
Esempio 5:
long (*var) (long, long); //puntatore a funzione che ha 2 param di tipo long e restituisce un long; la precedenza è cambiata dalle parentesi.
Esempio 6:
struct Rec
{
int a;
char b;
} (*Var[5])( struct Rec, struct Rec);
Var è un array di 5 puntatori a funzioni che restituiscono una struttura Rec e con 2 parametri di tipo Rec.
Per interpretare una dichiarazione complessa si può usare il metodo "clockwise spiral rule".
Ci sono 3 semplici regole da seguire:
Partendo dall’identificatore, muoversi secondo una spirale in senso orario; quando si incontra uno dei seguenti simboli sostituirlo con la corrispondente dichiarazione:
[X ] o [] Array X di dimensioni … oppure Array di dimensioni …
(type1, type2, …) funzione con parametri in ingresso type1, type2, … che restituisce ….
* puntatore/i a …
Continuare, muovendosi sempre secondo una spirale in senso orario, fino a quando non sono stati esaminati tutti gli elementi.
Esaminare, prima, tutti gli elementi racchiusi nelle parentesi tonde (...)