Le funzioni sono dichiarate globalmente e consistono in una firma in cui vengono definiti i tipi di argomenti e il valore di ritorno e in un corpo in cui viene dichiarata l'implementazione. |
Dichiarazione di funzione
Le funzioni globali forniscono il mezzo per implementare routine che devono operare su alcuni input e produrre un risultato. Le funzioni stesse non mantengono alcuna memoria, anche se possono aggiornare variabili globali o memoria passata per riferimento.
La funzione viene sempre dichiarata insieme al corpo della funzione. Non è necessario dichiarare i prototipi di funzione, poiché la funzione è visibile a livello globale, indipendentemente da dove è stata dichiarata.
// A simple function declaration |
Il tipo del valore di ritorno deve essere specificato prima del nome della funzione. Se la funzione non restituisce nulla, il tipo deve essere definito come "void". Dopo il nome della funzione, si specifica l'elenco dei parametri tra parentesi. Ogni parametro è definito dal suo tipo e dal suo nome.
Riferimenti ai parametri
I parametri e il valore di ritorno possono essere di qualsiasi tipo, utilizzabile anche per le dichiarazioni di variabili. Inoltre, è possibile fare in modo che una funzione assuma un valore per riferimento, ossia che il parametro non sia una copia del valore originale, ma faccia riferimento al valore.
I riferimenti ai parametri sono utilizzati principalmente per due scopi: un mezzo per fornire un output aggiuntivo dalla funzione, o come un modo più performante di passare i valori.
In script avanzato è necessario specificare l'intenzione del riferimento al parametro, cioè se è inteso come input, output o entrambi. Questo è necessario affinché il compilatore prepari il riferimento in modo che non possa essere invalidato a causa di qualche azione durante l'elaborazione della funzione.
I riferimenti di ingresso sono scritti come &in. Poiché il riferimento è inteso solo come input, il valore effettivo a cui si riferisce è normalmente una copia dell'originale, in modo che la funzione non modifichi accidentalmente il valore originale. Questi riferimenti non sono comunemente utilizzati, in quanto forniscono pochi vantaggi rispetto al passaggio degli argomenti per valore. Solo in alcune circostanze è possibile migliorare le prestazioni, soprattutto se il parametro è dichiarato anche come const.
I riferimenti all'uscita sono scritti come &out. Questi riferimenti hanno lo scopo di consentire alla funzione di restituire valori aggiuntivi. Quando si entra nella funzione, il riferimento punta a un valore non inizializzato. Dopo il ritorno della funzione, il valore assegnato dalla funzione verrà copiato nella destinazione determinata dal chiamante.
Quando il riferimento è inteso sia come ingresso che come uscita, viene dichiarato come &inout, o semplicemente &. In questo caso il riferimento punta al valore effettivo. Solo i tipi di riferimento, cioè quelli che possono avere degli handle, possono essere passati come riferimenti inout. Questo perché, per garantire che il riferimento rimanga valido durante l'intera esecuzione della funzione, il valore deve trovarsi nell'heap della memoria.
void Function(const int &in a, int &out b, Object &c) |
Riferimenti al ritorno
Una funzione può anche restituire dei riferimenti, che permetteranno al chiamante di modificare il valore puntato dal riferimento. Per dichiarare una funzione che restituisce un riferimento, includere il simbolo & tra il tipo di ritorno e il nome della funzione. Aggiungere const prima del tipo se il riferimento deve essere di sola lettura, cioè se non deve essere possibile modificare il valore a cui punta.
int property; |
La necessità di garantire che il riferimento sia valido anche dopo la restituzione della funzione al chiamante comporta alcune restrizioni. Non è necessario cercare di ricordare queste restrizioni, poiché il compilatore darà un errore se vengono violate, ma se si verifica un errore di compilazione quando viene restituito un riferimento, è bene capire perché si verifica, in modo da poter determinare come evitarlo.
I riferimenti alle variabili globali sono consentiti
Poiché una variabile globale si trova nell'ambito globale, la vita della variabile è più lunga dell'ambito della funzione. Una funzione può quindi restituire un riferimento a una variabile globale, o anche a un membro di un oggetto raggiungibile attraverso una variabile globale.
I riferimenti ai membri della classe sono consentiti
Un metodo di classe può restituire un riferimento a una proprietà della classe dello stesso oggetto; poiché il chiamante è tenuto a mantenere un riferimento all'oggetto, è noto che il membro esisterà anche dopo il ritorno del metodo.
Il metodo di classe può anche restituire riferimenti a variabili globali, come qualsiasi altra funzione.
Non è possibile restituire riferimenti a variabili locali
Poiché le variabili locali devono essere liberate quando la funzione esce, non è consentito restituire un riferimento ad esse. Lo stesso vale per i parametri ricevuti dalla funzione. Anche i parametri vengono ripuliti all'uscita della funzione, quindi non possono essere restituiti come riferimento.
Non si possono usare espressioni con parametri differiti
Per alcune chiamate di funzione con argomenti, potrebbe essere necessaria un'elaborazione degli argomenti dopo il ritorno della chiamata di funzione, ad esempio per ripulire l'oggetto di input o per assegnare i parametri di output. Se la funzione chiamata restituisce un riferimento, questo non può essere restituito a sua volta, perché potrebbe essere invalidato dalla valutazione differita degli argomenti.
Non si possono utilizzare espressioni che si basano su oggetti locali
Tutti gli oggetti locali devono essere ripuliti prima dell'uscita di una funzione. Per le funzioni che restituiscono riferimenti, questa pulizia avviene prima che venga valutata l'espressione di ritorno, altrimenti la pulizia degli oggetti locali potrebbe invalidare accidentalmente il riferimento. Per questo motivo non è possibile utilizzare espressioni che si basano su oggetti locali per valutare il riferimento da restituire.
È invece possibile utilizzare valori primitivi, che non richiedono la pulizia all'uscita.
Sovraccarico delle funzioni
L'overloading delle funzioni avviene quando più funzioni con lo stesso nome vengono dichiarate con parametri diversi. Si tratta di una caratteristica utile quando un'operazione deve essere in grado di lavorare con diversi tipi di input, pur producendo risultati simili.
Il compilatore è in grado di decidere quale funzione chiamare facendo corrispondere il tipo di ogni espressione dell'argomento al parametro della funzione ed eliminando le funzioni per le quali non è disponibile una conversione. Il compilatore esegue questa operazione per ogni argomento, dal primo all'ultimo. Quando tutti gli argomenti sono stati valutati, solo una funzione dovrebbe essere la migliore, altrimenti il compilatore darà un errore sull'impossibilità di determinare la funzione corretta da chiamare.
Il tipo di conversione che deve essere eseguita sull'argomento per ottenere il tipo del parametro determina la corrispondenza di una funzione. L'elenco seguente indica l'ordine di conversione di un tipo rispetto a un altro.
•nessuna conversione necessaria
•conversione in const
•conversione di un enum in un intero della stessa dimensione
•conversione di enum in intero di dimensione diversa
•la dimensione del tipo primitivo aumenta
•la dimensione del tipo primitivo diminuisce
•conversione da intero firmato a intero non firmato
•conversione da intero senza segno a intero firmato
•conversione da tipo intero a tipo float
•conversione da tipo float a tipo intero
•cast di riferimento
•conversione da oggetto a primitiva
•conversione in oggetto
•tipo di argomento variabile
Si noti che non è possibile creare sovraccarichi in cui l'unica differenza sia il tipo di ritorno. Questo perché il tipo di ritorno non fa parte dei criteri di selezione che il compilatore utilizza per determinare quale funzione chiamare; il tipo di ritorno è solo il risultato della funzione chiamata.
void Function(int a, float b, string c) {} |
Argomenti predefiniti
A volte l'implementazione di funzioni diverse per ogni sovraccarico non è necessaria quando la differenza può essere fornita con un valore predefinito di un parametro. È qui che gli argomenti predefiniti si rivelano utili.
Definendo gli argomenti predefiniti nella dichiarazione della funzione, lo script non deve fornire questi valori in modo specifico quando chiama la funzione, poiché il compilatore inserisce automaticamente gli argomenti predefiniti.
void Function(int a, int b = 1, string c = "") |
Quando si definisce un argomento predefinito per uno dei parametri, anche tutti i parametri successivi devono avere un argomento predefinito.
L'espressione dell'argomento predefinito può includere riferimenti a variabili o funzioni, ma solo se le variabili o le funzioni sono visibili nell'ambito globale.
int myvar = 42; |
L'espressione speciale 'void' può essere utilizzata come argomento predefinito per creare un parametro di uscita opzionale.
void func(int &out output = void) { output = 42; } |
Funzioni anonime
Le funzioni anonime, o lambda come vengono talvolta chiamate, sono funzioni dichiarate localmente per essere utilizzate con gli handle delle funzioni.
Di seguito viene fornita una rapida dimostrazione di come utilizzare le funzioni anonime.
funcdef bool CMP(int first, int second); |
La funzione anonima assume la firma dell'handle della funzione a cui è assegnata, quindi il tipo degli argomenti e il tipo di ritorno non devono essere dichiarati esplicitamente.
Le funzioni anonime non possono ancora accedere a variabili dichiarate nello stesso ambito della funzione, cioè non possono essere usate come chiusure.
Se ci sono più usi corrispondenti per la funzione anonima, sarà necessario informare esplicitamente i tipi di parametri, in modo da risolvere l'ambiguità.
funcdef void A(int); void main() |