PUNTATORI E ARRAY. Nel capitolo 6 sono stati introdotti gli array, ora che abbiamo una certa familiarita' con i puntatori possiamo approfondire l'argomento. Dopo aver definito un array, ad esempio di caratteri, il compilatore riserva una sezione contigua della memoria sufficiente a contenere tutti gli elementi dell'array stesso. Ovviamente ogni elemento dell'array potra' essere identificato dall'indirizzo di memoria nel quale e' stato inserito. Vediamo un semplice programma che ci aiutera' a chiarire il concetto. #includeint main() { int i=0; char parola[]="Sinclair"; while( i < 8 ) { printf("indirizzo dell'elemento %d di parola e': %ld\n", i, &parola[i] ); printf("ed il contenuto e': %c\n", parola[i] ); ++i; } } che dara' un output tipo: indirizzo dell'elemento 0 di parola e': 4364159 ed il contenuto e': S indirizzo dell'elemento 1 di parola e': 4364160 ed il contenuto e': i indirizzo dell'elemento 2 di parola e': 4364161 ed il contenuto e': n indirizzo dell'elemento 3 di parola e': 4364162 ed il contenuto e': c indirizzo dell'elemento 4 di parola e': 4364163 ed il contenuto e': l indirizzo dell'elemento 5 di parola e': 4364164 ed il contenuto e': a indirizzo dell'elemento 6 di parola e': 4364165 ed il contenuto e': i indirizzo dell'elemento 7 di parola e': 4364166 ed il contenuto e': r Un altro modo per risalire al valore di ogni elemento dell'array puo' consistere nel modificare la seconda printf nella seguente: (B) printf("ed il contenuto e': %c\n", *(&parola[i]) ); oppure (C) printf("ed il contenuto e': %c\n", *(parola+i) ); Queste due scritture svolgono la stessa funzione e sono, pertanto, intercambiabili. Esaminandole piu' nel dettaglio possiamo scoprire qualcosa di piu' sui puntatori. (A) printf("ed il contenuto e': %c\n", parola[i] ); si accede direttamente all'elemento i-esimo dell'array. (B) printf("ed il contenuto e': %c\n", *(&parola[i]) ); Utilizzare l'operatore di indirezione su di un indirizzo equivale ad accedere al contenuto della locazione di memoria che, in questo caso, e' parola[i]. L'utilizzo di puntatori ad indirizzi e', anche se corretto, non particolarmente razionale ed ha qui solo carattere dimostrativo. (C) printf("ed il contenuto e': %c\n", *(parola+i) ); questa scrittura e' invece assai piu' interessante in quanto permette di introdurre una interessante peculiarita' del C: l'aritmetica dei puntatori. -- ARITMETICA DEI PUNTATORI -- Poiche' un puntatore e' un numero, come tale puo' essere trattato: potremo addizionarlo o sottrarlo e, dal momento che un'array e' memorizzato in locazioni contigue di memoria, sara' logico supporre che incrementando o decrementando il suo valore sia possibile spostarsi da una cella di memoria ad un'altra. Analizziamo il nostro caso: indirizzo dell'elemento 0 di parola e': 4364159 ed il contenuto e': S Quando i vale 0 *(parola+i) equivale a parola[0], ovvero l'indirizzo 4364159 contiene il valore "S". Incrementando i di un'unita' *(parola+i) equivarra' a parola[1] ed il suo contenuto sara' "i" e cosi' via fino al termine dell'array. NOTARE la scrittura *(variabile+incremento): questo modo di digitare l'istruzione indica al compilatore di valutare l'indirizzo della variabile ("parola" nel nostro caso), spostarsi di "incremento" bytes rispetto al suddetto indiriz- zo e, infine, restituirne il contenuto. Scrivere: *variabile+incremento potrebbe dare luogo a problemi di interpretazione in fase di parsing percio' e' buona norma utilizzare le parentesi, il che favorisce anche una migliore comprensione da parte del programmatore. Dal momento che il nostro array e di tipo char, ogni incremento di unita' del suo indice spostera' il valore della memoria di un byte. Se avessimo utilizzato un array di tipo short ogni incremento ci avrebbe fatto spostare di due byte; quattro byte sarebbero stati utilizzati per un array di tipo int, ecc... Pertanto occorre ricordare che quando si utilizzano operazioni aritmetiche sui puntatori i risultati delle stesse influiranno sulla memoria in maniera proporzionale alle dimensioni del tipo utilizzato! Naturalmente gli incrementi ed i decrementi potranno essere di valore superiore ad uno. L'unica cosa da verificare e' la dimensione dell'array: indirizzare valori inferiori alla locazione di inizio dello stesso o superiori alla sua dimensione massima causera' malfunzionamenti del programma in quanto si potranno alterare altri dati o aree di memoria riservate al sistema operativo. Un esempio di utilizzo dei puntatori puo' essere la scrittura di un programma che scrive una stringa di caratteri al contrario. Questa, oltre che ad essere un ottimo sistema per dimostrare l'utilizzo dei puntatori e' anche una routine che puo' essere utilmente impiegata in campo grafico per creare l'effetto "rifletti" di un'immagine. /* RIFLESSIONE.C */ #include void main( void ); void main( void ) { char stringa[]={"Sinclair QL"}; char *s1, *s2; s1 = s2 = stringa; /* s1 e s2 puntano a stringa */ while( *s2 ) s2++; while( --s2 >= s1 ) putchar( *s2 ); printf( "\n" ); } Analizzando il programma notiamo che: 1- si dichiara un array denominato stringa e gli si assegna un valore; 2- si dichiarano due tipi puntatori s1 ed s2; 3- si fa si' che s1 ed s2 puntino all'indirizzo di stringa; 4- con il primo while si incrementa l'indirizzo di s2 fino a quando si raggiunge il terminatore di stringa, ovvero fino a quando *s2 e' diverso da 0 (ricordate che in C un test logico e' vero fino a che e' diverso da 0? ); 5- al termine del primo while si e' raggiunto l'indirizzo di fine stringa; 6- con il secondo while si decrementa l'indirizzo contenuto in s2 fino a quando si raggiunge il valore dell'indirizzo contenuto in s1 (ovvero l'inizio di stringa) e si stampa il contenuto della locazione di memoria puntata da *s2; 7- al termine avremo stampato il contenuto della nostra variabile partendo dall'ultima lettera fino ad arrivare alla prima. Analogo discorso vale con array di tipi diversi dal char, per cui potete provare a scrivere la stessa routine con un array di interi, avendo cura di definire i puntatori di tipo int invece che char e, magari, utilizzare una printf al posto della putchar. Esistono ancora altri utilizzi dei puntatori: parametri di funzioni e puntatori a funzione. Tuttavia, dal momento che non abbiamo ancora discusso l'argomento funzioni, si rimanda il discorso al momento opportuno. Nel prossimo paragrafo verra' trattata l'allocazione dinamica della memoria e si vedra' come i puntatori siano molto importanti in questo caso. Roberto Porro