Linguaggio C: Puntatori e Dichiarazioni complesse

1. Generalità

2. Array e puntatori

3. Dichiarazioni complesse

1.  Generalità
Il puntatore è una variabile che contiene l’indirizzo di un oggetto (variabile, funzione).
  • Operatore &: indirizzo di un oggetto;
  • Operatore unario di indirezione *:  applicato a un puntatore, restituisce il valore contenuto nella locazione di memoria il cui indirizzo è contenuto nel puntatore stesso (locazione di memoria “puntata” dal puntatore).

Definizione di variabili puntatore:  tipo  *identificatore;

Esempi :

      1. int a,*p;  //dichiarazione di una variabile di tipo intero (a) e di un puntatore ad interi (p) (p può contenere indirizzi di variabili intere).

        1. char *q;  //q è dichiarata come puntatore a caratteri (può contenere indirizzi di variabili di tipo carattere).

          1. float *n;  //n è dichiarata come puntatore a float (può contenere indirizzi di variabili di tipo float).

            1.  

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

                  }

              1. 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 cella di memoria indirizzata da q potrebbe essere qualsiasi.

                1. const int *i;  // puntatore ad un valore costante di tipo int; il  puntatore può essere modificato perché punti ad un altro valore, ma il valore a cui punta non può essere modificato.

                  1. int *const i; //puntatore costante ad un valore di tipo int; il valore puntato può essere modificato il puntatore no.

                 

                2.  Array e puntatori

                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 la dichiarazione:

                int vett[10], *p;

                si ha che:  vett&vett[0]  ( il nome del vettore vett indica l’indirizzo del primo elemento del vettore).

                pertanto è lecito scrivere:

                p = vett;  //è equivalente a p = &vett[0]; (a p viene assegnato l’indirizzo del primo elemento dell’array)   

                e si ha:

                *pvett[0]   (il contenuto della cella di memoria puntata da p contiene il primo elemento dell’array).

                *(p+i)vett[i]  (il contenuto della cella di memoria puntata da (p+i) contiene l’elemento i-esimo dell’array).

                p[i]*(vett+i)  (significato analogo alla notazione precedente).

                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 punta inizialmente al primo elemento dell’  array  (p=dati)

                il ciclo ha termine quando (p = &dati[10])

                nell’elemento puntato da p (*p) viene messo il valore 0, quindi p viene incrementato (incremento postfisso) per puntare  all’elemento successivo (*p++ = 0).

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

                    3.  Dichiarazioni complesse

                    Si può “modificare”  l’identificatore  con questi “modificatori”

                    * puntatore (a sinistra)

                    ( ) funzione (a destra)

                    [ ] array (a destra)  

                    Le ( ) [ ]  hanno la stessa precedenza con associazione da sin a dx e hanno la precedenza su *

                    Interpretazione dei dichiaratori complessi

                    1) si parte dall’ identificatore

                    2) guardare se a dx ci sono ( ) o [ ] e quindi a sin *

                    3) se a dx c’è una ) tornare indietro e appplicare 1) e 2)   ad ogni cosa entro parentesi

                    4) applicare lo specificatore di tipo

                    Es. 1

                              3bis

                    char *(*(*Var) ()  )[10];

                        5     2   1   3         4

                    1 – Var:  identificatore; è seguito da una “)”, si torna ai punti 1 e 2

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

                    Es. 2

                    int *var[5]:  array di 5 puntatori a int; [ ] ha la precedenza su * che è applicato al tipo degli elementi dell’array.

                    Es.3

                    int (*var)[5]  : puntatore ad un array di 5  interi; la precedenza è cambiata dalle parentesi.

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

                    Es.5

                    long (*var) (long, long) : puntatore a funzione che ha  2 param di tipo long e restituisce un long; la precedenza è cambiata dalle parentesi.

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

                    Es. 6 (dal sito:

                     

                    int (*(*foo)(double))[3];

                    (*foo) //foo is a pointer…

                    (*foo)(double) // to a function taking a double…

                    (*(*foo)(double)) // returning a pointer…

                    (*(*foo)(double))[3] // to an array of 3…

                    int (*(*foo)(double))[3]; // ints

                    Per interpretare una dichiarazione complessa si può usare la  “clockwise spiral rule”

                    Ci sono 3 semplici regole da seguire:

                        1. Partendo dall’identificatore, muoversi secondo una spirale in senso orario; quando si incontra uno dei seguenti simboli sostituirlo con la corrispondente dichiarazione:
                              1. [X ] o []    Array X di dimensioni …   oppure  Array  di dimensioni …

                              1. (type1, type2, …) funzione con parametri in ingresso type1, type2, … che restituisce ….  

                              1. * puntatore/i  a …

                          1. Continuare, muovendosi sempre secondo una spirale in senso orario, fino a quando non sono stati esaminati tutti gli elementi.

                          1. Esaminare, prima, tutti gli elementi racchiusi nelle parentesi  tonde (…)


                        Lascia un commento