|
ByVal e ByRef
Nel capitolo precedente, tutti i parametri sono stati dichiaranti anteponendo al loro nome la keyword ByVal. Essa ha il compito di comunicare
al programma che al parametro formale deve essere passata una copia del parametro attuale. Questo significa che qualsiasi codice sia
scritto entro il corpo del metodo, ogni manipolazione e ogni operazione eseguita su quel parametro agisce, di fatto, su un'altra variabile,
temporanea, e non sul parametro attuale fornito. Ecco un esempio:
Module Module1
Sub Change(ByVal N As Int32)
N = 30
End Sub
Sub Main()
Dim A As Int32 = 56
Change(A)
Console.WriteLine(A)
Console.ReadKey()
End Sub
End Module
A schermo apparirà la scritta "56": A è una variabile di Main, che viene passata come parametro attuale alla procedura Change.
In quest'ultima, N costituisce il parametro formale - il segnaposto - a cui, durante il passaggio dei parametri, viene attribuita un copia
del valore di A. In definitiva, per N viene creata un'altra area di memoria, totalmente distinta, e per questo motivo ogni operazione
eseguita su quest'ultima non cambia il valore di A. Di fatto, ByVal indica di trattare il parametro come un tipo value (passaggio
per valore).
Al contrario, ByRef indica di trattare il parametro come un tipo reference (passaggio per indirizzo). Questo significa che, durante il passaggio dei parametri, al
parametro formale non viene attribuito come valore una coppia di quello attuale, ma bensì viene forzato a puntare alla sua stessa cella
di memoria. In questa situazione, quindi, anche i tipi value come numeri, date e valori logici, si comportano come se fossero oggetti. Ecco
lo stesso esempio di prima, con una piccola modifica:
Module Module1
Sub Change(ByRef N As Int32)
N = 30
End Sub
Sub Main()
Dim A As Int32 = 56
Change(A)
Console.WriteLine(A)
Console.ReadKey()
End Sub
End Module
Nel codice, la sola differenza consiste nella keyword ByRef, la quale, tuttavia, cambia radicalmente il risultato. Infatti, a schermo apparirà
"30" e non "56". Dato che è stata applicata la clausola ByRef, N punta alla stessa area di memoria di A, quindi ogni alterazione
perpetrata nel corpo del metodo sul parametro formale si ripercuote su quello attuale.
A questo punto è molto importante sottolineare che i tipi reference si comportano SEMPRE allo stesso modo, anche se vengono
inseriti nell'elenco dei parametri accompagnati da ByVal. Eccone una dimostrazione:
Module Module1
Dim A As New Object
Sub Test(ByVal N As Object)
Console.WriteLine(N Is A)
End Sub
Sub Main()
Test(A)
Console.ReadKey()
End Sub
End Module
Se ByVal modificasse il comportamento degli oggetti, allora N conterrebbe una copia di A, ossia un altro oggetto semplicemente uguale, ma
non identico. Invece, a schermo appare la scritta "True", che significa "Vero", perciò N e A sono lo stesso oggetto, anche se N era
preceduto da ByVal.
La funzioni
Le funzioni sono simili alle procedure, ma possiedono qualche caratteristica in più. La loro sintassi è la seguente:
Function [name]([elenco parametri]) As [tipo]
'...
Return [risultato]
End Function
La prima differenza che salta all'occhio è l'As che segue l'elenco dei parametri, come a suggerire che la funzione sia di un certo tipo.
Ad essere precisi, quell'As non indica il tipo della funzione, ma piuttosto quello del suo risultato. Infatti, le funzioni restituiscono
qualcosa alla fine del loro ciclo di elaborazione. Per questo motivo, prima del termine del suo corpo, deve essere posta almeno un'istruzione
Return, seguita da un qualsiasi dato, la quale fornisce al chiamante il vero risultato di tutte le operazioni eseguite. Non è un
errore scrivere funzioni prive dell'istruzione Return, ma non avrebbe comunque senso: si dovrebbe usare una procedura in quel caso.
Ecco un esempio di funzione:
Module Module1
'Questa funzione calcola la media di un insieme
'di numeri decimali passati come array
Function Average(ByVal Values() As Single) As Single
'Total conterrà la somma totale di tutti
'gli elementi di Values
Dim Total As Single = 0
'Usa un For Each per ottenere direttamente i valori
'presenti nell'array piuttosto che enumerarli
'attraverso un indice mediante un For normale
For Each Value As Single In Values
Total += Value
Next
'Restituisce la media aritmetica, ossia il rapporto
'tra la somma totale e il numero di elementi
Return (Total / Values.Length)
End Function
Sub Main(ByVal Args() As String)
Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34}
'Notare che in questo caso ho usato lo stesso nome
'per il parametro formale e attuale
Console.WriteLine("Media: " & Average(Values))
Console.ReadKey()
End Sub
End Module
E un altro esempio in cui ci sono più Return:
Module Module1
Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double
Dim X As Double = 1
If Esponente = 0 Then
Return 1
Else
If Esponente = 1 Then
Return Base
Else
For i As Byte = 1 To Esponente
X *= Base
Next
Return X
End If
End If
End Function
Sub Main()
Dim F As Double
Dim b As Single
Dim e As Byte
Console.WriteLine("Inserire base ed esponente:")
b = Console.ReadLine
e = Console.ReadLine
F = Potenza(b, e)
Console.WriteLine(b & " elevato a " & e & " vale " & F)
Console.ReadKey()
End Sub
End Module
In quest'ultimo esempio, il corpo della funzione contiene ben tre Return, ma ognuno appartiene a un path di codice differente. Path
significa "percorso" e la locuzione appena usata indica il flusso di elaborazione seguito dal programma per determinati valori di Base ed
Esponente. Disegnando un diagramma di flusso della funzione, sarà facile capire come ci siano tre percorsi differenti, ossia quando
l'esponente vale 0, quando vale 1 e quando è maggiore di 1. È sintatticamente lecito usare due Return nello stesso path, o
addirittura uno dopo l'altro, ma non ha nessun senso logico: Return, infatti, non solo restituisce un risultato al chiamante, ma termina anche
l'esecuzione della funzione. A questo proposito, bisogna dire che esiste anche lo statement (=istruzione) Exit Function, che forza
il programma ad uscire immediatamente dal corpo della funzione: inutile dire che è abbastanza pericoloso da usare, poiché si corre il
rischio di non restituire alcun risultato al chiamante, il che può tradursi in un errore in fase di esecuzione.
Come ultima postilla vorrei aggiungere che, come per le varibili, non è strettamente necessario specificare il tipo del valore restituito
dalla funzione, anche se è fortemente consigliato: in questo caso, il programma supporrà che si tratti del tipo Object.
Usi particolari delle funzioni
Ci sono certe circostanze in cui le funzioni possono differire leggermente dal loro uso e dalla loro forma consueti. Di seguito sono elencati
alcuni casi:
Variabili Static
Le variabili Static sono una particolare eccezione alle variabili locali/temporanee. Avevo chiaramente scritto pochi paragrafi fa
che queste ultime esistono solo nel corpo del metodo, vengono create al momento dell'invocazione e distrutte al termine dell'esecuzione.
Le Static, invece, possiedono soltanto le prime due caratteristiche: non vengono distrutte alla fine del corpo, ma il loro valore si conserva
in memoria e rimane tale anche quando il flusso entra una seconda volta nel metodo. Ecco un esempio:
Module Module1
Sub Test()
Static B As Int32 = 0
B += 1
Console.WriteLine(B)
End Sub
Sub Main(ByVal Args() As String)
For I As Int16 = 1 To 6
Test()
Next
Console.ReadKey()
End Sub
End Module
Il programma stamperà a schermo, in successione, 1, 2, 3, 4, 5 e 6. Come volevasi dimostrare, nonostante B sia temporanea, mantiene il
suo valore tra una chiamata e la successiva.
|
|