Introduciamo a questo punto il concetto di puntatore, potente e delicato strumento di programmazione C, utile per poter proseguire il discorso su altre implicazioni delle funzioni.
Un puntatore e’ una variabile che contiene un indirizzo di memoria come valore, che rappresenta la posizione (locazione) all’interno della memoria di un’altra variabile.
Una variabile si dice che punta ad un’altra variabile quando contiene l’indirizzo di quest’ultima.
Con la seguente dichiarazione:
char *p;
abbiamo dichiarato una variabile p , puntatore a un valore carattere;
si dice che p punta ad un oggetto di tipo carattere.
Le variabili di tipo puntatore sono dette variabili dinamiche.
Attraverso la dichiarazione di una variabile di tipo puntatore, puo' vienire allocato spazio in memoria per essa non in modo statico (cioe’ a priori, in fase di compilazione) ma in modo dinamico (in fase di esecuzione).
Per il tipo di dato puntatore il linguaggio C mette come visto il costruttore di tipo puntatore, denotato col simbolo * .
Il formato generico della dichiarazione di una variabile di tipo puntatore e’ la seguente:
tipo
*nome_variabile;
dove tipo puo’ essere uno qualunque dei tipi base del C (int , char, float, double..)
nome_variabile e’ il nome della variabile puntatore.
Con il costruttore di tipo puntatore * e’ possibile dichiarare nuovi tipi di dato e variabili di tipo puntatore, attraverso la seguente sintassi:
<var_puntatore> ::= <costruttore_puntatore><identificatore>;
<tipo_puntatore> ::= typedef <costruttore_puntatore><identificatore>;
<costruttore_puntatore>::=<tipo> *
esempio: uso di typedef
Si puo’ definire un puntatore con typedef nel seguente modo:
typedef int * nuovo_puntatore;
nuovo_puntatore punt_interi;
punt_interi e’ un
puntatore ad interi.
Un puntatore lo si
puo’ definire anche con #define:
vediamo come
#define
PUNTATORE_INTERI int * ;
ma e’ preferibile
usare la typedef poiche’ usando una istruzione come la seguente: PUNTATORE_INTERO
punt1, punt2;
avremo che punt2 non
e’ un puntatore ma una variabile intera, mentre solo punt1 e’ un puntatore ad
interi.
Per manipolare i puntatori esistono due operatori & ed * ;
& ampersand (od e commerciale) e’ un operatore unario di indirizzo che restituisce l’indirizzo di memoria del suo operando;
mentre
* e’ l’operatore di dereferenziazione, che permette di accedere alla locazione avente un dato indirizzo.
Nota con * viene definita anche una variabile di tipo puntatore (il costruttore di tipo puntatore: non confonderlo con l’operatore di derefenziazione)
Ad esempio con la scrittura
punt=&cont;
abbiamo una istruzione che pone nella variabile punt l’indirizzo di memoria della variabile cont;
evidentemente affinche’ sia lecita tale istruzione, punt e’ stato precedentemente dichiarato di tipo puntatore ad esempio con una istruzione del tipo
int *punt; .
Importante non confondere l’indirizzo della variabile
(locazione di memoria in cui e’ contenuto il valore della variabile)
con il valore della variabile.
Se eseguiamo (valutiamo) la punt=&variabile, questa restituisce l’indirizzo di memoria di variabile e lo assegna alla variabile puntatore punt.
Allora la istruzione *variabile = 0; mette 0 nella locazione associata a variabile
(quella avente l’indirizzo &variabile) e printf(“…”, *punt)
stampa il contenuto della locazione di memoria associata a variabile
(cioe’ stampa il valore di variabile
..)
Adesso avendo punt= &cont;
Se la variabile cont e’ allocata all’indirizzo 500 , la variabile punt riceve dopo l’assegnamento (punt= &cont;) il valore 500
(punt riceve l’indirizzo di una variabile che ha nome cont).
L’operatore * e’ un operatore unario che restituisce il valore della variabile che si trova all’indirizzo indicato nella variabile che lo segue.
se punt contiene l’indirizzo di memoria della variabile cont (punt contiene 500) l’istruzione seguente:
val
= *punt;
assegna alla variabile val il valore di cont (ossia il contenuto della locazione di memoria 500)
Dopo queste assegnazioni in memoria avremo una cosa del genere, mostrata in Fig. 14.1, supponendo che val sia memorizzata all’indirizzo di memoria 504 e valga 5.
Fig.14.1
Indirizzo di memoria |
Contenuto della memoria |
variabili |
498 |
. |
. |
499 |
. |
. |
500 |
5 |
cont |
501 |
. |
. |
502 |
. |
. |
503 |
. |
. |
504 |
5 |
val |
505 |
. |
. |
… |
. |
. |
L’ operatore * viene detto operatore di deferimento od anche operatore di risoluzione del riferimento.
int x, y;
int *ind ; /*definizione di un puntatore ad interi*/
….
ind = &x; /* assegna a ind l’indirizzo di x lecito */
…
y= &x; /*assegna ad y l’indirizzo di x non lecito */
/* non lecito in quanto y non e’ abilitata a contenere indirizzi di locazioni intere*/
….
…
y = *ind; /*ad y viene assegnato il valore contenuto nella locazione ‘puntata’ da ind lecita */
Nell’esempio precedente
ind e’ una variabile che contiene un indirizzo
*ind e’ il contenuto della locazione di memoria di indirizzo ind (nel nostro caso tale contenuto e’ un intero avendo dichiarato ind come puntatore ad intero)
Osservazione
E’ la costante simbolica associata al carattere di codice ascii 0.
* ed & sono operatori unari come detto;
* si applica a qualunque espressione che ritorni un puntatore;
se p e’ un puntatore, *p e’ un L-VALORE , ossia puo’ essere usato dovunque ci si aspetti una variabile;
& si applica soltanto a L-VALORE
&x non e’ un L-VALORE: “&x =…” non ha senso.
Passaggio per indirizzo nelle funzioni
Il legame per riferimento od indirizzo ( passaggio per indirizzo ) si utilizza quando la chiamata deve modificare il valore dei parametri; in questo metodo viene copiato l’indirizzo del parametro attuale nel parametro formale. Questo indirizzo lo si utilizza per accedere alla variabile che e’ il parametro attuale della chiamata con il risultato che modifiche del parametro formale si riflettono sulla variabile usata al momento della chiamata.
Il linguaggio C usa il metodo per valore, anche se attraverso l’uso di puntatori, e’ possibile forzare il passaggio per indirizzo. Il puntatore ad una variabile var non e’ altro che un’altra variabile punt che contiene l’indirizzo di var; e considerando che:
a) &var e’ l’espressione che fornisce il valore dell’indirizzo della variabile var
b) *punt consente di accedere alla locazione di memoria di indirizzo punt
c) quindi se punt e’ uguale a &var, l’istruzione *punt=0; scrive 0 nella locazione di memoria puntata da punt, ossia assegna 0 alla variabile var "ossia il valore di var diventa 0".
Esempio
Programma che fornisce un esempio di passaggio per indirizzo:
#include<stdio.h>
void init (int *punt1, int *punt2){
*punt1=10; *punt2=10;
/*questa funzione accede alle variabili tramite indirizzo*/
}
main()
{
char car;
int x, y, z=1; /*x ed y non sono inizializzate*/
printf("prima %d %d %d \n", x,y,z);
init(&x,&y); /*nella chiamata della funzione vengono passati
gli indirizzi di x e di y*/
printf("dopo %d %d %d\n",x,y,z);
printf("\ndigitate un carattere \n");
scanf("%c",&car); /* serve per visualizzare l’output*/
}
l’output del programma usando Rhide del DJGPP e’:
prima 1919509620 1569570670 1
dopo 10 10 1
I valori 1919509620 1569570670 sono casuali; si riferiscono al contenuto attuale delle locazioni di memoria associate alle variabili x ed y (non inizializzate).
Puntatori come
argomenti di una funzione
(passaggio per
indirizzo)
Il seguente programma propone una funzione di nome change, che scambia i valori tra due interi.
#include<stdio.h>
main( )
{
void
change(int *uno, int *due);
int i,j;
printf(“inserite due numeri interi”);
scanf(“%d %d”, &i,&j); /*nota bene*/
printf(“\n\tprima i = %d e j= %d;”,i,j);
printf(“\n..chiamata della funzione change…”);
change(&i,&j);
printf(“\n\tdopo i= %d e j= %d”, i , j);
}
void
change(int *primo, int *secondo);
{
int var_aux; /*variabile di appoggio*/
var_aux=*primo ;
*primo=secondo ;
*secondo=var_aux ;
}
si noti come la chiamata alla funzione change avviene con parametri, passati per indirizzo .
La funzione scanf necessita degli indirizzi degli argomenti, poiche’ essa deve modificare gli argomenti(dato che deve assegnare loro nuovi valori) e quindi bisogna simulare per essi il passaggio per indirizzo.
Esempio: uso di puntatori in scanf()
int i, *punt_interi=&i;
/*
definizione della variabile intera i e della variabile puntatore punt_interi che e’ inizializzata con
l’indirizzo di i*/
…
scanf(“%d”,
punt_interi);
/*
mette il valore letto in i, dato che scrivere &i o punt_interi e’ lo stesso */
Funzioni che restituiscono un
puntatore
La seguente dichiarazione "prototipo" int *funz(int * , char *); di funzione prende due puntatori per argomenti (ad interi e a caratteri) e restituisce un puntatore ad interi.
La definizione della funzione potrebbe essere la seguente:
int *funz(int *num_intero, char *carattere)
{
int *ris;
…
return (ris);
/* restituzione del puntatore ad
interi */
}