Введение

Условные выражения дают программистам возможность предписывать программам производить определенные действия, если условие выполняется, и другое действие, если условие не выполняется. Нам часто нужно сравнивать определенную переменную с разными возможными значениями и выполнять разные действия для разных результатов. Это можно реализовать, используя только операторы if. Однако написание программного обеспечения предусматривает необходимость не только делать так, чтобы все работало, но и показывать свои намерения себе и другим разработчикам. switch — это альтернативный условный оператор, с помощью которого удобно показывать действия программ Go в ситуациях с разными возможными вариантами.

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

Структура оператора switch

Оператор switch обычно используются для описания действий, которые выполняет программа, когда переменная принимает определенные значения. В следующем примере показано, как добиться этого с помощью выражений 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")
    }
}

Результат будет выглядеть следующим образом:

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

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

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

Оператор switch начинается с ключевого слова switch, за которым идет переменная в базовой форме, для которой производится сравнение. Далее идет пара фигурных скобок ({}), в которых могут быть заключены разные варианты. Варианты описывают, какие действия должна выполнять программа Go, если переданная оператору switch переменная имеет значение, указанное в данном варианте. В следующем примере предыдущий пример конвертирован с использованием оператора 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")
        }
    }
}

Результат выглядит так же:

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

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

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

Если упрощенная форма оператора switch слишком узкая, мы можем использовать более общую форму оператора 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
    }
}

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

Output
Enter 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 была возможность задать эту переменную. После обработки всех ошибок парсинга мы используем два оператора if для сравнения догадки пользователя со значением target. Возвращаемая строка вместе с булевым значением определяют, какое сообщение будет выведено игроку, и будет ли закрыта игра.

Эти операторы 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
        }
    }
}

Результат выглядит примерно следующим образом:

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

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

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

Fallthrough

Иногда нам необходимо повторно использовать код, содержащийся в другом выбирающем предложении. В таких случаях можно указать Go запустить тело следующего выбирающего предложения, указанного с помощью ключевого слова 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")
        }
    }
}

Результат будет выглядеть примерно так:

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

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

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

Заключение

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

Если вы хотите узнать больше о языке программирования Go, ознакомьтесь с нашей серией статей о программировании на языке Go.

0 Comments

Creative Commons License