Array Dinamici in Object Pascal

Concetti Chiave
  • 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.

Nota

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.

Definizione

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:

Definizione

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
Definizione

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.

Definizione

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:

  1. Impostazione della lunghezza con SetLength;
  2. 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.

Definizione

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]
Definizione

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.