Puntatori ed Array

 


 

Analogia fra puntatori ed array

 

Quando abbiamo trattato gli array, avremmo dovuto fare le seguente riflessione: "Il C++ é un linguaggio tipato (ogni entità del linguaggio deve appartenere a un tipo); e allora, cosa sono gli array ?".

La risposta é:                   "Gli array sono dei puntatori!".

Quando si dichiara un array, in realtà si dichiara un puntatore, con alcune caratteristiche in più:

Esiste un'altra differenza fra la dichiarazione di un'array e quella di un puntatore: in un array l'area puntata può essere inizializzata tramite la lista degli elementi dell'array, mentre in un puntatore ciò non é ammesso. A questa regola fa eccezione il caso di un puntatore a char quando l'area puntata é inizializzata mediante una stringa literal (per compatibilità con vecchie versioni del linguaggio).
Es.:       char saluto[ ] = "Ciao"; ammesso - saluto é const
char saluto[ ] = {'C','i','a','o','\0'};   ammesso - saluto é const
char* saluto = {'C','i','a','o','\0'}; non ammesso
char* saluto = "Ciao"; ammesso !!! - saluto non é const !!!

nell'ultimo caso, tuttavia, non è concesso modificare la stringa (anche se è concesso modificare il puntatore!): il programma da' errore in fase di esecuzione! Per esempio, se poniamo:
            saluto[2]  = 'c';
la stringa diventa correttamente "Cico" se saluto è stato dichiarato array di char, mentre risulta un errore di "access violation" della memoria (?!) se saluto è stato dichiarato puntatore a char.
Conclusioni: non inizializzare mai un puntatore a char con una stringa literal! (oppure farlo solo se si è sicuri che la stringa non verrà mai modificata).

[p30]

 


Combinazione fra operazioni di deref. e di incremento

 

Le operazioni di deref. e di incremento (o decremento) possono applicarsi contemporaneamente allo stesso operando puntatore.
Es. :                     *p++

In questo caso l'incremento opera sul puntatore e non sulla variabile puntata e, al solito, agisce prima della deref. se é prefisso, oppure dopo la deref. se é suffisso. Da notare che l'espressione nel suo complesso può essere un l-value, mentre il semplice incremento (o decremento) di una variabile non lo é. Infatti, un'istruzione del tipo:
*p++ = c ;         viene espansa in :    *p = c ;     p = p+1 ;
e quindi é accettabile perché l'operazione di deref. può essere un l-value,
mentre l'istruzione:            a++ = c ;
é inaccettabile in quanto l'operazione di incremento non é un l-value.

 


 

Confronto fra operatore [ ] e deref. del puntatore "offsettato"

 

Poiché il nome (usato da solo) di un array ha il significato di puntatore al primo elemento dell'array, ogni altro elemento é accessibile tramite un'operazione di deref. del puntatore-array "offsettato", cioè incrementato di una quantità pari all'indice dell'elemento. Da questo e dalle note regole di aritmetica dei puntatori consegue che le espressioni (dato un array A):

A[i]                     e                    *(A+i)

conducono ad identico risultato e quindi sono perfettamente intercambiabili e possono essere entrambe usate sia come r-value che come l-value.

 


 

Funzioni con argomenti array

 

Quando, nella chiamata di una funzione, si passa come argomento un array (senza indici), in realtà si passa un puntatore, cioè l'indirizzo del primo elemento dell'array e pertanto i singoli elementi sono direttamente modificabili dall'interno della funzione. Questo spiega l'apparente anomalia di comportamento degli argomenti array (e in particolare delle stringhe), a cui abbiamo accennato trattando del passaggio degli argomenti by value.
Es. :   nel programma chiamante:     int A[ ] = {0,0,0};    .... funz(.... A,....) ; ....
nella funzione: void funz(....int A[ ] , ....) { ....A[1] = 5; ....}

il secondo elemento dell'array A risulta modificato, perché in realtà nella funzione viene eseguita l'operazione: *(A+1)= 5 (il valore 5 viene inserito nella locazione di memoria il cui indirizzo é A+1).

Nella dichiarazione (e nella definizione) della funzione, un argomento array può essere indifferentemente dichiarato come array o come puntatore (in questo caso non c'è differenza perché la memoria é già allocata nel programma chiamante). Tornando all'esempio, la funzione funz avrebbe potuto essere definita nel seguente modo: void funz(....int* A, ....)

Le due dichiarazioni sono perfettamente identiche; di solito si preferisce la seconda per evidenziare il fatto che il valore dell'argomento é un indirizzo (il puntatore creato per copia non é mai assunto const, anche se l'argomento é dichiarato come array: resta comunque valida la regola che ogni modifica del suo valore fatta sulla copia non si ripercuote sull'originale).

[p31]

 


 

Funzioni con argomenti puntatori passati by reference

 

Quando un argomento puntatore é dichiarato in una funzione come riferimento,
es.     void funz(....int*& A, ....),
nel programma chiamante il corrispondente argomento non può essere dichiarato come array, in quanto, se così fosse, sarebbe const e quindi non l-value (ricordiamo che gli argomenti passati by reference devono essere degli l-value, a meno che non siano essi stessi dichiarati const nella funzione).

 


 

Array di puntatori

 

In C++ (come in C) i puntatori, come qualsiasi altra variabile, possono essere raggruppati in array e definiti come nel seguente esempio:

int* A[10];          (definisce un array di 10 puntatori a int)

Come un array equivale a un puntatore, così un array di puntatori equivale a un puntatore a puntatore (con in più l'allocazione della memoria puntata, come nel caso di array generico). Se questo viene passato come argomento di una funzione, nella stessa può essere dichiarato indifferentemente come array di puntatori o come puntatore a puntatore.
Continuando l'esempio precedente:
programma chiamante:         funz(.... A,....) ;
dichiarazione di funz: void funz(....int** A, ....);

Il caso più frequente di array di puntatori é quello dell'array di stringhe, che consente anche l'inizializzazione tramite l'elenco, non dei valori dei puntatori, ma (atipicamente) delle stesse stringhe che costituiscono l'array.
Es.:                 char* colori[3] = {"Blu", "Rosso", "Verde"} ;

Come appare nell'esempio, le stringhe possono anche essere di differente lunghezza; in memoria sono allocate consecutivamente e, per ciascuna di esse, sono riservati tanti bytes quant'é la rispettiva lunghezza (terminatore compreso). Da certi compilatori la memoria allocata per ogni stringa é arrotondata per eccesso a un multiplo di un numero prefissato di bytes.

 


 

Torna all'Indice