Использование условного оператора switch в Go

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

Программа сгенерирует такой вывод:

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

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

Читайте также: Основные типы данных в Go

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

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

Программа выведет:

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

Здесь в main снова определяется срез вкусов мороженого, а оператор range выполняет итерацию через него. Но на этот раз мы использовали оператор switch, который будет проверять переменную flav. В коде есть два предложения case, которые указывают предпочтения во вкусах мороженого. Нам больше не нужны операторы continue, поскольку оператор switch будет выполнять только одно предложение case. Мы также можем объединить дублированную логику условных выражений «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
}
}

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

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. Строка, которую они возвращают (вместе с bool) управляет сообщениями, отображаемыми игроку, и ходом игры.

Читайте также: Обработка ошибок в Go

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

Эта программа выведет такой результат:

Enter 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 предоставляют другое ключевое слово для достижения этой цели.

Ключевое слово fallthrough

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

Мы увидим такой результат:

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

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

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

Заключение

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

Tags: ,