Logo

Marco Cantù
Essential Pascal


Tradotto dall'inglese da: Paolo Rossi

Capitolo 6
Procedure e funzioni

Un'altra importante idea messa in risalto dal Pascal e' il concetto di routine, fondamentalmente una serie di istruzioni con un nome unico, le quali possono essere attivate diverse volte usando il loro nome. In questo modo si evita di ripetere le stesse istruzioni piu' volte, avendo quindi una stessa versione del codice che puo' essere modificato per tutto il programma. Da questo punto do vista, si puo' pensare ad una routine come un meccanismo di base di incapsulazione. Tornero' su questo argomento con un esempio dopo aver introdotto la sintassi per le routine in Pascal.

Procedure e Funzioni Pascal

In Pascal, una routine puo' assumere due forme: una procedura e una funzione. In teoria, una procedura e' un'operazione chiesta al computer, una funzione e' un calcolo che ritorna un valore. Questa differenza e' accentuata dal fatto che una funzione ha un risultato, un valore di ritorno, mentre una procedura non lo ha. Entrambi i tipi di routine possono avere piu' parametri, di un certo tipo.

In pratica, comunque, la differenza tra funzioni e procedure e' molto limitata: si puo' chiamare una funzione per compiere un certo lavoro ed ignorare il risultato (che puo' essere un codice d'errore opzionale o qualcosa di simile) oppure si puo' invocare una procedura che passa un risultato tramite un suo parametro (altro materiale sui parametri per riferimento piu' avanti in questo capitolo).

Ecco le definizioni di una procedura e due versioni della stessa funzione, usando una sintassi leggermente differente:

procedure Hello;
begin
  ShowMessage ('Hello world!');
end;

function Double (Value: Integer) : Integer;
begin
  Double := Value * 2;
end;

// or, as an alternative
function Double2 (Value: Integer) : Integer;
begin
  Result := Value * 2;
end;

L'uso di Result invece del nome della funzione per assegnare il valore di ritorno, e' diventato abbastanza popolare e, secondo me, tende a rendere il codice piu' leggibile.

Una volta che queste routine sono state definite, si possono chiamare una o piu' volte. Si chiama una procedura per eseguire un lavoro, e si chiama una funzione per calcolare un valore:

procedure TForm1.Button1Click (Sender: TObject);
begin
  Hello;
end;
 
procedure TForm1.Button2Click (Sender: TObject);
var
  X, Y: Integer;
begin
  X := Double (StrToInt (Edit1.Text));
  Y := Double (X);
  ShowMessage (IntToStr (Y));
end;

Nota: Per il momento non ci si deve preoccupare della sintassi delle due procedure sopra, che sono effettivamente metodi. Semplicemente mettere due pulsanti su un form di Delphi, cliccare due volte su di essi a design-time e l'IDE di Delphi generera' il giusto codice di supporto: Adesso bisogna solo riempire le linee tra begin e end. Per compilare il codice sopra bisogna aggiungere anche un componente Edit al form.

Adesso possiamo ritornare sul concetto di incapsulazione che ho introdotto prima. Quando si chiama la funzione Double, nopn c'e' bisogno di conoscere l'algoritmo usato per implementarla. Se successivamente si trova un modo migliore di raddoppiare un numero, si puo' facilmente cambiare il codice della funzione, ma il resto codice rimarra' intatto (sebbene girera' piu' velocemente!). Lo stesso principio puo' essere applicato alla procedura Hello: Si puo' modificare l'output del programma cambiando il codice di questa procedura e il metodo Button2Click automaticamente cambiera' il suo effetto. Ecco come si puo' cambiare il codice:

procedure Hello;
begin
  MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;

Tip: Quando si chiama una procedura o funzione esistente o un metodo della VCL, bisogna ricordare il numero ed il tipo dei parametri. L'editor di Delphi aiuta suggerendo la lista di parametri di una funzione o procedura con hint appena si scrive il nome e si apre la parentesi. Questa funzionalita' e' chiamata Code Parameters ed e' parte della tecnologia Code Insight.

Parametri per Riferimento

Le routine Pascal consentono parametri passati per valore e per riferimento. Il passaggio di parametri per valore e' il default: il valore e' copiato sullo stack e le routine usano il e gestiscono la copia, non il valore originale.

Passare un parametro per riferimento significa il suo valore non e' copiato nello stack nel parametro formale della routine (evitando una copia spesso il programma e' piu' veloce). Il programma, invece, punta al valore originale, anche nel codice della routine. Questo permette alle procedure o funzioni di cambiare il valore di un parametro. I parametri passati per riferimento sono riconoscibili dalla keyword var.

Nota: Questa tecnica e' disponibile in diversi linguaggi di programmazione. Non e' presente in C, ma e' stata introdotta in C++, dove si usa il simbolo & (passaggio per riferimento). In Visual Basic qualsiasi parametro non specificato come ByVal e' passato per riferimento.

Ecco un esempio di passaggio per riferimento usando la keyword var:

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

In questo caso, il parametro e' usato sia per passare un valore alla procedura sia per ritornare un nuovo valore al codice chiamante. Quando si scrive:

var
  X: Integer;
begin
  X := 10;
  DoubleTheValue (X);

il valore della variabile X diventa 20, siccome la funzione usa un riferimento alla locazione di memoria originale della variabile X, cambiando il suo valore iniziale.

Passare parametri per riferimento ha senso per i tipi ordinali, per le stringhe vecchio stile e per record di dimensioni notevoli. Gli oggetti di Delphi, di fatto, sono invariabilmente passati per valore, siccome gli oggetti sono riferimenti. Per questa ragione passare un oggetto per riferimento ha poco senso (a parte casi speciali) , siccome e' come passare un riferimento ad un riferimento.

Le long string di Delphi hanno un comportamento leggermente differente: esse si comportano come riferimenti, ma se si cambia una delle variabili staringa che puntano alla stessa stringa in memoria, questa e' copiata prima dell'aggiornamento. Una long string passata come valore si comporta come un riferimento solamente in termini di occupazione di memoria e velocita' di operazione. Se invece si modifica il valore della stringa, il valore originale non e' cambiato. Al contrario, se si passa la long string per riferimento, si puo' cambiare il valore originale.

Nota: Delphi 3 introduce un nuovo tipo di parametro: out. Un parametro out non ha un valore iniziale ed e' usato solo per ritornare un valore. Questi parametri devono essere usati solo per procedure e funzioni COM, in generale e' meglio usare i piu' efficienti parametri var. Eccetto per non avere valore iniziale, un parametro out si comporta come un parametro var.

Parametri Constant

In alternativa ai parametri per riferimento, si possono usare i parametri const. Siccome non si puo' assegnare un nuovo valore ad un parametro costante nel corpo della routine, il compilatore puo' ottimizzare il passaggio dei parametri. Il compilatore puo' scegliere un approccio similare ai parametri per riferimento (o una const reference in termini di C++), ma il comportamento rimane simile ai parametri per valore, siccome il valore iniziale non puo' essere cambiato dalla routine.

Di fatto, se si tenta di compilare il seguente codice, Delphi generera' un errore:

function DoubleTheValue (const Value: Integer): Integer;
begin
  Value := Value * 2;      // compiler error
  Result := Value;
end;

Parametri Open Array

Diversamente dal C, una funzione o procedura Pascal ha sempre un numero fisso di parametri. Tuttavia, c'e' un modo per passare un numero variabile di parametri ad una routine usando un open array.

La definizione di base di un parametro open array e' quella di un tipo open array. Questo vuol dire che si indica il tipo del parametro ma non si conosce il numero di elementi di questo tipo che l'array contiene. Ecco un esempio di questo tipo di definizione:

function Sum (const A: array of Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(A) to High(A) do
    Result := Result + A[I];
end;

Usando High(A) si puo' conoscere la dimensione dell'array. Da notare anche l'uso del valore di ritorno della funzione, Result, per memorizzare valori temporanei. Si puo' chiamare questa funzione passandole un array di espressioni di tipo intero:

X := Sum ([10, Y, 27*I]);

Se si ha un array di interi, di qualsiasi dimensione, lo si puo' passare direttamente ad una routine che richiede un parametro open array o, invece, si puo' chiamare la funzione Slice per passare solo una porzione dell'array (come indicato dal suo secondo parametro). Ecco un esempio, dove l'array completo e' passato come parametro:

var
  List: array [1..10] of Integer;
  X, I: Integer;
begin
  // initialize the array
  for I := Low (List) to High (List) do
    List [I] := I * 2;
  // call
  X := Sum (List);

Se si cerca di passare solo una porzione dell'array alla funzione Slice, chiamarla in questo modo:

X := Sum (Slice (List, 5));
Si possono trovare tutti i frammenti di codice presenti in questa sezione nell'esempio OpenArr (vedere Fugura 6.1, piu' avanti, per il form).

Figura 6.1: L'esempio OpenArr quando il pulsante Partial Slice e' premuto

Nota: Gli open array con tipo in Delphi 4 sono completamente compatibili con gli array dinamici (introdotti in Delphi 4 e discussi nel capitolo 8). Gli array dinamici usano la stessa sintassi degli open array, con la differenza che si puo' usare una notazione del tipo array of Integer per dichiarare una variabile, non soltanto per passare un parametro.

Parametri Open Array Type-Variant

Oltre a questi tipi di open array, Delphi permette di definire open array di tipo variant o senza tipo. Questi particolari tipi di array hanno un nimero indefinito di valori, i quali possono essere utili per passare parametri.

Tecnicamente, la definizione di un array di costanti permette di passare un array con un numero indefinito di elementi di differente tipo ad una routine. Ad esempio, ecco la definizione della funzione Format (si vedra' il funzionamento di questa funzione nel Capitolo 7, riguardante le stringhe):

function Format (const Format: string;
  const Args: array of const): string;

Il secondo parametro e' un open array, il quale prende un numero di valori variabile. Di fatto, si puo' chiamare questa funzione nei seguenti modi:

N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);

Notare che si passa un parametro come valore costante, valore di una variabile ed espressione. Dichiarando una funzione di questo tipo e' semplice, ma come scriverla? Come conoscere i tipi dei parametri? I valori di un parametro open array di tipo variant sono compatibili con i valori di tipo TVarRec.

Nota: Non confondere il record TVarRec con il record TVarData usato dal typo Variant stesso. Queste due strutture hanno diversi scopi e non sono compatibili. Anche la lista dei possibili tipi e' differente, siccome TVarRec puo' contenere tipi di dati di Delphi, mentre TVarData puo' contenere tipi di dati OLE.

Il record TVarRec ha la seguente struttura:

type
  TVarRec = record
    case Byte of
      vtInteger:    (VInteger: Integer; VType: Byte);
      vtBoolean:    (VBoolean: Boolean);
      vtChar:       (VChar: Char);
      vtExtended:   (VExtended: PExtended);
      vtString:     (VString: PShortString);
      vtPointer:    (VPointer: Pointer);
      vtPChar:      (VPChar: PChar);
      vtObject:     (VObject: TObject);
      vtClass:      (VClass: TClass);
      vtWideChar:   (VWideChar: WideChar);
      vtPWideChar:  (VPWideChar: PWideChar);
      vtAnsiString: (VAnsiString: Pointer);
      vtCurrency:   (VCurrency: PCurrency);
      vtVariant:    (VVariant: PVariant);
      vtInterface:  (VInterface: Pointer);
  end;

Ogni possibile record ha il campo VType, anche se non e' facile vederlo al primo momento visto che e' dichiarato solo una volta, piu' il dato effettivo di dimensione integer (generalmente un riferimento o un puntatore).

Usando questa informazione si puo' effettivamente scrivere una funzione capace di operare su differenti tipi di dati. Nella funzione d'esempio SumAll, cerco di sommare valori di differenti tipi, trasformando le stringhe in interi, i caratteri nel corrispondente valore ordinale e aggiungendo 1 per il valore booleano True. Il codice e' basato sull'istruzione case, ed e' abbastanza semplice, anche se bisogna dereferenziare i puntatori abbastanza spesso:

function SumAll (const Args: array of const): Extended;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(Args) to High (Args) do
    case Args [I].VType of
      vtInteger: Result :=
        Result + Args [I].VInteger;
      vtBoolean:
        if Args [I].VBoolean then
          Result := Result + 1;
      vtChar:
        Result := Result + Ord (Args [I].VChar);
      vtExtended:
        Result := Result + Args [I].VExtended^;
      vtString, vtAnsiString:
        Result := Result + StrToIntDef ((Args [I].VString^), 0);
      vtWideChar:
        Result := Result + Ord (Args [I].VWideChar);
      vtCurrency:
        Result := Result + Args [I].VCurrency^;
    end; // case
end;

Ho aggiunto questo codice all'esempio OpenArr, il quale chiama la funzione SumAll quando un dato pulsante e' premuto:

procedure TForm1.Button4Click(Sender: TObject);
var
  X: Extended;
  Y: Integer;
begin
  Y := 10;
  X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);
  ShowMessage (Format (
    'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n', [X]));
end;

Si puo' vedere l'output di questa chiamata e il form dell'esempio OpenArr, nella Figura 6.2.

Figura 6.2: Il form dell'esempio OpenArr, con il message box visualizzato quando il pulsante "Untyped" e' premuto.

Convenzioni di chiamata in Delphi

La versione a 32 bit di Delphi ha introdotto un nuovo metodo per passare i parametri, conosciuto come fastcall: Dove possibile, fino a tre parametri possono essere passati nei registri della CPU, rendendo la chiamata alla funzione piu' veloce. La convenzione di chiamata fastcall (usata di Default in Delphi 3) e' indicata dalla keyword register.

Il problema e' che questa e' la convenzione di default e le funzioni che la usano non sono compatibili con Windows: le funzioni delle API Win32 deveno essere dichiarate usando la convenzione di chiamata stdcall, un misto tra la convenzione di chiamata originale del Pascal (usata in Win16) e la convenzione cdecl (usata dal linguaggio C).

Generalmente non ci sono ragioni per non usare la nuova convenzione di chiamata veloce, a meno che non si deve fare una chiamata esterna a Windows o definire una funzione callback. Si vedra' un esempio di usa della convenzione stdcall alla fine di questo capitolo. Si puo' trovare un sommario delle convenzioni di chiamate di Delphi nella guida in linea.

Cos'e' un Metodo?

Se si e' gia' lavorato in Delphi o letto i manuali, probabilmente il termine metodo e' gia' noto. Un metodo e' un tipo speciale di funzione o procedura che e' relazionata ad un tipo di dato. In Delphi, ogni volta che si gestisce un evento, bisogna definire un metodo, generalmente una procedura. In generale, comunque, il termine metodo e' usato per indicare sia funzioni sia procedure relazionate ad una classe.

Si sono gia' visti diversi metodi negli esempi fin qui esaminati. Ecco un metodo vuoto automaticamente aggiunto da Delphi nel codice sorgente di un form:

procedure TForm1.Button1Click(Sender: TObject);
begin
  {here goes your code}
end;

Dichiarazioni Forward

Quando bisogna usare un identificatore (di qualsiasi tipo), il compilatore deve avere gia' incontrato qualche tipo di dichiarazione per conoscere a cosa si riferisce. Per questa ragione, solitamente, bisogna costruire una dichiarazione completa prima di usare qualsiasi routine. Tuttavia, ci sono casi in cui questo non e' possibile. Se la procedura A chiama la procedura B e la procedura B chiama la procedura A, quando si comincia a scrivere i codice, ci troviamo a chiamare una routine della quale il compilatore non ha ancora incontrato nessuna dichiarazione.

Se si cerca di dichiarare l'esistenza di una procedura o funzione con un certo nome e certi parametri, senza provvedere l'effettivo codice, si puo' scrivere la procedura o funzione seguita dalla keyword forward:

procedure Hello; forward;

Piu' tardi, il codice deve fornire una completa dichiarazione della procedura, ma questa puo' essere chiamata anche prima di essere completamente definita. Ecco un semplice esempio, giusto per dare l'idea:

procedure DoubleHello; forward;

procedure Hello;
begin
  if MessageDlg ('Do you want a double message?',
      mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    DoubleHello
  else
    ShowMessage ('Hello');
end;

procedure DoubleHello;
begin
  Hello;
  Hello;
end;

Questo metodo permette di scrivere codice mutuamente ricorsivo: DoubleHello chiama Hello, ma Hello puo' comunque chiamare DoubleHello. Certo ci deve essere una condizione per terminare la recursione, per evitare uno stack overflow. Si puo' trovare questo codice, con leggeri cambiamenti, nell'esempio DoubleH.

Tuttavia una dichiarazione di procedura forward non e' molto comune in Delphi, c'e' un caso similare che e' molto piu' frequente. Quando si dichiara una procedura o funzione nella sezione di interfaccia di una unit (le unit verranno discusse nel prossimo capitolo), e' considerata una dichiarazione forward, anche se la keyword forward non e' presente. In realta' non si puo' scrivere il corpo di una routine nella sezione d'interfaccia di una unit. Allo stesso tempo, bisogna fornire nella stessa unit la vera implementazione di qualsiasi routine che e' stata dichiarata.

La stessa cosa avviene per la dichiarazione di un metodo all'interno di una classe che e' automaticamente generata da Delphi (come quando si aggiunge un evento ad un form o ad i suoi componenti). I gestori d'evento dichiarati nella classe TForm sono dichiarazioni forward: il codice sara' fornito nella sezione di implementazione della unit. Ecco uno stralcio del codice sorgente di un precedente esempio, con la dichiarazione del metodo Button1Click:

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

Tipi Procdurali

Un altra funzionalita' unica dell'Object Pascal e' la presenza dei tipi procedurali. Questo e' veramente un argomento avanzato, che solamente un numero ristretto di programmatori Delphi usa regolarmente. Comunque, siccome discuteremo argomenti simili nel prossimo capitolo (in modo specifico method pointers, una tecnica pesantemente usata in Delphi), vale la pena di dare un'occhiata. I programmatori inesperti, possono saltare questa sezione per adesso e ritornarvi successivamente.

In Pascal, c'e' il concetto di tipo procedurale (che e' similare al concetto di functio pointer del linguaggio C). La dichiarazione di un tipo procedurale indica la lista di parametri ed eventualmente il tipo ritornato nel caso di una funzione. Ad esempio, si puo' dichiarare la procedura con un parametro intero passato per riferimento come in:

type
  IntProc = procedure (var Num: Integer);

Questo tipo procedurale e' compatibile con qualsiasi routine che ha esattamente gli stessi parametri (o la stessa function signature, per usare un termine C). Ecco un esempio di una routine compatibile:

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

Nota: Nelle versioni a 16 bit di Delphi, le routine devono essere dichiarate usando la direttiva far per essere usate come effettivi valori di tipo procedurale.

I tipi procedurali possono essere usati per due diversi scopi: si possono dichiarare variabili di tipo procedurale o passare un tipo procedurale (un puntatore a funzione) come parametro ad un'altra routine. Con i tipi e le dichiarazioni di procedure gia' viste, si puo' scrivere questo codice:

var
  IP: IntProc;
  X: Integer;
begin
  IP := DoubleTheValue;
  X := 5;
  IP (X);
end;

Questo codice ha lo stesso effetto della seguente versione piu' breve:

var
  X: Integer;
begin
  X := 5;
  DoubleTheValue (X);
end;

La prima versione e' chiaramente piu' complessa, quindi perche' usarla? In alcuni casi, poter decidere quale funzione chiamare e chiamarla realmente piu' tardi puo' essere utile. E' possibile costruire un esempio complesso che mostri questo approccio. Ad ogni modo, preferisco mostrare un esempio abbastanza semplice chiamato ProcType. Questo esempio e' piu' complesso di quelli visti finora, per rendere la cosa piu' realistica.

Creare semplicemente un nuovo progetto e mettere due radio button, un pulsante, e due label nel form, come mostrato in Figura 6.3. Questo esempio e' basato su due procedure. Una procedura e' usata per raddoppiare il valore del parametro. Questa procedura e' simile a quella del precedente esempio in questa sezione. La seconda procedura e' usata per triplicare il valore del parametro e quindi chiamata TripleTheValue:

Figura 6.3: Il form dell'esempio ProcType.

procedure TripleTheValue (var Value: Integer);
begin
  Value := Value * 3;
  ShowMessage ('Value tripled: ' + IntToStr (Value));
end;

Entrambe le procedure mostrano quello che succede, per avvisare che sono state chiamate. Questa e' una semplice tecnica di debug che si puo' usare per testare se o quando una certa porzione di codice e' eseguita, invece di aggiungere un breakpoint.

Ogni volta che un utente preme il pulsante Apply, una delle due procedure e' eseguita, dipendendo dallo status dei radio button. Di fatto, quando si hanno due radio button in un form, solo uno dei due puo' esser selezionato nello stesso momento. Questo codice puo' essere implementato testando il valore dei radio button nel codice dell'evento OnClick del pulsante Apply. Per dimostrare l'uso dei tipi procedurali, ho invece usato un approccio piu' lungo ma interessante. Ogni volta che un utente clicca su uno dei due radio button, una delle due procedure e' memorizzata in una variabile:

procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
  IP := DoubleTheValue;
end;

Quando l'utente preme il pulsante, la procedura memorizzata viene eseguita:

procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
  IP (X);
end;

Per permettere a tre differenti funzioni di accedere alle variabili IP ed X, bisogna renderle visibili a tutto il form, cioe' non possono essere dichiarate localmente (all'interno dei metodi). Una soluzione a questo problema e' di mettere queste variabili all'interno della dichiarazione del form:

type
  TForm1 = class(TForm)
    ...
  private
    { Private declarations }
    IP: IntProc;
    X: Integer;
  end;

Si vedra' il significato esatto di questo codice nel prossimo capitolo, ma per il momento, bisogna modificare il codice generato da Delphi per la classe come indicato sopra e aggiungere la definizione del tipo procedurale visto in precedenza. Per inizializzare queste due variabili con valori adeguati, si puo' gestire l'evento OnCreate del form (selezionare questo evento nell'Object Inspector dopo aver attivato il form, o semplicemente fare doppio-click sul form stesso). Suggerisco di riferirsi al listato per studiare i dettagli di questo esempio.

Nota: Si puo' vedere un esempio pratico dell'uso di tipi procedurali nel Capitolo 9, nella sezione Una Funzione Callback di Windows.

newFunction Overloading

L'idea dell'overloading e' semplice: Il compilatore permette di definire due funzioni o procedure usando lo stesso nome, a patto che i parametri siano differenti. Controllando i parametri, di fatti, il compilatore puo' determinare quale delle versioni della routine si vuole chiamare.

Considerare questa serie di funzioni estratte dalla unit Math della VCL:

function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
function Min (A,B: Single): Single; overload;
function Min (A,B: Double): Double; overload;
function Min (A,B: Extended): Extended; overload;

Quando si chiama Min(10, 20), il compilatore determina facilmente che si sta chiamando la prima funzione del gruppo, cosi' il valore di ritorno sara' un intero.

Le regole di base sono due:

Ecco tre versioni overloaded della procedura ShowMsg che ho aggiunto all'esempio OverDef (un'applicazione che dimostra parametri di default e overloading):

procedure ShowMsg (str: string); overload;
begin
  MessageDlg (str, mtInformation, [mbOK], 0);
end;

procedure ShowMsg (FormatStr: string;
  Params: array of const); overload;
begin
  MessageDlg (Format (FormatStr, Params),
    mtInformation, [mbOK], 0);
end;

procedure ShowMsg (I: Integer; Str: string); overload;
begin
  ShowMsg (IntToStr (I) + ' ' + Str);
end;

Le tre funzioni mostrano un message box con una stringa, dopo aver formattato la stringa in modi differenti. Ecco le tre chiamate del programma:

ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');

Quello che mi ha sorpreso positivamente e' la tecnologia di Delphi Code Parameters che lavora veramente bene con i parametri overloaded. Come si apre la parentesi dopo il nome della routine, tutti i parametri disponibili sono elencati. Come si scrive il parametro, Delphi usa il suo tipo per determinare quali dei parametri alternativi sono ancora da usare. Nella Figura 6.4 si puo' vedere che appena si scrive una stringa Delphi mostra solo le versioni compatibili (omettendo le versioni della procedura ShowMsg che hanno un intero come primo parametro).

Figura 6.4: Le alternative multiple offerte dal Code Parameters per le routine overloaded sono filtrate a seconda del parametro gia' disponibile.

Il fatto che ogni versione di una routine overloaded deve essere contrassegnata correttamente, implica che non si puo' fare l'overload di una routine esistente che non e' contrassegnata dalla keyword overload. (Il messaggio di errore che si ottiene quando si tenta e': '<name>' was not marked with the 'overload' directive). Comunque, non si puo' fare l'overload di una routine che e' stata originariamente dichiarata in una unit diversa. Questo e' per motivi di compatibilita' con le precedenti versioni di Delphi, che permettono a unit differenti di riusare lo stesso nome di routine. Notare, comunque, che questo caso speciale non e' una funzionalita' extra dell'overloading, ma un'indicazione dei problemi che possono apparire.

Ad esempio, si puo' aggiungere ad una unit il seguente codice:

procedure MessageDlg (str: string); overload;
begin
  Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
end;

Questo codice non esegue sul serio l'overload della funzione MessageDlg. Di fatto se si scrive:

MessageDlg ('Hello');

si avra' un messaggio d'errore indicante che alcuni dei parametri richiesti mancano. Il solo modo di chiamare la versione locale invece dell'originale funzione della VCL, e' di riferirsi esplicitamente alla unit locale, cioe' qualcosa di lontano dall'idea di overloading:

OverDefF.MessageDlg ('Hello');

newParametri di Default

Una nuova funzionalita' di Delphi 4 e' che si puo' fornire un valore di default ad un parametro di una funzione e si puo' chiamare la funzione con o senza questo parametro. Ecco di seguito un esempio. Si puo' definire la seguento incapsulazione del metodo MessageBox dell'oggetto globale Application, che usa PChar invece di stringhe, fornendo due parametri di default:

procedure MessBox (Msg: string;
  Caption: string = 'Warning';
  Flags: LongInt = mb_OK or mb_IconHand);
begin
  Application.MessageBox (PChar (Msg),
    PChar (Caption), Flags);
end;

Con questa definizione, si puo' chiamare la procedura in questi differenti modi:

MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention');
MessBox ('Hello', 'Message', mb_OK);

Nella Figura 6.5, si puo' vedere che il Code Parameters di Delphi usa uno stile differente per indicare i parametri che hanno un valore di default, cosi' si puo' facilmente determinare quali parametri possono essere omessi.

Figura 6.5: Il Code Parameters di Delphi segna con parentesi quadre i parametri che hanno valori di default, si possono omettere questi parametri nele chiamate.

Da notare che Delphi non genera nessun codice speciale per supportare i parametri di default e nemmeno crea copie multiple della routine. I parametri mancanti sono semplicemente aggiunti dal compilatore al codice chiamante.

C'e' un importante restrizione che influisce sull'uso edi parametri di default: Non si possono "saltare" parametri. Ad esempio, non si puo' passare il terzo parametro alla funzione dopo aver omesso il secondo:

MessBox ('Hello', mb_OK); // error  

Questa e' la regola principale per i parametri di default: In una chiamata, si possono solamente omettere parametri a partire dall'ultimo. In altre parole, se si omette un parametro bisogna omettere anche tutti i successivi.

There are a few other rules for default parameters as well:

Ci sono altre poche regole per i parametri di default:

Usando i parametri default e l'overloading allo stesso tempo puo' causare qualche problema, visto che possono entrare in conflitto. Ad esempio, se si aggiunge al precedente esempio la seguente nuova versione della procedura ShowMsg:

procedure ShowMsg (Str: string; I: Integer = 0); overload;
begin
  MessageDlg (Str + ': ' + IntToStr (I),
    mtInformation, [mbOK], 0);
end;

allora il compilatore non si lamentera' di questa definizione. Comunque la chiamata:

ShowMsg ('Hello');

e' segnalata dal compilatore come Ambiguous overloaded call to 'ShowMsg'. Da notare che questo errore viene generato da una linea di codice che compilava correttamente prima della dichiarazione della nuova funzione overloaded. In pratica, non si ha modo di chiamare la procedura ShowMsg con un parametro stringa, siccome il compilatore non sa se si vuole chiamare la versione con solo un parametro stringa o quella con il parametro stringa e il parametro intero con valore di default. Quando il compilatore ha questi dubbi, si blocca e chiede al programmatore di dichiarare le proprie intenzioni piu' chiaramente.

Conclusioni

Scrivere procedure e funzioni e' un elemento chiave della programmazione, benche' in Delphi si tende a scivere metodi (procedure e funzioni connesse a classi e oggetti).

Invece di muovere nelle caratteristiche object-oriented, comunque, i prossimi capitoli mostreranno i dettagli di altre tecniche di programmazione Pascal, cominciando dalle stringhe.

Prossimo Capitolo: La gestione delle stringhe

© Copyright Marco Cantù, Wintech Italia Srl 1995-2000