Lezione XIV             frames  noframes

 

Cenni sui puntatori e passaggio per indirizzo nelle funzioni

 

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.

 

Definizione di un puntatore

 

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.

 

 

Gli operatori sui puntatori

 

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.

 

Esempio

 

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.

 

 

 

 

Esempio: operatori sui puntatori

 

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 */

 

Nota

 

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

 

la costante  null e’ il puntatore a nulla.

E’ la costante simbolica associata al carattere di codice ascii 0.

 

Osservazione

 

*   * 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 */

}

 


 

esercizio

 

 

 Test 14