Corso di Visual Basic (Parte 9) di Maurizio Crespi

Transcript

Corso di Visual Basic (Parte 9) di Maurizio Crespi
Corso di Visual Basic
(Parte 9)
di Maurizio Crespi
La nona lezione del corso dedicato alla programmazione in Visual Basic si pone lo scopo
di illustrare le funzioni definibili dall'utente e il concetto di ricorsione
Nella scorsa lezione sono state descritte le subroutine e sono stati evidenziati i benefici
che il loro uso dà al programmatore. Questo mese l'argomento sarà ulteriormente
approfondito con l'introduzione delle funzioni, ovvero di procedure in grado di restituire un
valore senza l'ausilio di una variabile globale o di un parametro passato per riferimento,
nonché con la descrizione dell'uso avanzato che è possibile fare di esse. Prima però è
opportuno compiere un passo indietro per riportare alla mente i concetti esposti nella
precedente puntata del corso. Come sempre, il fine è raggiunto mediante l'illustrazione
delle soluzioni degli esercizi in essa proposti.
Le soluzioni degli esercizi della scorsa lezione
Si tratta di due semplicissimi esercizi, il cui scopo è quello di aiutare il lettore a riconoscere
la differenza fra la modalità di passaggio dei parametri per valore e quella per riferimento.
Primo esercizio
Il primo esercizio prevede la realizzazione di una procedura in grado di restituire il valore
assoluto di un numero intero per mezzo di un parametro passato per riferimento.
Dai testi scolastici è noto che il valore assoluto di un numero è pari ad esso se è positivo
oppure, in caso contrario, al suo opposto. Volendo fare a meno di ricorrere alla funzione
Abs prevista dalla libreria standard, è possibile effettuare il calcolo per mezzo di una
struttura If. La soluzione dell'esercizio è pertanto la seguente:
Sub ValAssoluto(ByVal Numero As Double, ByRef Risultato As Double)
If Numero > 0 Then
Risultato = Numero
Else
Risultato = -1 * Numero
End If
End Sub
Si noti che il numero di cui deve essere calcolato il valore assoluto è fornito alla procedura
tramite un parametro passato per valore.
Si supponga ora di voler eseguire la seguente porzione di codice:
r=0
n=45
ValAssoluto n, r
MsgBox(Str$(r))
Un eventuale tentativo da parte della routine di modificare il dato fornitole come primo
parametro non ha effetto sulla variabile n posta nel blocco di codice chiamante. Diverso è
invece il discorso per quanto riguarda la variabile r. Essa, infatti, è passata come
parametro per riferimento; la procedura può quindi intervenire direttamente sul suo valore.
Ciò permette alla routine di restituire un dato senza far uso di variabili globali.
Secondo esercizio
Il secondo esercizio richiede la realizzazione di una procedura in grado di richiamare
quella appena illustrata per calcolare la somma dei valori assoluti di 3 numeri passati
come parametri per valore. La soluzione è banale ed è rappresentata dal codice riportato
di seguito:
Sub SommaVAssoluti(ByVal n1 As Double, ByVal n2 As Double, ByVal
n3 As Double, ByRef Somma As Double)
Dim r1 As Double
Dim r2 As Double
Dim r3 As Double
ValAssoluto n1, r1
ValAssoluto n2, r2
ValAssoluto n3, r3
Somma = r1 + r2 + r3
End Sub
Anche in questo caso, il risultato è restituito al blocco di codice chiamante per mezzo di un
parametro passato per riferimento.
Le funzioni
Un sensibile miglioramento della leggibilità del codice e della comodità d'uso dello
strumento di sviluppo deriva dalla possibilità di restituire un valore evitando l'uso di una
variabile globale o di un parametro passato per riferimento. Ciò è possibile per mezzo
delle funzioni. Il linguaggio prevede una quantità elevatissima di funzioni standard. Ad
esempio, per calcolare il valore assoluto di un numero, esiste la funzione Abs, che riceve
in ingresso un dato numerico e ne restituisce il modulo. La riga
x = Abs(y)
fa sì che alla variabile x sia assegnato il valore assoluto del numero contenuto nella
variabile y. Come accade per le procedure, è possibile incrementare l'insieme delle
funzioni disponibili creandone ad hoc per soddisfare le proprie esigenze. A tal fine è
necessario racchiudere le istruzioni in strutture dichiarate per mezzo della parola chiave
Function, secondo la sintassi di seguito riportata:
[Public|Private] Function <nome> [(<definizione_parametro_1>,
... <definizione_parametro_n>)] As <tipo>
[<dichiarazione_variabili_locali>]
<istruzione_1>
...
<istruzione_n>
<nome>=<valore>
End Function
Com'è possibile notare, l'analogia con le procedure è notevole. A differenza di esse, è
necessario indicare un tipo di dati standard al termine della riga di dichiarazione. Esso
identifica il formato in cui deve essere restituito il risultato. È inoltre necessario fare in
modo che all'interno del blocco di codice sia presente una riga che preveda
l'assegnamento di un valore a una variabile avente lo stesso nome della funzione. Tale
dato è quello restituito al blocco chiamante.
Si supponga di voler realizzare una semplice funzione in grado di moltiplicare un numero
intero per 4. Il codice necessario è il seguente:
Function Quadruplo(Numero As Integer)
Quadruplo = Numero * 4
End Function
Il valore assunto dal parametro Numero è moltiplicato per 4 e restituito dalla funzione.
È possibile utilizzare la struttura appena creata in un'istruzione di assegnamento scrivendo
il suo nome seguito dai parametri posti fra parentesi tonde, come nell'esempio che segue,
in cui il risultato della valutazione della funzione Quadruplo è assegnato alla variabile x.
x = Quadruplo(y)
Lo stesso scopo può essere ottenuto anche per mezzo di una procedura dotata di un
parametro passato per riferimento. In questo caso il codice è il seguente:
Sub Quadruplo(Numero As Integer, Risultato As Integer)
Risultato = Numero * 4
End Sub
In questo caso, volendo assegnare alla variabile x il risultato, è necessario scrivere:
Quadruplo(y,x)
Pur essendo le due soluzioni del tutto equivalenti, appare evidente la maggiore semplicità
della prima. Si noti che una funzione può restituire un solo valore. Qualora si presentasse
la necessità di fornire al blocco chiamante più di un'informazione, è necessario ricorrere ai
parametri passati per riferimento.
Esercizio
Si provi a realizzare una funzione in grado di ricevere come parametro una stringa
alfanumerica e restituire il valore logico True se essa contiene il nome di un giorno della
settimana.
La ricorsione
Si supponga di voler realizzare una funzione in grado di calcolare il fattoriale di un numero
intero. Ricordando che il fattoriale è definito solo per valori numerici positivi o nulli (in
quest'ultimo caso giova ricordare che il fattoriale di 0 è 1), è possibile prevedere la
seguente implementazione:
Function Fattoriale(ByVal n As Integer) As Long
Dim i As Integer
Dim Risultato As Long
If n >= 0 Then
Risultato = 1
For i = 2 To n
Risultato = Risultato * i
Next i
Else
Risultato = 0
End If
Fattoriale = Risultato
End Function
Si tratta di una funzione in grado di ricevere come parametro (per valore) un dato di tipo
numerico intero e di restituire un long. L'algoritmo prevede dapprima la verifica che il
numero oggetto di elaborazione sia positivo o nullo; successivamente, esegue un ciclo che
provvede ad effettuare la serie di moltiplicazioni necessaria per il calcolo del risultato. Tale
valore è infine assegnato alla funzione per fare in modo che essa lo restituisca. Si noti che
se il dato fornito in ingresso è negativo, la funzione restituisce il valore 0. Ciò è accettabile,
non essendo possibile che il fattoriale di un numero sia nullo; si tratta quindi di un valido
indicatore della presenza di un errore.
L'algoritmo appena descritto non è l'unico in grado di effettuare il calcolo del fattoriale di un
numero. Ad esempio, è possibile scrivere la seguente implementazione:
Function Fattoriale(ByVal n As Integer) As Long
If n >= 0 Then
If n = 0 Then
Fattoriale = 1
Else
Fattoriale = n * Fattoriale(n - 1)
End If
End If
End Function
Il numero delle righe di codice si è ridotto, a vantaggio della leggibilità. Tuttavia, a prima
vista il lettore può rimanere sconcertato dall'uso della funzione Fattoriale all'interno della
propria definizione. Ciò può apparire come un errore. In realtà, tale tecnica è
perfettamente lecita e prende il nome di ricorsione.
Per rendersi conto del corretto funzionamento, si provi ad osservare ciò che avviene
calcolando il fattoriale di un numero qualsiasi, ad esempio 3.
Essendo n pari a 3, la funzione esegue il calcolo
Fattoriale(3)=3*Fattoriale(2)
Ma, analogamente
Fattoriale(2)=2*Fattoriale(1)
e
Fattoriale(1)=1*Fattoriale(0)
Per mezzo di una struttura If , è distinto il caso in cui n è nullo. In questa condizione la
funzione restituisce il valore 1. Quindi,
Fattoriale(3)=3*(2*(1*(1)))
Ciò concorda con la definizione matematica del fattoriale. L'uso della ricorsione permette
in alcuni casi di semplificare notevolmente la scrittura di una funzione e di migliorarne al
tempo stesso la leggibilità. Tuttavia, presenta numerose insidie. Si provi a valutare il
risultato prodotto dalla riga
Calcola(7)
dove la funzione Calcola è definita come segue:
Function Calcola(n As Integer) As Integer
Calcola = n + Calcola(n - 1)
End Function
Il risultato è costituito da un errore di sistema. Infatti, non essendo prevista alcuna
condizione di uscita, ovvero non esistendo un valore del parametro per cui è restituito un
risultato non dipendente da una successiva chiamata della funzione, si genera una
successione di invocazioni di quest'ultima destinata a non avere fine, almeno sino
all'esaurimento dello spazio di memoria dedicato allo stack del sistema. Appare quindi
evidente una condizione fondamentale che deve essere soddisfatta da tutte le funzioni
ricorsive: deve sempre essere prevista una condizione di uscita.
È bene tuttavia non abusare della ricorsione. Infatti, sebbene in alcuni casi semplifichi
notevolmente il compito del programmatore, talvolta può comportare un sensibile aumento
della richiesta di risorse da parte del sistema, data la necessità che esso ha di mantenere
contemporaneamente attive più istanze della stessa funzione.
Esercizio
Si provi a realizzare una funzione che, dato un parametro n intero positivo passato per
valore, sia in grado di restituire la somma dei primi n numeri naturali. A tal fine si faccia
uso della ricorsione.
La procedura Main
Lo studio delle subroutine termina con una procedura un po' particolare, la cui importanza
è tutt'altro che trascurabile. Si tratta della procedura Main.
Un'applicazione realizzata in Visual Basic è generalmente dotata di un'interfaccia utente
grafica, in cui l'interazione con gli elementi attivi determina il flusso del programma.
Tuttavia, in alcuni casi, soprattutto per la realizzazione di semplici utility in grado di
operare in modo invisibile all'utente, è necessario sopprimere l'interfaccia grafica. Quando
ciò avviene, il progetto risulta privo di form. Il codice deve pertanto essere posto altrove,
ovvero all'interno di moduli. Affinché una sequenza di istruzioni sia eseguita all'avvio di
un'applicazione è necessario inserirla all'interno di una procedura denominata Main.
Occorre inoltre selezionare tale routine come oggetto di avvio nella finestra delle proprietà
del progetto in luogo di un form. Una semplice procedura Main è la seguente:
Sub Main()
If Date$ = "12-25-1998" Then
MsgBox "Buon Natale"
End If
End Sub
Si tratta di una piccola applicazione che può essere eseguita automaticamente all'avvio di
Windows e che normalmente non fornisce alcun feedback all'utente. Solo il giorno di
Natale del 1998 essa ha un effetto visibile, in quanto provvede a visualizzare un
messaggio di auguri. Si noti l'uso della funzione standard Date$ che restituisce una stringa
contenente la data corrente. Il ricorso alla procedura Main non è tuttavia riservato solo alle
applicazioni prive di form. A volte può essere utile fare in modo che un programma
all'avvio sia in grado di valutare una condizione e in base ad essa scegliere fra i form che
lo compongono quello che deve essere visualizzato. Si supponga di disporre di una
funzione denominata SelezionaForm, di cui saranno trascurati i dettagli implementativi, in
grado di leggere il registro di sistema per determinare la lingua utilizzata dalla versione in
uso del sistema operativo e di restituire una stringa contenente una delle seguenti
sequenze alfanumeriche: "Italiano", "Inglese", "Tedesco", "Francese". Si supponga altresì
di aver realizzato un progetto caratterizzato dalla presenza di 4 form, differenti fra loro per
la lingua in cui sono scritte le etichette che identificano i controlli e di aver dato ad essi
rispettivamente i nomi Ita, Ing, Ted, Fra. È allora possibile scrivere una procedura Main in
grado di avviare il form corrispondente alla lingua presente nel sistema. Il codice è il
seguente:
Sub Main()
Select Case SelezionaForm()
Case "Italiano":
Ita.Show vbModal
Case "Inglese":
Ing.Show vbModal
Case "Tedesco":
Ted.Show vbModal
Case "Francese"
Fra.Show vbModal
End Select
End Sub
Si notino alcune particolarità; la funzione SelezionaForm, ad esempio, non prevede
parametri. Affinché sia utilizzabile correttamente, è comunque necessario richiamarla
specificando le parentesi, sebbene fra esse non sia posto alcun dato o nome di variabile.
Si noti inoltre l'uso del metodo Show, che permette di avviare il caricamento e la
successiva visualizzazione di un form. L'uso della costante predefinita vbModal accanto ad
esso stabilisce che la procedura in fase di esecuzione non può passare all'istruzione
successiva finché il form risulta visibile. Solo dopo la sua chiusura il flusso delle istruzioni
può riprendere e, non essendo presente altro codice, il programma può terminare.
Conclusioni
Le procedure e le funzioni sono strumenti estremamente efficaci, in grado di fornire un
notevole aiuto al programmatore. I moderni linguaggi di programmazione orientati agli
eventi, come Visual Basic, si fondano in modo pressoché totale sulla presenza di tali
strutture.
L'acquisizione della necessaria dimestichezza nel loro uso diventa pertanto un obbligo per
colui che desidera sfruttare al meglio le potenzialità dello strumento. Per questo motivo,
ancora una volta la lezione si chiude rivolgendo al lettore un invito ad esercitarsi sugli
argomenti trattati, servendosi a tal fine anche degli esercizi proposti.