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.