Flusso di controllo con istruzioni switch

Completato

Analogamente ad altri linguaggi di programmazione, Go supporta le istruzioni switch. Le istruzioni switch vengono usate per evitare il concatenamento di più istruzioni if. Usando le istruzioni switch si evitano le difficoltà che derivano dalla manutenzione e dalla lettura di codice che include molte istruzioni if. Con queste istruzioni è anche più semplice costruire condizioni complesse. Le istruzioni switch verranno presentate nelle sezioni seguenti.

Sintassi di base per switch

Analogamente all'istruzione if, la condizione switch non richiede le parentesi. Nella sua forma più semplice, un'istruzione switch ha un aspetto simile al seguente:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    sec := time.Now().Unix()
    rand.Seed(sec)
    i := rand.Int31n(10)

    switch i {
    case 0:
        fmt.Print("zero...")
    case 1:
        fmt.Print("one...")
    case 2:
        fmt.Print("two...")
    }

    fmt.Println("ok")
}

Se il codice precedente viene eseguito più volte, verrà visualizzato un output diverso ogni volta. (Ma se si esegue il codice in Go Playground, si otterrà lo stesso risultato ogni volta. Questa è una delle limitazioni del servizio).

Go confronta ogni caso dell'istruzione switch fino a quando non viene trovata una corrispondenza per la condizione. Si noti tuttavia che il codice precedente non copre tutti i possibili casi dei valori della variabile num. Se num finisce per essere 5, l'output del programma è ok.

È anche possibile essere più specifici in merito al caso d'uso predefinito e includerlo come segue:

switch i {
case 0:
    fmt.Print("zero...")
case 1:
    fmt.Print("one...")
case 2:
    fmt.Print("two...")
default:
    fmt.Print("no match...")
}

Si noti che, nel caso default non si scrive un'espressione di convalida. Il valore della variabile i viene convalidato rispetto alle istruzioni case e l'istruzione case default gestisce tutti i valori non convalidati.

Usare più espressioni

Occasionalmente, più di un'espressione corrisponde a una sola istruzione case. In Go, se si vuole che un'istruzione case includa più di un'espressione, separare le espressioni usando le virgole (,). Questa tecnica consente di evitare codice duplicato.

L'esempio di codice seguente mostra come includere più espressioni.

package main

import "fmt"

func location(city string) (string, string) {
    var region string
    var continent string
    switch city {
    case "Delhi", "Hyderabad", "Mumbai", "Chennai", "Kochi":
        region, continent = "India", "Asia"
    case "Lafayette", "Louisville", "Boulder":
        region, continent = "Colorado", "USA"
    case "Irvine", "Los Angeles", "San Diego":
        region, continent = "California", "USA"
    default:
        region, continent = "Unknown", "Unknown"
    }
    return region, continent
}
func main() {
    region, continent := location("Irvine")
    fmt.Printf("John works in %s, %s\n", region, continent)
}

Si noti che i valori inclusi nelle espressioni per l'istruzione case corrispondono al tipo di dati della variabile convalidata dall'istruzione switch. Se si include un valore intero come nuova istruzione case, il programma non verrà compilato.

Richiamare una funzione

Con switch è anche possibile richiamare una funzione. Da questa funzione è possibile scrivere istruzioni case per i valori restituiti possibili. Ad esempio, il codice seguente chiama la funzione time.Now(). L'output stampato dipende dal giorno della settimana corrente.

package main

import (
    "fmt"
    "time"
)

func main() {
    switch time.Now().Weekday().String() {
    case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
        fmt.Println("It's time to learn some Go.")
    default:
        fmt.Println("It's the weekend, time to rest!")
    }

    fmt.Println(time.Now().Weekday().String())
}

Quando si chiama una funzione da un'istruzione switch, è possibile modificarne la logica senza modificare l'espressione perché viene sempre convalidato il risultato restituito dalla funzione.

È anche possibile chiamare una funzione da un'istruzione case. Usare questa tecnica, ad esempio, per trovare la corrispondenza con un modello specifico usando un'espressione regolare. Ecco un esempio:

package main

import "fmt"

import "regexp"

func main() {
    var email = regexp.MustCompile(`^[^@]+@[^@.]+\.[^@.]+`)
    var phone = regexp.MustCompile(`^[(]?[0-9][0-9][0-9][). \-]*[0-9][0-9][0-9][.\-]?[0-9][0-9][0-9][0-9]`)

    contact := "foo@bar.com"

    switch {
    case email.MatchString(contact):
        fmt.Println(contact, "is an email")
    case phone.MatchString(contact):
        fmt.Println(contact, "is a phone number")
    default:
        fmt.Println(contact, "is not recognized")
    }
}

Si noti che il blocco switch non include un'espressione di convalida. Questo concetto verrà approfondito nella sezione successiva.

Omettere una condizione

In Go è possibile omettere una condizione in un'istruzione switch come in un'istruzione if. Questo modello equivale a confrontare un valore true come se si forzasse l'esecuzione dell'istruzione switch all'infinito.

Di seguito è riportato un esempio di come scrivere un'istruzione switch senza una condizione:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().Unix())
    r := rand.Float64()
    switch {
    case r > 0.1:
        fmt.Println("Common case, 90% of the time")
    default:
        fmt.Println("10% of the time")
    }
}

Il programma esegue sempre questo tipo di istruzione switch perché la condizione è sempre true. Un blocco switch condizionale può essere più facile da gestire rispetto a una lunga catena di istruzioni if e else if.

Flusso della logica nel caso successivo

In alcuni linguaggi di programmazione si scrive una parola chiave break alla fine di ogni istruzione case. In Go, invece, quando la logica rientra in un caso, esce dal blocco switch a meno che non la si interrompa in modo esplicito. Per far passare la logica al caso immediatamente successivo, usare la parola chiave fallthrough.

Per comprendere meglio questo modello, vedere l'esempio di codice seguente.

package main

import (
    "fmt"
)

func main() {
    switch num := 15; {
    case num < 50:
        fmt.Printf("%d is less than 50\n", num)
        fallthrough
    case num > 100:
        fmt.Printf("%d is greater than 100\n", num)
        fallthrough
    case num < 200:
        fmt.Printf("%d is less than 200", num)
    }
}

Eseguire il codice e analizzare l'output:

15 is less than 50
15 is greater than 100
15 is less than 200

Si nota qualche errore?

Notare che poiché num è 15 (minore di 50), corrisponde al primo caso. Ma num non è maggiore di 100. Poiché la prima istruzione case contiene la parola chiave fallthrough, la logica passa immediatamente alla successiva istruzione case senza convalidare il caso. È quindi necessario prestare attenzione quando si usa la parola chiave fallthrough. Il comportamento creato da questo codice potrebbe non essere quello desiderato.