I puntatori




La memoria RAM del calcolatore non è altro che un insieme di locazioni di memoria; per poter localizzare ciascuna locazione, ognuna di esse è identificata da un numero, ovvero il suo indirizzo. Questo significa che:

(1) per scrivere qualcosa nella memoria dobbiamo conoscere l'indirizzo del punto esatto in cui scrivere
(2) se conosciamo l'indirizzo di una data locazione possiamo leggere ciò che ci sta scritto


Il C ci permette di accedere a tale indirizzo, ovvero di localizzare dove la variabile sia stata effettivamente memorizzata.


Fino ad oggi abbiamo gestito le variabili all'interno dei blocchi di codice in cui tali variabili erano visibili, quindi non c'è stata la reale necessità di utilizzare l'indirizzo della locazione di memoria in cui i valori di tali variabili erano stati memorizzati.




















Ripasso: la sintassi da usare con i puntatori.

int main()
{char a='r';                // allocazione di memoria per le variabili
 int numero = 45 ;
 double var = 34.533;

 char *pchar;             // dichiarazione dei puntatori
 int *pint;
 double *pdouble;

 pchar = & a;             // inizializzazione dei puntatori
 pint = & numero;
 pdouble = & var;

  }
























































IMPORTANTE: così come la dichiarazione di una variabile implica che venga riservata (allocata) della memoria per la variabile stessa, così dichiarare un puntatore implica che venga riservata della memoria per la memorizzazione del puntatore, ma NON IMPLICA che venga automaticamente riservata della memoria per una variabile avente il tipo identico a quello cui fa riferimento il puntatore.




Ad esempio:

int num;                // viene riservata memoria per la variabile num
int *pnum;            // viene riservata memoria per un puntatore
int *altropunt;       // viene riservata ulteriore memoria per un altro puntatore
pnum = & num ;     // assegno al puntatore un valore pari all'indirizzo di num nella memoria

*pnum = 4 ;         

/* con l'istruzione precedente ho inserito nella locazione di memoria cui fa riferimento la variabile pnum, puntatore ad intero, il valore 4; nella medesima locazione di memoria risiede la variabile num, quindi ho assegnato tale valore alla variabile num */

*altropunt = 5;

/* Il puntatore altropunt non era stato inizializzato e quindi il valore in esso contenuto, interpretabile come uno degli indirizzi della memoria, è del tutto arbitrario. Non siamo perciò autorizzati ad accedere alla corrispondente locazione di memoria; essa potrebbe essere utilizzata, ad esempio, da un altro programma. Così facendo si esegue un accesso illegale alla memoria */

























L'unico metodo che conosciamo, al momento, per riservare della memoria per il programma, ovvero di effettuare degli accessi legali alla memoria stessa, è quello di dichiarare una variabile. Per ora, quindi, ricordiamoci di usare un puntatore SOLO se questo fa riferimento ad una variabile già dichiarata all'interno del codice.




// inizio esempio

#include <stdio.h>
#include <stdlib.h>

int main()
{int num1,num2;
 double var;
 int *punt;
 double *pdouble;
 printf("inserisci un numero maggiore di 3000");

// l'struzione che segue è errata !

 scanf("%d",punt);

// ora correggiamo il codice
 punt = & num1 ;   // punt ora contiene l'indirizzo di un'area di memoria cui il programma può accedere
           
 printf("\n causa accesso illegale, inserisci un altro valore");

// ora l'istruzione è corretta !

 scanf("%d",punt);

// il valore della variabile num1 è stato inizializzato grazie al puntatore
// ora posso quindi passare num1 alla funzione che inizializza il generatore di numeri casuali

 srand(num1);

// ora uso punt per visualizzare il valore della variabile num2
 
 punt = & num2;
 num2= rand() % 30 ;
 printf("Il valore di num2 e' pari a : %d",*punt);






























// posso usare punt (puntatore a int) per inizializzare var (di tipo double) ?

 punt = & var ; // e' lecito ? ->    

 pdouble = & var ;
 printf("inserisci un valore decimale");
 scanf("%lf", & var );

 *pdouble = (*pdouble) * num2 ; // modifico il valore di var usando pdouble

 printf("il valore della variabile double e' %f", var );
 printf("\n ed il suo indirizzo e' %p",pdouble);


/* NOTA: non ho avuto bisogno di indicare a printf() quale sia il tipo di variabile cui fa riferimento il puntatore ... come mai ? */


 return 0; }


// fine esempio


Regole che vanno scritte su un foglio da tenere vicino al letto e leggere tutte le sere prima di addormentarsi:


è molto più pericoloso usare un puntatore non inizializzato di quanto non sia usare una variabile normale non inizializzata
 
mai assegnare ad un puntatore l'indirizzo di una variabile di tipo diverso da quello specificato nella dichiarazione del puntatore





























Spostiamoci ora verso un impiego un po' più interessante dei puntatori:

Ripensiamo a quali operazioni vengono compiute quando eseguiamo una chiamata ad una funzione.









Quando, viceversa, la funzione riceve solo il valore di una variabile (valore che viene copiato in una variabile locale, visibile solo alla funzione stessa) essa non è in grado di modificare il valore della variabile.

































Se una funzione riceve come parametro l'indirizzo di una data variabile, la funzione stessa è in grado di localizzare la variabile nella memoria, di leggerne il valore e di alterarlo.

























// inizio del codice

#include "myfun.h"
#define MAX 30

int main()
{int temp,vettore[MAX],i, scambi;
  char c1,c2;
  printf("\n inserire due caratteri per inizializzare il generatore di numeri casuali\n");
  printf("\nprimo carattere :");
  c1=mygetchar();
  printf("\nsecondo carattere :");
  c2=mygetchar();
  srand(c1*c2*c2*c1);

  for(i = 0; i< MAX; i++)
    {vettore[i] = int_rand(0,500);
      printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

 scambi = 1;
 while(scambi)
 {scambi = 0;
   for( i = 0; i< MAX -1; i++)
    {if( vettore[i] <= vettore[i+1] )
       {;}
      else
       {scambi = 1;
        temp = vettore[i];
          vettore[i] = vettore[i+1];
          vettore[i+1] = temp;
}
    }
 } 
  // ora stampo il risultato

  for(i = 0; i< MAX ; i++)
    {printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

  return 0;}

// fine del codice


Per migliorare la leggibilità del codice, implementiamo una funzione che scambia tra loro i valori di due variabili e poi inseriamola nell'algoritmo di ordinamento. La funzione deve ricevere come parametri gli indirizzi delle variabili, altrimenti non riesce a modificare i loro valori. Per rendere noto a main() il risultato dello scambio non possiamo sfruttare l'istruzione return, perché con essa possiamo restituire al massimo un valore per volta.




// esempio 1: NON FUNZIONA !

void scambia(int,int);

void scambia(int a,int b)
{int temp;
  temp = a;
  a = b ;
  b = temp;}

















// esempio 2: FUNZIONA ! (da inserire in myfun.h)

void scambia(int *,int *);

void scambia( int *pa, int *pb)
{int temp;
  temp = (*pa) ;
   (*pa) = (*pb) ;
   (*pb) = temp ;
}


// inizio codice

#include "myfun.h"
#define MAX 30

int main()
{int a,b;

  printf("inserisci due numeri interi:\n primo numero -> ");
  scanf("%d",&a );
  printf("\n secondo numero -> ");
  scanf("%d",&b );

  printf("i numeri inseriti sono : %d e %d",a,b);

// effettuiamo lo scambio

  scambia( &a , &b );

  printf("\n dopo lo scambio\n");
  printf("i numeri inseriti sono : %d e %d",a,b);

 return 0;}
  
// fine codice

























// inizio del codice

#include "myfun.h"
#define MAX 30

int main()
{int temp,vettore[MAX],i, scambi;
  char c1,c2;
  printf("\n inserire due caratteri per inizializzare il generatore di numeri casuali\n");
  printf("\nprimo carattere :");
  c1=mygetchar();
  printf("\nsecondo carattere :");
  c2=mygetchar();
  srand(c1*c2*c2*c1);

  for(i = 0; i< MAX; i++)
    {vettore[i] = int_rand(0,500);
      printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

 scambi = 1;
 while(scambi)
 {scambi = 0;
   for( i = 0; i< MAX -1; i++)  // mi fermo prima altrimenti "esco" dal vettore
    {if( vettore[i] <= vettore[i+1] )
       {;}
      else
       {scambi = 1;
         scambia(& (vettore[i]) ,& (vettore [i+1]) );
       }
    }
 } 
  // ora stampo il risultato

  for(i = 0; i< MAX ; i++)
    {printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

  return 0;}

// fine del codice






















Possiamo però spingerci un po' più in là ...


void if_scambia(int *,int *,int * );

void if_scambia( int *pa, int *pb,int *pscambia )    // supponiamo di disporle in ordine crescente
{int temp;

 if( *pa < *pb )
  {temp = (*pa) ;
   (*pa) = (*pb) ;
   (*pb) = temp ;
   *pscambia = 1; }
 else
  {*pscambia = 0;}

}







// inizio del codice

#include "myfun.h"
#define MAX 30

int main()
{int temp,vettore[MAX],i, scambi;
  char c1,c2;
  printf("\n inserire due caratteri per inizializzare il generatore di numeri casuali\n");
  printf("\nprimo carattere :");
  c1=mygetchar();
  printf("\nsecondo carattere :");
  c2=mygetchar();
  srand(c1*c2*c2*c1);

  for(i = 0; i< MAX; i++)
    {vettore[i] = int_rand(0,500);
      printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

 scambi = 1;
 while(scambi)
 {scambi = 0;
   for( i = 0; i< MAX -1; i++)  
    { if_scambia( & (vettore[i]), & (vettore[i+1]), & scambi); }
 } 
  // ora stampo il risultato

  for(i = 0; i< MAX ; i++)
    {printf("\nil valore nella posizione %d del vettore e\' %d",i,vettore[i]);}

  return 0;}

// fine del codice




















Regoletta per ricordarsi in che modo si indichi nel codice il valore di una variabile, noto il puntatore alla variabile. Pensiamo alla dichiarazione di un puntatore:


double *puntatore;


inseriamo delle parentesi per raggruppare i 3 elementi


double ( * puntatore ) ;


(double *)  puntatore ;