Как написать переключатели в Go

Вступление

Conditional statements дает программистам возможность направлять свои программы на выполнение определенных действий, если условие истинно, и других действий, если условие ложно. Часто мы хотим сравнить несколькоvariable с несколькими возможными значениями, предпринимая разные действия в каждом случае. Это можно сделать, используя толькоif statements. Написание программного обеспечения, однако, заключается не только в том, чтобы заставить вещи работать, но и в том, чтобы сообщить о своем намерении будущему себе и другим разработчикам. switch - альтернативный условный оператор, полезный для сообщения действий, предпринимаемых вашими программами Go, когда они представлены с различными параметрами.

Все, что мы можем написать с помощью оператора switch, также можно записать с помощью операторовif. В этом руководстве мы рассмотрим несколько примеров того, что может делать оператор switch, операторыif, которые он заменяет, и где его наиболее целесообразно применять.

Структура операторов переключателя

Переключатель обычно используется для описания действий, выполняемых программой, когда переменной назначаются определенные значения. В следующем примере показано, как это сделать с помощью операторовif:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        if flav == "strawberry" {
            fmt.Println(flav, "is my favorite!")
            continue
        }

        if flav == "vanilla" {
            fmt.Println(flav, "is great!")
            continue
        }

        if flav == "chocolate" {
            fmt.Println(flav, "is great!")
            continue
        }

        fmt.Println("I've never tried", flav, "before")
    }
}

Это сгенерирует следующий вывод:

Outputchocolate is great!
vanilla is great!
strawberry is my favorite!
I've never tried banana before

В пределахmain мы определяемslice вкусов мороженого. Затем мы используемfor loop для их обхода. Мы используем три оператораif, чтобы распечатать разные сообщения, указывающие на предпочтения разных вкусов мороженого. Каждый операторif должен использовать операторcontinue, чтобы остановить выполнение циклаfor, чтобы сообщение по умолчанию в конце не печаталось для предпочтительных вкусов мороженого.

Поскольку мы добавляем новые настройки мороженого, мы должны продолжать добавлять операторыif для обработки новых случаев. Дублированные сообщения, как в случае"vanilla" и"chocolate", должны иметь повторяющиеся операторыif. Для будущих читателей нашего кода (включая нас самих) повторяющийся характер операторовif скрывает важную часть того, что они делают - сравнения переменной с несколькими значениями и выполнения различных действий. Кроме того, наше резервное сообщение отделено от условных, что делает его не связанным. Операторswitch может помочь нам лучше организовать эту логику.

Операторswitch начинается с ключевого словаswitch и сопровождается в его самой простой форме некоторой переменной, с которой выполняется сравнение. За ним следует пара фигурных скобок ({}), где может отображаться несколькоcase clauses. Предложения case описывают действия, которые ваша программа Go должна выполнять, когда переменная, предоставленная оператору switch, равна значению, указанному в предложении case. В следующем примере предыдущий пример преобразуется для использованияswitch вместо нескольких операторовif:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        switch flav {
        case "strawberry":
            fmt.Println(flav, "is my favorite!")
        case "vanilla", "chocolate":
            fmt.Println(flav, "is great!")
        default:
            fmt.Println("I've never tried", flav, "before")
        }
    }
}

Вывод такой же, как и раньше:

Outputchocolate is great!
vanilla is great!
strawberry is my favorite!
I've never tried banana before

Мы снова определили вкус мороженого вmain и использовали операторrange для перебора каждого вкуса. Однако на этот раз мы использовали операторswitch, который исследует переменнуюflav. Мы используем два предложенияcase для обозначения предпочтений. Нам больше не нужны операторыcontinue, так как только одно предложениеcase будет выполнено операторомswitch. Мы также можем комбинировать повторяющуюся логику условных выражений"chocolate" и"vanilla", разделяя их запятыми в объявлении предложенияcase. Предложениеdefault служит нашим всеобъемлющим предложением. Он будет работать с любыми вариантами, которые мы не учли в теле оператораswitch. В этом случае"banana" вызовет выполнениеdefault, напечатав сообщениеI've never tried banana before.

Эта упрощенная форма операторовswitch предназначена для их наиболее частого использования: сравнения переменной с несколькими альтернативами. Это также обеспечивает удобство, когда мы хотим выполнить одно и то же действие для нескольких разных значений и некоторые другие действия, когда ни одно из перечисленных условий не выполняется, используя предоставленное ключевое словоdefault.

Когда эта упрощенная формаswitch оказывается слишком ограничивающей, мы можем использовать более общую форму оператораswitch.

Общие положения переключателя

Операторыswitch полезны для группировки наборов более сложных условных выражений, чтобы показать, что они каким-то образом связаны. Это чаще всего используется при сравнении некоторой переменной с диапазоном значений, а не с конкретными значениями, как в предыдущем примере. В следующем примере реализуется игра в угадайку с использованием операторовif, которые могут выиграть от оператораswitch:

package main

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

func main() {
    rand.Seed(time.Now().UnixNano())
    target := rand.Intn(100)

    for {
        var guess int
        fmt.Print("Enter a guess: ")
        _, err := fmt.Scanf("%d", &guess)
        if err != nil {
            fmt.Println("Invalid guess: err:", err)
            continue
        }

        if guess > target {
            fmt.Println("Too high!")
            continue
        }

        if guess < target {
            fmt.Println("Too low!")
            continue
        }

        fmt.Println("You win!")
        break
    }
}

Вывод будет зависеть от выбранного случайного числа и от того, насколько хорошо вы играете в игру. Вот вывод из одного примера сессии:

OutputEnter a guess: 10
Too low!
Enter a guess: 15
Too low!
Enter a guess: 18
Too high!
Enter a guess: 17
You win!

В нашей игре в угадывание нужно случайное число для сравнения предположений, поэтому мы используем функциюrand.Intn из пакетаmath/rand. Чтобы гарантировать получение разных значений дляtarget каждый раз, когда мы играем в игру, мы используемrand.Seed для рандомизации генератора случайных чисел на основе текущего времени. Аргумент от100 доrand.Intn даст нам число в диапазоне 0–100. Затем мы используем циклfor, чтобы начать сбор предположений от игрока.

Функцияfmt.Scanf дает нам возможность читать вводимые пользователем данные в выбранную нами переменную. Он принимает глагол форматной строки, который преобразует введенные пользователем данные в ожидаемый нами тип. %d здесь означает, что мы ожидаемint, и мы передаем адрес переменнойguess, чтобыfmt.Scanf мог установить эту переменную. Послеhandling any parsing errors мы затем используем два оператораif, чтобы сравнить предположение пользователя со значениемtarget. string, которые они возвращают, вместе сbool, контролируют сообщение, отображаемое игроку, и то, будет ли игра завершена.

Эти операторыif скрывают тот факт, что все диапазоны значений, с которыми сравнивается переменная, каким-то образом связаны. С первого взгляда также может быть сложно определить, пропустили ли мы какую-то часть диапазона. В следующем примере выполняется рефакторинг предыдущего примера для использования вместо этого оператораswitch:

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    target := rand.Intn(100)

    for {
        var guess int
        fmt.Print("Enter a guess: ")
        _, err := fmt.Scanf("%d", &guess)
        if err != nil {
            fmt.Println("Invalid guess: err:", err)
            continue
        }

        switch {
        case guess > target:
            fmt.Println("Too high!")
        case guess < target:
            fmt.Println("Too low!")
        default:
            fmt.Println("You win!")
            return
        }
    }
}

Это сгенерирует вывод, подобный следующему:

OutputEnter a guess: 25
Too low!
Enter a guess: 28
Too high!
Enter a guess: 27
You win!

В этой версии игры в угадывание мы заменили блок операторовif на операторswitch. Мы опускаем аргумент выражения дляswitch, потому что нас интересует только использованиеswitch для сбора условных выражений вместе. Каждое предложениеcase содержит другое выражение, сравнивающееguess сtarget. Как и в первый раз, когда мы заменили операторыif наswitch, нам больше не нужны операторыcontinue, поскольку будет выполнено только одно предложениеcase. Наконец, предложениеdefault обрабатывает случай, когдаguess == target, поскольку мы охватили все другие возможные значения двумя другими предложениямиcase.

В примерах, которые мы видели до сих пор, будет выполнен ровно один оператор case. Иногда вам может потребоваться объединить поведение нескольких предложенийcase. Операторыswitch предоставляют еще одно ключевое слово для достижения такого поведения.

Провалиться

Иногда вам может понадобиться повторно использовать код, содержащийся в другом предложенииcase. В этих случаях можно попросить Go запустить тело следующего предложенияcase, указанное с помощью ключевого словаfallthrough. Этот следующий пример изменяет наш предыдущий пример вкуса мороженого, чтобы более точно отражать наш энтузиазм по поводу клубничного мороженого:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        switch flav {
        case "strawberry":
            fmt.Println(flav, "is my favorite!")
            fallthrough
        case "vanilla", "chocolate":
            fmt.Println(flav, "is great!")
        default:
            fmt.Println("I've never tried", flav, "before")
        }
    }
}

Мы увидим этот вывод:

Outputchocolate is great!
vanilla is great!
strawberry is my favorite!
strawberry is great!
I've never tried banana before

Как мы видели ранее, мы определяем срезstring для представления ароматов и перебираем его, используя циклfor. Выражениеswitch здесь идентично тому, которое мы видели ранее, но с добавлением ключевого словаfallthrough в конце предложенияcase для"strawberry". Это заставит Go запустить телоcase "strawberry":, сначала распечатав строкуstrawberry is my favorite!. Когда он встречаетfallthrough, он запускает тело следующего предложенияcase. Это вызовет выполнение телаcase "vanilla", "chocolate": с выводомstrawberry is great!.

Ключевое словоfallthrough нечасто используется разработчиками Go. Обычно повторное использование кода, реализованное с использованиемfallthrough, может быть лучше получено путем определения функции с общим кодом. По этим причинам использованиеfallthrough обычно не рекомендуется.

Заключение

Операторыswitch помогают нам донести до других разработчиков, читающих наш код, что набор сравнений каким-то образом связан друг с другом. Они значительно упрощают добавление другого поведения при добавлении нового дела в будущем и позволяют гарантировать, что все, что мы забыли, также обрабатывается правильно с помощью предложенийdefault. В следующий раз, когда вы обнаружите, что пишете несколько операторовif, которые все включают одну и ту же переменную, попробуйте переписать его с помощью оператораswitch - вам будет легче переделать, когда придет время рассмотреть другую альтернативу. ценность.

Если вы хотите узнать больше о языке программирования Go, ознакомьтесь сHow To Code in Go series целиком.