Adesso vediamo un'altro tipo di sottoprogramma, la sub-function, detta anche chiamata di funzione.
sub functionnome_funzione (lista_parametri) as tipo_dato_ritorno [ dichiarazioni locali ] corpo della funzione end sub
Sub function
Una funzione è simile ad una procedura, ma che però ritorna un valore quando viene eseguita, mentre la procedura non lo fa.
Il nome della funzione è un nome scelto arbitrariamente, per ricordarci cosa fanno le istruzioni contenute.
La lista dei parametri è composta da uno o più parametri (detti parametri formali), definiti alla stessa maniera di come si definiscono le variabili.
Il tipo dato di ritorno è il tipo di dato del risultato della funzione, che sarà assegnato alla variabile chiamante.
Per indicare il valore del risultato, nel corpo della funzione bisogna usare la variabile result (automaticamente creata locale da mikroBasic), per assegnare il valore di ritorno di una funzione.
Una funzione viene invocata col suo relativo nome, con i parametri da passare (detti parametri attuali) messi nella stessa sequenza dei loro parametri formali corrispondenti.
Per essere parte integrante di un programma, la funzione deve trovarsi, come collocazione nel listato, prima della label "main" e segue le stesse regole delle procedure.
program prova_funz
dim pere as byte dim mele as byte dim ricavo as word
sub function somma(dim x, y as byte) as word result = x + y end sub
main: trisB = 0 while true ricavo = somma(10, 35) pere = 25 mele = 30 if somma(pere, mele) > 50 then portB.0 = 1 end if wend End.
Esempio di sub-function
Definisco le variabili globali pere e mele di tipo byte e ricavo di tipo word.
Definisco la funzione somma con 2 parametri formali (x, y) di tipo byte e il risultato di tipo word.
Result è una variabile locale della funzione, creata automaticamente da mikroBasic, perciò non serve definirla. E' la variabile che riceverà il risultato della funzione
(result = x + y). In questo caso, il compito che deve fare la funzione, è quello di fare la somma dei 2 valori passati nei parametri.
Nella main assegno a ricavo il risultato della funzione somma, chiamata passandole i parametri attuali (10, 35) (in pratica ricavo = result).
Poi assegno dei valori alle variabili pere e mele e, se la somma di queste, è maggiore di 50 (risultato della funzione somma), imposto ad 1 il bit portB.0.
Cos'è lo stack?
Quando un sottoprogramma (subroutine, procedura o funzione) viene chiamato, l'indirizzo dell'istruzione di ritorno deve essere salvato da qualche parte.
Lo stack è uno spazio di memoria indipendente al di fuori della RAM o ROM, dove vengono scritti gli indirizzi di ritorno dei sottoprogrammi.
La parola stack in inglese significa "catasta, pila" e su questa catasta e' possibile depositare, uno sull'altro, piu' indirizzi e poi recuperarli quando servono.
Questo tipo di memorizzazione viene anche denominata LIFO dall'inglese Last In First Out, in cui l'ultimo elemento inserito (last in) deve necessariamente essere il primo ad uscire (first out).
Grazie a questa caratteristica, è possibile effettuare più chiamate a sottoprogrammi annidate e mantenere sempre traccia del punto in cui proseguire l'esecuzione, nel momento in cui si incontra la fine del sottoprogramma.
program prova_nested ..... .....
sub procedure ritardo delay_ms(10) end sub
sub procedure proc_due ritardo end sub
sub procedure proc_uno proc_due end sub
main: ..... ..... proc_uno ..... ..... goto main End.
Limitazioni di chiamate annidate
La chiamata annidata rappresenta, ad esempio, una chiamata, all'interno del corpo procedura, ad un'altra procedura o funzione.
Nell'esempio vediamo nella main
1) la chiamata a proc_uno, la quale, a sua volta,
2) chiama la proc_due, che a sua volta
3) chiama ritardo, che a sua volta
4) chiama delay_ms.
Tutte queste chiamate si dice che sono annidate ed i loro indirizzi di ritorno saranno impilati nello stack per essere presi al ritorno di ognuna di esse.
In questo caso, le richieste sono 3, perchè delay_ms, essendo inline, non ha effetto sullo stack.
mikroBasic limita il numero di chiamate annidate a:
- 8 richieste per la famiglia PIC12,
- 8 richieste per la famiglia PIC16,
- 31 richieste per la famiglia PIC18.
Notare che le routine incorporate (built-in routines) non contano contro questo limite, a causa della loro realizzazione "inline".
Il numero di chiamate annidate permesse diminuisce di uno, se si usa uno degli operatori " * , / , % " nel codice.
Diminuisce ulteriormente di uno, se nel programma si usa l'interrupt.
Se si supera il numero permesso di chiamate annidate, mikroBasic segnala l'errore di stack overflow.
Passare parametri per valore e per riferimento
Program prova_byval dim limone as byte
sub procedure pausa_ms(dim ms as word) dim i as word for i = 1 to ms delay_ms(1) next i end sub
sub procedure gelato(dim banana as byte) limone = banana + 3 banana = limone end sub
Quando dobbiamo passare delle variabili ai parametri delle procedure e funzioni, queste, per impostazione predefinita, vengono passate "per valore" e la parola chiave byval non è necessaria.
Quando, in un parametro di una procedura, si trova scritto ad es: gelato(dim banana as byte) è come se fosse scritto: gelato(dim byval banana as byte) perché, per default, è sott'inteso il passaggio per valore.
Ecco, a sinistra, un esempio che non fa niente, ma è solo per spiegare la differenza del passaggio per valore e per riferimento.
N.B.
Ho inserito un ritardo di 2 secondi per poter vedere il risultato sui LED connessi a portB.
In questo caso, viene chiamata la procedura gelato e viene passato, come parametro, la variabile portB (portB è un registro, una variabile di tipo byte è un registro, quindi... portB è una variabile di tipo byte).
Nella procedura gelato, il valore di banana, sarà la copia di quello di portB (cioè 5), che poi, nella procedura verrà sommato con 3 ed il risultato verrà messo in limone.
Poi banana assumerà il valore di limone.
Quando si ritorna dalla procedura, il valore di banana "muore" con la procedura, perché è come se fosse una variabile locale. Il valore di portB sarà ancora 5 ed il valore di limone sarà 8 (5+3), perché è una variabile globale ed è stata modificata nella procedura gelato.
Program prova_byref dim limone as byte
sub procedure pausa_ms(dim ms as word) dim i as word for i = 1 to ms delay_ms(1) next i end sub
sub procedure gelato(dim byref banana as byte) limone = banana + 3 banana = limone end sub
Se, invece, dobbiamo passare la variabile per riferimento dobbiamo usare la parola chiave byref.
Byref indica che la variabile viene presa per riferimento e cioè, si fa riferimento al suo indirizzo in memoria.
Vediamo con un'altro esempio, simile a quello di prima, le differenze.
N.B.
Anche qui, ho inserito un ritardo di 2 secondi per poter vedere il risultato sui LED connessi a portB.
Anche in questo caso, viene chiamata la procedura gelato e viene passato, come parametro, il registro portB.
Nella procedura gelato, il valore di banana sarà quello di portB (cioè 5), che poi, nella procedura, verrà sommato con 3 ed il risultato verrà messo in limone.
Poi banana assumerà il valore di limone.
Quando si ritorna dalla procedura, il valore di portB, però, non sarà ancora 5, ma avrà il valore di limone (8 cioè 5+3).
Perché succede questo? Non c'è nessuna istruzione che modifica portB. E allora, cosa è successo?
La differenza sta nel passaggio della variabile, per riferimento. Banana, per riferimento, ha preso l'indirizzo del registro portB ed è come se fosse diventata portB, a tutti gli effetti. Ha solo cambiato nome MOMENTANEAMENTE. Se si modifica banana, si modifica, in realtà, portB.
Quando, nella procedura, si trova l'istruzione "banana = limone", è come se fosse stato scritto portB = limone.
Con byval, viene COPIATO il valore della variabile, mentre con byref, viene MODIFICATA la variabile direttamente.
Vediamo un esempio pratico analizzando ByteToStr, una procedura della Conversions Library.
La procedura ByteToStr converte un valore, di tipo byte, in una stringa di 3 caratteri per permettere la visualizzazione su display LCD.
Essa è definita con 2 parametri formali: number, di tipo byte, che riceverà il numero da convertire; output, di tipo string di 3 caratteri, che conterrà il risultato della conversione.
Notiamo che il primo parametro sarà passato per valore, mentre il secondo sarà passato per riferimento.
Essendo una procedura della libreria, non possiamo vedere il codice sorgente contenuto, però ci basta sapere quali dati dobbiamo fornire, per farla funzionare perfettamente.
Proviamo questo programmino, dove ho inserito anche gli argomenti che abbiamo trattato precedentemente.
program prova_param
dim testo_str as string[3] dim num_byte as byte
sub procedure pausa_ms(dim ms as word) dim i as word for i = 1 to ms delay_ms(1) next i end sub
sub function somma(dim x, y as byte) as byte result = x + y end sub
Iniziamo definendo la variabile testo_str, di tipo string di 3 caratteri e la variabile num_byte, di tipo byte.
Poi definiamo la procedura pausa_ms (che abbiamo già visto) e la funzione somma (anch'essa già vista prima).
Nella main, inizializziamo il display LCD (Lcd_init) ed escludiamo la visualizzazione del cursore (Lcd_cursor_off), dopodichè entriamo in un ciclo infinito (while-wend).
Azzeriamo il display (Lcd_clear) ed assegnamo alla variabile num_byte, il risultato della funzione somma.
Ora vogliamo visualizzare il valore di num_byte e, per fare questo, dobbiamo convertire il valore numerico in una stringa di caratteri per trasferirla, poi, al display LCD il quale accetta solo caratteri.
Questo compito lo esegue la procedura ByteToStr e quindi passiamo, al primo parametro, il valore da convertire, num_byte.
Al secondo parametro passiamo, invece, il nome della variabile stringa che dovrà ricevere il risultato della conversione, testo_str; non passiamo il valore ma il riferimento alla variabile (l'indirizzo in memoria).
Adesso visualizziamo sul display (Lcd_out), nella prima riga, un messaggio e, nella seconda riga, la stringa testo_str indicante il valore di num_byte.
Attendiamo 2 secondi e riazzeriamo il display.
Adesso vediamo un'altro modo di passare parametri. Nella procedura ByteToStr, invece di passare il valore di una variabile di tipo byte nel primo parametro, possiamo passare il risultato (di tipo byte) di una funzione, in questo caso somma.
Il resto è uguale a quello che abbiamo visto prima e ripetiamo in un ciclo infinito.
sub procedure proc_1 dim errore as byte ... 'qui facciamo qualcosa if errore = true then exit end if ... 'codice che non verrà 'eseguito se errore è vero end sub
Saltare fuori da una procedura o funzione
Se fosse necessario uscire da una procedura o funzione, senza eseguire parte del codice, esiste una istruzione apposita, exit.
Exit esegue un salto alla fine della procedura o funzione e torna al punto seguente la chiamata.
Uscendo da una funzione, il valore di ritorno sarà quello della variabile locale result, al momento dell'uscita.
non sei autenticato, per questo non puoi visualizzare i commenti sulla pagina. Se sei registrato accedi oppure registrati.
difficoltà
costo
informazioni
Questa pagina è stata creata da beduino il 24/12/2008 ore 12:44 ultima modifica del 17/08/2010 ore 20:40la pagina è stata visitata 10533 volte
Lo staff di www.grix.it non si assume responsabilità sul contenuto di questa pagina.
Se tu sei l'autore originale degli schemi o progetti proposti in questo articolo, e ritieni che siano stati violati i tuoi diritti, contatta lo staff di www.grix.it per segnalarlo.