- Programmazione » Programmazione » Guida C - Manuale programmazione con articoli e risorse interessanti
I puntatori: gli array
PUNTATORI
Un array (o vettore) è una sequenza di dati dello stesso tipo, sistemati in memoria... in fila indiana. Una stringa è, per definizione, un array di char. Si possono dichiarare array di int, di double, o di qualsiasi altro tipo. Il risultato è, in pratica, riservare in memoria lo spazio necessario a contenere un certo numero di variabili di quel tipo. In effetti, si può pensare ad un array anche come ad un gruppo di variabili, aventi tutte identico nome ed accessibili, quindi, referenziandole attraverso un indice. Il numero di "variabili" componenti l'array è indicato nella dichiarazione:
int iArr[15];
La dichiarazione di un array è analoga a quella di una variabile, ad eccezione del fatto che il nome dell'array è seguito dal numero di elementi che lo compongono, racchiuso tra parentesi quadre. Quella dell'esempio forza il compilatore a riservare lo spazio necessario a memorizzare 15 interi, dunque 30 byte. Per accedere a ciascuno di essi occorre sempre fare riferimento al nome dell'array, iArr: il singolo int desiderato è individuato da un indice tra parentesi quadre, che ne indica la posizione.
iArr[0] = 12;
iArr[1] = 25;
for(i = 2; i < 15; i++)
iArr[i] = i;
for(i = 0; i < 15;) {
printf("iArr[%d] = %d
" ,i,iArr[i]);
i++;
}
Nell'esempio i primi due elementi dell'array sono
inizializzati a 12 e 25, rispettivamente.
Il primo ciclo for inizializza i successivi elementi (dal
numero 2 al numero 14) al valore che
i assume ad ogni iterazione. Il secondo ciclo for
visualizza tutti gli elementi dell'array. Preme
sottolineare che gli elementi di un array sono numerati a
partire da 0 (e non da 1), come ci si potrebbe attendere.
Dunque, l'ultimo elemento di un array ha indice inferiore
di 1 rispetto al numero di elementi in esso presenti. Si vede
chiaramente che gli elementi di iArr, dichiarato
come array di 15 interi, sono referenziati con indice che va
da 0 a 14.
Che accade se si tenta di referenziare un elemento che non
fa parte dell'array, ad esempio iArr[15]? Il
compilatore non fa una grinza: iArr[15] può
essere letto e scritto tranquillamente... E' ovvio che
nel primo caso (lettura) il valore letto non ha alcun
significato logico ai fini del programma, mentre nel secondo
caso (scrittura) si rischia di perdere (sovrascrivendolo)
qualche altro dato importante. Anche questa volta il
compilatore si limita a mettere a disposizione del
programmatore gli strumenti per gestire la memoria, senza
preoccuparsi di controllarne più di tanto
l'operato. Per il compilatore, iArr[15] è
semplicemente la word che si trova a 30 byte
dall'indirizzo al quale l'array è memorizzato.
Che farne, è affare del programmatore[17].
Un array, come qualsiasi altro oggetto in memoria, ha un
indirizzo. Questo è individuato e scelto dal
compilatore. Il programmatore non può modificarlo, ma
può conoscerlo attraverso il nome dell'array
stesso, usandolo come un puntatore. In C, il nome di un array
equivale, a tutti gli effetti, ad un puntatore all'area
di memoria assegnata all'array. Pertanto, le righe di
codice che seguono sono tutte lecite:
int *iPtr;
printf("indirizzo di iArr: %X
" ,iArr);
iPtr = iArr;
printf("indirizzo di iArr: %X
" ,iPtr);
printf("primo elemento di iArr: %d
" ,*iArr);
printf("secondo elemento di iArr: %d
" ,*(iArr+1));
++iPtr;
printf("secondo elemento di iArr: %d
" ,*iPtr);
mentre non sono lecite le seguenti:
++iArr; // l'indirizzo di un array non puo' essere modificato
iArr = iPtr; // idem
ed è lecita, ma inutilmente complessa, la seguente:
iPtr = &iArr;
in quanto il nome dell'array ne restituisce, di per se
stesso, l'indirizzo, rendendo inutile l'uso
dell'operatore & (address of).
Il lettore attento dovrebbe avere notato che l'indice di
un elemento di un array ne esprime l'offset, in termini
di numero di elementi, dal primo elemento dell'array
stesso. In altre parole, il primo elemento di un array ha
offset 0 rispetto a se stesso; il secondo ha offset
1 rispetto al primo; il terzo ha offset 2,
cioè dista 2 elementi dal primo...
Banale? Mica tanto. Il compilatore "ragiona" sugli
arrays in termini di elementi, e non di byte.
Ripensando alle stringhe, appare ora evidente che esse non
sono altro che array di char. Si differenziano solo
per l'uso delle virgolette; allora il problema del
concatenamento di stringhe può essere risolto con un
array:
iPtr = &iArr;
iPtr = &iArr;
Nell'esempio abbiamo così a disposizione 100 byte
in cui copiare e concatenare le nostre stringhe.
Puntatori ed array hanno caratteristiche fortemente simili.
Si differenziano perché ad un array non può
essere assegnato un valore[18], e perché un
array riserva direttamente, come si è visto, lo spazio
necessario a contenere i suoi elementi. Il numero di elementi
deve essere specificato con una costante. Non è mai
possibile utilizzare una variabile. Con una variabile,
utilizzata come indice, si può solo accedere agli
elementi dell'array dopo che questo è stato
dichiarato.
Gli array, se dichiarati al di fuori di qualsiasi
funzione[19], possono essere inizializzati:
int iArr[] = {12,25,66,0,144,-2,26733};
char string[100] = {'C','i','a','o'};
float fArr[] = {1.44,,0.3};
Per inizializzare un array contestualmente alla dichiarazione
bisogna specificare i suoi elementi, separati da virgole e
compresi tra parentesi graffe aperta e chiusa. Se non si
indica tra le parentesi quadre il numero di elementi, il
compilatore lo desume dal numero di elementi inizializzati
tra le parentesi graffe. Se il numero di elementi è
specificato, e ne viene inizializzato un numero inferiore,
tutti quelli "mancanti" verranno inizializzati a
0 dal compilatore. Analoga regola vale per gli
elementi "saltati" nella lista di inizializzazione:
l'array fArr contiene 3 elementi, aventi valore
1.44, 0.0 e 0.3
rispettivamente.
Su string si può effettuare una
concatenazione come la seguente senza rischi:
strcat(string," Pippo");
La stringa risultante, infatti, è "Ciao
Pippo", che occupa 11 byte compreso il
NULL terminale: sappiamo però di averne a
disposizione 100.
Sin qui si è parlato di array monodimensionali,
cioè di array ogni elemento dei quali è
referenziabile mediante un solo indice. In realtà, il
C consente di gestire array multidimensionali, nei quali per
accedere ad un elemento occorre specificarne più
"coordinate". Ad esempio:
int iTab[3][6];
dichiara un array a 2 dimensioni, rispettivamente di 3 e 6 elementi. Per accedere ad un singolo elemento bisogna, allo stesso modo, utilizzare due indici:
int i, j, iTab[3][6];
for(i = 0; i < 3; ++i)
for(j = 0; j < 6; ++j)
iTab[i][j] = 0;
Il frammento di codice riportato dichiara l'array
bidimensionale iTab e ne inizializza a 0
tutti gli elementi. I due cicli for sono nidificati,
il che significa che le iterazioni previste dal secondo
vengono compiute tutte una volta per ogni iterazione prevista
dal primo. In tal modo vengono "percorsi" tutti gli
elementi di iTab. Infatti il modo in cui il
compilatore C alloca lo spazio di memoria per gli array
multidimensionali garantisce che per accedere a tutti gli
elementi nella stessa sequenza in cui essi si trovano in
memoria, è l'indice più a destra quello che
deve variare più frequentemente.
E' evidente, d'altra parte, che la memoria è
una sequenza di byte: ciò implica che pur essendo
iTab uno strumento che consente di rappresentare
molto bene una tabella di 3 righe e 6 colonne, tutti i suoi
elementi stanno comunque "in fila indiana".
Pertanto, l'inizializzazione di un array
multidimensionale contestuale alla sua dichiarazione
può essere effettuata come segue:
int *tabella[2][5] = {{3, 2, 0, 2, 1},{3, 0, 0, 1, 0}};
Gli elementi sono elencati proprio nell'ordine in cui si trovano in memoria; dal punto di vista logico, però, ogni gruppo di elementi nelle coppie di graffe più interne rappresenta una riga. Dal momento che, come già sappiamo, il C è molto elastico nelle regole che disciplinano la stesura delle righe di codice, la dichiarazione appena vista può essere spezzata su due righe, al fine di rendere ancora più evidente il parallelismo concettuale tra un array bidimensionale ed una tabella a doppia entrata:
int *tabella[2][5] = {{3, 2, 0, 2, 1},
{3, 0, 0, 1, 0}};
Si noti che tra le parentesi quadre, inizializzando
l'array contestualmente alla dichiarazione, non è
necessario specificare entrambe le dimensioni, perché
il compilatore può desumere quella mancante dal
computo degli elementi inizialiazzati: nella dichiarazione
dell'esempio sarebbe stato lecito scrivere
tabella[][5] o tabella[2][].
Dalle affermazioni fatte discende infoltre che gli elementi
di un array bidimensionale possono essere referenziati anche
facendo uso di un solo indice:
int *iPtr;
iPtr = tabella;
for(i = 0; i < 2*5; i++)
printf("%d
" ,iPtr[i];
In genere i compilatori C sono in grado di gestire array multidimensionali senza un limite teorico (a parte la disponibilità di memoria) al numero di dimensioni. E' tuttavia infrequente, per gli utilizzi più comuni, andare oltre la terza dimensione.