Array Dinamici in Object Pascal
- Gli array dinamici in Object Pascal sono array la cui dimensione può essere modificata durante l'esecuzione del programma.
- Gli array dinamici sono allocati dinamicamente nella memoria heap, permettendo una gestione più efficiente della memoria rispetto agli array statici.
- La procedura
SetLength
viene utilizzata per allocare e ridimensionare gli array dinamici. - Gli array dinamici partono sempre da un indice zero e non supportano limiti inferiori non-zero o indici non-interi.
- La memoria degli array dinamici viene gestita automaticamente dal compilatore, riducendo il rischio di errori di memoria rispetto alla gestione manuale richiesta in altri linguaggi come C.
Array Dinamici
Nel Pascal classico gli array avevano dimensioni fisse e limitati al numero di elementi quando si dichiarava il tipo di dati. Object Pascal, invece, ha introdotto il supporto per gli array dinamici.
Gli array dinamici sono allocati dinamicamente nella memoria. Ciò significa che la memoria per gli elementi dell'array viene allocata sull'heap al momento dell'esecuzione, quando si conosce la dimensione richiesta. Inoltre, permettono anche di ridimensionare l'array durante la vita della variabile array, se necessario.
Gli array dinamici, inoltre, hanno il conteggio dei riferimenti (rendendo il passaggio di parametri molto più veloce, poiché viene passato solo il riferimento, e non una copia dell'array completo). Con questo meccanismo, più variabili possono fare riferimento allo stesso array, senza che esso debba essere copiato. Quando, ad esempio, si assegna un array dinamico a un'altra variabile, entrambe le variabili puntano allo stesso array in memoria.
Una volta terminato, è possibile cancellare un array impostandone la variabile su nil
o impostando la lunghezza a zero, e poiché gli array dinamici hanno conteggio dei riferimenti il compilatore libererà automaticamente la memoria. Si noti che questo è vero per la memoria utilizzata per gli elementi dell'array: se l'array contiene riferimenti ad altre posizioni di memoria (come riferimenti a oggetti) è necessario assicurarsi di pulire la memoria utilizzata da quegli oggetti prima di liberare l'array stesso.
Differenza rispetto al linguaggio C
La differenza principale tra gli array dinamici in Object Pascal e gli array allocati dinamicamente in C è che in C si deve gestire manualmente la memoria, allocandola con malloc
e liberandola con free
. In Object Pascal, invece, il compilatore gestisce automaticamente la memoria per gli array dinamici, rendendo il codice più sicuro e meno soggetto a errori di memoria.
Con un array dinamico, si dichiara un tipo di array senza specificare il numero di elementi e poi si alloca la dimensione dell'array utilizzando la procedura SetLength
:
var
Array1: array of Integer;
begin
// A questo punto l'array non è ancora allocato
// La riga commentata che segue causerebbe un errore
// Array1[0] := 100;
// Ora allochiamo l'array con 10 elementi
SetLength(Array1, 10);
// Ora l'array è allocato e possiamo usarlo
Array1[0] := 100;
// ...
Non è possibile utilizzare l'array fino a quando non si è assegnata la sua lunghezza, allocando la memoria richiesta sull'heap. Se si procede comunque, si otterrebbe un errore di Range Check (se la corrispondente opzione del compilatore è attiva) o un Access Violation su Windows o un errore di segmentation fault su Linux.
La chiamata a SetLength
imposta tutti i valori a zero. Il codice di inizializzazione rende possibile iniziare a leggere e scrivere valori dell'array immediatamente, senza alcun timore di errori di memoria (a meno che non si violino i confini dell'array).
Se si ha bisogno di allocare memoria esplicitamente, non è necessario liberarla direttamente. Nel frammento di codice sopra, quando il codice termina e la variabile Array1
esce dall'ambito, il compilatore libererà automaticamente la sua memoria (in questo caso i dieci interi che sono stati allocati). Quindi, mentre è possibile assegnare una variabile array dinamico a nil
o chiamare SetLength
con 0, questo generalmente non è necessario e raramente viene effettuato.
Array Dinamici
In Object Pascal un array dinamico è un array la cui dimensione può essere modificata durante l'esecuzione del programma.
Per dichiarare un array dinamico, si utilizza la sintassi:
var
NomeArray: array of TipoDiDato;
Dopo aver dichiarato l'array, è necessario allocare la memoria per esso utilizzando la procedura SetLength
, specificando il numero di elementi desiderati:
SetLength(NomeArray, NumeroDiElementi);
Solo dopo questa chiamata l'array è pronto per essere utilizzato.
Un'importante differenza che gli array dinamici hanno rispetto a quelli statici è che l'indice di partenza è sempre zero:
Gli array dinamici partono sempre da zero
In Object Pascal l'indice di un array dinamico inizia invariabilmente da zero e va fino al numero di elementi meno uno. In altre parole, gli array dinamici non supportano due caratteristiche degli array Pascal statici classici, il limite inferiore non-zero e gli indici non-interi. Allo stesso tempo, corrispondono più da vicino a come funzionano gli array nella maggior parte dei linguaggi basati sulla sintassi C.
Modificare la Dimensione di un Array Dinamico
La procedura SetLength
può essere utilizzata anche per ridimensionare un array.
In tal caso, il risultato finale sarà:
- Un array più grande, se la nuova dimensione è maggiore della dimensione corrente; gli elementi preesistenti manterranno i loro valori, e i nuovi elementi saranno inizializzati a zero.
- Un array più piccolo, se la nuova dimensione è minore della dimensione corrente; gli elementi che eccedono la nuova dimensione saranno eliminati e, quindi, perduti.
Per interrogare la dimensione corrente di un array dinamico, è possibile, come con gli array statici, utilizzare le funzioni Length
, High
e Low
. Per gli array dinamici, tuttavia, Low
restituisce sempre 0, e High
restituisce sempre la lunghezza meno uno. Questo implica che, per un array vuoto, High
restituisce -1, ossia un valore inferiore a quello restituito da Low
.
Proviamo a mettere insieme tutti i concetti visti finora in un esempio. Supponiamo di voler realizzare un programma che calcola la media e la varianza di un array dinamico. Questo programma chiede in input la dimensione e gli elementi dell'array, e poi calcola e mostra i risultati. Ecco il codice completo:
program StatisticaArrayDinamico;
uses
SysUtils;
var
Valori: array of Double;
Somma, SommaQuad, Media, Varianza: Double;
I, N: Integer;
begin
{ Chiede in input la dimensione dell'array }
Write('Quanti valori vuoi inserire? ');
ReadLn(N);
{ Alloca l'array con la dimensione specificata }
SetLength(Valori, N);
{ Chiede in input gli elementi dell'array }
for I := 0 to High(Valori) do
begin
Write(Format('Valore [%d]: ', [I]));
ReadLn(Valori[I]);
end;
{ Calcola la somma e la somma dei quadrati }
Somma := 0;
SommaQuad := 0;
for I := 0 to High(Valori) do
begin
Somma := Somma + Valori[I];
SommaQuad := SommaQuad + Valori[I] * Valori[I];
end;
{ Calcola la media e la varianza }
Media := Somma / Length(Valori);
Varianza := (SommaQuad / Length(Valori)) - (Media * Media);
{ Mostra i risultati }
WriteLn(Format('Media: %.2f', [Media]));
WriteLn(Format('Varianza: %.2f', [Varianza]));
end.
Provando a compilare ed eseguire questo programma, si otterrà un output simile al seguente:
Quanti valori vuoi inserire? 5
Valore [0]: 10
Valore [1]: 20
Valore [2]: 30
Valore [3]: 40
Valore [4]: 50
Media: 30.00
Varianza: 200.00
Si noti che in questo esempio abbiamo adoperato la funzione Format
per formattare le stringhe di output, che è una funzione molto utile della unità SysUtils
. La studieremo nel dettaglio nelle prossime lezioni.
Copia e Assegnazione di Array Dinamici
Un punto importante da comprendere sugli array dinamici è la differenza tra copia e assegnazione.
Chiariamo con un esempio. Supponiamo di avere due array dinamici di interi:
var
IntArray1: array of Integer;
IntArray2: array of Integer;
E supponiamo di aver allocato e inizializzato IntArray1
con alcuni valori:
SetLength(IntArray1, 5);
IntArray1[0] := 1;
IntArray1[1] := 2;
IntArray1[2] := 3;
IntArray1[3] := 4;
IntArray1[4] := 5;
Se proviamo ad assegnare IntArray1
a IntArray2
:
IntArray2 := IntArray1;
Ci si potrebbe aspettare che IntArray2
contenga una copia indipendente di IntArray1
, ma ciò non è vero. In realtà, dopo questa assegnazione, entrambe le variabili puntano allo stesso array in memoria. Quindi, se si modifica un elemento di IntArray2
, si influenzerà anche IntArray1
, e viceversa. In altre parole, assegnare un array dinamico a un altro crea un alias, non una copia.
Infatti, se andiamo a modificare un elemento di IntArray2
:
IntArray2[0] := 100;
Anche IntArray1[0]
sarà 100, perché entrambi puntano allo stesso array.
Per verificarlo, basta compilare ed eseguire il seguente codice completo:
program ArrayDinamiciAssegnazione;
uses
SysUtils;
var
IntArray1: array of Integer;
IntArray2: array of Integer;
begin
{ Alloca e inizializza IntArray1 }
SetLength(IntArray1, 5);
IntArray1[0] := 1;
IntArray1[1] := 2;
IntArray1[2] := 3;
IntArray1[3] := 4;
IntArray1[4] := 5;
{ Assegna IntArray1 a IntArray2 (alias) }
IntArray2 := IntArray1;
{ Modifica un elemento di IntArray2 }
IntArray2[0] := 100;
{ Mostra i valori di entrambi gli array }
for var I := 0 to High(IntArray1) do
begin
WriteLn(Format('IntArray1[%d] = %d, IntArray2[%d] = %d',
[I, IntArray1[I], I, IntArray2[I]]));
end;
end.
Il risultato sarà:
IntArray1[0] = 100, IntArray2[0] = 100
IntArray1[1] = 2, IntArray2[1] = 2
IntArray1[2] = 3, IntArray2[2] = 3
IntArray1[3] = 4, IntArray2[3] = 4
IntArray1[4] = 5, IntArray2[4] = 5
Assegnazione di Array Dinamici
In Object Pascal, assegnare un array dinamico a un altro crea un alias, non una copia. Entrambe le variabili puntano allo stesso array in memoria.
Modificare un elemento di una delle variabili influenzerà l'altra.
Per copiare effettivamente un array dinamico in un altro, è possibile utilizzare la funzione Copy
, che crea una nuova copia dell'array in memoria. La funzione Copy
accetta tre parametri: l'array da copiare, l'indice di partenza e il numero di elementi da copiare. Pertanto, Copy
permette di copiare anche solo una porzione di un array:
array_destinazione := Copy(array_sorgente, indice_inizio, numero_elementi);
Un altro punto importante è che Copy
provvede in automatico alla gestione della memoria per l'array di destinazione, quindi non è necessario chiamare SetLength
prima di usarla.
Chiariamo, modificando l'esempio precedente per utilizzare Copy
:
program ArrayDinamiciCopia;
uses
SysUtils;
var
IntArray1: array of Integer;
IntArray2: array of Integer;
begin
{ Alloca e inizializza IntArray1 }
SetLength(IntArray1, 5);
IntArray1[0] := 1;
IntArray1[1] := 2;
IntArray1[2] := 3;
IntArray1[3] := 4;
IntArray1[4] := 5;
{ Copia IntArray1 in IntArray2 (copia indipendente) }
IntArray2 := Copy(IntArray1, 0, Length(IntArray1));
{ Modifica un elemento di IntArray2 }
IntArray2[0] := 100;
{ Mostra i valori di entrambi gli array }
for var I := 0 to High(IntArray1) do
begin
WriteLn(Format('IntArray1[%d] = %d, IntArray2[%d] = %d',
[I, IntArray1[I], I, IntArray2[I]]));
end;
end.
Il risultato ora sarà:
IntArray1[0] = 1, IntArray2[0] = 100
IntArray1[1] = 2, IntArray2[1] = 2
IntArray1[2] = 3, IntArray2[2] = 3
IntArray1[3] = 4, IntArray2[3] = 4
IntArray1[4] = 5, IntArray2[4] = 5
Come si può vedere, ora IntArray1
e IntArray2
sono indipendenti, e modificare uno non influenza l'altro.
Copia di Array Dinamici
In Object Pascal, per creare una copia indipendente di un array dinamico, si utilizza la funzione Copy
. Questa funzione crea un nuovo array in memoria con gli elementi specificati dall'array sorgente:
array_destinazione := Copy(array_sorgente, indice_inizio, numero_elementi);
Assegnazione di Array Costanti e Concatenazione
In Object Pascal è inoltre possibile assegnare un array costante ad un array dinamico, e concatenare due o più array dinamici. Vediamo queste due operazioni in ordine.
Tipicamente l'inizializzazione di un array dinamico avviene in due passaggi:
- Impostazione della lunghezza con
SetLength
; - Inizializzazione degli elementi uno per uno.
Quindi il codice tipico per inizializzare un array dinamico di interi con i valori da 1 a 5 sarebbe:
ArrayDinamico: array of Integer;
I: Integer;
begin
SetLength(ArrayDinamico, 5);
ArrayDinamico[0] := 1;
ArrayDinamico[1] := 2;
ArrayDinamico[2] := 3;
ArrayDinamico[3] := 4;
ArrayDinamico[4] := 5;
Possiamo però semplificare questo codice utilizzando un array costante, che in Object Pascal si dichiara racchiudendo gli elementi tra parentesi quadre:
ArrayDinamico: array of Integer;
begin
ArrayDinamico := [1, 2, 3, 4, 5];
Questo codice non solo è più compatto e semplice da leggere, ma è anche più efficiente, perché l'array viene allocato e inizializzato in un solo passaggio.
Inizializzazione di Array Dinamici con Array Costanti
In Object Pascal, è possibile inizializzare un array dinamico utilizzando un array costante. La sintassi è la seguente:
ArrayDinamico := [elemento1, elemento2, elemento3, ...];
L'array risultante avrà la lunghezza e gli elementi specificati nell'array costante.
Un'altra operazione utile sugli array dinamici è la concatenazione, che permette di unire due o più array dinamici in un unico array. In Object Pascal, la concatenazione si effettua utilizzando l'operatore +
:
var
Array1, Array2, ArrayConcatenato: array of Integer;
begin
Array1 := [1, 2, 3];
Array2 := [4, 5, 6];
ArrayConcatenato := Array1 + Array2; // Risultato: [1, 2, 3, 4, 5, 6]
L'operazione di concatenazione è talmente flessibile che permette di:
- Concatenare due array dinamici;
- Concatenare un array dinamico con un array costante;
- Concatenare più array dinamici e costanti in una singola espressione.
Ad esempio, le seguenti righe di codice sono tutte valide:
var
Array1, Array2, Array3, ArrayRisultato: array of Integer;
begin
Array1 := [1, 2, 3];
Array2 := [4, 5];
Array3 := [6, 7, 8];
ArrayRisultato := Array1 + Array2 + Array3;
ArrayRisultato := Array1 + [9, 10]; // Concatenazione mista
Inoltre, il risultato di una concatenazione può essere assegnato direttamente all'array originale, permettendo di espandere un array dinamico con nuovi elementi:
var
ArrayDinamico: array of Integer;
begin
ArrayDinamico := [1, 2, 3];
ArrayDinamico := ArrayDinamico + [4, 5]; // Ora ArrayDinamico è [1, 2, 3, 4, 5]
Concatenazione di Array Dinamici
In Object Pascal, è possibile concatenare due o più array dinamici utilizzando l'operatore +
. La sintassi è la seguente:
ArrayConcatenato := Array1 + Array2 + ... + ArrayN;
L'operazione di concatenazione può includere sia array dinamici che array costanti, e il risultato è un nuovo array dinamico contenente tutti gli elementi in ordine.