Creare funzioni

Completato

In Go le funzioni consentono di raggruppare un set di istruzioni che è possibile chiamare da altre parti dell'applicazione. Anziché creare un programma con numerose istruzioni, è possibile usare le funzioni per organizzare il codice e renderlo più leggibile. Il codice più leggibile è anche più gestibile.

Fino a questo punto, è stata chiamata la funzione fmt.Println() ed è stato scritto codice nella funzione main(). In questa sezione si vedrà come è possibile creare funzioni personalizzate. Verranno anche esaminate alcune altre tecniche che è possibile usare con le funzioni in Go.

Funzione principale

La funzione con cui si è interagito finora è la funzione main(). Tutti i programmi eseguibili in Go hanno questa funzione perché è il punto di partenza del programma. È possibile avere una sola funzione main() nel programma. Se si crea un pacchetto Go, non è necessario scrivere una funzione main(). La creazione dei pacchetti verrà presentata in uno dei moduli successivi.

Prima di passare ai concetti di base della creazione di funzioni personalizzate in Go, è importante esaminare un aspetto cruciale della funzione main(). Come si può notare, la funzione main() non ha parametri e non restituisce nulla. Ma questo non significa che non sia in grado di leggere valori dall'utente, ad esempio gli argomenti della riga di comando. Per accedere agli argomenti della riga di comando in Go, è possibile usare il pacchetto os e la variabile os.Args, che include tutti gli argomenti passati al programma.

Il codice seguente legge due numeri dalla riga di comando e li somma:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    number1, _ := strconv.Atoi(os.Args[1])
    number2, _ := strconv.Atoi(os.Args[2])
    fmt.Println("Sum:", number1+number2)
}

La variabile os.Args include ogni argomento della riga di comando passato al programma. Poiché questi valori sono di tipo string, è necessario convertirli in int per sommarli.

Per eseguire il programma, usare questo comando:

go run main.go 3 5

Di seguito è riportato l'output generato:

Sum: 8

Si vedrà ora come è possibile effettuare il refactoring del codice precedente e creare la prima funzione personalizzata.

Funzioni personalizzate

Di seguito è illustrata la sintassi per la creazione di una funzione:

func name(parameters) (results) {
    body-content
}

Si noti che è possibile usare la parola chiave func per definire una funzione e quindi assegnarvi un nome. Dopo il nome, si specifica l'elenco di parametri per la funzione. Si possono specificare zero o più parametri. È anche possibile definire i tipi restituiti della funzione e anche in questo caso possono essere zero o più. (Nella sezione successiva verrà illustrato come restituire più valori.) Dopo aver definito tutti questi valori, è possibile scrivere il contenuto del corpo della funzione.

Per applicare questa tecnica, verrà effettuato il refactoring del codice della sezione precedente per sommare i numeri in una funzione personalizzata. Verrà usato il codice seguente:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum := sum(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

func sum(number1 string, number2 string) int {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    return int1 + int2
}

Questo codice crea una funzione denominata sum che accetta due argomenti string, ne esegue il cast a int e quindi restituisce il risultato della somma. Quando si definisce un tipo restituito, la funzione deve restituire un valore di quel tipo.

In Go è anche possibile impostare un nome sul valore restituito di una funzione, come se si trattasse di una variabile. Ad esempio, è possibile effettuare il refactoring della funzione sum come segue:

func sum(number1 string, number2 string) (result int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    result = int1 + int2
    return
}

Si noti che ora è necessario racchiudere il valore del risultato della funzione tra parentesi. È anche possibile usare la variabile all'interno della funzione ed è sufficiente aggiungere una riga return alla fine. Go restituirà i valori correnti delle variabili restituite. La semplicità dello scrivere la parola chiave return alla fine della funzione è allettante, soprattutto quando si hanno più valori restituiti. Questo approccio non è consigliato. Può non essere chiaro cosa restituisce la funzione.

Restituire più valori

In Go una funzione può restituire più di un valore. È possibile definire questi valori in modo analogo a come si definiscono i parametri della funzione. In altre parole, si specifica un tipo e un nome, ma il nome è facoltativo.

Si immagini, ad esempio, di voler creare una funzione che somma due numeri, ma li moltiplica anche. Il codice della funzione sarà simile al seguente:

func calc(number1 string, number2 string) (sum int, mul int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    sum = int1 + int2
    mul = int1 * int2
    return
}

Sono ora necessarie due variabili per archiviare i risultati della funzione. In caso contrario, il codice non verrà compilato. Ecco come si presenta:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, mul := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
    fmt.Println("Mul:", mul)
}

Un'altra funzionalità interessante di Go è che se non è necessario uno dei valori restituiti da una funzione, è possibile rimuoverlo assegnando il valore restituito alla variabile _. La variabile _ è il modo idiomatico usato da Go per ignorare i valori restituiti e consente la compilazione del programma. Quindi, se si vuole solo la somma, è possibile usare il codice seguente:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, _ := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

Verranno fornite ulteriori informazioni su come ignorare i valori restituiti dalle funzioni durante la presentazione della gestione degli errori in uno dei prossimi moduli.

Modificare i valori dei parametri della funzione (puntatori)

Quando si passa un valore a una funzione, ogni modifica apportata a tale funzione non influirà sul chiamante. Go è un linguaggio di programmazione con passaggio per valore. Ogni volta che si passa un valore a una funzione, Go accetta tale valore e crea una copia locale (una nuova variabile in memoria). Le modifiche apportate a tale variabile nella funzione non influiscono su quella inviata alla funzione.

Si immagini, ad esempio, di creare una funzione per aggiornare il nome di una persona. Si noti che cosa accade quando si esegue questo codice:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(firstName)
    fmt.Println(firstName)
}

func updateName(name string) {
    name = "David"
}

Anche se il nome è stato modificato in "David" nella funzione, l'output è ancora "John". L'output non è cambiato perché la modifica nella funzione updateName ha effetto solo sulla copia locale. Go ha passato il valore della variabile e non la variabile stessa.

Se si vuole che la modifica apportata nella funzione updateName abbia effetto sulla variabile firstName nella funzione main, è necessario usare un puntatore. Un puntatore è una variabile che contiene l'indirizzo di memoria di un'altra variabile. Quando si invia un puntatore a una funzione, non si passa un valore, si passa un indirizzo di memoria. Quindi, tutte le modifiche apportate alla variabile influiscono sul chiamante.

In Go sono disponibili due operatori per l'uso dei puntatori:

  • L'operatore & accetta l'indirizzo dell'oggetto che lo segue.
  • L'operatore * dereferenzia un puntatore, Ciò consente di accedere all'oggetto in corrispondenza dell'indirizzo contenuto nel puntatore.

L'esempio precedente verrà ora modificato per chiarire il funzionamento dei puntatori:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(&firstName)
    fmt.Println(firstName)
}

func updateName(name *string) {
    *name = "David"
}

Eseguire il codice precedente. Si noti che l'output ora mostra David anziché John.

La prima cosa da fare è modificare la firma della funzione per indicare che si vuole ricevere un puntatore. A tale scopo, modificare il tipo di parametro da string a *string. (Quest'ultimo è ancora una stringa, ma ora è un puntatore a una stringa). Quindi, quando si assegna un nuovo valore a tale variabile, è necessario aggiungere l'asterisco (*) sul lato sinistro della variabile per restituire il valore di tale variabile. Quando si chiama la funzione updateName, non si invia il valore ma l'indirizzo di memoria della variabile. Il simbolo & a sinistra della variabile indica l'indirizzo della variabile.