Una funzione ti consente di raggruppare il codice in un'unità riutilizzabile.
È composta dalla parola chiave func, dal nome della funzione e da un elenco di zero o più parametri e tipi separati da virgole, racchiusi tra parentesi tonde.
Tutti i parametri devono avere un tipo esplicito; non c'è inferenza di tipo per i parametri. Non esistono valori predefiniti per i parametri, quindi tutti i parametri di una funzione sono obbligatori.
import "fmt"
// No parameters
func PrintHello() {
fmt.Println("Hello")
}
// Two parameters
func PrintGreetingName(greeting string, name string) {
fmt.Println(greeting + " " + name)
}
I parametri dello stesso tipo possono essere dichiarati insieme, seguiti da un'unica dichiarazione di tipo.
import "fmt"
func PrintGreetingName(greeting, name string) {
fmt.Println(greeting + " " + name)
}
Vediamo rapidamente due termini che vengono spesso confusi tra loro: parameters e arguments.
I parametri di una funzione sono i nomi definiti nella firma della funzione, come greeting e name nella funzione PrintGreetingName qui sopra.
Gli argomenti di una funzione sono i valori concreti passati ai parametri della funzione quando chiamiamo la funzione.
Per esempio, nell'esempio sotto, "Hello" e "Katrina" sono gli argomenti passati ai parametri greeting e name:
PrintGreetingName("Hello", "Katrina")
I parametri della funzione sono seguiti da zero o più valori restituiti, che devono anch'essi avere un tipo esplicito.
I singoli valori restituiti sono lasciati senza parentesi, mentre più valori restituiti sono racchiusi tra parentesi.
I valori vengono restituiti al codice chiamante dalle funzioni usando la parola chiave return.
In una funzione possono esserci più istruzioni return.
L'esecuzione della funzione termina non appena incontra una di queste istruzioni return.
Se una funzione deve restituire più valori, questi sono separati da virgole.
func Hello(name string) string {
return "Hello " + name
}
func HelloAndGoodbye(name string) (string, string) {
return "Hello " + name, "Goodbye " + name
}
Chiamare una funzione significa specificare il nome della funzione e passare gli argomenti per ciascuno dei parametri della funzione tra parentesi.
import "fmt"
// No parameters, no return value
func PrintHello() {
fmt.Println("Hello")
}
// Called like this:
PrintHello()
// One parameter, one return value
func Hello(name string) string {
return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")
// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)
Oltre ai parametri, anche i valori restituiti possono avere un nome opzionale.
Se si usano valori restituiti con nome, un'istruzione return senza argomenti restituirà quei valori.
Questo è noto come return «nudo».
func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
sum, mult = a+b, a*b
sum -= c
mult -= c
return
}
È anche importante chiarire il concetto di passaggio per valore e passaggio per riferimento.
Per prima cosa, chiariamo il passaggio per valore.
Nell'esempio sotto, quando passiamo la variabile val alla funzione MultiplyByTwo, passiamo una copia di val.
Per questo motivo, newVal ha il valore aggiornato 4, ma la variabile originale val è ancora 2.
Dietro le quinte, Go crea essenzialmente una copia del valore originale, così solo questa copia, cioè v, viene modificata dalla funzione.
val := 2
func MultiplyByTwo(v int) int {
v = v * 2
return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function
A rigor di termini, in Go tutti gli argomenti sono passati per valore, cioè viene fatta una copia del valore o dei dati forniti alla funzione.
Ma se non vuoi fare una copia dei dati passati a una funzione e vuoi modificare i dati all'interno della funzione, allora dovresti usare pointers come argomenti, cioè il passaggio per riferimento.
Usiamo un pointer per ottenere il passaggio per riferimento.
Passando argomenti pointer a una funzione, possiamo modificare i dati sottostanti passati alla funzione invece di operare solo su una copia dei dati.
Per ora, è sufficiente sapere che i tipi puntatore si riconoscono dal * davanti al tipo nella firma della funzione.
func HandlePointers(x, y *int) {
// Some logic to handle integer values referenced by pointers x and y
}
Se il concetto di pointer ti confonde, non preoccuparti.
Abbiamo una sezione dedicata più avanti nel programma per aiutarti a padroneggiare i puntatori.
Nota che slices e maps sono eccezioni alla regola sopra.
Quando passiamo uno slice o una map come argomenti a una funzione, vengono trattati come tipi puntatore anche se non c'è un * esplicito nel tipo.
Questo significa che se passiamo uno slice o una map a una funzione e ne modifichiamo i dati sottostanti, le modifiche si rifletteranno sullo slice o sulla map originali.
In questo esercizio scriverai altro codice per preparare e cucinare la tua fantastica lasagna, tratta dal tuo libro di ricette preferito.
Hai quattro compiti. Il primo riguarda la cottura vera e propria, gli altri tre la preparazione perfetta.
Per la prossima lasagna che preparerai, vuoi assicurarti di avere abbastanza tempo a disposizione per poterti godere la cottura. Hai già deciso quali strati avrà la tua lasagna. Ora vuoi stimare quanto tempo richiederà la preparazione in base a questo.
Implementa una funzione PreparationTime che accetta una slice di strati come []string e il tempo medio di preparazione per strato in minuti come int.
La funzione deve restituire la stima del tempo totale di preparazione in base al numero di strati, come int.
Go non ha valori predefiniti per le funzioni.
Se il tempo medio di preparazione viene passato come 0 (il valore iniziale predefinito per un int), allora si deve usare il valore predefinito 2.
layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12
Oltre a riservare il tempo, vuoi anche assicurarti di avere abbastanza sauce e noodles per cucinare la lasagna dei tuoi sogni. Per ogni strato di noodles della tua lasagna ti serviranno 50 grammi di noodles. Per ogni strato di sauce della tua lasagna ti serviranno 0,2 litri di sauce.
Definisci la funzione Quantities che accetta come parametro una slice di strati come []string.
La funzione dovrà poi determinare la quantità di noodles e sauce necessaria per preparare il pasto.
Il risultato deve essere restituito come due valori: noodles come int e sauce come float64.
Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4
Tempo fa hai fatto visita a un amico e hai mangiato la lasagna a casa sua. Era fantastica e aveva qualcosa di speciale. L'amico ti ha mandato la lista degli ingredienti e ti ha detto che l'ultimo elemento della lista è l'«ingrediente segreto» che ha reso il pasto così speciale. Ora vuoi aggiungere anche tu quell'ingrediente segreto alla tua ricetta.
Scrivi una funzione AddSecretIngredient che accetta come parametri due slice di ingredienti di tipo []string.
Il primo parametro è la lista che ti ha mandato il tuo amico, il secondo è la lista degli ingredienti della tua ricetta.
L'ultimo elemento della tua lista di ingredienti è sempre "?".
La funzione deve sostituirlo con l'ultimo elemento della lista del tuo amico.
Nota: AddSecretIngredient non restituisce nulla: devi modificare direttamente la lista dei tuoi ingredienti.
La lista con gli ingredienti del tuo amico non deve essere modificata.
Inoltre, dato che la slice viene passata a una funzione come puntatore, le modifiche ai due argomenti []string passati ad AddSecretIngredient verranno applicate direttamente.
friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}
AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}
Le quantità elencate nel tuo libro di ricette bastano per una lasagna di due porzioni soltanto. Dato che la prossima volta vuoi cucinare per più persone, vuoi calcolare le quantità per numeri diversi di porzioni.
Implementa una funzione ScaleRecipe che accetta due parametri.
float64 necessarie per 2 porzioni.La funzione deve restituire una slice di float64 con le quantità necessarie per il numero di porzioni desiderato.
Vuoi però mantenere la ricetta originale.
Questo significa che l'argomento quantities non deve essere modificato in questa funzione.
quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
Iscriviti a Exercism per imparare e padroneggiare Go con 34 concetti166 esercizi e il mentoring di persone reali, tutto gratis.