Barninga Z
a- a+

I puntatori C: gli array

PUNTATORI 

Gli array 

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. 

 



Ti potrebbe interessare anche

commenta la notizia

C'è 1 commento
Pier Paolo
Condividi le tue opinioni su questo articolo!