CAPITOLO 6 Procedure Obiettivi Costruire i programmi in modo modulare partendo da piccoli pezzi chiamati procedure Introdurre i metodi Math disponibili in Framework Class Library Creare nuove procedure Capire i meccanismi utilizzati per scambiare le informazioni fra le procedure Descrivere le tecniche di simulazione che utilizzano la generazione di numeri casuali Comprendere come la visibilità degli identificatori sia limitata a specifiche parti di un programma Scrivere e utilizzare le procedure ricorsive 6.1 Introduzione La maggior parte dei programmi per computer scritti per risolvere problemi reali hanno dimensioni decisamente superiori rispetto ai programmi presentati nei primi capitoli di questo libro. L esperienza ha dimostrato che il modo migliore per sviluppare e mantenere un programma di grandi dimensioni è quello di costruirlo partendo da pezzi più piccoli e più facili da gestire rispetto al programma originale. Questo capitolo presenta le caratteristiche del linguaggio Visual Basic che facilitano la progettazione, l implementazione, il funzionamento e la manutenzione di programmi di grandi dimensioni. 6.2 Moduli, classi e procedure I programmi Visual Basic sono composti da vari pezzi, che includono i moduli e le classi. Il programmatore combina nuovi moduli e classi preconfezionate disponibili in.net Framework Class Library (FCL). I moduli e le classi sono formati da pezzi più piccoli: le procedure. Le procedure, quando sono combinate in una classe, si chiamano metodi. Framework Class Library fornisce una ricca collezione di classi e metodi che permettono di eseguire i più comuni calcoli matematici, manipolare stringhe e caratteri, controllare gli errori, gestire l input/output e svolgere molte altre utili operazioni. Tutto questo semplifica il lavoro del programmatore, perché i metodi offrono molte delle funzionalità che servono al programmatore. Nei precedenti capitoli abbiamo introdotto alcune classi FCL, come Console, che fornisce i metodi per l input e l output dei dati.
146 CAPITOLO 6 Ingegneria del software 6.1 Familiarizzate con le classi e i metodi che si trovano in Framework Class Library (FCL). Ingegneria del software 6.2 Dove possibile, utilizzate sempre le classi e i metodi FCL, anziché scrivere nuove classi e nuovi metodi. Ciò consente di ridurre il tempo di sviluppo del programma e impedisce di commettere nuovi errori. Obiettivo efficienza 6.1 I metodi FCL sono stati scritti per essere eseguiti con efficienza. Sebbene FCL disponga di molti metodi che svolgono i compiti più comuni, tuttavia non può disporre di tutte le funzionalità che potrebbero essere richieste dai programmatori. Per questo motivo, Visual Basic consente a un programmatore di definire le sue procedure per soddisfare specifiche esigenze di un programma; queste procedure sono chiamate procedure definite dal programmatore. I programmatori scrivono le procedure per definire specifici compiti che un programma può usare molte volte durante la sua esecuzione. Sebbene la stessa procedura definita dal programmatore possa essere eseguita in più punti del programma, le istruzioni che definiscono effettivamente la procedura vengono scritte una volta soltanto. Una procedura viene invocata da una chiamata di procedura. Questa chiamata specifica il nome della procedura e fornisce le informazioni (come argomenti) di cui la procedura chiamata ha bisogno per completare le attività per le quali è stato progettata. Quando la procedura completa il suo compito, restituisce il controllo alla procedura chiamante (caller). In alcuni casi, la procedura restituisce anche un risultato alla procedura chiamante. Una tipica analogia per spiegare tutto questo è quella della struttura gerarchica di un azienda. Un capo ufficio (la procedura chiamante) chiede a un impiegato (la procedura chiamata) di eseguire un determinato compito e poi di restituire i risultati ottenuti, una volta che questo compito sarà completato. Il capo ufficio non ha bisogno di sapere come quell impiegato eseguirà il compito che gli ha assegnato; l impiegato potrebbe chiamare altri impiegati e il capo ufficio non ne sarebbe a conoscenza. Vedremo presto perché, nascondendo i dettagli di un implementazione, sia possibile ottimizzare la progettazione del software. La Figura 6.1 mostra la procedura Boss che comunica con le procedure Worker1, Worker2 e Worker3 in modo gerarchico. Notate come Worker1 si comporti da boss nei confronti di Worker4 e Worker5 in questo particolare esempio. Sono vari i motivi per cui è bene suddividere un programma in varie procedure. Per prima cosa, questo approccio rende più gestibile lo sviluppo del programma. Un altra motivazione è il riutilizzo del software, ovvero la possibilità di utilizzare le procedure esistenti come blocchi di costruzione per creare nuovi programmi. Applicando opportune convenzioni ai nomi e alle definizioni delle procedure, i programmi possono essere creati partendo da procedure standardizzate che svolgono compiti specifici, anziché utilizzare un codice personalizzato. Una terza motivazione è quella di evitare la ripetizione del codice all interno di un programma. Inserendo il codice in una procedura, è possibile eseguirlo in diversi punti del programma con una semplice chiamata della procedura.
PROCEDURE 147 Figura 6.1 Relazione gerarchica fra procedure Buona abitudine 6.1 Applicate i concetti della modularità per migliorare la chiarezza e l organizzazione dei programmi. Questo non soltanto aiuta gli altri a capire i vostri programmi, ma semplifica anche lo sviluppo, il collaudo e il debugging del codice. Ingegneria del software 6.3 Per favorire il riutilizzo del software, ogni procedura dovrebbe limitarsi a eseguire un solo compito ben definito, mentre il nome della procedura dovrebbe esprimere in modo chiaro tale compito. Ingegneria del software 6.4 Se non riuscite a scegliere un nome conciso che esprime l attività svolta da una procedura, probabilmente la procedura sta cercando di svolgere troppi compiti diversi. Di solito è meglio suddividerla in procedure più piccole. 6.3 Procedure Sub I programmi finora presentati contenevano almeno la definizione di una procedura (Main Main) che chiamava i metodi FCL (come Console.WriteLine) per svolgere i compiti del programma. Vediamo adesso come scrivere le procedure personalizzate. Esaminate l applicazione della Figura 6.2 che usa una procedura Sub (che è chiamata dalla procedura Main) per visualizzare alcuni dati sui pagamenti. Il programma contiene due definizioni di procedure. Le righe 6-14 definiscono la procedura Sub Main, che viene eseguita quando viene caricata l applicazione. Le righe 17-21 definiscono la procedura Sub PrintPay, che è eseguita quando viene chiamata da un altra procedura, in questo caso da Main. La procedura Main effettua quattro chiamate (righe 9-12) della procedura Sub PrintPay, che viene eseguita quattro volte. In questo esempio gli argomenti della procedura sono delle costanti, ma possono essere anche variabili o espressioni. Per esempio, l istruzione PrintPay(employeeOneExtraHours, employeeonewage * 1.5) può essere utilizzata per visualizzare le informazioni sui pagamenti di un impiegato il cui lavoro straordinario (employeeoneextrahours employeeoneextrahours) ha un costo pari a una volta e mezza l orario normale (employeeonewage employeeonewage).
148 CAPITOLO 6 1 Figura 6.2: Payment.vb 2 Visualizza le informazioni sui pagamenti 3 4 Module modpayment 5 6 Sub Main() 7 8 chiama la procedura PrintPay 4 volte 9 PrintPay(40, 10.5) 10 PrintPay(38, 21.75) 11 PrintPay(20, 13) 12 PrintPay(50, 14) 13 14 End Sub Main 15 16 visualizza l importo nella finestra di comando 17 Sub PrintPay(ByVal hours As Double, ByVal wage As Decimal) 18 19 pay = hours * wage 20 Console.WriteLine( The payment is {0:C}, hours * wage) 21 End Sub PrintPay 22 23 End Module modpayment The payment is $420.00 The payment is $826.50 The payment is $260.00 The payment is $700.00 Figura 6.2 Procedura Sub per visualizzare i pagamenti Quando Main chiama PrintPay, il programma crea una copia del valore di ogni argomento (40 e 10.5 nella riga 9). Il controllo del programma passa alla prima riga della procedura PrintPay; questa procedura riceve i valori copiati e li memorizza nelle variabili hours e wage. Le variabili che ricevono i valori copiati degli argomenti sono dette parametri. Poi, PrintPay calcola il prodotto hours * wage e visualizza il risultato, utilizzando il formato della valuta, come indica la lettera C (iniziale di currency) nella riga 20. Quando viene eseguita l istruzione End Sub nella riga 21, il controllo ritorna alla procedura Main che ha effettuato la chiamata (caller). La prima riga della procedura PrintPay (riga 17) mostra (fra parentesi) che PrintPay dichiara la variabile hours di tipo Double e la variabile wage di tipo Decimal. Questi parametri memorizzano i valori passati a PrintPay all interno di questa procedura. Notate che l intera definizione della procedura PrintPay appare all interno del corpo del modulo modpayment. Tutte le procedure devono essere definite all interno di un modulo o di una classe. Il formato della definizione di una procedura Sub è il seguente: Sub nome-procedura(lista-parametri) dichiarazioni e istruzioni End Sub
PROCEDURE 149 La prima riga è detta intestazione della procedura. Il nome-procedura, che segue la parola chiave Sub nell intestazione della procedura, può essere qualsiasi identificatore valido ed è utilizzato per chiamare la procedura all interno del programma. La lista-parametri è un elenco di elementi separati da una virgola in cui la procedura Sub dichiara il tipo e il nome di ogni parametro. Ci deve essere almeno un argomento nella chiamata della procedura per ogni parametro dell intestazione (c è un eccezione a questa regola, come vedremo nel Paragrafo 6.17). Gli argomenti devono essere compatibili anche con i tipi di parametri (Visual Basic deve essere in grado di assegnare il valore dell argomento al parametro). Per esempio, un parametro di tipo Double può ricevere i valori 7.35, 22 o.03546, ma non i caratteri ciao, perché Double non può contenere stringhe di caratteri. Il Paragrafo 6.6 descrive dettagliatamente questo tema. Se una procedura non riceve alcun valore, la lista dei parametri è vuota (il nome della procedura è seguito da una coppia di parentesi vuote). Buona abitudine 6.2 Inserite una riga vuota prima e dopo la definizione di una procedura per mettere in evidenza le procedure dal resto del codice e migliorare la leggibilità dei programmi. Errore tipico 6.1 È un errore di sintassi definire una procedura all esterno della definizione di una classe o di un modulo. Notate che le dichiarazioni dei parametri nell intestazione della procedura PrintPay (riga 17) sono simili alle dichiarazioni delle variabili, con la differenza che usano la parola chiave ByVal, anziché Dim. ByVal specifica che il programma chiamante deve passare al parametro una copia del valore dell argomento; tale valore può essere utilizzato nel corpo della procedura Sub. Il Paragrafo 6.9 esamina dettagliatamente il passaggio degli argomenti. Errore tipico 6.2 È un errore di sintassi dichiarare una variabile nel corpo della procedura con lo stesso nome del parametro nell intestazione della procedura. Collaudo e messa a punto 6.1 Anche se è consentito, un argomento passato a una procedura non dovrebbe avere lo stesso nome del corrispondente parametro nella definizione della procedura. Questa distinzione evita ogni ambiguità che potrebbe determinare errori logici. Le dichiarazioni e le istruzioni nella definizione della procedura formano il corpo della procedura. Il corpo della procedura contiene il codice Visual Basic che svolge le azioni, manipolando o interagendo con i parametri. Il corpo della procedura deve terminare con le parole chiave End Sub, che definiscono la fine della procedura. Il corpo di una procedura è detto anche blocco. Un blocco è una sequenza di istruzioni e dichiarazioni raggruppate insieme come il corpo di una struttura; termina con l istruzione End, Next, Else o Loop, a seconda del tipo di struttura. Le variabili possono essere dichiarate in qualsiasi blocco; i blocchi possono essere nidificati. Errore tipico 6.3 È un errore di sintassi definire una procedura all interno di un altra; le procedure non possono essere nidificate.
150 CAPITOLO 6 Il controllo ritorna al caller quando l esecuzione raggiunge l istruzione End Sub (la fine del corpo della procedura). In alternativa, è possibile utilizzare le parole chiave Return ed Exit Sub in qualsiasi punto di una procedura per restituire il controllo al punto in cui è stata chiamata la procedura Sub. Più avanti descriveremo meglio Return ed Exit Sub. Buona abitudine 6.3 La scelta di nomi significativi per le procedure e i parametri rende i programmi più leggibili e riduce la lunghezza dei commenti. Ingegneria del software 6.5 I nomi delle procedure di solito sono verbi, perché una procedura svolge delle operazioni sui dati. Per convenzione, i nomi delle procedure definite dai programmatori iniziano con la prima lettera maiuscola. Per esempio, una procedura che invia un messaggio di posta elettronica potrebbe essere chiamata SendMail. Ingegneria del software 6.6 Una procedura che richiede un numero elevato di parametri, di solito, svolge troppi compiti. È consigliabile suddividere la procedura in procedure più piccole che svolgono compiti più semplici. Come regola generale, l intestazione di una procedura dovrebbe occupare una sola riga. Ingegneria del software 6.7 Come regola generale, una procedura dovrebbe occupare un solo foglio di carta o, meglio ancora, non dovrebbe superare la metà di un foglio. Indipendentemente dalla lunghezza, la procedura deve svolgere bene il suo compito. Collaudo e messa a punto 6.2 La procedure piccole sono più semplici da provare, correggere e capire di quelle più grandi. Obiettivo efficienza 6.2 Quando un programmatore divide una procedura in numerose procedure che comunicano fra di loro, questo processo di comunicazione richiede tempo e, a volte, riduce le prestazioni del programma. Ingegneria del software 6.8 Le intestazioni e le chiamate delle procedure devono concordare con il numero, il tipo e l ordine dei parametri. Il Paragrafo 6.17 tratta le eccezioni a questa regola. 6.4 Procedure Function Le procedure Function sono simili alle procedure Sub, con una differenza importante: le procedure Function restituiscono un valore alla procedura chiamante, le procedure Sub no. L applicazione della Figura 6.3 usa la procedura Function Square per calcolare i quadrati dei numeri interi compresi fra 1 e 10.
PROCEDURE 151 1 Figura 6.3: SquareInteger.vb 2 Procedura Function per calcolare i quadrati 3 4 Module modsquareinteger 5 6 Sub Main() 7 Dim i As Integer contatore 8 9 Console.WriteLine( Number & vbtab & Square & vbcrlf) 10 11 calcola i quadrato di numeri interi da 1 a 10 12 For i = 1 To 10 13 Console.WriteLine(i & vbtab & Square(i)) 14 Next 15 16 End Sub Main 17 18 Esegue Function Square 19 soltanto se la funzione è esplicitamente chiamata 20 Function Square(ByVal y As Integer) As Integer 21 Return y ^ 2 22 End Function Square 23 24 End Module modsquareinteger Number Square 1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 10 100 Figura 6.3 La procedura Function Square calcola il quadrato di numeri interi La struttura For (righe 12-14) visualizza i risultati dei quadrati dei primi 10 numeri interi. Ogni iterazione del ciclo calcola il quadrato della variabile di controllo i e visualizza il risultato nella finestra di comando. La procedura Function Square viene chiamata (riga 13) con l espressione Square(i). Quando il controllo del programma raggiunge questa espressione, viene chiamata la funzione Function Square (righe 20-22). A questo punto, il programma crea una copia del valore di i (l argomento) e il controllo passa alla prima riga di Function Square. La procedura Square riceve la copia del valore di i, che registra nel parametro y. La riga 21 è un istruzione Return, che termina l esecuzione della procedura e fornisce il risultato di y ^ 2 al caller. Il risultato viene restituito al punto della riga 13 dove è stata chiamata la procedura Square. La riga 13 visualizza il valore di i e il valore fornito da Square nella finestra di comando. Questo processo si ripete 10 volte.
152 CAPITOLO 6 Il formato della definizione di una procedura Function è il seguente: Function nome-procedura(lista-parametri) ) As tipo-risultato dichiarazioni e istruzioni End Function I termini nome-procedura, lista-parametri, dichiarazioni e istruzioni hanno lo stesso significato dei corrispondenti termini nella definizione di una procedura Sub. Nell intestazione della procedura Function, il termine tipo-risultato indica il tipo di dati del risultato che la procedura fornirà al programma che l ha chiamata. L istruzione Return espressione può essere inserita nel corpo di una procedura Function e restituisce il valore dell espressione al caller. Se necessario, Visual Basic tenta di convertire l espressione nel tipo-risultato della procedura Function. Le procedure Function restituiscono un solo valore. Quando viene eseguita l istruzione Return, il controllo ritorna immediatamente al punto in cui la procedura è stata chiamata. Errore tipico 6.4 Se l espressione di un istruzione Return non può essere convertita nel tipo-risultato della procedura Function, si verifica un errore durante l esecuzione (errore di runtime). Errore tipico 6.5 Se una procedura Function non fornisce un risultato (manca l istruzione Return), la procedura restituisce il valore di default, che spesso genera un errore di output. 6.5 Metodi Un metodo è una procedura qualsiasi che è contenuta all interno di una classe. Abbiamo già presentato vari metodi FCL (ovvero i metodi contenuti in classi che appartengono a FCL). I programmatori possono definire i loro metodi all interno di classi personalizzate, per esempio una classe utilizzata per definire un applicazione Windows. La Figura 6.4 riporta un applicazione Windows che usa due metodi per calcolare il maggiore di tre valori Double. Molte delle applicazioni finora presentate hanno facilitato l interazione con l utente utilizzando la finestra di comando (dove l utente può digitare un valore di input per il programma) o una finestra di dialogo (che presenta un messaggio all utente e gli consente di fare clic sul pulsante OK per confermare i dati della finestra). Nel Capitolo 4 abbiamo introdotto le applicazioni Windows creando un programma che visualizzava le informazioni in una label di un form. Sebbene la finestra di comando e le finestre di dialogo siano strumenti validi per ricevere l input dall utente e visualizzare l output, tuttavia le loro capacità sono limitate: la finestra di comando può ricevere una sola riga di input alla volta dall utente e la finestra di dialogo può visualizzare soltanto un messaggio. È normale ricevere più input alla volta (come i tre valori di questo esempio) o visualizzare contemporaneamente più blocchi di dati. Per introdurre un interfaccia di programmazione più sofisticata, il programma della Figura 6.4 usa la gestione degli eventi dell interfaccia GUI (ovvero la capacità di reagire a una variazione dello stato dell interfaccia GUI, come quando l utente fa clic su un pulsante).
PROCEDURE 153 La classe FrmMaximum usa un interfaccia GUI che è formata da tre TextBoxes (txtfirst txtfirst, txtsecond e txtthird) per l input dell utente, un pulsante (cmdmaximum cmdmaximum) per svolgere i calcoli e quattro Label, fra cui lblmaximum, per visualizzare i risultati. Abbiamo creato questi componenti visivamente, mediante Toolbox, e abbiamo cambiato le loro proprietà nella finestra Properties. Le righe 7-21 sono dichiarazioni che indicano il nome di ogni componente. Sebbene queste righe di codice facciano parte del codice generato da Visual Studio.NET, le abbiamo riportate per indicare gli oggetti che fanno parte del form (come sempre, il codice completo di questo programma può essere scaricato dal sito Web indicato nella Prefazione del libro). 1 Figura 6.4: Maximum.vb 2 Trova il massimo di tre numeri di input 3 4 Public Class FrmMaximum 5 Inherits System.Windows.Forms.Form 6 7 richiede i tre valori di input 8 Friend WithEvents lblone As System.Windows.Forms.Label 9 Friend WithEvents lbltwo As System.Windows.Forms.Label 10 Friend WithEvents lblthree As System.Windows.Forms.Label 11 12 visualizza il risultato 13 Friend WithEvents lblmaximum As System.Windows.Forms.Label 14 15 legge tre numeri 16 Friend WithEvents txtfirst As System.Windows.Forms.TextBox 17 Friend WithEvents txtsecond As System.Windows.Forms.TextBox 18 Friend WithEvents txtthird As System.Windows.Forms.TextBox 19 20 legge gli input e calcola il risultato 21 Friend WithEvents cmdmaximum As System.Windows.Forms.Button 22 23 codice generato da Visual Studio.NET 24 25 acquisisce i valori dai box di testo, avvia Maximum 26 Private Sub cmdmaximum_click(byval sender As System.Object, _ 27 ByVal e As System.EventArgs) Handles cmdmaximum.click 28 29 Dim value1, value2, value3 As Double 30 31 value1 = txtfirst.text 32 value2 = txtsecond.text 33 value3 = txtthird.text 34 35 lblmaximum.text = Maximum(value1, value2, value3) 36 End Sub cmdmaximum_click 37 Figura 6.4 Il metodo che determina il massimo di tre numeri (continua)
154 CAPITOLO 6 38 trova il massimo di tre valori 39 Function Maximum(ByVal valueone As Double, _ 40 ByVal valuetwo As Double, ByVal valuethree As Double) 41 42 Return Math.Max(Math.Max(valueOne, valuetwo), valuethree) 43 End Function Maximum 44 45 End Class FrmMaximum Figura 6.4 Il metodo che determina il massimo di tre numeri La riga 5 indica che la classe FrmMaximum eredita (Inherits Inherits) le proprietà dalla classe System.Windows.Forms.Form. Ricordiamo che tutti i form derivano da System.Windows. Forms.Form. Una classe può ereditare gli attributi e i comportamenti (dati e metodi) da un altra classe, se il nome della classe viene specificato a destra della parola chiave Inherits. Il Capitolo 9 tratta dettagliatamente l ereditarietà. FrmMaximum contiene due metodi definiti dal programmatore. Il metodo Maximum (righe 39-43) riceve tre parametri Double e fornisce il valore massimo di questi parametri. Notate che la definizione di questo metodo somiglia alla definizione di una procedura Function in un modulo. Il programma include anche il metodo cmdmaximum_click (righe 26-36). Quando l utente fa doppio clic su un componente, come Button, in modalità progetto, l IDE genera un metodo che gestisce un evento (gestore di eventi). Un evento rappresenta un azione dell utente, per esempio fare clic su un pulsante (Button Button) o modificare un valore. Un gestore di eventi è un metodo che viene eseguito quando si verifica un determinato evento. In questo caso, il metodo cmdmaximum_click gestisce l evento in cui l utente fa clic sul pulsante cmdmaximum. I programmatori scrivono il codice per svolgere determinati compiti quando si verificano tali eventi. Impiegando gli eventi e gli oggetti, i programmatori possono creare applicazioni che consentono di interagire in modo più sofisticato con l utente rispetto a quanto finora visto. I nomi dei gestori degli eventi creati dall IDE iniziano con il nome dell oggetto, seguito dal simbolo di sottolineatura e dal nome dell evento. Nel Capitolo 12 spiegheremo le tecniche che consentono a un programmatore di creare i propri gestori di eventi. Quando l utente fa clic sul pulsante cmdmaximum, viene eseguita la procedura cmdmaximum_click (righe 26-36). Le righe 31-33 leggono i valori contenuti nei tre box di testo utilizzando la proprietà Text. I valori vengono convertiti implicitamente nel tipo Double e memorizzati nelle variabili value1, value2 e value3.
PROCEDURE 155 La riga 35 chiama il metodo Maximum (righe 39-43) con gli argomenti value1, value2 e value3. I valori di questi argomenti vengono poi memorizzati nei parametri valueone, valuetwo e valuethree nel metodo Maximum. Questo metodo fornisce il risultato dell espressione nella riga 42, che effettua due chiamate del metodo Max della classe Math. Il metodo Max calcola il massimo dei suoi due argomenti Double; la riga 42 prima confronta valueone e valuetwo e poi il valore fornito a valuethree dalla prima chiamata del metodo. Le chiamate dei metodi, come Math.Max, che sono definite in una classe di FCL devono includere il nome della classe e l operatore punto (.), detto anche operatore di accesso ai membri. Le chiamate dei metodi definiti nella classe che contiene le chiamate, invece, richiedono soltanto che sia specificato il nome del metodo. Quando il controllo del programma ritorna al metodo cmdmaximum_click, la riga 35 assegna il valore fornito dal metodo Maximum alla proprietà Text di lblmaximum, che così viene visualizzato all utente. Notate che, quando digitate la parentesi aperta dopo il nome di un metodo o di una procedura, Visual Studio visualizza una finestra che contiene i nomi e i tipi degli argomenti della procedura. Questa è la caratteristica Parameter Info dell IDE (Figura 6.5). La finestra Parameter Info semplifica notevolmente il codice, perché identifica le procedure accessibili e i loro argomenti. La finestra Parameter Info visualizza anche le informazioni delle procedure definite dai programmatori e tutti i metodi contenuti in FCL. Buona abitudine 6.4 Per rendere più significative le informazioni fornite dalla finestra Parameter Info, assegnate ai parametri dei nomi che descrivono bene le loro funzioni. Visual Basic dispone anche della caratteristica IntelliSense, che visualizza tutti i membri di una classe. Per esempio, quando il programmatore digita l operatore punto (.) dopo il nome di una classe (Math Math, nella Figura 6.6), IntelliSense fornisce un elenco di tutti i metodi disponibili nella classe. La classe Math contiene numerosi metodi che consentono al programmatore di svolgere molte delle più comuni operazioni matematiche. Il prospetto della Figura 6.7 riporta alcuni metodi FCL della classe Math. Le variabili x e y di questo prospetto sono di tipo Double, tuttavia molti metodi hanno versioni che accettano valori di altro tipo come argomenti. La classe Math definisce anche due costanti matematiche: Math.PI e Math.E. La costante Math.PI (3.14159265358979323846) è il rapporto fra la circonferenza di un cerchio e il suo diametro. La costante Math.E (2.7182818284590452354) è la base dei logaritmi naturali (calcolati con il metodo Math.Log). La finestra Parameter Info Figura 6.5 La finestra Parameter Info dell IDE di Visual Studio.NET
156 CAPITOLO 6 La finestra IntelliSense Figura 6.6 La finestra IntelliSense dell IDE di Visual Studio.NET Errore tipico 6.6 È un errore di sintassi chiamare un metodo della classe Math senza anteporre al metodo il nome della classe Math e l operatore punto (.). Ingegneria del software 6.9 Non è necessario aggiungere un riferimento assembly per utilizzare i metodi della classe Math in un programma, in quanto questa classe si trova nel namespace System, che viene implicitamente aggiunto a tutte le applicazioni della console. 6.6 Promozione degli argomenti Un altra importante caratteristica delle definizioni delle procedure è la coercizione degli argomenti, che consiste nel forzare un argomento a diventare del tipo appropriato per essere passato a una procedura. Visual Basic supporta le conversioni di ampliamento e di riduzione. La conversione di ampliamento si verifica quando un tipo di dati viene trasformato in un altro tipo (che di solito può contenere più dati) senza perdita di dati. La conversione di riduzione si verifica quando c è il rischio di perdere dati durante la trasformazione da un tipo di dati a un tipo che può contenere meno dati. La Figura 6.8 elenca le conversioni di ampliamento supportate da Visual Basic. Per esempio, il metodo Sqrt della classe Math può essere chiamato con un argomento intero, anche se il metodo è definito nella classe Math in modo da ricevere un argomento Double. L istruzione Console.Write(Math.Sqrt(4)) valuta correttamente Math.Sqrt(4) e visualizza il valore 2. Visual Basic promuove (converte) il valore intero 4 nel valore Double 4.0 prima che il valore venga passato a Math.Sqrt. In questo caso, il valore dell argomento non corrisponde esattamente al tipo di parametro nella definizione del metodo, quindi viene effettuata una conversione di ampliamento implicita che trasforma il valore nel tipo appropriato, prima che il metodo venga chiamato. Visual Basic effettua anche conversioni di riduzione degli argomenti che vengono passati alle procedure. Per esempio, se la variabile di stringa number contiene il valore 4, il metodo Math.Sqrt(number) fornisce correttamete il valore 2. Tuttavia, alcune conversioni di ridu-
PROCEDURE 157 Metodo Descrizione Esempio Abs(x) Calcola il valore assoluto di x Abs(23.7) è 23.7 Abs(0) è 0 Abs( 23.7) è 23.7 Ceiling(x) Arrotonda x all intero più piccolo Ceiling(9.2) è 10.0 non minore di x Ceiling( 9.8) è 9.0 Cos(x) Calcola il coseno di x (x in radianti) Cos(0.0) è 1.0 Exp(x) Calcola la potenza e x Exp(1.0) è approssimativamente pari a 2.71828182845905 Exp(2.0) è approssimativamente pari a 7.38905609893065 Floor(x) Arrotonda x all intero più grande Floor(9.2) è 9.0 non maggiore di x Floor( 9.8) è 10.0 Log(x) Calcola il logaritmo naturale Log(2.7182818284590451) è (in base e) di x approssimativamente pari a 1.0 Log(7.3890560989306504) è approssimativamente pari a 2.0 Max(x, y) Fornisce il valore massimo fra x e y Max(2.3, 12.7) è 12.7 (ha le versioni per i valori Single, Max( 2.3, 12.7) è 2.3 Integer e Long) Min(x, y) Fornisce il valore minimo fra x e y Min(2.3, 12.7) è 2.3 (ha le versioni per i valori Single, Min( 2.3, 12.7) è 12.7 Integer e Long) Pow(x, y) Calcola x elevato a y (x y ) Pow(2.0, 7.0) è 128.0 Pow(9.0,.5) è 3.0 Sin(x) Calcola il seno di x (x in radianti) Sin(0.0) è 0.0 Sqrt(x) Calcola la radice quadrata di x Sqrt(9.0) è 3.0 Sqrt(2.0) è 1.4142135623731 Tan(x) Calcola la tangente di x (x in radianti) Tan(0.0) è 0.0 Figura 6.7 Metodi della classe Math zione possono fallire, provocando degli errori logici e di runtime. Per esempio, se number contiene il valore ciao e viene passato al metodo Math.Sqrt, si verifica un errore di runtime. Nel prossimo paragrafo esamineremo alcune precauzioni che può prendere il programmatore per evitare questi problemi. Errore tipico 6.7 Quando si effettua una conversione di riduzione (per esempio, da Double a Integer), la trasformazione di un valore da un tipo di dato primitivo a un altro tipo di dato primitivo potrebbe modificare il valore. Inoltre, la conversione da un valore intero a un valore floating-point e poi di nuovo a un valore intero può introdurre nel risultato degli errori di arrotondamento.
158 CAPITOLO 6 Tipi di dati Boolean Byte Char Date Decimal Double Integer Long Object Short Single String Tipi di conversioni Object Short, Integer, Long, Decimal, Single, Double o Object String o Object Object Single, Double o Object Object Long, Decimal, Single, Double o Object Decimal, Single, Double o Object nessuno Integer, Long, Decimal, Single, Double o Object Double o Object Object Figura 6.8 Conversioni di ampliamento di Visual Basic La promozione degli argomenti si applica non soltanto ai valori dei dati primitivi che vengono passati come argomenti ai metodi, ma anche alle espressioni che contengono i valori di due o più tipi di dati. Queste espressioni sono chiamate espressioni di tipo misto. In un espressione di tipo misto ogni valore viene promosso al tipo più alto dell espressione (in altre parole, la conversione di ampliamento continua finché i valori non saranno dello stesso tipo). Per esempio, se singlenumber è di tipo Single e integernumber è di tipo Integer, quando Visual Basic calcola l espressione singlenumber + integernumber il valore di integernumber viene convertito nel tipo Single e poi sommato a single- Number, generando un risultato di tipo Single. Sebbene i tipi di dati originali dei valori siano mantenuti, viene creata una versione temporanea di ogni valore da utilizzare nell espressione e i tipi di dati delle versioni temporanee vengono modificati in modo appropriato. 6.7 Conversione dei tipi di dati Visual Basic offre varie opzioni per controllare il modo in cui il compilatore dovrà gestire i tipi di dati. Queste opzioni possono aiutare i programmatori a evitare gli errori derivanti dalle conversioni di riduzione degli argomenti, rendendo il codice più leggibile e sicuro. La prima opzione è Option Explicit, che è impostata a On per default (nel senso che è stata abilitata nei programmi Visual Basic creati nei Capitoli 2-5). Option Explicit forza il programmatore a dichiarare esplicitamente tutte le variabili prima che siano utilizzate nel programma. Forzando le dichiarazioni esplicite, si eliminano gli errori ortografici e altri errori subdoli che possono verificarsi se Option Explicit non è attivata. Per esempio, se Option Explicit è impostata a Off, il compilatore interpreta i nomi delle variabili che hanno errori ortografici come nuove dichiarazioni di variabili, generando così degli errori difficili da identificare.
PROCEDURE 159 Un altra opzione, che per default è impostata a Off, è Option Strict. Visual Basic fornisce Option Strict come strumento per migliorare la leggibilità dei programmi e per ridurre il tempo di debugging. Se è impostata a On, Option Strict forza il compilatore a controllare tutte le conversioni degli argomenti e richiede al programmatore di svolgere una conversione esplicita per tutte le conversioni di riduzione che potrebbero causare la perdita dei dati (conversione da Double a Integer) o la conclusione del programma (conversione di una variabile di tipo String, come ciao, a Integer). I metodi della classe Convert cambiano i tipi di dati in modo esplicito. Il nome del metodo di conversione è formato dalla parola chiave To seguita dal nome del tipo di dati in cui il metodo dovrà convertire il suo argomento. Per esempio, per memorizzare la stringa immessa in input dall utente nella variabile number di tipo Integer (rappresentata in Visual Basic.NET come tipo Int32, un numero intero a 32 bit) con Option Strict impostata a On, utilizziamo l istruzione number = Convert.ToInt32(Console.ReadLine()) Quando Option Strict è impostata a Off, Visual Basic svolge implicitamente queste conversioni di tipo, nel senso che il programmatore potrebbe non rendersi conto che sta per essere effettuata una conversione di riduzione. Se i dati che stanno per essere convertiti non sono compatibili con il nuovo tipo di dati, si verifica un errore di runtime. Option Strict richiama l attenzione del programmatore sulle conversioni di riduzione, in modo che possano essere eliminate del tutto o gestite in modo appropriato. Nel Capitolo 11 descriveremo le tecniche per gestire gli errori provocati dal fallimento delle conversioni di riduzione degli argomenti. Ingegneria del software 6.10 Le conversioni esplicite consentono ai programmi di essere eseguiti in modo più efficiente, perché non è necessario determinare il tipo di dati da modificare prima di effettuare la conversione. Da questo punto in poi, tutti gli esempi avranno Option Strict impostata a On. Option Strict può essere attivata attraverso l IDE facendo clic con il pulsante destro del mouse sul nome del progetto in Solution Explorer. Dal menu risultante, selezionate Properties per aprire la finestra di dialogo Property Pages rappresentata nella Figura 6.9. Dall albero delle directory, che si trova a sinistra in questa finestra di dialogo, selezionate Build dall elenco Common Properties. Al centro della finestra di dialogo c è una casella a discesa chiamata Option Strict. Per default, questa opzione è impostata a Off. Selezionate On dalla casella a discesa e fate clic sul pulsante Apply. Se Option Strict è impostata a On nella finestra Property Pages, i suoi effetti si applicano globalmente all intero progetto. Il programmatore può anche abilitare Option Strict all interno di un singolo file di codice, digitando Option Strict On all inizio del codice, prima delle dichiarazioni e delle istruzioni Imports. 6.8 Valori e riferimenti Nel prossimo paragrafo descriveremo le tecniche per passare gli argomenti alle procedure come valori o come riferimenti. Per capire questo, occorre prima fare una distinzione fra i tipi di dati in Visual Basic.
160 CAPITOLO 6 Figura 6.9 La finestra di dialogo Property Pages con Option Strict impostata a On I tipi di dati di Visual Basic possono essere classificati come valori primitivi o come riferimenti a valori primitivi. Una variabile di tipo valore primitivo contiene i dati effettivi. Di solito, il valore primitivo viene utilizzato per un singolo dato, come un valore Integer o Double. Invece, una variabile di tipo riferimento contiene l indirizzo di una locazione della memoria dove sono registrati i dati. La locazione in memoria può contenere più dati. I dati di tipo riferimento sono globalmente detti oggetti e sono descritti nei Capitoli 8, 9 e 10. Entrambi i tipi di dati (valore e riferimento) includono tipi predefiniti e tipi creati dal programmatore. I tipi di valori predefiniti includono i tipi numeri interi (Byte Byte, Short, Integer e Long), i tipi floating-point (Single e Double) e i tipi Boolean, Date, Decimal e Char. I tipi di riferimenti predefiniti includono Object e String (anche se il tipo String spesso si comporta più come un valore, come vedremo nel prossimo paragrafo). I tipi di valori che possono essere costruiti dal programmatore includono le strutture (Structure Structure) e le enumerazioni (Enum Enum). I tipi di riferimenti che possono essere creati dal programmatore includono le classi, le interfacce e le classi delegate. I tipi di dati definiti dal programmatore sono descritti nei Capitoli 8, 9 e 14. La tabella nella Figura 6.10 elenca i tipi di dati primitivi che formano i blocchi di costruzione per tipi di dati più complessi, come le classi. Se Option Explicit è impostata a On, tutte le variabili devono avere un tipo di dati prima di essere utilizzate in un programma. La tabella fornisce per ogni tipo di valore la dimensione in bit (un byte è formato da 8 bit) e l intervallo dei valori consentiti. Per favorire la portabilità, la Microsoft ha scelto di utilizzare gli standard internazionali sia per i formati dei caratteri (Unicode) sia per i numeri floatingpoint (IEEE 754). I valori digitati direttamente nel codice del programma si chiamano literal. Ogni literal corrisponde a uno dei tipi di dati primitivi. Abbiamo già visto i valori literal per i tipi di dati
PROCEDURE 161 Tipo Dimensione (bit) Valori (*) Standard Boolean 16 True o False Char 16 Un carattere Unicode Unicode Byte 8 Da 0 a 255 Date 64 Dal 1 gennaio 0001 al 31 dicembre 9999 Da 0:00:00 a 23:59:59 Decimal 128 Da 1.0E 28 a 7.9E+28 Short 16 Da 32,768 a 32,767 Integer 32 Da 2,147,483,648 a 2,147,483,647 Long 64 Da 9,223,372,036,854,775,808 a 9,223,372,036,854,775,807 Single 32 Da ±1.5E 45 a ±3.4E+38 IEEE 754 Double 64 Da ±5.0E 324 a ±1.7E+308 IEEE 754 Object 32 Dato di qualsiasi tipo String Da 0 a circa 2 miliardi di caratteri Unicode Unicode (*) Nella notazione anglosassone, il punto (.) serve a separare la parte intera dalla parte decimale di un numero, mentre la virgola (,) serve a separare le migliaia della parte intera. Figura 6.10 Tipi di dati primitivi di Visual Basic più comuni, come String, Integer e Double. Tuttavia, alcuni tipi di dati di Visual Basic usano delle notazioni speciali per creare i valori literal. Per esempio, per creare un valore literal di tipo Char, basta aggiungere la lettera c dopo un singolo carattere di tipo String. L istruzione Dim character As Char = Z c dichiara la variabile character di tipo Char e la inizializza con la lettera Z. Analogamente, è possibile creare i valori literal di specifici numeri interi, aggiungendo a un valore il carattere S (per Short), I (per Integer) o L (per Long). Per creare literal floating-point, basta accodare al valore floating-point il carattere F (per Single) o R (per Double). Il carattere D può essere utilizzato per creare literal Decimal. Visual Basic consente anche di creare literal floating-point nella notazione scientifica: digitate il numero floating-point seguito dal character E e da un esponente positivo o negativo di una potenza di 10. Per esempio, 1.909E 5 corrisponde al valore 0.00001909. Questa notazione è utile per specificare valori floating-point che sono troppo grandi o troppo piccoli da scrivere nella notazione fixed-point. La Figura 6.11 elenca i caratteri di tipo adottati da Visual Basic e i corrispondenti esempi di literal per ogni tipo di dato. Tutti i valori literal devono essere compresi negli intervalli dei corrispondenti tipi di dati riportati nella Figura 6.10.
162 CAPITOLO 6 Tipo di dato Carattere di tipo Esempio Char c u c Single F 9.802E+31F Double R 6.04E 187R Decimal D 128309.76D Short S 3420S Integer I 867I Long L 19235827493259374L Figura 6.11 Literal con i caratteri di tipo 6.9 Passare gli argomenti Gli argomenti possono essere passati alle procedure in due modi: come valori o come riferimenti. Quando un argomento viene passato come valore, il programma crea una copia del valore dell argomento e la passa alla procedura chiamata. Quando l argomento viene passato come valore, le modifiche apportate alla copia della procedura chiamata non influiscono sul valore della variabile originale. Se, invece, l argomento viene passato come riferimento, il caller consente alla procedura chiamata di accedere e modificare direttamente i dati originali. La Figura 6.12 dimostra come passare gli argomenti di tipo valore come valori e come riferimenti (il Capitolo 7 descrive come passare gli argomenti di tipo riferimento come valori e come riferimenti). Il programma passa tre variabili di tipo valore (number1 number1, number2 e number3) in vari modi alle procedure SquareByValue (righe 39-45) e SquareByReference (righe 48-54). La parola chiave ByVal nell intestazione della procedura di SquareByValue (riga 39) indica che gli argomenti di tipo valore devono essere passati come valori. Quando la variabile number1 viene passata a SquareByValue (riga 13), una copia del valore memorizzato in number1 (2) viene passata alla procedura. Quindi, il valore di number1 nella procedura che effettua la chiamata (Main Main) non viene modificato quando il parametro number viene elevato al quadrato nella procedura SquareByValue (riga 42). La procedura SquareByReference usa la parola chiave ByRef (riga 48) per ricevere il suo parametro di tipo valore come riferimento. Quando Main chiama SquareByReference (riga 23), viene passato un riferimento al valore memorizzato in number2, che consente a SquareByReference di accedere direttamente al valore memorizzato nella variabile originale. Quindi, il valore memorizzato in number2 dopo l esecuzione di SquareByReference è uguale al valore finale del parametro number. Quando gli argomenti sono racchiusi fra parentesi tonde, una copia del valore dell argomento viene passato alla procedura, anche se l intestazione della procedura include la parola chiave ByRef. Quindi, il valore di number3 non cambia dopo che è stato passato a SquareByReference (riga 33) fra parentesi. Passare gli argomenti di tipo valore con la parola chiave ByRef è utile quando occorre che le procedure possano modificare direttamente i valori degli argomenti. Tuttavia, passare gli argomenti come riferimenti può compromettere la sicurezza dei dati, perché la procedura chiamata può modificare i dati del caller.
PROCEDURE 163 Una variabile tipo riferimento passata con la parola chiave ByVal viene passata effettivamente come riferimento, in quanto il valore che viene copiato è il riferimento per l oggetto. Sebbene Visual Basic consenta ai programmatori di utilizzare la parola chiave ByRef con i parametri tipo riferimento, di solito, non è necessario fare questo, tranne con il tipo String. Sebbene, tecnicamente, gli argomenti String siano riferimenti, tuttavia non possono essere modificati direttamente quando sono passati con la parola chiave ByVal, a causa di alcuni piccoli dettagli del tipo di dati String, che saranno descritti nel Capitolo 14. 1 Figura 6.12: ByRefTest.vb 2 Passare gli argomenti come riferimenti 3 4 Module modbyreftest 5 6 calcola il quadrato di tre valori; visualizza i risultati 7 Sub Main() 8 Dim number1 As Integer = 2 9 10 Console.WriteLine( Passing a value-type argument by value: ) 11 Console.WriteLine( Before calling SquareByValue, & _ 12 number1 is {0}, number1) 13 SquareByValue(number1) passa number1 come valore 14 Console.WriteLine( After returning from SquareByValue, & _ 15 number1 is {0} & vbcrlf, number1) 16 17 Dim number2 As Integer = 2 18 19 Console.WriteLine( Passing a value-type argument & _ 20 by reference: ) 21 Console.WriteLine( Before calling SquareByReference, & _ 22 number2 is {0}, number2) 23 SquareByReference(number2) passa number2 come riferimento 24 Console.WriteLine( After returning from & _ 25 SquareByReference, number2 is {0} & vbcrlf, number2) 26 27 Dim number3 As Integer = 2 28 29 Console.WriteLine( Passing a value-type argument & _ 30 by reference, but in parentheses: ) 31 Console.WriteLine( Before calling SquareByReference & _ 32 using parentheses, number3 is {0}, number3) 33 SquareByReference((number3)) passa number3 come valore 34 Console.WriteLine( After returning from & _ 35 SquareByReference, number3 is {0}, number3) 36 37 End Sub Main 38 39 quadrato di un argomento passato come valore (ByVal) 40 Sub SquareByValue(ByVal number As Integer) Figura 6.12 Applicazione di ByVal e ByRef per passare gli argomenti (continua)
164 CAPITOLO 6 41 Console.WriteLine( After entering SquareByValue, & _ 42 number is {0}, number) 43 number *= number 44 Console.WriteLine( Before exiting SquareByValue, & _ 45 number is {0}, number) 46 End Sub SquareByValue 47 48 quadrato di un argomento passato come riferimento (ByRef) 49 Sub SquareByReference(ByRef number As Integer) 50 Console.WriteLine( After entering SquareByReference & _ 51, number is {0}, number) 52 number *= number 53 Console.WriteLine( Before exiting SquareByReference & _ 54, number is {0}, number) 55 End Sub SquareByReference 56 57 End Module modbyreftest Passing a value-type argument by value: Before calling SquareByValue, number1 is 2 After entering SquareByValue, number is 2 Before exiting SquareByValue, number is 4 After returning from SquareByValue, number1 is 2 Passing a value-type argument by reference: Before calling SquareByReference, number2 is 2 After entering SquareByReference, number is 2 Before exiting SquareByReference, number is 4 After returning from SquareByReference, number2 is 4 Passing a value-type argument by reference, but in parentheses: Before calling SquareByReference using parentheses, number3 is 2 After entering SquareByReference, number is 2 Before exiting SquareByReference, number is 4 After returning from SquareByReference, number3 is 2 Figura 6.12 Applicazione di ByVal e ByRef per passare gli argomenti Collaudo e messa a punto 6.3 Quando gli argomenti sono passati come valori, le modifiche della copia della procedura chiamata non influenzano il valore della variabile originale. Questo evita potenziali effetti collaterali che potrebbero ostacolare lo sviluppo di corretti e affidabili sistemi software. Passate sempre gli argomenti di tipo valore come valori, a meno che non vogliate che la procedura chiamata possa modificare direttamente i dati del caller. Ingegneria del software 6.11 Sebbene le parole chiave ByVal e ByRef possano essere utilizzate per passare le variabili tipo riferimento come valori o come riferimenti, la procedura chiamata può manipolare direttamente la variabile tipo riferimento del caller in entrambi i casi. Di conseguenza, raramente è appropriato utilizzare ByRef con le variabili tipo riferimento. Questo particolare argomento sarà analizzato nel Capitolo 7.
PROCEDURE 165 6.10 Durata degli identificatori Nei precedenti capitoli abbiamo utilizzato gli identificatori per vari scopi, per esempio come nomi di variabili, classi e procedure definite dai programmatori. Gli identificatori contenuti in un programma possiedono altri attributi, tra cui la durata e la visibilità. La durata o ciclo di vita di un identificatore è il periodo durante il quale l identificatore esiste nella memoria. Alcuni identificatori esistono soltanto per un breve periodo di tempo, altri vengono ripetutamente creati e distrutti; altri ancora esistono per l intera esecuzione di un programma. La visibilità di un identificatore è la porzione del programma dove l identificatore può essere oggetto di riferimenti. Alcuni identificatori possono essere referenziati all interno dell intero programma, mentre altri soltanto in specifiche porzioni del programma. Questo paragrafo prende in esame la durata degli identificatori, mentre il prossimo parlerà della loro visibilità. Ingegneria del software 6.12 Quando un istruzione Return fornisce le informazioni di una procedura Function, le variabili di tipo valore sono sempre restituite come valori (una copia dei valori originali), mentre le variabili di tipo riferimento sono sempre restituite come riferimenti (viene restituito un riferimento a un oggetto). Gli identificatori che rappresentano delle variabili locali in una procedura (variabili e parametri dichiarati nel corpo della procedura) hanno una durata automatica. Le variabili con durata automatica vengono create quando il controllo del programma raggiunge la procedura dove sono dichiarate, esistono per tutto il tempo in cui è attiva la procedura e vengono distrutte quando la procedura termina (le variabili di una procedura possono essere dichiarate anche con la parola chiave Static; in questo caso, la variabile viene creata e inizializzata durante la prima esecuzione della procedura, che mantiene il suo valore fra le successive chiamate). Da questo momento in poi, faremo riferiremo a queste variabili usando il termine variabili automatiche o variabili locali. Le variabili dichiarate all interno di un modulo o di una classe, ma all esterno della definizione di una procedura, esistono finché la classe o il modulo che le contengono sono in memoria. Le variabili dichiarate in un modulo esistono durante l esecuzione del programma. Per default, una variabile dichiarata in una classe, come la classe Form per un applicazione Windows, è una variabile di istanza. Nel caso della classe Form, questo significa che la variabile viene creata quando la classe Form viene caricata in memoria ed esiste finché Form non sarà chiusa. Le variabili di istanza di una classe sono descritte nel Capitolo 8. Ingegneria del software 6.13 La durata automatica è un esempio del principo dei privilegi minimi, che stabilisce che ogni componente di un sistema deve godere dei diritti e dei privilegi che sono strettamente necessari per svolgere il suo compito. Questo consente di ridurre i rischi di errori accidentali nei sistemi. Perché mantenere in memoria variabili che non sono richieste? 6.11 Regole di visibilità La visibilità di una variabile, di un riferimento o metodo è la parte di programma in cui è possibile accedere all identificatore. Un identificatore può avere visibilità nella classe, nel modulo nel namespace e nel blocco.
166 CAPITOLO 6 I membri di una classe hanno visibilità nella classe; questo significa che sono visibili nel cosiddetto spazio di dichiarazione di una classe. La visibilità inizia a partire dall identificatore di classe, dopo la parola chiave Class e termina con l istruzione End Class. Questa visibilità permette ai metodi della classe di chiamare direttamente tutti i membri definiti nella classe e di accedere ai membri ereditati in quella classe (come diremo nel Capitolo 8, i metodi Shared sono un eccezione a questa regola). In un certo senso, i membri di una classe sono globali per i metodi della classe in cui sono definiti. Questo significa che i metodi possono modificare direttamente le variabili di istanza della classe (le variabili dichiarate nella definizione della classe, ma all esterno della definizione di un metodo) e invocare altri metodi della classe. In Visual Basic.NET gli identificatori dichiarati all interno di un blocco, come il corpo di una definizione di procedura o il corpo di una struttura If/Then, hanno visibilità nel blocco (spazio di dichiarazione delle variabili locali). La visibilità nel blocco inizia dalla dichiarazione dell identificatore e termina con End o un istruzione equivalente (per esempio, Next). Le variabili locali di una procedura hanno visibilità nel blocco. Anche i parametri di una procedura hanno visibilità nel blocco, perché sono trattati come le variabili locali della procedura. Qualsiasi blocco può contenere dichiarazioni di variabili. Quando i blocchi sono nidificati nel corpo di una procedura, si verifica un errore se un identificatore dichiarato in un blocco esterno ha lo stesso nome di un identificatore dichiarato in un blocco interno. Tuttavia, se una variabile locale di una procedura chiamata ha lo stesso nome di una variabile con visibilità nella classe (per esempio, una variabile di istanza), la variabile visibile nella classe viene tenuta nascosta finché la procedura chiamata non avrà terminato l esecuzione. Le variabili dichiarate in un modulo hanno visibilità nel modulo, che è simile alla visibilità nella classe. Le variabili dichiarate in un modulo sono accessibili a tutte le procedure definite nel modulo. Analogamente alle variabili con visibilità nella classe, le variabili con visibilità nel modulo sono nascoste quando hanno lo stesso identificatore di una variabile locale. Per default, le procedure definite in un modulo hanno visibilità nel namespace, nel senso che sono accessibili all interno di un progetto. La visibilità nel namespace è utile nei progetti che contengono più componenti (per esempio, moduli e classi). Se un progetto contiene un modulo e una classe, i metodi della classe possono accedere alle procedure del modulo. Sebbene le variabili dichiarate in un modulo abbiano visibilità nel modulo, esse possono acquisire la visibilità nel namespace, se si sostituisce la parola chiave Dim con Public nella dichiarazione. Nel Paragrafo 6.18 spiegheremo come aggiungere i moduli ai progetti. Buona abitudine 6.5 Evitate di usare i nomi di variabili locali che nascondono i nomi di variabili di classi o di moduli. Il programma della Figura 6.13 applica il concetto di visibilità alle variabili di istanza e alle variabili locali. La variabile di istanza value è dichiarata e inizializzata a 1 nella riga 12. Come è stato spiegato in precedenza, questa variabile è nascosta in qualsiasi procedura che dichiara una variabile di nome value. Il metodo FrmScoping_Load dichiara una variabile locale value (riga 19) e la inizializza a 5. Questa variabile è visualizzata nella label lbloutput (la dichiarazione nella riga 7 in effetti fa parte del codice generato da Visual Studio.NET) per mostrare che la variabile di istanza value è nascosta in FrmScoping_Load.
PROCEDURE 167 1 Figura 6.13: Scoping.vb 2 Applicazione delle regole di visibilità 3 4 Public Class FrmScoping 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents lbloutput As System.Windows.Forms.Label 8 9 codice generato da Visual Studio.NET 10 11 la variabile di istanza può essere usata ovunque in una classe 12 Dim value As Integer = 1 13 14 dimostra la visibilità nella classe e nel blocco 15 Private Sub FrmScoping_Load(ByVal sender As System.Object, _ 16 ByVal e As System.EventArgs) Handles MyBase.Load 17 la variabile locale di FrmScoping_Load nasconde 18 la variabile di istanza 19 Dim value As Integer = 5 20 21 lbloutput.text = local variable value in & _ 22 FrmScoping_Load is & value 23 24 MethodA() MethodA ha un valore locale automatico 25 MethodB() MethodB usa il valore della variab. di istanza 26 MethodA() MethodA crea un nuovo valore locale automatico 27 MethodB() la variabile di istanza mantiene il suo valore 28 29 lbloutput.text &= vbcrlf & vbcrlf & local variable & _ 30 value in FrmScoping_Load is & value 31 End Sub FrmScoping_Load 32 33 la variabile locale automatica nasconde la var. di istanza 34 Sub MethodA() 35 Dim value As Integer = 25 inizializzata a ogni chiamata 36 37 lbloutput.text &= vbcrlf & vbcrlf & local variable & _ 38 value in MethodA is & value & after entering MethodA 39 value += 1 40 lbloutput.text &= vbcrlf & local variable & _ 41 value in MethodA is & value & before exiting MethodA 42 End Sub MethodA 43 44 usa il valore della variabile di istanza 45 Sub MethodB() 46 lbloutput.text &= vbcrlf & vbcrlf & instance variable & _ 47 value is & value & after entering MethodB 48 value *= 10 Figura 6.13 Regole di visibilità in una classe (continua)
168 CAPITOLO 6 49 lbloutput.text &= vbcrlf & instance variable & _ 50 value is & value & before exiting MethodB 51 End Sub MethodB 52 53 End Class FrmScoping Figura 6.13 Regole di visibilità in una classe Il programma definisce altri due metodi, MethodA e MethodB, che non ricevono argomenti e non restituiscono alcun dato. Ogni metodo viene chiamato due volte da FrmScoping_Load. MethodA definisce la variabile locale value (riga 35) e la inizializza a 25. Quando viene chiamato MethodA, la variabile viene visualizzata nella label lbloutput, incrementata e visualizzata di nuovo prima della conclusione del metodo. La variabile automatica value viene distrutta quando termina MethodA. Quindi, ogni volta che questo metodo viene chiamato, la variabile value deve essere rigenerata e inizializzata a 25. MethodB non dichiara alcuna variabile. Di conseguenza, quando questa procedura fa riferimento alla variabile value, viene utilizzata la variabile di istanza value (riga 12). Quando viene chiamato MethodB, viene visualizzata la variabile di istanza, moltiplicata per 10 e visualizzata di nuovo prima della fine del metodo. La volta successiva che viene chiamato MethodB, la variabile di istanza mantiene il suo valore modificato 10 e la riga 48 fa sì che value (riga 12) diventi 100. Infine, il programma visualizza di nuovo la variabile locale value nel metodo FrmScoping_Load per mostrare che nessuna chiamata di metodo ha modificato questa variabile. 6.12 Generazione di numeri casuali Adesso faremo una breve e divertente digressione per studiare una popolare applicazione della programmazione: la simulazione e il gioco. In questo e nel prossimo paragrafo, svilupperemo un programma di gioco ben strutturato che include vari metodi. Il programma utilizza molte delle strutture di controllo che abbiamo già studiato. Ciò che attira la gente nei casinò è il fattore della casualità, ovvero la possibilità che il caso possa convertire una manciata di monete in ricchezza. L elemento della casualità può essere introdotto anche nelle applicazioni per computer, utilizzando la classe Random (che appartiene al namespace System). Considerate le seguenti istruzioni: Dim randomobject As Random = New Random() Dim randomnumber As Integer = randomobject.next()
PROCEDURE 169 La prima istruzione dichiara randomobject come riferimento a un oggetto di tipo Random. Il valore di randomobject è inizializzato con la parola chiave New, che crea una nuova istanza della classe Random (ovvero un oggetto Random). In Visual Basic la parola chiave New crea un oggetto del tipo specificato e fornisce la locazione in memoria dell oggetto. La seconda istruzione dichiara la variabile randomnumber di tipo Integer e le assegna il valore fornito dal metodo Next della classe Random. L accesso al metodo Next avviene specificando il nome del metodo dopo il nome di riferimento (randomobject randomobject) e l operatore punto (.). Il metodo Next genera un numero intero positivo compreso fra zero e la costante Int32.MaxValue (2.147.483.647). Se il metodo Next genera valori casuali, ogni valore in questo intervallo ha la stessa probabilità di essere scelto quando viene eseguito Next. I valori forniti da Next, in effetti, sono numeri pseudo-casuali ovvero una sequenza di valori prodotti da un complesso calcolo matematico. Questo calcolo matematico richiede un valore di base (seed) che, se è diverso ogni volta che viene eseguito il programma, fa sì che anche le operazioni matematiche siano diverse; di conseguenza, i numeri generati possono essere considerati casuali. Quando creiamo un oggetto Random, l ora corrente del giorno diventa il valore di base dei calcoli. In alternativa, possiamo passare un valore di base come argomento, inserendolo fra parentesi dopo New Random. Passando lo stesso valore di base due volte, si ottiene la stessa serie di numeri casuali. È meglio utilizzare l ora corrente del giorno come valore di base, perché è molto probabile che cambi l istante di tempo ogni volta che creiamo un oggetto Random. Molti programmi richiedono la generazione di numeri casuali. L intervallo dei valori prodotti da Next (da 0 a 2.147.483.647) spesso è diverso da quello richiesto da una particolare applicazione. Per esempio, un programma che simula il lancio di una moneta richiede soltanto il valore 0 per testa e 1 per croce. Un programma che simula il lancio di un dado a sei facce richiede un numero intero casuale compreso fra 1 e 6. Passando un argomento al metodo Next in questo modo value = 1 + randomobject.next(6) possiamo generare numeri interi da 1 a 6. Quando passiamo un solo argomento a Next, i valori forniti da Next saranno compresi nell intervallo da 0 fino al valore dell argomento, che però viene escluso. Questo processo si chiama scaling. Il numero 6 è detto fattore di scala. La precedente istruzione fa scorrere l intervallo dei numeri generati, sommando 1 al risultato precedente, in modo che i valori forniti da Next siano compresi fra 1 e 6, anziché fra 0 e 5. I valori prodotti da Next sono sempre compresi nell intervallo x <= x + randomobject.next(y) < y Visual Basic semplifica questo processo consentendo al programmatore di passare due argomenti a Next. Per esempio, la precedente istruzione può essere scritta in questo modo: value = randomobject.next(1, 7) Notate che bisogna specificare 7 come secondo argomento del metodo Next per generare numeri interi da 1 a 6. Il primo argomento indica il valore minimo dell intervallo desiderato, mentre il secondo argomento è uguale a 1 + il valore massimo dell intervallo desiderato. Dunque, i valori generati da questa versione di Next saranno compresi nell intervallo x <= randomobject.next(x, y) < y
170 CAPITOLO 6 In questo caso, x è il valore di scorrimento e y x è il fattore di scala. La Figura 6.14 dimostra l uso della classe Random e del metodo Next, simulando 20 lanci di un dado a sei facce e visualizzando il valore di ogni lancio in un MessageBox. Notate che tutti i valori appartengono all intervallo 1-6 (estremi inclusi). 1 Figura 6.14: RandomInteger.vb 2 Generazione di numeri interi casuali 3 4 Imports System.Windows.Forms 5 6 Module modrandominteger 7 8 Sub Main() 9 Dim randomobject As Random = New Random() 10 Dim randomnumber As Integer 11 Dim output As String = 12 Dim i As Integer 13 14 For i = 1 To 20 15 randomnumber = randomobject.next(1, 7) 16 output &= randomnumber & 17 18 If i Mod 5 = 0 Then i è un multiplo di 5? 19 output &= vbcrlf 20 End If 21 22 Next 23 24 MessageBox.Show(output, 20 Random Numbers from 1 to 6, _ 25 MessageBoxButtons.OK, MessageBoxIcon.Information) 26 End Sub Main 27 28 End Module modrandominteger Figura 6.14 Numeri interi casuali generati dal metodo Next della classe Random Il programma della Figura 6.15 usa la classe Random per simulare il lancio di dadi a sei facce. Alcune funzioni di questo programma saranno utilizzate in un altro esempio (Figura 6.16) per dimostrare che i numeri generati da Next hanno approssimativamente la stessa probabilità.
PROCEDURE 171 Nel programma della Figura 6.15 abbiamo utilizzato il metodo cmdroll_click, che viene eseguito ogni volta che l utente fa clic su cmdroll, che esegue quattro volte il metodo DisplayDie (una volta per ogni Label del Form). Il metodo DisplayDie (righe 35-44) visualizza quattro dadi, che vengono lanciati ogni volta che l utente fa clic su cmdroll. Notate che, quando questo programma viene eseguito, le immagini dei dadi non vengono visualizzate finché l utente non fa clic per la prima volta su cmdroll. Il metodo DisplayDie specifica l immagine appropriata al valore della faccia del dado che è stato calcolato da Next (riga 38). Notate che randomobject è dichiarata come una variabile di istanza di FrmRollDice (riga 21). Questo consente allo stesso oggetto Random di essere utilizzato ogni volta che viene eseguito DisplayDie. Abbiamo utilizzato la proprietà Image (riga 41) per visualizzare un immagine in una label. Impostiamo il valore della proprietà con un istruzione di assegnazione (righe 41-43). Abbiamo specificato l immagine da visualizzare mediante la procedura FromFile nella classe Image (contenuta nel namespace System.Drawing). Il metodo Directory.GetCurrentDirectory (contenuto nel namespace System.IO) fornisce la locazione della cartella dove si trova il progetto corrente, che include bin, la directory che contiene i file compilati del progetto. Le immagini del dado devono essere registrate in questa cartella affinché le soluzioni delle Figure 6.15 e 6.16 possano funzionare correttamente. Le immagini utilizzate in questo esempio e in molti altri esempi di questo capitolo sono state create con Adobe Photoshop Elements e possono essere scaricate dal sito Web di questo libro. Notate che bisogna includere una direttiva Imports (riga 4) per usare le classi di System.IO, non per usare le classi di System.Drawing. Per default, le applicazioni Windows importano vari namespace, inclusi Microsoft.VisualBasic, System, System.Drawing, System.Windows.Forms e System.Collections. Questi namespace vengono importati per l intero progetto, quindi non occorre specificare le corrispondenti direttive Imports in ogni file di progetto. È possibile importare altri namespace in un progetto attraverso la finestra di dialogo Property Pages (che potete aprire selezionando Project Properties dalla barra dei menu) nella lista Imports sotto la voce Common Properties. Alcuni dei namespace importati per default non sono utilizzati in questo programma. Per esempio, non abbiamo ancora utilizzato il namespace System.Collections, che consente ai programmatori di creare gruppi di oggetti. L applicazione Windows della Figura 6.16 lancia 12 dadi per mostrare che i numeri generati dalla classe Random hanno all incirca le stesse frequenze di probabilità. Il programma visualizza le frequenze cumulative di ogni faccia del dado in un TextBox. 1 Figura 6.15: RollDice.vb 2 Lancia quattro dadi 3 4 Imports System.IO 5 6 Public Class FrmRollDice 7 Inherits System.Windows.Forms.Form 8 9 pulsante Roll per lanciare i dadi 10 Friend WithEvents cmdroll As System.Windows.Forms.Button 11 Figura 6.15 Il programma che simula il lancio di quattro dadi (continua)
172 CAPITOLO 6 12 label per visualizzare le immagini dei dadi 13 Friend WithEvents lbldie1 As System.Windows.Forms.Label 14 Friend WithEvents lbldie2 As System.Windows.Forms.Label 15 Friend WithEvents lbldie3 As System.Windows.Forms.Label 16 Friend WithEvents lbldie4 As System.Windows.Forms.Label 17 18 codice generato da Visual Studio.NET 19 20 dichiara il riferimento all oggetto Random 21 Dim randomnumber As Random = New Random() 22 23 visualizza il risultato dei quattro lanci 24 Private Sub cmdroll_click(byval sender As System.Object, _ 25 ByVal e As System.EventArgs) Handles cmdroll.click 26 27 il metodo che assegna a caso una faccia a ogni dado 28 DisplayDie(lblDie1) 29 DisplayDie(lblDie2) 30 DisplayDie(lblDie3) 31 DisplayDie(lblDie4) 32 End Sub cmdroll_click 33 34 acquisisce un immagine casuale del dado 35 Sub DisplayDie(ByVal dielabel As Label) 36 37 genera un numero intero casuale compreso fra 1 e 6 38 Dim face As Integer = randomnumber.next(1, 7) 39 40 carica l immagine corrispondente 41 dielabel.image = Image.FromFile( _ 42 Directory.GetCurrentDirectory & \Images\die & _ 43 face &.png ) 44 End Sub DisplayDie 45 46 End Class FrmRollDice Figura 6.15 Il programma che simula il lancio di quattro dadi
PROCEDURE 173 Dopo il codice della Figura 6.16 sono illustrate due immagini: quella a sinistra mostra il programma appena viene eseguito; quella a destra mostra il programma dopo che l utente ha fatto clic sul pulsante Roll più di 200 volte. I numeri prodotti dal metodo Next sono casuali, in quanto le frequenze dei valori delle facce dei dadi (1-6) sono approssimativamente le stesse (come indica l immagine a destra). Per mostrare che i lanci dei dadi hanno le stesse probabilità, il codice della Figura 6.16 è stato modificato per calcolare alcuni dati statistici. Nella riga 31 abbiamo dichiarato i contatori per ogni lancio possibile. Notate che i contatori sono variabili di istanza, cioè variabili con visibilità nella classe. Le righe 60-76 visualizzano le frequenze di ogni lancio come percentuali, utilizzando il codice di formato P. Come dimostra l output del programma, abbiamo utilizzato la funzione Next per simulare il lancio di un dado a sei facce. Se effettuate un numero di lanci relativamente elevato, potrete notare che ogni faccia (valori da 1 a 6) ha la stessa probabilità di presentarsi (pari all incirca a un sesto). Notate che non abbiamo utilizzato nessun Case Else nella struttura Select (righe 91-111), perché sappiamo che i valori generati appartengono all intervallo 1-6. Nel Capitolo 7 sostituiremo l intera struttura Select di questo programma con un istruzione di una sola riga. Eseguite il programma diverse volte e analizzate i risultati. Ogni volta che eseguite il programma, viene generata una sequenza diversa di numeri casuali; questo determina una variazione delle frequenze. 6.13 Esempio: un gioco di azzardo Uno dei giochi di azzardo più popolari è il gioco a dadi craps (tiro sfortunato), che viene regolarmente giocato nei casinò e nei vicoli di tutto il mondo. Le regole del gioco sono semplici: Un giocatore lancia due dadi. Ogni dado ha sei facce. Queste facce contengono 1, 2, 3, 4, 5 e 6 puntini. Dopo che i dadi si sono fermati, si calcola la somma dei puntini sulle due facce rivolte verso l alto. Se la somma è 7 o 11 al primo tiro, il giocatore ha vinto. Se la somma è 2, 3 o 12 al primo tiro, il giocatore ha perso (e ha vinto il banco ). Se la somma è 4, 5, 6, 8, 9 o 10 al primo tiro, questa somma diventa il punteggio del giocatore. Per vincere, è necessario continuare a lanciare i dadi finché non si ottiene il proprio punteggio. Il giocatore perde qualora realizza un 7 prima di ottenere il proprio punteggio. Il programma della Figura 6.17 simula questo gioco. Notate che il giocatore deve semore lanciare due dadi. Quando eseguite l applicazione, fate clic sul pulsante Play per avviare il gioco. Il form mostra i risultati di ogni lancio. Le videate mostrano il risultato di due giocate. Le righe 9-21 indicano che questo programma usa le classi PictureBox, Label, Button e GroupBox del namespace System.Windows.Forms. Sebbene Windows Form Designer usi il nome completo per queste classi (per esempio, System.Windows.Forms.PictureBox), noi abbiamo mostrato soltanto i nomi delle classi per semplicità. I nomi delle classi sono sufficienti in questo caso, perché il namespace System.Windows.Forms viene importato per default per le applicazioni Windows. Questo programma introduce nuovi componenti GUI. Il primo, chiamato GroupBox, visualizza il punteggio del giocatore. GroupBox è un contenitore che viene usato per raggruppare componenti correlati. Dentro pointdicegroup di GroupBox aggiungiamo due PictureBoxes, che sono i componenti che visualizzano le immagini. Per aggiungere un componente a GroupBox, basta trascinarlo con il mouse e rilasciarlo dentro GroupBox.
174 CAPITOLO 6 1 Figura 6.16: RollTwelveDice.vb 2 Lancio di dadi e distribuzione delle frequenze 3 4 Imports System.IO 5 6 Public Class FrmRollTwelveDice 7 Inherits System.Windows.Forms.Form 8 9 label per visualizzare le immagini dei dadi 10 Friend WithEvents lbldie1 As System.Windows.Forms.Label 11 Friend WithEvents lbldie2 As System.Windows.Forms.Label 12 Friend WithEvents lbldie3 As System.Windows.Forms.Label 13 Friend WithEvents lbldie4 As System.Windows.Forms.Label 14 Friend WithEvents lbldie5 As System.Windows.Forms.Label 15 Friend WithEvents lbldie6 As System.Windows.Forms.Label 16 Friend WithEvents lbldie7 As System.Windows.Forms.Label 17 Friend WithEvents lbldie8 As System.Windows.Forms.Label 18 Friend WithEvents lbldie9 As System.Windows.Forms.Label 19 Friend WithEvents lbldie10 As System.Windows.Forms.Label 20 Friend WithEvents lbldie11 As System.Windows.Forms.Label 21 Friend WithEvents lbldie12 As System.Windows.Forms.Label 22 23 visualizza le frequenze dei lanci 24 Friend WithEvents displaytextbox As _ 25 System.Windows.Forms.TextBox 26 27 codice generato da Visual Studio.NET 28 29 dichiarazioni 30 Dim randomobject As Random = New Random() 31 Dim ones, twos, threes, fours, fives, sixes As Integer 32 33 Private Sub cmdroll_click _ 34 (ByVal sender As System.Object, _ 35 ByVal e As System.EventArgs) Handles cmdroll.click 36 37 assegna a caso le facce ai 12 dadi mediante DisplayDie 38 DisplayDie(lblDie1) 39 DisplayDie(lblDie2) 40 DisplayDie(lblDie3) 41 DisplayDie(lblDie4) 42 DisplayDie(lblDie5) 43 DisplayDie(lblDie6) 44 DisplayDie(lblDie7) 45 DisplayDie(lblDie8) 46 DisplayDie(lblDie9) 47 DisplayDie(lblDie10) 48 DisplayDie(lblDie11) 49 DisplayDie(lblDie12)
PROCEDURE 175 50 51 Dim total As Integer = ones + twos + threes + fours + _ 52 fives + sixes 53 54 Dim output As String 55 56 visualizza le frequenze delle facce 57 output = Face & vbtab & vbtab & _ 58 Frequency & vbtab & Percent 59 60 output &= vbcrlf & 1 & vbtab & vbtab & ones & _ 61 vbtab & vbtab & String.Format( {0:P}, ones / total) 62 63 output &= vbcrlf & 2 & vbtab & vbtab & twos & vbtab & _ 64 vbtab & String.Format( {0:P}, twos / total) 65 66 output &= vbcrlf & 3 & vbtab & vbtab & threes & vbtab & _ 67 vbtab & String.Format( {0:P}, threes / total) 68 69 output &= vbcrlf & 4 & vbtab & vbtab & fours & vbtab & _ 70 vbtab & String.Format( {0:P}, fours / total) 71 72 output &= vbcrlf & 5 & vbtab & vbtab & fives & vbtab & _ 73 vbtab & String.Format( {0:P}, fives / total) 74 75 output &= vbcrlf & 6 & vbtab & vbtab & sixes & vbtab & _ 76 vbtab & String.Format( {0:P}, sixes / total) & vbcrlf 77 78 displaytextbox.text = output 79 End Sub cmdroll_click 80 81 visualizza l immagine di un singolo dado 82 Sub DisplayDie(ByVal dielabel As Label) 83 84 Dim face As Integer = randomobject.next(1, 7) 85 86 dielabel.image = _ 87 Image.FromFile(Directory.GetCurrentDirectory & _ 88 \Images\die & face &.png ) 89 90 conteggio delle facce dei dadi 91 Select Case face 92 93 Case 1 94 ones += 1 95 96 Case 2 97 twos += 1 Figura 6.16 La classe Random usata per simulare il lancio di 12 dadi a sei facce (continua)
176 CAPITOLO 6 98 99 Case 3 100 threes += 1 101 102 Case 4 103 fours += 1 104 105 Case 5 106 fives += 1 107 108 Case 6 109 sixes += 1 110 111 End Select 112 113 End Sub DisplayDie 114 115 End Class FrmRollTwelveDice Figura 6.16 La classe Random usata per simulare il lancio di 12 dadi a sei facce Prima di introdurre le definizioni dei metodi, il programma include varie dichiarazioni, come la prima enumerazione (Enum Enum) nelle righe 26-32 e i primi identificatori Const nelle righe 35-36. Gli identificatori Const e le enumerazioni migliorano la leggibilità del programma perché descrivono meglio i numeri o le stringhe che hanno un significato speciale. Gli identificatori Const e le enumerazioni aiutano i programmatori a garantire che i valori siano coerenti in tutto il programma. La parola chiave Const crea un solo identificatore costante; le enumerazioni sono utilizzate per definire gruppi di costanti correlate. In questo
PROCEDURE 177 caso, abbiamo creato gli identificatori Const per i nomi dei file che sono utilizzati in tutto il programma e una enumerazione di nomi descrittivi per le varie combinazioni di dadi del gioco craps (SNAKE_EYES SNAKE_EYES, TREY, CRAPS, YO_LEVEN e BOX_CARS). Gli identificatori Const devono essere assegnati a valori costanti e non possono essere modificati dopo che sono stati dichiarati. Dopo le dichiarazioni degli identificatori costanti e le dichiarazioni delle variabili di istanza (righe 38-41), viene definito il metodo cmdplay_click (righe 44-84). Il metodo cmdplay_click gestisce gli eventi cmdplay.click (che si verificano quando l utente fa doppio clic su cmdplay in modalità progetto). In questo esempio, il compito del metodo è elaborare l interazione dell utente con il pulsante cmdplay (che visualizza il testo Play nell interfaccia dell utente). Quando l utente fa clic sul pulsante Play, il metodo cmdplay_click imposta un nuovo gioco, inizializzando diversi valori (righe 48-50). Impostando la proprietà Image di picpointdie1 e picpointdie2 a Nothing (righe 53-54), PictureBox si presenta vuoto. La parola chiave Nothing può essere utilizzata con una variabile di tipo riferimento per specificare che nessun oggetto è associato alla variabile. Il metodo cmdplay_click esegue il lancio di apertura del gioco, chiamando RollDice (riga 56). Internamente, RollDice genera due numeri casuali (righe 116-132) e chiama il metodo DisplayDie (righe 106-113), che carica l immagine appropriata del dado all interno di PictureBox. Quando RollDice termina, la struttura Select (righe 59-82) analizza il valore fornito da RollDice per determinare come dovrà continuare il gioco (finire con una vincita o una perdita oppure consentire altri lanci). A seconda del valore del lancio, i pulsanti cmdroll e cmdplay vengono abilitati o disabilitati. Quando un pulsante è disabilitato, nessuna azione può essere svolta se il giocatore fa clic su di esso. I pulsanti possono essere abilitati o disabilitati impostando la proprietà Enabled, rispettivamente, a True o a False. Se il pulsante cmdroll è abilitato, facendo clic su di esso viene chiamato il metodo cmdroll_click (righe 87-103), che esegue un altro lancio di dadi. Infine, il metodo cmdroll_click analizza il risultato del lancio e fa sapere al giocatore se ha vinto oppure no. 1 Figura 6.17: CrapsGame.vb 2 Il gioco a dadi Craps 3 4 Imports System.IO 5 6 Public Class FrmCrapsGame 7 Inherits System.Windows.Forms.Form 8 9 Friend WithEvents cmdroll As Button lancia i dadi 10 Friend WithEvents cmdplay As Button nuovo gioco 11 12 visualizza i dadi dopo un lancio 13 Friend WithEvents picdie1 As PictureBox 14 Friend WithEvents picdie2 As PictureBox 15 Figura 6.17 Il gioco a dadi Craps simulato con la classe Random (continua)
178 CAPITOLO 6 16 pointdicegroup raggruppa i dadi con i punti 17 Friend WithEvents pointdicegroup As GroupBox 18 Friend WithEvents picpointdie1 As PictureBox 19 Friend WithEvents picpointdie2 As PictureBox 20 21 Friend WithEvents lblstatus As Label 22 23 codice generato da Visual Studio.NET 24 25 costanti del lancio dei dadi 26 Enum DiceNames 27 SNAKE_EYES = 2 28 TREY = 3 29 CRAPS = 7 30 YO_LEVEN = 11 31 BOX_CARS = 12 32 End Enum 33 34 costanti del nome del file e delle directory 35 Const FILE_PREFIX As String = /images/die 36 Const FILE_SUFFIX As String =.png 37 38 Dim mypoint As Integer 39 Dim mydie1 As Integer 40 Dim mydie2 As Integer 41 Dim randomobject As Random = New Random() 42 43 inizia un nuovo gioco e determina il punteggio 44 Private Sub cmdplay_click(byval sender As System.Object, _ 45 ByVal e As System.EventArgs) Handles cmdplay.click 46 47 inizializza le variabili per il nuovo gioco 48 mypoint = 0 49 pointdicegroup.text = Point 50 lblstatus.text = 51 52 elimina le immagini dei dadi 53 picpointdie1.image = Nothing 54 picpointdie2.image = Nothing 55 56 Dim sum As Integer = RollDice() 57 58 controlla il lancio dei dadi 59 Select Case sum 60 61 Case DiceNames.CRAPS, DiceNames.YO_LEVEN 62 63 disabilita il pulsante Roll che lancia i dadi 64 cmdroll.enabled = False 65 lblstatus.text = You Win!!!
PROCEDURE 179 66 67 Case DiceNames.SNAKE_EYES, _ 68 DiceNames.TREY, DiceNames.BOX_CARS 69 70 cmdroll.enabled = False 71 lblstatus.text = Sorry. You Lose. 72 73 Case Else 74 mypoint = sum 75 pointdicegroup.text = Point is & sum 76 lblstatus.text = Roll Again! 77 DisplayDie(picPointDie1, mydie1) 78 DisplayDie(picPointDie2, mydie2) 79 cmdplay.enabled = False 80 cmdroll.enabled = True 81 82 End Select 83 84 End Sub cmdplay_click 85 86 determina l esito del lancio successivo 87 Private Sub cmdroll_click(byval sender As System.Object, _ 88 ByVal e As System.EventArgs) Handles cmdroll.click 89 90 Dim sum As Integer = RollDice() 91 92 controlla l esito del lancio 93 If sum = mypoint Then 94 lblstatus.text = You Win!!! 95 cmdroll.enabled = False 96 cmdplay.enabled = True 97 ElseIf sum = DiceNames.CRAPS Then 98 lblstatus.text = Sorry. You Lose. 99 cmdroll.enabled = False 100 cmdplay.enabled = True 101 End If 102 103 End Sub cmdroll_click 104 105 visualizza l immagine del dado 106 Sub DisplayDie(ByVal picdie As PictureBox, _ 107 ByVal face As Integer) 108 109 assegna l immagine del dado al box dell immagine 110 picdie.image = _ 111 Image.FromFile(Directory.GetCurrentDirectory & _ 112 FILE_PREFIX & face & FILE_SUFFIX) 113 End Sub DisplayDie Figura 6.17 Il gioco a dadi Craps simulato con la classe Random (continua)
180 CAPITOLO 6 114 115 simula i lanci casuali dei dadi 116 Function RollDice() As Integer 117 Dim die1, die2 As Integer 118 119 genera un numero intero casuale 120 die1 = randomobject.next(1, 7) 121 die2 = randomobject.next(1, 7) 122 123 visualizza i lanci 124 DisplayDie(picDie1, die1) 125 DisplayDie(picDie2, die2) 126 127 imposta i valori 128 mydie1 = die1 129 mydie2 = die2 130 131 Return die1 + die2 132 End Function RollDice 133 134 End Class FrmCrapsGame Figura 6.17 Il gioco a dadi Craps simulato con la classe Random 6.14 Ricorsione I programmi finora esaminati sono generalmente formati da procedure che si chiamano in modo disciplinato e gerarchico. In alcuni casi, potrebbe essere utile avere procedure che chiamano sé stesse. Una procedura ricorsiva è una procedura che chiama sé stessa direttamente
PROCEDURE 181 o indirettamente (attraverso un altra procedura). La ricorsione è un argomento complesso, di cui si parla spesso nei corsi superiori di informatica. In questo paragrafo e nel successivo saranno presentati dei semplici esempi di ricorsione. In primo luogo considereremo la ricorsione dal punto di vista concettuale. Gli approcci ricorsivi alla soluzione dei problemi hanno un certo numero di elementi in comune. Per risolvere un problema, viene chiamata una procedura ricorsiva. La procedura in realtà sa risolvere soltanto i casi più semplici, ovvero i cosiddetti casi di base. Quando la procedura viene chiamata per risolvere un problema di base, restituisce semplicemente un risultato. Se viene chiamata per risolvere un problema più complesso, lo suddivide in due parti concettuali: una che sa risolvere e l altra che non sa risolvere. Per rendere fattibile la ricorsione, la seconda parte del problema deve somigliare al problema originale, ma deve essere un po più semplice o più breve. Dato che questo nuovo problema somiglia a quello originale, la procedura chiama una nuova copia di sé stessa per risolvere il problema più semplice: questa è la chiamata ricorsiva o passo della ricorsione. Di solito, il passo della ricorsione include anche la parola chiave Return, perché il suo risultato viene combinato con la porzione del problema che la procedura sa risolvere. Questa serie di chiamate darà un risultato che alla fine sarà restituito al programma che ha effettuato la prima chiamata (caller). Il passo della ricorsione viene eseguito finché rimane aperta la chiamata originaria della procedura (ovvero finché la procedura non sarà terminata). Il passo della ricorsione può generare molte altre chiamate ricorsive, finché la procedura continua a suddividere ogni nuovo problema in due parti concettuali (una risolvibile e una più semplice). Affinché la ricorsione possa terminare, la successiva semplificazione dei problemi deve convergere al caso di base. A questo punto, la procedura riconosce il caso di base e restituisce un risultato alla sua copia precedente. Viene generata una sequenza di restituzioni lungo tutta la catena di chiamate, finché la prima procedura chiamata non restituisce al caller il risultato finale. Per applicare questi concetti, scriveremo un programma ricorsivo che esegue un tipico calcolo matematico. Il fattoriale di un intero intero non negativo n (si scrive n! e si legge n fattoriale ) è definito dalla seguente formula: n! = n (n 1) (n 2)... 1 Notate che 1! è uguale a 1 e, per definizione, 0! è uguale a 1. Per esempio, 5! è uguale a 5 * 4 * 3 * 2 * 1 (= 120). Il fattoriale di un numero intero (number number) maggiore o uguale a zero può essere calcolato ciclicamente (non in modo ricorsivo) utilizzando la struttura For/Next come segue: Dim counter, factorial As Integer = 1 For counter = number To 1 Step 1 factorial *= counter Next Analizzando la seguente relazione, possiamo ricavare la definizione ricorsiva della procedura fattoriale: n! = n (n 1)!
182 CAPITOLO 6 Per esempio, 5! è chiaramente uguale a 5 * 4!, come dimostrato qui di seguito: 5! = 5 4 3 2 1 5! = 5 (4 3 2 1) 5! = 5 (4!) Il calcolo ricorsivo di 5! procede nel modo illustrato nella Figura 6.18. La Figura 6.18 a) mostra come la successione delle chiamate ricorsive procede finché non viene calcolato 1! che termina la ricorsione. La Figura 6.18 b) mostra i valori restituiti da ogni chiamata ricorsiva al suo caller, finché non viene calcolato e restituito il valore finale. Il programma della Figura 6.19 applica la ricorsione per calcolare e visualizzare i numeri fattoriali (la scelta del tipo Long sarà spiegata tra breve). Il metodo ricorsivo Factorial (righe 33-41) controlla innanzi tutto se la condizione di chiusura è vera (riga 35), cioè se number è minore o uguale a 1. Se number è minore o uguale a 1, Factorial restituisce 1 e non sono necessarie altre ricorsioni. Se number è maggiore di 1, la riga 38 esprime il problema come il prodotto di number per una chiamata ricorsiva di Factorial, che calcola il fattoriale di number 1. Notate che Factorial(number 1) è un problema un po più semplice del calcolo originale Factorial(number). Function Factorial (riga 33) riceve un parametro di tipo Long e restituisce un risultato dello stesso tipo. Come potete vedere nella Figura 6.19, i valori fattoriali diventano grandi molto rapidamente (abbiamo scelto il tipo Long per consentire al programma di calcolare i fattoriali maggiori di 12!). Sfortunatamente, i valori prodotti da Factorial crescono così rapidamente che persino il tipo Long non è adeguato a rappresentarli. Questo è punto di debolezza comune a molti linguaggi di programmazione. Come vedremo nel Capitolo 8, Visual Basic è un linguaggio estensibile che consente ai programmatori di creare nuovi tipi di dati (le classi) per soddisfare specifiche esigenze; per esempio, un programmatore potrebbe creare una classe HugeInteger che consente a un programma di calcolare i fattoriali di numeri arbitrariamente grandi. Errore tipico 6.8 Se una procedura ricorsiva non restituisce un valore, possono verificarsi degli errori logici. Figura 6.18 Calcolo ricorsivo di 5!
PROCEDURE 183 Errore tipico 6.9 Se si omette il caso di base o si scrive il passo di ricorsione in modo che non converga sul caso di base, si generano delle ricorsioni infinite, che alle fine consumano tutta la memoria. Questa situazione è analoga ai cicli infiniti delle soluzioni iterative (non ricorsive). 1 Figura 6.19: Factorial.vb 2 Calcolo dei fattoriali mediante la ricorsione 3 4 Public Class FrmFactorial 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents lblenter As Label chiede un intero 8 Friend WithEvents lblfactorial As Label indica l output 9 10 Friend WithEvents txtinput As TextBox legge l intero 11 Friend WithEvents txtdisplay As TextBox visualizza l output 12 13 Friend WithEvents cmdcalculate As Button genera l output 14 15 codice generato da Visual Studio.NET 16 17 Private Sub cmdcalculate_click(byval sender As System.Object, _ 18 ByVal e As System.EventArgs) Handles cmdcalculate.click 19 20 Dim value As Integer = Convert.ToInt32(txtInput.Text) 21 Dim i As Integer 22 Dim output As String 23 24 txtdisplay.text = 25 26 For i = 0 To value 27 txtdisplay.text &= i &! = & Factorial(i) & vbcrlf 28 Next 29 30 End Sub cmdcalculate_click 31 32 genera in modo ricorsivo il numero fattoriale 33 Function Factorial(ByVal number As Long) As Long 34 35 If number <= 1 Then caso di base 36 Return 1 37 Else 38 Return number * Factorial(number - 1) 39 End If 40 41 End Function Factorial 42 43 End Class FrmFactorial Figura 6.19 Programma ricorsivo per calcolare i fattoriali (continua)
184 CAPITOLO 6 Figura 6.19 Programma ricorsivo per calcolare i fattoriali 6.15 Applicazione della ricorsione: la serie di Fibonacci La serie di Fibonacci 0, 1, 1, 2, 3, 5, 8, 13, 21,... inizia con 0 e 1 e definisce ogni successivo numero di Fibonacci come la somma dei due numeri precedenti. La serie ha dei riscontri in natura e descrive in particolare una forma di spirale. Il rapporto tra due numeri consecutivi di Fibonacci converge a circa 1,618. Questo valore ha ripetuti riscontri in natura ed è stato definito rapporto aureo. Gli architetti sono soliti progettare finestre, case ed edifici in modo che il rapporto fra lunghezza e altezza sia quello aureo. La serie di Fibonacci può essere definita in modo ricorsivo come segue: fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(n) = fibonacci(n 1) + fibonacci(n 2) Notate che ci sono due casi di base nel calcolo dei numeri di Fibonacci: per definizione, fibonacci(0) è 0 e fibonacci(1) è 1. L applicazione della Figura 6.20 calcola in modo ricorsivo l i-esimo numero di Fibonacci, utilizzando il metodo Fibonacci. L utente inserisce un numero intero nel box di testo, che indica l i-esimo numero Fibonacci da calcolare; poi fa clic su cmdcalculate. Il metodo cmdcalculate_click (che viene eseguito automaticamente quando l utente fa clic sul pulsante Calculate) chiama in modo ricorsivo il metodo Fibonacci per calcolare il numero di Fibonacci specificato. Notate che i numeri di Fibonacci, come quelli fattoriali del precedente paragrafo, tendono a diventare grandi rapidamente; per questo motivo abbiamo scelto il tipo di dati Long per il parametro e per il valore restituito dal metodo Fibonacci. Nella Figura 6.20 le videate mostrano i risultati del calcolo di vari numeri di Fibonacci. La gestione degli eventi in questo esempio è simile a quella dell applicazione Maximum della Figura 6.4. In questo caso, l utente immette un valore nel box di testo e fa clic sul pulsante Calculate Fibonacci per eseguire il metodo cmdcalculate.
PROCEDURE 185 La chiamata del metodo Fibonacci (riga 23) da parte di cmdcalculate_click non è ricorsiva, ma tutte le successive chiamate (riga 33) lo sono. Ogni volta che viene chiamato il metodo Fibonacci, viene immediatamente valutato il caso di base, che si verifica quando number è 0 o 1 (riga 30). Se questa condizione è vera, viene restituito il valore di number, perché fibonacci(0) è 0 e fibonacci(1) è 1. Se number è maggiore di 1, il passo di ricorsione genera due chiamate ricorsive, ciascuna delle quali rappresenta un problema un po più semplice di quello della chiamata originale di Fibonacci. La Figura 6.21 illustra il meccanismo usato dal metodo Fibonacci per calcolare Fibonacci(3). 1 Figura 6.20: Fibonacci.vb 2 Calcolo ricorsivo dei numeri di Fibonacci 3 4 Public Class FrmFibonacci 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents lblprompt As Label richiede l input 8 Friend WithEvents lblresult As Label mostra il risultato 9 10 Friend WithEvents cmdcalculate As Button esegue i calcoli 11 12 Friend WithEvents txtinputbox As TextBox legge un intero 13 14 codice generato da Visual Studio.NET 15 16 visualizza un numero di Fibonacci in txtinputbox 17 Private Sub cmdcalculate_click(byval sender As System.Object, _ 18 ByVal e As System.EventArgs) Handles cmdcalculate.click 19 20 legge l input 21 Dim number As Integer = Convert.ToInt32(txtInputBox.Text) 22 23 lblresult.text = Fibonacci Value is & Fibonacci(number) 24 End Sub cmdcalculate_click 25 26 calcola in modo ricorsivo il numero di Fibonacci 27 Function Fibonacci(ByVal number As Integer) As Long 28 29 verifica i casi di base 30 If number = 1 OrElse number = 0 Then 31 Return number 32 Else 33 Return Fibonacci(number - 1) + Fibonacci(number - 2) 34 End If 35 36 End Function Fibonacci 37 38 End Class FrmFibonacci Figura 6.20 Generazione ricorsiva dei numeri di Fibonacci (continua)
186 CAPITOLO 6 Figura 6.20 Generazione ricorsiva dei numeri di Fibonacci È importante notare che l uso di un programma ricorsivo come quello che genera i numeri di Fibonacci richiede particolare attenzione: ogni chiamata del metodo Fibonacci che non soddisfa uno dei casi di base (0 o 1) genera due chiamate ricorsive del metodo. Questo meccanismo innesca rapidamente un incremento esponenziale di chiamate. Per esempio, il calcolo del numero 20 di Fibonacci con il programma della Figura 6.20 richiede 21.891 chiamate del metodo Fibonacci; il calcolo del numero 30 di Fibonacci richiede 2.692.537 chiamate del metodo Fibonacci. Al crescere del numero di Fibonacci da calcolare, aumenta sempre di più il numero di chiamate del metodo Fibonacci e, quindi, il tempo di elaborazione. Per esempio, il numero 31 di Fibonacci richiede 4.356.617 chiamate, mentre il numero 32 di Fibonacci richiede 7.049.155 chiamate. Come potete notare, il numero di chiamate aumenta molto rapidamente: 1.664.080 chiamate aggiuntive per passare dal numero 30 al 31 e 2.692.538 chiamate aggiuntive per passare dal 31 al 32. La differenza nel numero di chiamate fra i numeri 31 e 32 è più di 1,5 volte la differenza di chiamate fra i numeri 30 e 31. Problemi di questa natura possono umiliare anche i più potenti calcolatori del mondo! In questo campo di applicazioni, detto teoria della complessità, gli esperti di informatica sanno come sia difficile creare algoritmi in grado di svolgere in modo efficiente i calcoli richiesti. I temi della teoria della complessità sono trattati dettagliatamente in corsi di informatica superiore.
PROCEDURE 187 Figura 6.21 Chiamate ricorsive del metodo Fibonacci Obiettivo efficienza 6.3 Evitate di creare programmi ricorsivi stile-fibonacci che innescano un incremento esponenziale di chiamate. 6.16 Ricorsione e iterazione Nei paragrafi precedenti abbiamo analizzato due metodi che possono essere facilmente implementati in modo ricorsivo o iterativo. In questo paragrafo confronteremo i due approcci e discuteremo i motivi per cui, in situazioni particolari, un programmatore può preferire un approccio all altro. L iterazione e la ricorsione si basano entrambe su una struttura di controllo: l iterazione utilizza una struttura di ripetizione (come For, While o Do/Loop Until); la ricorsione utilizza una struttura di selezione (come If/Then, If/Then/Else o Select). Sebbene entrambi i processi richiedano una ripetizione, l iterazione usa esplicitamente una struttura iterativa, mentre la ricorsione attua la ripetizione chiamando più volte una procedura. Anche le condizioni di conclusione impiegate dai due processi sono differenti. L iterazione controllata da un contatore continua a modificare il contatore finché il suo valore soddisfa la condizione di continuazione del ciclo. La ricorsione, invece, continua a generare versioni più semplici del problema originale finché non viene riconosciuto un caso di base (che conclude il processo). L iterazione e la ricorsione possono ripetersi entrambe all infinito: l iterazione determina un ciclo infinito se la condizione di continuazione non diventa mai falsa; la ricorsione infinita si verifica quando il passo di ricorsione non riesce a semplificare il problema originale in modo da convergere al caso di base. La ricorsione presenta molti svantaggi; chiama ripetutamente il meccanismo e, di conseguenza, cresce il costo delle chiamate della procedura in termini di tempo del processore e di spazio in memoria. Ogni chiamata ricorsiva implica la creazione di un altra copia delle variabili della procedura; quando sono richiesti più livelli di ricorsione, questo meccanismo può consumare una considerevole quantità di memoria. L iterazione, di solito, si svolge all interno di una procedura, risparmiando così i costi relativi alle continue chiamate della procedura e all allocazione di memoria aggiuntiva. Perché un programmatore dovrebbe scegliere la ricorsione?
188 CAPITOLO 6 Ingegneria del software 6.14 Qualsiasi problema che può essere risolto in modo ricorsivo può essere risolto anche in modo iterativo. Di solito, l approccio ricorsivo viene preferito a quello iterativo quando la ricorsione rispecchia in modo più naturale il problema e genera un programma che è più facile da capire e provare. Le soluzioni ricorsive vengono adottate anche quando le soluzioni iterative non sono evidenti. Obiettivo efficienza 6.4 Evitate di usare la ricorsione nelle situazioni in cui l efficienza è un fattore critico. Le chiamate ricorsive richiedono tempo e consumano più memoria. Errore tipico 6.10 Se la chiamata di una procedura non ricorsiva chiama accidentalmente sé stessa attraverso un altra procedura, può innescarsi un processo di ricorsione infinita. Molti libri sulla programmazione trattano il tema della ricorsione più avanti di quanto abbiamo fatto in questo libro. Noi crediamo che la ricorsione sia un argomento importante e complesso, per questo abbiamo preferito affrontarlo subito, aggiungendo altri esempi nella parte restante del libro. 6.17 Procedure sovraccaricate e argomenti facoltativi In Visual Basic le procedure possono avere più set di parametri. Questa caratteristica, che si chiama overloading delle procedure (sovraccarico), consente ai programmatori di creare più procedure con lo stesso nome, ma con numero e tipi di argomenti differenti. L overloading permette di ridurre la complessità dei programmi e di realizzare applicazioni più flessibili. Le procedure possono ricevere anche argomenti facoltativi (Optional Optional). Definendo un argomento Optional, la procedura che effettua la chiamata può decidere quali argomenti passare alla procedura chiamata. Gli argomenti facoltativi, di solito, specificano un valore di default che viene assegnato al parametro quando l argomento facoltativo non viene passato alla procedura chiamata. In generale, le procedure sovraccaricate sono più flessibili di quelle con argomenti facoltativi. Per esempio, il programmatore può specificare dati restituiti di tipo differente per le procedure sovraccaricate. Tuttavia, gli argomenti facoltativi offrono un metodo molto semplice di specificare i valori di default. 6.17.1 Procedure sovraccaricate Grazie all overloading, un programmatore può definire più procedure con lo stesso nome, purché queste procedure abbiano set di parametri differenti (in numero, tipo e ordine). Quando viene chiamata una procedura sovraccaricata, il compilatore seleziona la procedura appropriata esaminando il numero, il tipo e l ordine degli argomenti della procedura chiamata. Spesso, l overloading delle procedure viene utilizzato per creare più procedure con lo stesso nome che svolgono compiti simili con dati di tipo diverso.
PROCEDURE 189 Buona abitudine 6.6 Le procedure sovraccaricate che svolgono compiti strettamente correlati possono migliorare notevolmente la leggibilità dei programmi. Il programma della Figura 6.22 usa il metodo sovraccaricato Square per calcolare il quadrato di un numero di tipo Integer e Double. 1 Figura 6.22: Overload.vb 2 Overloading delle procedure 3 4 Public Class FrmOverload 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents outputlabel As Label 8 9 codice generato da Visual Studio.NET 10 11 Private Sub FrmOverload_Load(ByVal sender As System.Object, _ 12 ByVal e As System.EventArgs) Handles MyBase.Load 13 14 outputlabel.text = The square of Integer 7 is & _ 15 square(7) & vbcrlf & The square of Double & _ 16 7.5 is & square(7.5) 17 End Sub FrmOverload_Load 18 19 Function Square(ByVal value As Integer) As Integer 20 Return Convert.ToInt32(value ^ 2) 21 End Function Square 22 23 Function Square(ByVal value As Double) As Double 24 Return value ^ 2 25 End Function Square 26 27 End Class FrmOverload Figura 6.22 Metodi sovraccaricati Le procedure sovraccaricate si distinguono per la loro firma (signature), che è una combinazione del nome della procedura e dei tipi di parametri. Se il compilatore esaminasse soltanto i nomi delle procedure durante la compilazione, il codice della Figura 6.22 sarebbe ambiguo (il compilatore non riuscirebbe a distinguere i due metodi Square). Il compilatore usa un processo logico, detto risoluzione dell overloading, per determinare la procedura da chiamare. Questo processo ricerca prima tutte le procedure che potrebbero essere utilizzate in base al numero e al tipo di argomenti che sono presenti. Anche quando può sembrare che
190 CAPITOLO 6 ci sia una sola procedura che soddisfa i criteri di ricerca, è importante ricordare che Visual Basic promuove le variabili con conversioni implicite quando vengono passate come argomenti. Dopo avere trovato tutte le procedure che soddisfano i criteri di ricerca, il compilatore seleziona quella che soddisfa meglio tali criteri. Questo processo di selezione è svolto da un algoritmo best-fit che analizza tutte le conversioni implicite effettuate da Visual Basic. Per spiegare meglio questo concetto, esaminiamo il programma della Figura 6.22. Il compilatore potrebbe utilizzare il nome logico Square of Integer per il metodo Square che specifica un parametro Integer (riga 19) e il nome logico Square of Double per il metodo Square che specifica un parametro Double (riga 23). Se la definizione di un metodo ExampleSub iniziasse con l istruzione Function ExampleSub(ByVal a As Integer, ByVal b As Double) _ As Integer il compilatore potrebbe usare il nome logico ExampleSub of Integer and Double. Analogamente, se i parametri fossero specificati con l istruzione Function ExampleSub(ByVal a As Double, ByVal b As Integer) _ As Integer il compilatore potrebbe utilizzare il nome logico ExampleSub of Double and Integer. L ordine dei parametri è importante per il compilatore. I due precedenti metodi ExampleSub sono differenti per il compilatore. Finora, i nomi logici dei metodi usati dal compilatore non hanno mai citato i tipi di dati restituiti dai metodi; questo perché le chiamate delle procedure non possono essere distinte in base ai tipi di dati che restituiscono. Il programma della Figura 6.23 mostra gli errori di sintassi generati dal compilatore quando due procedure hanno la stessa firma, ma restituiscono tipi di dati differenti. Le procedure sovraccaricate che hanno liste di parametri differenti possono restituire dati di tipo diverso. Le procedure sovraccaricate non devono avere necessariamente lo stesso numero di parametri. Errore tipico 6.11 Procedure sovraccaricate con elenchi di parametri identici e dati restituiti di tipo differente generano un errore di sintassi. La finestra di output illustrata nella Figura 6.23 è la Task List di Visual Studio. Per default, la Task List appare in fondo all IDE quando si verifica un errore del compilatore. 1 Figura 6.23: Overload2.vb 2 Uso di procedure sovraccaricate con la stessa firma 3 ma dati restituiti di tipo differente 4 5 Public Class FrmOverload2 6 Inherits System.Windows.Forms.Form 7 8 Friend WithEvents outputlabel As Label 9 10 codice generato da Visual Studio.NET 11 12 Private Sub FrmOverload2_Load(ByVal sender As System.Object, _
PROCEDURE 191 13 ByVal e As System.EventArgs) Handles MyBase.Load 14 15 outputlabel.text = The square of Integer 7 is & _ 16 square(7) & vbcrlf & The square of Double & _ 17 7.5 is & square(7.5) 18 End Sub FrmOverload2_Load 19 20 Function Square(ByVal value As Double) As Integer 21 Return Convert.ToInt32(value ^ 2) 22 End Function Square 23 24 Function Square(ByVal value As Double) As Double 25 Return value ^ 2 26 End Function Square 27 28 End Class FrmOverload2 Figura 6.23 Errore di sintassi generato da procedure sovraccaricate con stessi parametri e dati restituiti di tipo differente 6.17.2 Argomenti facoltativi Visual Basic consente ai programmatori di creare delle procedure che accettano uno o più argomenti facoltativi. Quando un parametro è dichiarato facoltativo, il caller ha la facoltà di passare oppure no quel particolare argomento. Gli argomenti facoltativi sono specificati nell intestazione della procedura con la parola chiave Optional. Per esempio, l intestazione della procedura Sub ExampleProcedure(ByVal value1 As Boolean, Optional _ ByVal value2 As Long = 0) definisce facoltativo l ultimo parametro. Qualsiasi chiamata di ExampleProcedure deve passare almeno un argomento, altrimenti si verifica un errore di sintassi. Il caller può scegliere di passare un secondo argomento a ExampleProcedure. Questo è dimostrato dalla seguente chiamata di ExampleProcedure: ExampleProcedure() ExampleProcedure(True) ExampleProcedure(False, 10) La prima chiamata di ExampleProcedure provoca un errore di sintassi, perché bisogna specificare almeno un argomento. La seconda chiamata di ExampleProcedure è valida, perché viene passato un argomento. L argomento facoltativo, value2, non è specificato nella chiamata della procedura. Anche l ultima chiamata di ExampleProcedure è valida: il valore False viene passato come argomento obbligatorio, mentre il valore 10 viene passato come argomento facoltativo.
192 CAPITOLO 6 Nella chiamata che passa soltanto un argomento (True True) a ExampleProcedure, il parametro value2 viene impostato a 0 per default (questo è il valore specificato nell intestazione della procedura). Gli argomenti Optional devono specificare un valore di default mediante il segno uguale (=) seguito da un valore. Per esempio, l intestazione di ExampleProcedure imposta 0 il valore di default del parametro value2. I valori di default possono essere utilizzati soltanto con i parametri dichiarati facoltativi. Errore tipico 6.12 È un errore di sintassi non specificare il valore di default di un parametro facoltativo. Errore tipico 6.13 È un errore di sintassi dichiarare un parametro non facoltativo a destra di un parametro facoltativo. L esempio della Figura 6.24 mostra come utilizzare gli argomenti facoltativi. Il programma calcola il risultato di una potenza; la base e l esponente sono specificati dall utente. Se l utente non specifica l esponente (l argomento facoltativo), viene utilizzato il valore di default (2). La riga 27 determina se txtpower contiene un valore; in caso affermativo, i valori di TextBox sono convertiti in Integer e passati a Power, altrimenti il valore di txtbase viene convertito in Integer e passato come primo argomento a Power (riga 31). Il secondo argomento, che ha un valore pari a 2, viene fornito dal compilatore e non è visibile al programmatore nella chiamata. Il metodo Power (righe 38-49) specifica che il suo secondo argomento è Optional. Se omesso, il secondo argomento assume il valore di default 2. 1 Figura 6.24: Power.vb 2 Calcola la potenza di un valore (il quadrato, per default) 3 4 Public Class FrmPower 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents txtbase As TextBox legge la base 8 Friend WithEvents txtpower As TextBox legge l esponente 9 10 Friend WithEvents inputgroup As GroupBox 11 12 Friend WithEvents lblbase As Label richiede la base 13 Friend WithEvents lblpower As Label richiede l esponente 14 Friend WithEvents lbloutput As Label visualizza l output 15 16 Friend WithEvents cmdcalculate As Button genera l output 17 18 codice generato da Visual Studio.NET 19 20 legge l input e visualizza il risultato 21 Private Sub cmdcalculate_click(byval sender As System.Object, _ 22 ByVal e As System.EventArgs) Handles cmdcalculate.click 23 24 Dim value As Integer
PROCEDURE 193 25 26 chiama la versione di Power in funzione dell esponente 27 If Not txtpower.text = Then 28 value = Power(Convert.ToInt32(txtBase.Text), _ 29 Convert.ToInt32(txtPower.Text)) 30 Else 31 value = Power(Convert.ToInt32(txtBase.Text)) 32 End If 33 34 lbloutput.text = Convert.ToString(value) 35 End Sub cmdcalculate_click 36 37 usa l iterazione per calcolare la potenza 38 Function Power(ByVal base As Integer, _ 39 Optional ByVal exponent As Integer = 2) As Integer 40 41 Dim total As Integer = 1 42 Dim i As Integer 43 44 For i = 1 To exponent 45 total *= base 46 Next 47 48 Return total 49 End Function Power 50 51 End Class FrmPower Figura 6.24 Applicazione dell argomento Optional con il metodo Power 6.18 Moduli I programmatori usano i moduli per raggruppare procedure correlate in modo che possano essere riutilizzate in altri progetti. Sotto molti aspetti, i moduli sono simili alle classi, in quanto consentono ai programmatori di costruire componenti riutilizzabili, senza bisogno di conoscere approfonditamente la programmazione orientata agli oggetti. Per usare i moduli in un progetto, bisogna conoscere le regole della visibilità, in quanto alcune procedure e variabili di un modulo sono accessibili da altre parti di un progetto. In generale, i moduli devono essere indipendenti, nel senso che le procedure del modulo non devono richiedere l accesso a variabili e procedure esterne al modulo, tranne quando tali valori sono passati come argomenti. La Figura 6.25 presenta moddice che raggruppa in un modulo varie procedure correlate ai dadi; le procedure così raggruppate possono essere riutilizzate in altri programmi che usano i dadi. Function RollDie (righe 11-13) simula un singolo lancio di dadi e restituisce
194 CAPITOLO 6 il risultato. Function RollAndSum (righe 17-28) usa una struttura For (righe 22-24) per chiamare RollDie il numero di volte indicato da dicenumber e sommare i risultati dei lanci dei dadi. Function GetDieImage (righe 30-37) restituisce l immagine del dado (Image Image) che corrisponde al parametro dievalue. Il parametro facoltativo baseimagename rappresenta il prefisso del nome dell immagine da utilizzare. Se l argomento viene omesso, viene usato il prefisso di default die. Nota: per aggiungere nuovi moduli, selezionate Project Add Module. FrmDiceModuleTest nella Figura 6.26 applica la procedura moddice che risponde ai clic sul pulsante. La procedura cmdrolldie1_click (righe 23-27) lancia un dado e ottiene l immagine di default. Per chiamare la procedura contenuta in moddice, aggiungiamo al nome del modulo l operatore punto (.) e il nome della procedura. Utilizzando la funzionalità offerta da moddice, il corpo di questa procedura richiede una sola istruzione (riga 26). Questo ci consente di creare facilmente un pulsante simile: cmdrolldie2. In questo caso, la procedura cmdrolldie2_click (righe 29-34) usa l argomento facoltativo come prefisso del nome dell immagine e seleziona un altra immagine. La procedura cmdrollten_click (righe 36-40) assegna alla proprietà Text di lblsum il risultato di 10 lanci. 1 Figura 6.25: DiceModule.vb 2 Una raccolta di procedure correlate ai dadi 3 4 Imports System.IO 5 6 Module moddice 7 8 Dim randomobject As Random = New Random() 9 10 lancia un singolo dado 11 Function RollDie() As Integer 12 Return randomobject.next(1, 7) 13 End Function RollDie 14 15 procedura di somma dei lanci del dado 16 Function RollAndSum(ByVal dicenumber As Integer) _ 17 As Integer 18 19 Dim i As Integer 20 Dim sum As Integer = 0 21 22 For i = 1 To dicenumber 23 sum += RollDie() 24 Next 25 26 Return sum 27 End Function RollAndSum 28 29 restituisce l immagine del dado 30 Function GetDieImage(ByVal dievalue As Integer, _ 31 Optional ByVal baseimagename As String = die ) _ 32 As System.Drawing.Image 33
PROCEDURE 195 34 Return Image.FromFile( _ 35 Directory.GetCurrentDirectory & _ 36 \Images\ & baseimagename & dievalue &.png ) 37 End Function GetDieImage 38 39 End Module moddice Figura 6.25 Un modulo che raggruppa alcune procedure correlate Per il programma riportato nella Figura 6.26 abbiamo aggiunto DiceModule.vb al progetto, per potere accedere alla procedura definita in moddice. Per includere un modulo in un progetto, selezionate File Add Existing Item. Selezionate il nome del file del modulo nella finestra di dialogo che appare sullo schermo e fate clic su Open. Per default, una copia del file viene aggiunta alla directory del progetto, a meno che non specifichiate di aprire il file del modulo come file collegato (linked). Una volta che il modulo è stato aggiunto al progetto, le procedure contenute nel modulo hanno visibilità nel namespace. Per default, le procedure con visibilità nel namespace sono accessibili a tutte le altre parti del progetto, come i moduli nelle classi e le procedure in altri moduli. Sebbene non sia necessario, il programmatore può inserire il file che contiene il codice del modulo nella stessa directory degli altri file del progetto. 1 Figura 6.26: DiceModuleTest.vb 2 Applica le procedure moddicemodule 3 4 Imports System.Drawing 5 6 Public Class FrmDiceModuleTest 7 Inherits System.Windows.Forms.Form 8 9 Friend WithEvents lblsum As Label mostra la somma di 10 lanci 10 11 Friend WithEvents dicegroup As GroupBox 12 13 immagini dei dadi 14 Friend WithEvents picdie1 As PictureBox 15 Friend WithEvents picdie2 As PictureBox 16 17 Friend WithEvents cmdrolldie1 As Button lancia il dado blu 18 Friend WithEvents cmdrollten As Button simula 10 lanci 19 Friend WithEvents cmdrolldie2 As Button lancia il dado rosso 20 21 codice generato da Visual Studio.NET 22 23 Private Sub cmdrolldie1_click(byval sender As System.Object, _ 24 ByVal e As System.EventArgs) Handles cmdrolldie1.click 25 26 picdie1.image = moddice.getdieimage(moddice.rolldie()) 27 End Sub cmdrolldie1_click 28 Figura 6.26 Prova delle procedure moddice (continua)
196 CAPITOLO 6 29 Private Sub cmdrolldie2_click(byval sender As System.Object, _ 30 ByVal e As System.EventArgs) Handles cmdrolldie2.click 31 32 picdie2.image = moddice.getdieimage(moddice.rolldie(), _ 33 reddie ) 34 End Sub cmdrolldie2_click 35 36 Private Sub cmdrollten_click(byval sender As System.Object, _ 37 ByVal e As System.EventArgs) Handles cmdrollten.click 38 39 lblsum.text = Convert.ToString(modDice.RollAndSum(10)) 40 End Sub cmdrollten_click 41 42 End Class FrmDiceModuleTest Figura 6.26 Prova delle procedure moddice Esercizi di autovalutazione 6.1 Completate le seguenti frasi: a) Le procedure in Visual Basic possono essere definite nei e nelle. b) Una procedura è eseguita con una. c) Una variabile conosciuta solo all interno del metodo in cui è stata definita è una. d) L istruzione in una procedura Function chiamata viene utilizzata per restituire al caller il valore di un espressione. e) Una procedura definita con la parola chiave non restituisce un valore. f) La di un identificatore è la porzione del programma in cui l identificatore può essere utilizzato. g) I tre modi per restituire il controllo da una procedura Sub chiamata al caller sono, e. h) Il metodo della classe Random genera numeri casuali. i) Le variabili dichiarate in un blocco o nell elenco dei parametri di una procedura hanno durata. j) Una procedura chiama sé stessa direttamente o indirettamente è una procedura. k) Una tipica procedura ricorsiva ha due componenti: una che fornisce un mezzo per terminare la ricorsione verificando un caso e l altra che esprime il problema come una chiamata ricorsiva di un problema leggermente più semplice di quello originario.
PROCEDURE 197 l) In Visual Basic è possibile avere varie procedure con lo stesso nome che operano con diversi tipi e/o numeri di argomenti. Questa caratteristica è chiamata delle procedure. m) Le variabili locali dichiarate all inizio di una procedura hanno visibilità, come i parametri della procedura, che sono considerate variabili locali della procedura. n) L iterazione usa una struttura. o) La ricorsione usa una struttura. p) La ricorsione attua la ripetizione attraverso le chiamate ripetute. q) È possibile definire procedure con lo stesso, ma con liste di parametri differenti. r) La ricorsione termina quando viene raggiunto il. s) La è un elenco di elementi separati da una virgola che contengono le dichiarazioni dei parametri ricevuti dalla procedura chiamata. t) Il è il tipo di dati del risultato restituito da una procedura Function. u) Un è un segnale che viene trasmesso quando si svolge una particolare azione, per esempio quando l utente fa clic su un pulsante. 6.2 Determinate se le seguenti affermazioni sono vere o false. Se sono false, spiegate il perché. a) Il metodo Abs di Math arrotonda il suo parametro al numero intero più piccolo. b) Il metodo Exp di Math calcola la potenza e x. c) Una procedura ricorsiva è una procedura che chiama sé stessa. d) La trasformazione da Single a Double richiede una conversione di ampliamento. e) La variabile di tipo Char non può essere convertita nel tipo Integer. f) Quando una procedura chiama sé stessa, si verifica il caso di base. g) Se una procedura ricorsiva non restituisce un valore, quando è richiesto, si verifica un errore di logica. h) La ricorsione infinita si verifica quando una procedura converge al caso di base. i) Visual Basic supporta gli argomenti Optional. j) Qualsiasi problema che può essere risolto in modo ricorsivo può essere risolto anche in modo iterativo. 6.3 Esaminate il programma della Figura 6.27 e stabilite la visibilità (nella classe o nel blocco) dei seguenti elementi: a) La variabile i. b) La variabile base. c) Il metodo Cube. d) Il metodo FrmCubeTest_Load. e) La variabile output. 6.4 Scrivete un applicazione per verificare se le chiamate dei metodi della classe Math nel programma della Figura 6.27 producono i risultati indicati. 1 Figura 6.27: CubeTest.vb 2 Visualizza i cubi dei numeri interi da 1 a 10 3 4 Public Class FrmCubeTest 5 Inherits System.Windows.Forms.Form 6 7 Friend WithEvents lbloutput As Label 8 9 codice generato da Visual Studio.NET 10 11 Dim i As Integer Figura 6.27 Il codice che visualizza il cubo dei primi 10 numeri interi (continua)
198 CAPITOLO 6 12 13 Private Sub FrmCubeTest_Load(ByVal sender As System.Object, _ 14 ByVal e As System.EventArgs) Handles MyBase.Load 15 16 Dim output As String = 17 18 For i = 1 To 10 19 output &= Cube(i) & vbcrlf 20 Next 21 22 lbloutput.text = output 23 End Sub FrmCubeTest_Load 24 25 Function Cube(ByVal base As Integer) As Integer 26 Return Convert.ToInt32(base ^ 3) 27 End Function Cube 28 29 End Class FrmCubeTest Figura 6.27 Il codice che visualizza il cubo dei primi 10 numeri interi 6.5 Scrivete le intestazioni per le seguenti procedure: a) La procedura Hypotenuse riceve due argomenti floating-point in doppia precisione (side1 e side2) e restituisce un risultato floating-point in doppia precisione. b) La procedura Smallest riceve tre numeri interi (x, y e z) e restituisce un numero intero. c) La procedura Instructions non riceve alcun argomento e non restituisce alcun valore. d) La procedura IntegerToSingle riceve un numero intero (number number) come argomento e restituisce un risultato floating-point. 6.6 Trovate l errore in ciascuno dei seguenti segmenti di programma e correggetelo: a) Sub Printer1(ByVal value As Single) Dim value As Single Console.WriteLine(value) End Sub Printer1 b) Function Sum(ByVal x As Integer, ByVal y As Integer) _ As Integer Dim result As Integer result = x + y End Function Sum c) Sub General1() Console.WriteLine( Inside procedure General1 ) Sub General2() Console.WriteLine( Inside procedure General2 ) End Sub General2 End Sub General1 d) Sub Product() Dim a As Integer = 6 Dim b As Integer = 5 Dim result As Integer = a * b Console.WriteLine( Result is & result) Return result End Sub Product
PROCEDURE 199 e) Function Sum(ByVal value As Integer) As Integer If value = 0 Then Return 0 Else value += Sum(value 1) End If End Function Sum Risposte agli esercizi di autovalutazione 6.1 a) metodi, classi. b) chiamata. c) variabile locale. d) Return. e) Sub. f) visibilità. g) Return, Exit Sub, incontrare l istruzione End Sub. h) Next. i) automatica. j) ricorsiva. k) di base. l) overloading (sovraccarico). m) nel blocco. n) di ripetizione. o) di selezione. p) di una procedura. q) nome. r) caso di base. s) lista dei parametri. t) tipo valore. u) evento. 6.2 a) Falso. Il metodo Abs di Math calcola il valore assoluto di un numero. b) Vero. c) Vero. d) Vero. e) Falso. Il tipo di dati Char può essere trasformato nel tipo Integer con una conversione di riduzione. f) Falso. Una procedura che chiama sé stessa è detta procedura ricorsiva. g) Vero. h) Falso. La ricorsione infinita si verifica quando una procedura ricorsiva non converge al caso di base. i) Vero. j) Vero. 6.3 a) Visibilità nella classe. b) Visibilità nel blocco. c) Visibilità nella classe. d) Visibilità nella classe. e) Visibilità nel blocco. 6.4 Il codice della Figura 6.28 verifica i risultati delle chiamate dei metodi della classe Math: 1 Esercizio 6.4: MathTest.vb 2 Applicazione dei metodi della classe Math 3 4 Module modmathtest 5 6 Sub Main() 7 Console.WriteLine( Math.Abs(23.7) = & _ 8 Convert.ToString(Math.Abs(23.7))) 9 Console.WriteLine( Math.Abs(0.0) = & _ 10 Convert.ToString(Math.Abs(0))) 11 Console.WriteLine( Math.Abs( 23.7) = & _ 12 Convert.ToString(Math.Abs( 23.7))) 13 Console.WriteLine( Math.Ceiling(9.2) = & _ 14 Convert.ToString(Math.Ceiling(9.2))) 15 Console.WriteLine( Math.Ceiling( 9.8) = & _ 16 Convert.ToString(Math.Ceiling( 9.8))) 17 Console.WriteLine( Math.Cos(0.0) = & _ 18 Convert.ToString(Math.Cos(0))) 19 Console.WriteLine( Math.Exp(1.0) = & _ 20 Convert.ToString(Math.Exp(1))) 21 Console.WriteLine( Math.Exp(2.0) = & _ 22 Convert.ToString(Math.Exp(2))) 23 Console.WriteLine( Math.Floor(9.2) = & _ 24 Convert.ToString(Math.Floor(9.2))) 25 Console.WriteLine( Math.Floor( 9.8) = & _ 26 Convert.ToString(Math.Floor( 9.8))) 27 Console.WriteLine( Math.Log(2.718282) = & _ 28 Convert.ToString(Math.Log(2.718282))) Figura 6.28 Verifica dei risultati dei metodi della classe Math (continua)
200 CAPITOLO 6 29 Console.WriteLine( Math.Log(7.389056) = & _ 30 Convert.ToString(Math.Log(7.389056))) 31 Console.WriteLine( Math.Max(2.3, 12.7) = & _ 32 Convert.ToString(Math.Max(2.3, 12.7))) 33 Console.WriteLine( Math.Max( 2.3, 12.7) = & _ 34 Convert.ToString(Math.Max( 2.3, 12.7))) 35 Console.WriteLine( Math.Min(2.3, 12.7) = & _ 36 Convert.ToString(Math.Min(2.3, 12.7))) 37 Console.WriteLine( Math.Min( 2.3, 12.7) = & _ 38 Convert.ToString(Math.Min( 2.3, 12.7))) 39 Console.WriteLine( Math.Pow(2, 7) = & _ 40 Convert.ToString(Math.Pow(2, 7))) 41 Console.WriteLine( Math.Pow(9,.5) = & _ 42 Convert.ToString(Math.Pow(9, 0.5))) 43 Console.WriteLine( Math.Sin(0.0) = & _ 44 Convert.ToString(Math.Sin(0))) 45 Console.WriteLine( Math.Sqrt(9.0) = & _ 46 Convert.ToString(Math.Sqrt(9))) 47 Console.WriteLine( Math.Sqrt(2.0) = & _ 48 Convert.ToString(Math.Sqrt(2))) 49 Console.WriteLine( Math.Tan(0.0) = & _ 50 Convert.ToString(Math.Tan(0))) 51 52 End Sub Main 53 54 End Module modmathtest Math.Abs(23.7) = 23.7 Math.Abs(0.0) = 0 Math.Abs( 23.7) = 23.7 Math.Ceiling(9.2) = 10 Math.Ceiling( 9.8) = 9 Math.Cos(0.0) = 1 Math.Exp(1.0) = 2.71828182845905 Math.Exp(2.0) = 7.38905609893065 Math.Floor(9.2) = 9 Math.Floor( 9.8) = 10 Math.Log(2.718282) = 1.00000006310639 Math.Log(7.389056) = 1.99999998661119 Math.Max(2.3, 12.7) = 12.7 Math.Max( 2.3, 12.7) = 2.3 Math.Min(2.3, 12.7) = 2.3 Math.Min( 2.3, 12.7) = 12.7 Math.Pow(2, 7) = 128 Math.Pow(9,.5) = 3 Math.Sin(0.0) = 0 Math.Sqrt(9.0) = 3 Math.Sqrt(2.0) = 1.4142135623731 Math.Tan(0.0) = 0 Figura 6.28 Verifica dei risultati dei metodi della classe Math 6.5 a) Function Hypotenuse(ByVal side1 As Double, _ ByVal side2 As Double) As Double b) Function Smallest(ByVal x As Integer, _ ByVal y As Integer, ByVal z As Integer) As Integer
PROCEDURE 201 c) Sub Instructions() d) Function IntegerToSingle(ByVal number As Integer) As Single 6.6 a) Errore: il parametro value è ridefinito nella definizione della procedura. Correzione: cancellate la dichiarazione Dim value As Single. b) Errore: si suppone che la procedura restituisca un numero intero, ma non lo fa. Correzione: cancellate l istruzione result = x + y e inserite la seguente istruzione nel metodo: Esercizi Return x + y oppure aggiungete la seguente istruzione alla fine del corpo del metodo: Return result c) Errore: la procedura General2 è definita nella procedura General1. Correzione: spostate la definizione di General2 all esterno della definizione di General1. d) Errore: la procedura restituisce un valore, ma è definita come procedura Sub. Correzione: cambiate la procedura Sub in Function con il dato restituito di tipo Integer. e) Errore: il risultato di value += Sum(value 1) non viene restituito da questo metodo ricorsivo, causando un errore di logica. Correzione: modificate l istruzione nella clausola Else in questo modo: Return value + Sum(value 1) 6.7 Qual è il valore di x dopo dopo l esecuzione di ciascuna di queste istruzioni? a) x = Math.Abs(7.5) b) x = Math.Floor(7.5) c) x = Math.Abs(0.0) d) x = Math.Ceiling(0.0) e) x = Math.Abs( 6.4) f) x = Math.Ceiling( 6.4) g) x = Math.Ceiling( Math.Abs( 8 + Math.Floor( 5.5))) 6.8 Un garage addebita un importo minimo di $2,00 per parcheggiare fino a tre ore. Il garage addebita un addizionale di $0,50 per ogni ora o frazione di ora oltre le tre ore. L addebito massimo per un periodo di 24 ore è $10,00. Supponete che nessun cliente parcheggi per più di 24 ore alla volta. Scrivete un programma che calcola e visualizza il costo del parcheggio per ciascuno dei tre clienti che ieri hanno parcheggiato le auto in questo garage. Le ore di parcheggio per ogni cliente devono essere immesse in un TextBox. Il programma deve visualizzare il costo del parcheggio per il cliente corrente. Il programma deve utilizzare il metodo CalculateCharges per determinare l addebito di ogni cliente. Utilizzate le tecniche descritte in questo capitolo per leggere il valore Double da un TextBox. 6.9 Scrivete un metodo IntegerPower(base, exponent) che restituisce il valore di base exponent Per esempio, IntegerPower(3, 4) = 3 * 3 * 3 * 3. Supponete che exponent sia un numero intero positivo e che base sia un numero intero. Il metodo IntegerPower dovrà utilizzare un cliclo For/Next o While per controllare i calcoli. Non utilizzate alcun metodo della classe Math né l operatore di elevamento a potenza ^. Incorporate questo metodo in un applicazione Windows che legge i valori interi di base e exponent immessi dall utente nei TextBox ed esegue i calcoli con il metodo IntegerPower. 6.10 Definite un metodo Hypotenuse che calcola la lunghezza dell ipotenusa di un triangolo rettangolo, note le lunghezze dei due cateti (side1 e side2). Il metodo riceve due argomenti di tipo Double e restituisce l ipotenusa come valore Double. Incorporate questo metodo in un applicazione Windows
202 CAPITOLO 6 che legge i valori interi di side1 e side2 immessi dall utente nei TextBox ed esegue i calcoli con il metodo Hypotenuse. Determinate la lunghezza dell ipotenusa per i triangoli del seguente prospetto. Triangolo Cateto (side1) Cateto (side2) 1 3.0 4.0 2 5.0 12.0 3 8.0 15.0 6.11 Scrivete un metodo SquareOfAsterisks che visualizza un quadrato di asterischi il cui lato è specificato nel parametro intero side. Per esempio, se side è 4, il metodo visualizza il quadrato: **** **** **** **** Inserite questo metodo in un applicazione Windows che legge il valore intero di side digitato dall utente e traccia il quadrato con il metodo SquareOfAsterisks. Questo metodo deve acquisire i dati dai TextBox e deve visualizzare il risultato in una Label. 6.12 Modificate il metodo creato nell Esercizio 6.11 in modo da formare il quadrato con il carattere contenuto nel parametro fillcharacter. Per esempio, se side è 5 e fillcharacter è #, il metodo dovrebbe visualizzare il seguente quadrato: ##### ##### ##### ##### ##### 6.13 Scrivete un applicazione Windows che simula il lancio di una moneta. Il programma deve lanciare la moneta ogni volta che l utente preme il pulsante Lancia. Contate il numero di volte che si presentano le due facce della moneta e visualizzate i risultati. Il programma deve chiamare un metodo separato Flip, che non riceve argomenti e restituisce False per croce e True per testa. Nota: se il programma simula realisticamente il lancio di una moneta, ogni faccia dovrà apparire circa la metà delle volte. 6.14 I computer giocano un ruolo sempre più importante nel campo educativo. Scrivete un programma che aiuta uno studente di scuola elementare ad apprendere la moltiplicazione. Utilizzate il metodo Next da un oggetto di tipo Random per produrre due numeri interi positivi composti da una cifra. Il programma dovrà visualizzare una domanda come questa: Quanto fa 6 per 7? Lo studente digiterà la risposta in un TextBox e il programma la controllerà. Se la risposta è corretta, il programma visualizzerà Molto bene! in una Label, poi farà un altra domanda. Se la risposta è sbagliata, il programma visualizzerà No. Riprova. nella stessa Label. Lasciate che lo studente provi ancora la stessa moltiplicazione, finché non avrà risposto correttamente. Per generare una nuova domanda, deve essere usato un metodo separato. Questo metodo deve essere chiamato una volta quando il programma inizia l esecuzione e poi ogni volta che l utente risponde correttamente alla domanda. 6.15 (Le Torri di Hanoi) La leggenda narra che, in un tempio dell Estremo Oriente, alcuni preti stavano tentando di spostare una pila di dischi da un paletto a un altro. La pila iniziale era formata da 64 dischi infilati in un paletto e ordinati dal più grande al più piccolo, procedendo dal basso verso l alto (Figura 6.29). I preti dovevano spostare i dischi nel secondo paletto, rispettando alcune regole: è consentito spsotare un solo disco per volta; un disco più grande non può essere impilato su uno più
PROCEDURE 203 Figura 6.29 Le Torri di Hanoi nel caso di quattro dischi piccolo. È disponibile un terzo paletto, dove è possibile impilare temporaneamente i dischi. Secondo la leggenda, se i preti avessero completato il compito, sarebbe arrivata la fine del mondo (per questo non dovremmo aiutarli). Supponiamo che i preti stiano tentando di spostare i dischi dal paletto 1 al 3. Vogliamo sviluppare un algoritmo che visualizza l esatta sequenza degli spostamenti dei dischi da un paletto all altro. Se volessimo risolvere questo problema con le tecniche convenzionali, ci ritroveremmo presto impantanati nella gestione degli spostamenti dei dischi. Se, invece, affrontiamo il problema con la tecnica della ricorsione, sarà più semplice trovare la soluzione. Lo spostamento di n dischi può essere visto come lo spostamento di n 1 dischi (ecco la ricorsione), in questo modo: 1. Spostare n 1 dischi dal paletto 1 al 2, utilizzando il paletto 3 come deposito temporaneo. 2. Spostare l ultimo disco (il più grande) dal paletto 1 al 3. 3. Spostare n 1 dischi dal paletto 2 al 3, utilizzando il paletto 1 come deposito temporaneo. Il processo terminerà quando l ultimo compito sarà lo spostamento di un solo disco (n = 1), che rappresenta il caso di base della ricorsione. Questo compito potrà essere svolto spostando il disco, senza bisogno di usare un paletto come deposito temporaneo. Scrivete un programma per risolvere il problema delle Torri di Hanoi. L utente potrà inserire il numero di dischi in un TextBox.Utilizzate un metodo ricorsivo Tower con quattro parametri: 1. Il numero dei dischi da spostare. 2. Il paletto sul quale questi dischi sono inizialmente impilati. 3. Il paletto sul quale questa pila di dischi deve essere spostata. 4. Il paletto che deve essere utilizzato come deposito temporaneo. Il programma deve visualizzare in un TextBox (con possibilità di fare scorrere i testi) le istruzioni necessarie per spostare i dischi dal paletto iniziale a quello di destinazione. Per esempio, per spostare una pila di tre dischi dal paletto 1 al 3, il programma dovrà visualizzare la seguente serie di mosse: 1 3 (Questo significa spostare un disco dal paletto 1 al paletto 3) 1 2 3 2 1 3 2 1 2 3 1 3