Scrivere una Funzione

Da Gambas-it.org - Wikipedia.

Una Funzione [note: 1 2 3 4] è una routine, chiamata da una routine principale, alla quale è delegato il compito di svolgere una serie di operazioni, e delle quali essa restituisce alla routine principale chiamante il risultato.

La Funzione è l'operazione attinente ad una fase del processo del programma.

Poiché una funzione deve eseguire un compito, si ritiene non opportuno scrivere una funzione per eseguire più istruzioni insieme. E' preferibile scrivere funzioni per singoli compiti ed una ulteriore funzione che svolga l'operazione complessa, invocando le funzioni precedenti.


Definizione di una Funzione

La funzione, creata dal programmatore, in Gambas è sostanzialmente definita come segue:

[Public/Private] Function Nome_Funzione( dichiarazione dei parametri, se previsti As tipo_passato ) As Tipo_ritornato
  
  eventuali_dichiarazioni_di_tipi_di_variabili
  
  istruzioni
  
  Return valore_da_ritornare
  
End

La parola chiave Function, che specifica e definisce una Funzione chiamata, può essere preceduta dalla parola chiave Public o Private a seconda delle necessità di visibilità della Funzione medesima.

Una Funzione, per essere utilizzata dal processo del programma, deve essere invocata, chiamata da una specifica Funzione chiamante. Tale Funzione chiamante deve avere un nome identificativo identico a quello della Funzione chiamata.

Inoltre, se è previsto che la Funzione chiamante passi alla Funzione chiamata uno o più valori, affinché essi vengano processati dalle istruzioni contenute dalla Funzione chiamata, detti valori pasati devono essere del tipo identico a quello dichiarato nella Funzione chiamata. In tal senso i valori, passati dalla Funzione chiamante sono definiti " argomenti ", mentre i valori dichiarati - e quindi poi ricevuti - dalla Funzione chiamata sono definiti " parametri ".

La Funzione chiamata ritorna sempre un valore, che è il risultato delle operazioni svolte dalle istruzioni presenti nella Funzione. [note: 5 ]


Mostriamo di seguito un semplice esempio pratico con la modalità più comune di ritorno di valore:

Public Sub Main()

 Dim i, rit As Integer
 
  i = 10
  
' La seguente Funzione "chiamante" invoca la Funzione "chiamata" e le passa un valore come "argomento",
' affinché sia processato, e dalla quale riceverà un valore come risultato di quel processo:
  rit = Calcolare(i)
  
  Print "Valore ritornato: "; i
  
End


' La seguente Funzione "chiamata" riceve un valore, come "parametro" dalla Funzione "chiamante".
' Quindi lo elabora con le istruzioni da essa contenute, e lo restituisce alla Funzione "chiamante":
Private Function Calcolare(valore As Integer) As Integer
 
 Dim n, r As Integer
 
  n = 100
  
  r = valore * n
  
  Return r
  
End


Passaggio dei valori ad una Funzione

Nel paragrafo precedente abbiamo visto con la parola Return la modalità più frequente e più semplice per ritornare in Gambas un valore da una Funzione. Ma non è la sola modalità.

In Gambas la routine principale chiamante può passare attraverso i suoi argomenti uno o più valori alla Funzione chiamata. Il passaggio degli argomenti ad una Funzione può essere di tre tipi:

  • per valore;
  • per indirizzo;
  • per riferimento.


Passaggio per valore

Il passaggio per valore è senz'altro la modalità più frequente di passare dei dati ad una Funzione chiamata.

Con il passaggio per valore viene effettuata una copia della variabile passata alla Funzione chiamata come argomento della routine principale chiamante. Inoltre, le modifiche alla variabile del parametro passato, effettuate nella Funzione chiamata, restano confinate nell'ambito locale di detta Funzione secondaria. Ciò significa che la variabile, passata come parametro dalla routine chiamante a quella chiamata, non vede modificarsi il suo valore, nonostante la sua copia abbia subito una modifica del proprio valore a seguito di una o più operazioni nella Funzione secondaria chiamata. La viariabile presente nella routine principale chiamante resta intatta, immodificata, qualunque cosa avvenga alla sua copia nella Funzione chiamata.
Se, dunque, non vi fosse ritorno di valore della varibile-copia, ora con valore modificato, alla routine principale chiamante, non sarebbe possibile utilizzare nella predetta routine chiamante le modifiche apportate dalle operazioni, alle quali la Funzione secondaria chiamata è stata preposta.


Mostriamo un semplice esempio:

Public Sub Main() ' Routine principale

 Dim a, b, risultato_di_ritorno As Integer

  a = 2
  b = 3

' Chiama la Funzione, e le passa i due valori, affinché li elabori.
' Alla fine la Funzione restituirà il risultato che riempirà la variabile integer "risultato_di_ritorno":
  risultato_di_ritorno = funzione_chiamata(a, b)
 
' Mostriamo il risultato in console:
  Print "Risultato di ritorno dalla funzione = "; risultato_di_ritorno

End


' La funzione secondaria "Funzione_Chiamata" contiene il proprio nome ed i parametri come variabili dei valori passati dalla routine chiamante, e non prevede l'aggiunta della parola chiave "Sub".
' Essa, poiché restituisce - in questo nostro esempio - un valore Integer, termina la propria dichiarazione con "As Integer" .
Private Function Funzione_Chiamata(aF As Integer, bF As Integer) As Integer

 Dim somma As Integer

' Non è necessario dichiarare le due variabili, poiché lo si è già fatto nei parametri della funzione:
  somma = aF + bF

' Viene restituito il risultato alla routine chiamante:
  Return somma
 
End

In console avremo coerentemente:
Risultato di ritorno dalla funzione = 5


...e se la Funzione deve restituire due o più risultati ?

Se la Funzione deve restituire più di un risultato non è possibile utilizzare l'istruzione Return, giacché essa restituisce un solo valore.

Per ottenere da una medesima Funzione due o più risultati (quindi senza dover utilizzare una variabile globale), possiamo utilizzare due modalità:

  • uso di una variabile vettoriale o una Struttura;
  • il passaggio per indirizzo.

Uso di un vettore

Mostriamo un esempio di una Funzione che restituisce un duplice risultato:

Public Sub Main()

 Dim a, b As Integer
 Dim risultato_di_ritorno As Integer[]
 
  a = 2
  b = 3

' Chiama la Funzione, e le passa i due valori, affinché li elabori.
' Alla fine la Funzione restituirà il risultato che riempirà la variabile vettoriale integer[] "risultato_di_ritorno":
  risultato_di_ritorno = Funzione_Chiamata(a, b)

' Mostriamo il primo risultato (la somma) in console:
  Print "Risultato (somma) di ritorno dalla funzione = "; risultato_di_ritorno[0]

' Mostriamo il secondo risultato (la differenza) in console:
  Print "Risultato (differenza) di ritorno dalla funzione = "; risultato_di_ritorno[1]

End


' La routine Funzione, poiché restituisce - in questo nostro esempio - un valore vettore Integer[], termina la propria dichiarazione con: As Integer[].
Public Function Funzione_Chiamata(aF As Integer, bF As Integer) As Integer[]

 Dim somma, differenza As Integer
 Dim doppiorisultato As New Integer[]

  somma = aF + bF
  differenza = bF - aF

' Nel primo campo dell'array inseriamo il valore contenuto dalla variabile Integer "somma":
  doppiorisultato.Add(somma)

' Nel secondo campo dell'array inseriamo il valore contenuto dalla variabile Integer "differenza":
  doppiorisultato.Add(differenza)


' Dunque ora la variabile Integer[] "doppiorisultato" contiene due valori.
' Tale duplice contenuto di questa variabile Integer[] viene restituito alla routine principale chiamante:
  Return doppiorisultato

End

In console avremo coerentemente:
Risultato (somma) di ritorno dalla funzione = 5
Risultato (differenza) di ritorno dalla funzione = 1


Passaggio per indirizzo

Il passaggio per indirizzo prevede che la routine principale chiamante passi come propri argomenti alla Funzione chiamata gli indirizzi di memoria delle variabili della routine chiamante contenenti i valori che dovranno essere gestiti dalle istruzioni della Funzione chiamata. [Nota 6]

La particolarità del passaggio degli argomenti per indirizzo è che tale modalità di passaggio ci consente di eliminare l'istruzione Return, e di ritornare, così, il valore dalla Funzione chiamata alla routine principale chiamante attraverso i parametri medesimi della Funzione. Inoltre, esso non determina la creazione di una copia della variabile èassata (come invece avviene per il passaggio per valore).

Il passaggio per Indirizzo di un argomento può avvenire mediante:

  • i Vettori;
  • le Strutture;
  • i Puntatori.

Ciò in quanto le rispettive variabili contengono gli indirizzi alle aree di memoria da esse puntate ove sono memorizzati i vari dati.

Più in particolare quando si deve passare l'indirizzo di memoria di una variabile, diversa da un Vettore e da una Struttura, si userà la funzione VarPtr( ), altrimenti si passa un Puntatore ad un'area di memoria opportunamente allocata con al funzione Alloc( ).

Semplici esempi pratici

Mostriamo un esempio pratico con un Vettore:

Public Sub Main()
 
 Dim vett As New Integer[4]
 Dim i As Integer
 
  Funzione(vett)
  
  For Each i In vett
    Print i
  Next
  
End


Private Function Funzione(ii As Integer[])
 
 Dim c As Integer
 
   For c = 0 To ii.Max
     ii[c] = c + 1000
   Next
 
End


Ora un esempio con una Struttura:

Public Struct STRUTTURA
  b As Byte
  s As Short
  i As Integer
  l As Long
End Struct


Public Sub Main()
 
 Dim sr As New STRUTTURA
 
  Funzione(sr)
  
  With sr
    Print .b
    Print .s
    Print .i
    Print .l
  End With
  
End


Private Function Funzione(tr As STRUTTURA)
 
 With tr
   .b = 100
   .s = 1000
   .i = 100000
   .l = 10000000
 End With
 
End


Quindi un paio di esempi nei quali la funzione passa l'indirizzo di memoria di una variabile dichiarata. Per scrivere nella variabile, ricevuta come parametro di tipo Puntatore della Funzione chiamata, il risultato finale delle operazioni compiute in tale Funzione, bisognerà ovviamente utilizzare i Memory Stream unitamente alla funzione Write.

Mostriamo un esempio, nel quale la funzione passa l'indirizzo di memoria di una variabile dichiarata di tipo Intero Lungo:

Public Sub Main()
 
 Dim num As Long
 
  Funzione(VarPtr(num))
   
  Print num
  
End


Private Function Funzione(p As Pointer)
 
 Dim st As Stream
 
  st = Memory p For Write
  Write #st, 1234567890 As Long
  st.Close
 
End

Dello stesso tipo appena visto, il seguente esempio, ove avviene lo scambio di valori fra due variabili (swapping):

Public Sub Main()
 
 Dim i1, i2 As Integer
 
  i1 = 1000
  i2 = 2000
   
  Scambio(VarPtr(i1), VarPtr(i2))
   
  Print i1, i2
  
End


Private Function Scambio(x As Pointer, y As Pointer)
 
 Dim z As Integer
 Dim st As Stream
    
  z = Int@(x)
   
  st = Memory x For Write
  Write #st, Int@(y) As Integer
  st.Close
   
  st = Memory y For Write
  Write #st, z As Integer
  st.Close
  
End


Nei seguenti due esempi, invece, sarà passata una variabile di tipo Puntatore che punta ad un'area di memoria appositamente riservata.
In questo esempio l'area di memoria conterrà valori numerici generici:

Public Sub Main()
 
 Dim ptr As Pointer
 Dim c As Integer
 
  ptr = Alloc(SizeOf(gb.Byte), 8)
 
  Funzione(ptr)
   
  For c = 0 To 7
    Print Byte@(ptr + c)
  Next
  
  Free(ptr)
  ptr = 0
  
End


Private Function Funzione(p As Pointer)
 
 Dim st As Stream
 Dim b As Byte
    
  st = Memory p For Write
  For b = 1 To 8
    Write #st, b * 10 As Byte
  Next
  st.Close
 
End

In quest'altro esempio sarà passata un'area di memoria contenente i valori prettamente attinenti a caratteri stringa:

Public Sub Main()

 Dim p As Pointer
 
  p = Alloc("abcde")
  
  Funzione(p)
   
  Print String@(p)
     
  Free(p)
  p = 0

End


Private Function Funzione(po As Pointer)
 
 Dim st As Stream
   
  st = Memory po For Write
  
  Write #st, 70 As Byte
  
  st.Close
 
End


Ritorno di più valori

Come già accennato, la modalità del passaggio per indirizzo ci consente di ritornare alla routine chiamante anche più valori.

Mostriamo un esempio pratico, nel quale vengono ritornati due valori attraverso i parametri della Funzione chiamata e di quella chiamante:

Public Sub Main()

 Dim dividendo, divisore As Integer
 Dim p1, p2 As Pointer

  Print "Scrivere i valori del dividendo e del divisore."
  Input dividendo
  Input divisore

  p1 = VarPtr(dividendo)
  p2 = VarPtr(divisore)

  Funzione_Chiamata(p1, p2)

  Print "Quoziente: "; Int@(p1)
  Print "Resto: "; Int@(p2)

End


Public Function division(divid As Pointer, divis As Pointer)

 Dim i1, i2, ris As Integer
 Dim st As Stream
 
  i1 = Int@(divid)
  i2 = Int@(divis)
  ris = i1 / i2

  st = Memory divid For Write
  Write #st, ris As Integer
  st.Close
   
  ris = i1 Mod i2

  st = Memory divis For Write
  Write #st, ris As Integer
  st.Close

End


Ritorno di valori per indirizzo senza utilizzo come argomento di un esplicito Puntatore

Abbiamo visto nel paragrafo appena precedente che il ritorno di un valore attraverso i parametri (o argomenti) della Funzione prevedeva che il parametro (o i parametri, nel caso si intenda far ritornare più valori) deve essere dichiarato come tipo Puntatore, dato che si tratta di passaggio dell'indirizzo di memoria della variabile, presente nella routine principale, contenente il valore da gestire nella Funzione secondaria chiamata.

Qualora il tipo di argomenti siano una Struttura oppure un vettore, non v'è necessità che i parametri, passati alla Funzione chiamata, siano di tipo Puntatore.

Mostriamo due esempi pratici.

Public Struct STRUTTURA
  b As Byte
  s As Short
  i As Integer
End Struct


Public Sub Main()

 Dim st As New STRUTTURA
 
  With st
    .b = 99
    .s = 9999
    .i = 999999
  End With
 
 
  Funzione_Chiamata(st)
   
  With st
    Print .b
    Print .s
    Print .i
  End With

End


Private Function Funzione_Chiamata(n As STRUTTURA)
 
  With n
    .b += 99
    .s += 9999
    .i += 999999
  End With
 
End


Public Sub Main()

 Dim ss As String[] = ["a", "b", "c"]
  
  Print ss.Count
   
  Prova(ss)
   
  Print ss.Count

End

 
Private Function Prova(vett As String[])

 vett.Remove(1)
  
End


Passaggio per Riferimento

Il passaggio per riferimento in Gambas prevede sia nella Funzione chiamante sia nella Funzione chiamata l'uso della parola chiave ByRef prima dell'argomento da passare.

Ovviamente anche questa modalità, come per il passaggio per indirizzo, non necessita dell'uso dell'istruzione Return al termine della Funzione chiamata.

Mostriamo un esempio:

Public Sub Main()

Dim a, b As Integer

 a = 2
 b = 3

  Funzione_Chiamata(ByRef a, b)

  Print "Risultato di ritorno dalla funzione = "; a

End


Private Function Funzione_Chiamata(ByRef aRif As Integer, bI As Integer)

 Dim somma As Integer
 
' Si effettua l'operazione fra i due argomenti, ed il risultato viene restituito attraverso il parametro individuato con "ByRef":
  aRif = aRif * bI

End


Ritorno di più valori

Anche con il passaggio per riferimento mediante la parola chiave ByRef è possibile ritornare alla funzione chiamante due o più valori. Per fare ciò si dovrà porre la parola chiave ByRef su due o più argomenti da passare delle Funzioni chiamante e chiamata.


Scrivere ed invocare una Funzione in un Modulo

La scrittura e la successiva invocazione di una Funzione in un Modulo segue la procedura normale.


Facciamo un semplissimo esempio.

Nella Classe principale avremo il seguente codice:

Public Sub Form_Open()  
 
Dim by As Byte  
  
' Invochiamo la funzione nel Modulo e le passiamo un valore di tipo Byte (in questo esempio: 6 ).
' Essa, a sua volta, ci passerà un valore che inseriremo nella variabile "by":
   by = Module1.funzioneNelModulo(6)
 
   Print by
 
End


Nel Modulo avremo:

Public Function funzioneNelModulo(a As Byte) As Byte  ' Riceve dalla routine chiamante un valore di tipo "Byte", ed un valore di tipo "Byte" a sua volta restituirà  
  
 Dim b As Byte  
  
  b = 2  
  
' La Funzione effettua l'operazione per la quale è stata concepita:
    b = b * a  
  
' Restituisce, quindi, alla routine chiamante, presente nella Classe principale, il risultato dell'operazione:
    Return b
  
End



Note

[1] «La "funzione" di una cosa è ciò che la sola cosa può compiere.» (Platone, La Repubblica)

[2] La Funzione " offre un metodo conveniente per racchiudere una parte di programma in grado di eseguire un compito specifico, in modo che sia riutilizzabile senza preoccuparsi della sua implementazione. " (Kernighan B. W. - Ritchie D. M., Il linguaggio C - seconda edizione, Pearson edizioni)

[3] Si rinvia anche a Wikipedia per una definizione più ampia della Funzione.

[4] Possiamo distinguere quattro tipi di Funzioni:

  • native ed autonome. Si tratta di quelle Funzione che sussistono di per sé all'interno di Gambas, avendo così una dignità propria (ad esempio: Alloc(), SizeOf(), Max() e così via).
  • metodi di Classi Gambas. Si tratta di quelle Funzioni di Gambas che non sono autonome, ma esistono in quanto risorse tipiche di una Classe. Tali Funzioni (Metodi) possono essere utilizzati solo se richiamate attraverso un Oggetto di una Classe. Esempi di tali Funzioni: Object.SizeOf(), Button1.Move(), vettore.Push(), Paint.DrawImage() e così via.
  • Funzioni scritte dal programmatore (oggetto della presente pagina): sono appunto specifiche Funzioni, scritte dal programmatore, per assolvere ad un particolare compito che si esaurisce con l'applicazione, nella quale la Funzione è stata appositamente creata.
  • Funzioni esterne. Tali funzioni non appartengono al linguaggio Gambas, bensì a librerie esterne di funzioni. Tali funzioni esterne possono essere richiamate e gestite da Gambas mediante la parola Extern.

[5] Anche se non è una regola rigida che possa dare errore, in Gambas una Funzione restituisce sempre un valore alla Funzione chiamante; pertanto si preferisce definirla con la parola chiave Function (preceduta dalla parola chiave Public o Private a seconda ovviamente delle necessità), rispetto ad una routine che non restituisce alcun valore, e che - in tal caso - verrà definita con la parola chiave Sub o Procedure (preceduta dalla parola chiave Public o Private a seconda ovviamente delle necessità),.
Ricapitolando:
- se la subroutine chiamata restituisce un valore alla Funzione chiamante, allora la subroutine chiamata sarà considerata una Funzione in senso stretto, e verrà dichiarata formalmente con la parola chiave Function:

Public/Private Function Nome_Funzione(parametro As tipo_valore, ......) As tipo_valore

  .....
  .....
  
  Return valore
  
End

- se invece la subroutine chiamata non restituisce un valore alla Funzione chiamante, allora la subroutine chiamata sarà considerata una mera Procedura, e si preferisce dichiararla formalmente con la parola chiave Procedure o con Sub:

Public/Private Procedure Nome_Procedura(parametro As tipo_valore, ......)

  .....
  .....
  
End

[6] Al riguardo vedere anche la seguente pagina della WIKI: http://www.gambas-it.org/wiki/index.php?title=Passaggio_di_un_argomento_per_%27indirizzo%27_ad_una_Funzione