|
Array
Molto
spesso nei programmi si ha l'esigenza di manipolare
un gruppo di variabili dello stesso tipo che contengono
valori tra loro correlati. Immaginiamo di voler scrivere
un programma che calcoli la media delle temperature
giornaliere; se vogliamo prendere in considerazione
6 misurazioni all'ora, una ogni 10 minuti, ci serviranno
ben 144 variabili.
int
temp1 = 15; // ore 0.00
int temp2 = 16; // ore 0.10
int temp3 = 16; // ore 0.20
int temp4 = 16; // ore 0.30
....
int temp144 = 14; // ore 11.50
Oltre
alla scarsa praticità di dover dichiarare 144
variabili, non esiste nessun modo pratico per effettuare
dei calcoli che abbraccino tutto l'insieme dei valori:
l'unico modo per calcolare la media sarebbe quello di
realizzare una gigantesca espressione artimetica del
tipo
int
media = (temp1 + temp2 + temp3 + ....+ temp143 + temp144)
/ 144;
Per
questo tipo di operazioni, è utile ricorrere
ad un array, uno strumento concettualmente simile ad
una tabella, che accomuna sotto un unico nome un insieme
di variabili dello stesso tipo:
int[]
temp = new int[144];
La
creazione e l'utilizzo degli Array presenta delle differenze
rispetto all'utlizzo delle normali variabili. Vediamole
di seguito una ad una.
Dichiarazione
di array
La
dichiarazione di un array ha una sintassi un po' più
complessa della dichiarazione di variabile semplice.
Come per le variabili semplici dobbiamo indicare un
tipo ed un nome, con la differenza che, dopo aver specificato
il tipo, è necessario postporre una coppia di
parentesi quadre.
int[]
vettoreDiInteri;
Assegnamento
La
variabile 'vettoreDiInteri' appena dichiarata non è
un vettore, ma solamente un reference ad un vettore.
Il vettore vero e proprio è un oggetto di memoria
separato, che deve essere definito opportunamente. Prima
di essere inizializzata, essa ha il valore 'null', un
valore costante che indica che la variabile non referenzia
alcun vettore. Per creare un vettore dobbiamo ricorrere
alla parola riservata 'new', come nell'esempio seguente:
vettoreDiInteri
= new int[10];
Il
valore specificato tra parentesi quadre è la
dimensione del vettore: è possibile specificare
un qualunque valore intero positivo. Il vettore appena
creato è formato da dieci elementi, inizializzati
a zero.

Figura
1 - Un vettore è un oggetto di memoria composto
da un
certo numero di elementi, ognuno dei quali può
contenere un valore
Dereferenziazione
La
dereferenziazione è l'operazione che permette
di assegnare un valore ad un elemento del vettore. Per
dereferenziare un elemento di un vettore, occorre specificare
il nome del vettore seguito dal numero dell'elemento
tra parentesi quadre:
vettoreDiInteri[1]
= 10;
Gli
elementi di un vettore si contano a partire da zero:
pertanto se vogliamo assegnare il valore 27 al decimo
elemento del vettore dobbiamo scrivere:
vettoreDiInteri[9]
= 27;

Figura
2 - Lo stesso vettore dopo aver
dereferenziato il secondo e il decimo elemento
Differenza
tra Assegnamento e Dereferenziazione
Bisogna
fare molta attenzione a capire la differenza tra dereferenziazione
ed assegnamento. La dereferenziazione è un'operazione
indiretta: essa non opera sulla variabile, ma sull'oggetto
di memoria puntato da essa. Se noi creiamo una nuovo
ref ed eseguiamo un assegnamento pari al ref di un vettore
già esistente, ci troviamo nella situazione in
cui due variabili puntano allo stesso vettore, come
nell'esempio seguente, esemplificato dalla Figura 3:
int[]
vettoreDiInteri2;
vettoreDiInteri2 = vettoreDiInteri;

Figura 3 - Due variabili che fanno riferimento allo
stesso vettore.
In
una situazione come questa le operazioni vettoreDiInteri[5]
= 10 e vettoreDiInteri[5] = 10 avranno entrambe il risultato
di porre a 10 l'elemento numero 5 dell'unico vettore
puntato dalle due variabili. Per procedere alla effettiva
copia di un vettore, è necessario dapprima creare
un vettore delle stesse dimensioni, quindi copiare uno
ad uno gli elementi del primo nel secondo. Questa operazione
può essere eseguita con un ciclo while, come
si vede nell'esempio seguente:
//
crea un vettore e lo inizializza
int[] v1 = new int[5];
v1[0] = 10;
v1[1] = 12;
v1[2] = 14;
v1[3] = 16;
v1[4] = 18;
// crea un vettore della stessa dimensione di v1
int[] v2 = new int[5];
int
i = 0;
while(i < v1.lengt) {
// copia il valore della i-esima cella
// di v1 nella i-esima cella di v2
v2[i]
= v1[i];
}
Inizializzazione automatica di un
vettore
Un
vettore può essere inizializzato con una serie
di valori, in modo simile a come si può fare
con le variabili. L'istruzione:
int[]
vettore = {10,12,14,16,18};
equivale
alla sequenza:
int[]
vettore = new int[5];
vettore[0] = 10;
vettore[1] = 12;
vettore[2] = 14;
vettore[3] = 16;
vettore[4] = 18;
Lunghezza
di un vettore
I
vettori creati con l'operatore 'new' hanno esattamente
la dimensione specificata nella dichiarazione. Gli indici
sono numerati a partire da 0, per cui l'ultimo elemento
avrà indice pari alla dimensione del vettore
meno uno. Ad esempio, in un vettore da 10 elementi,
gli indici sono compresi tra 0 e 9. Il programmatore
può essere interessato a conoscere la dimensione
di un array a runtime: per questo scopo ogni vettore
dispone di un'apposita variabile 'lenght', accessibile
attraverso l'operatore '.':
int
vettoreDiInteri[] = new int[10];
System.out.print("La dimensione del vettore è
");
System.out.println(vettoreDiInteri.length);
Un
esempio di manipolazione di vettori
Il
vettore è uno strumento potentissimo, che permette
di lavorare su porzioni di memoria anche molto grandi
usando un numero ridotto di istruzioni. I cicli while
permettono di valutare uno ad uno gli elementi di un
array, e di effettuare qualche tipo di operazione su
di essi. Per calcolare la media dei valori contenuti
in un ipotetico vettore 'vettoreDiInteri', posso utilizzare
un frammento di codice di questo tipo:
int
i = 0;
int somma = 0;
int media = 0;
while(i<vettoreDiInteri.lengt)
{
somma = somma + vettoreDiInteri[i];
i++;
}
media = somma / sommaDiInteri.length;
Il
seguente esempio permette di togliersi una soddisfazione:
quella di scrivere un programma che sfrutti una gran
parte della memoria del nostro computer. Per consumare
una grande quantità di memoria con un solo programma,
esistono quattro approcci di base:
-
Scrivere un programma enorme che effettui calcoli
banali
- Scrivere
un programma piccolo che esegua calcoli complessi
- Scrivere
un programma banale che esegua calcoli banali su un
grande numero di valori
-
Utilizzare un mix delle tecniche precedenti.
La
disponibilità di memoria ram a basso costo permette
oggi di assemblare PC con quantità tali di memoria
RAM da rendere sempre più difficile raggiungere
l'obbiettivo con la prima di queste tecniche. La seconda,
tipica dei programmi di manipolazione di immagini, non
è adatta ad un corso base di programmazione.
Fortunatamente la terza di queste tecniche risulta essere
perfetta in questa circostanza.
Un
vettore di byte di dimensione 1024 occupa esattamente
un K-byte di memoria. Un vettore di int della stessa
dimensione ne occupa 4, dal momento che un int è
grande 4 byte. Se voglio creare un vettore di interi
da 64 megabyte, posso calcolarne la dimensione moltiplicando
64 per 1048576 (pari a 1024 al quadrato), e dividendo
per quattro. Una volta creato un simile vettore, lo
posso riempire di valori casuali scelti tra 0 e 10000;
infine posso calcolare la somma di tutti i valori, e
la relativa media aritmetica. Si noti l'uso di una variabile
di tipo 'long' per memorizzare la somma di tutti i numeri:
è facile comprendere che una variabile intera
non sarebbe sufficiente.
public
class MemoryConsumer {
public static void main(String argv[]) {
long sum = 0;
long average = 0;
// calcola la dimensione del
vettore.
// Se si dispone di poca memoria,
ridurre
// il valore della variabile
megabytes.
int megaBytes = 64;
int dim = megaBytes * 1048576
/ 4;
int[] bigArray = new int[dim];
// riempie il vettore di valori
casuali
int i = 0;
while ( i < bigArray.length
) {
bigArray[i] = (int)(32000
* Math.random());
i++;
}
// calcola la somma di tutti
i valori
i = 0;
while ( i < bigArray.length
) {
sum = sum + bigArray[i];
i++;
}
// calcola la media
average = sum / bigArray.length;
// stampa i risultati
System.out.print("La somma
dei numeri presenti nel vettore è");
System.out.println(sum);
System.out.print("La media
della somma dei numeri presenti nel vettore
è ");
System.out.println(average);
}
}
Il
programma deve essere salvato, come di consueto, in
un file dal nome "MemoryConsumer.java"; per
compilarlo bisogna digitare il comando
javac
MemoryConsumer.java
mentre
per eseguirlo bisogna ricorrere all'istruzione
java
MemoryConsumer
Dopo
qualche istante, il programma stamperà un output
del tipo:
La somma di tutti i numeri presenti nel vettore è
239999200405
La media della somma di tutti i numeri presenti nel
vettore è 15999
Per
poter eseguire questo programma, è necessario
disporre di un computer con almeno 256 MB di ram. Se
non si dispone di memoria sufficiente, il computer segnalerà
un errore:
java.lang.OutOfMemoryError
<<no stack trace available>>
Exception in thread "main"
In
questo caso si provi a diminuire il valore della variabile
'megaBytes', portandolo ad esempio a 32, quindi si ricompili
ed esegua.
Vettori
multidimensionali
Il
linguaggio Java consente di creare vettori bi-dimensionali,
ricorrendo ad una sintassi del tipo:
int
i[][] = new int[10][15];
I
vettori bidimensionali sono concettualmente simili ad
una tabella rettangolare, dotata di righe e colonne.
Il seguente programma riprende l'idea delle tabelline
pitagoriche, creando dapprima una rappresentazione in
un vettore bidimensionale, quindi stampando quest'ultimo
sullo schermo:
public
class Tabelline2 {
public
static void main(String argv[]) {
int[][] tabellina = new int[11][11];
// crea la tabellina in un vettore
bidimensionale
int i = 0;
int j = 0;
while(i <= 10) {
while(j <= 10)
{
int
prodotto = i*j;
tabellina[i][j]
= prodotto;
j =
j + 1;
}
i = i + 1;
j = 0;
}
// stampa il contenuto del vettore
i = 0;
j = 0;
while(i <= 10) {
while(j <= 10)
{
int
prodotto = i*j;
System.out.print(tabellina[i][j]);
System.out.print("\t");
j =
j + 1;
}
i = i + 1;
j = 0;
System.out.println();
}
}
}
Allo
stesso modo è possibile definire vettori con
un numero qualunque di dimensioni:
int
v1[][][] = new int[10][15][5];
int v2[][][][] = new int[10][15][12][5];
Tali
strutture, in ogni caso, risultano decisamente poco
utilizzate.
Vettori
incompleti
I
vettori n-dimensionali vengono implementati in Java
come array di array. Questa scelta implementativa rende
possibile la realizzazione di tabelle non rettangolari,
come nell'esempio seguente:
//
crea un vettore con una componente incompleta
int tabella[][] = new int[5][];
tabella[0]
= new int[3];
tabella[1] = new int[2];
tabella[2] = new int[5];
tabella[3] = new int[2];
tabella[4] = new int[6];

Figura 4 - Rappresentazione in memoria di un vettore
non rettangolare
Anche
in questo caso siamo in presenza di un costrutto scarsamente
utilizzato, che tuttavia vale la pena conoscere.
Inizializzazione automatica di un
vettore multidimensionale
Un
vettore può essere inizializzato con una serie
di valori, in modo simile a come si può fare
con i vettori semplici. Ovviamente è necessario
ricorrere ad un costrutto un po' più complesso,
che tenga conto della particolare struttura di questi
vettori. La seguente istruzione, ad esempio, crea un
vettore a tre componenti in verticale, in cui la prima
riga ha tre colonne, la seconda due e la terza quattro,
e contemporaneamente inizializza gli elementi con i
valori specificati, come si può vedere in figura
5:
int[]
vettore = { { 10,12,14},{16,18},{20,22,24,26}};

Figura 5 - Un altro esempio di vettore non rettangolare
Conclusioni
Questo
mese abbiamo trattato i vettori, una classica struttura
dati comune a quasi tutti i linguaggi di programmazione.
Oltre all'uso elementare, abbiamo analizzato alcune
particolarità dei vettori in Java, come la possibilità
di definire vettori multidimensionali, vettori incompleti
e vettori non rettangolari. Il mese prossimo cominceremo
a guardare i costrutti di programmazione di Java non
ancora introdotti.
|