1. Зачем нужен switch, если уже есть if/else
Когда вы только начинаете писать условия, if/else кажется универсальным молотком: им действительно можно “прибить” почти любую развилку. Но как только вариантов становится больше двух‑трёх, код начинает напоминать лестницу в подъезде без света: вроде можно пройти, но хочется держаться за стену. switch в Go — это способ сделать “выбор из вариантов” более компактным, читабельным и честным.
switch особенно хорошо подходит, когда у вас есть некое значение (например, команда "add" или номер дня недели), и вы хотите выполнить разную логику в зависимости от этого значения. Вместо длинной цепочки else if вы пишете единый блок, где все варианты видны сразу.
Сравните “лесенку”:
package main
import "fmt"
func main() {
op := "*"
if op == "+" {
fmt.Println("сложение")
} else if op == "-" {
fmt.Println("вычитание")
} else if op == "*" {
fmt.Println("умножение")
} else {
fmt.Println("неизвестная операция")
}
}
И ту же идею через switch:
package main
import "fmt"
func main() {
op := "*"
switch op {
case "+":
fmt.Println("сложение")
case "-":
fmt.Println("вычитание")
case "*":
fmt.Println("умножение")
default:
fmt.Println("неизвестная операция")
}
}
Читается проще: “переключаемся по op, вот варианты, вот default”.
2. Базовая форма: switch x { case ... }
С точки зрения синтаксиса switch в Go выглядит пугающе только первые десять секунд, пока мозг не распознает знакомые фигурные скобки. В самой простой форме у вас есть выражение после switch (например, op), затем блок, где идут case и необязательный default. Каждый case — это “если значение совпало, выполняем этот блок”.
Важно держать в голове модель: Go берёт выражение после switch, сравнивает его с вариантами в case, выполняет первый подходящий case и выходит из switch. Никаких скрытых прыжков по умолчанию вниз “как в некоторых других языках” тут нет (про это — ниже, в части про fallthrough).
Мини‑пример со строкой:
package main
import "fmt"
func main() {
role := "admin"
switch role {
case "admin":
fmt.Println("полный доступ")
case "user":
fmt.Println("обычный доступ")
default:
fmt.Println("гость")
}
}
Мини‑пример с числами:
package main
import "fmt"
func main() {
day := 6
switch day {
case 1:
fmt.Println("понедельник")
case 6:
fmt.Println("суббота")
default:
fmt.Println("какой-то другой день")
}
}
В этих примерах switch проверяет равенство. То есть по смыслу это “красивый набор if x == value”.
3. case и default: порядок важен
Когда вы пишете switch, легко начать воспринимать его как “таблицу соответствий”. В целом это правда, но есть важный нюанс: варианты проверяются сверху вниз, и сработает первый подходящий case. Для switch x (где сравнение на равенство) это редко даёт неожиданные эффекты, потому что обычно значения разные. Но привычку “ставить более специфичное выше” полезно тренировать уже сейчас — она пригодится в форме switch { case cond: ... }.
default — это ветка “на всякий случай”. Она выполнится, если ни один case не подошёл. Формально default не обязателен, но практически он часто делает программу более дружелюбной: вместо молчания или странного поведения вы явно сообщаете “я не знаю такой вариант”.
Небольшой пример “команда не распознана”:
package main
import "fmt"
func main() {
cmd := "dance"
switch cmd {
case "add":
fmt.Println("режим сложения")
case "mul":
fmt.Println("режим умножения")
default:
fmt.Println("неизвестная команда:", cmd)
}
}
4. Несколько значений в одном case
Иногда у вас есть разные значения, которые должны вести себя одинаково. Например, операции "+" и "add" — по сути одно и то же: “сложить”. Или дни недели 6 и 7 — “выходные”. В таких случаях switch позволяет перечислить несколько значений через запятую в одном case.
Это полезно по двум причинам: (1) вы не дублируете один и тот же код; (2) вы прямо в коде фиксируете “эти значения — одна группа”, и читателю не нужно это угадывать.
Пример с операциями:
package main
import "fmt"
func main() {
op := "+"
switch op {
case "+", "add":
fmt.Println("будем складывать")
case "-", "sub":
fmt.Println("будем вычитать")
default:
fmt.Println("операция не поддерживается")
}
}
Пример с выходными:
package main
import "fmt"
func main() {
day := 7
switch day {
case 6, 7:
fmt.Println("выходной")
default:
fmt.Println("будний день")
}
}
Технически это читается так: “если day равен 6 или 7”.
5. Почему в Go нет fallthrough по умолчанию
Если вы когда‑нибудь видели switch в C/C++/Java, вы могли помнить странное поведение: если не написать break, выполнение “проваливается” в следующий case. Это часто порождает баги: человек добавил новый case, забыл break, и всё поехало. Go решил: “давайте не будем делать сюрпризы по умолчанию”. Поэтому в Go switch выполняет ровно один case и выходит.
Ключевое слово fallthrough в Go существует, но мы его сознательно не используем в рамках базового курса: оно редко нужно новичку и чаще мешает читаемости, чем помогает. Гораздо важнее запомнить простое правило: “один case — один вход — один выход”.
Проверим мысленно такой код:
package main
import "fmt"
func main() {
x := 1
switch x {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}
}
При x == 1 будет выведено только "one", и программа пойдёт дальше, не печатая "two". Это и есть нормальное, “безопасное по умолчанию” поведение.
Кстати, switch очень часто применяют в реальном Go‑коде для “человекочитаемых” представлений значений: например, в методе String() у своего типа делают switch и возвращают строки для каждого варианта, а если вариант неизвестный — возвращают запасной текст. Часто там есть fallback через return fmt.Sprintf(...).
6. switch { case cond: ... } вместо длинного else if
Иногда вам нужно выбирать не по равенству значения, а по диапазонам или условиям. Типичный пример — оценка по баллам: “если >= 90 — A, если >= 75 — B, иначе — C”. Конечно, это можно сделать через else if. Но в Go есть очень приятная форма: switch без выражения.
В таком switch каждое case — это булево условие. По сути, switch { ... } означает: “выполняй первый case, который даст true”.
Пример с оценками:
package main
import "fmt"
func main() {
score := 82
switch {
case score >= 90:
fmt.Println("A")
case score >= 75:
fmt.Println("B")
default:
fmt.Println("C")
}
}
Обратите внимание на порядок. Если вы поставите case score >= 75 выше, то при score == 92 вы получите "B" (потому что первое истинное условие сработало). То есть здесь порядок — это смысл.
Ещё один пример, ближе к “валидации”: проверим число на знак и чётность:
package main
import "fmt"
func main() {
n := -4
switch {
case n > 0 && n%2 == 0:
fmt.Println("положительное чётное")
case n > 0:
fmt.Println("положительное нечётное")
default:
fmt.Println("не положительное")
}
}
Да, это можно было бы написать через if, но switch {} иногда даёт более ровную структуру: “набор взаимоисключающих правил”.
Мини-схема: как “думает” switch
Прежде чем мы пойдём дальше, полезно на секунду формализовать, что именно происходит при выполнении switch. Когда вы отлаживаете программу, мозгу проще, если у него есть “алгоритм в голове”, а не ощущение “ну там магия”.
flowchart TD
A[Старт switch] --> B{Есть выражение после switch?}
B -- да --> C[Вычислить значение X]
B -- нет --> D[Считаем, что X = true]
C --> E[Проверяем case сверху вниз]
D --> E
E --> F{Нашёлся первый подходящий case?}
F -- да --> G[Выполнить его блок]
F -- нет --> H{Есть default?}
H -- да --> I[Выполнить default]
H -- нет --> J[Ничего не делать]
G --> K[Выйти из switch]
I --> K
J --> K
Эта схема одинаково подходит и для switch x { ... }, и для switch { case cond: ... } (там просто X условно равен true, а case-ы дают true/false).
7. Мини-приложение: “CalcBox” на switch
Сейчас сделаем маленький шаг в сторону прикладного кода: добавим switch в консольную утилиту. Мы не строим полноценное меню (для этого обычно нужен цикл), но даже один запуск программы может быть удобным: пользователь вводит команду и два числа, а программа выбирает, что делать. Это очень реалистичный паттерн: одна команда — одно действие — один результат.
Договоримся о формате ввода: сначала команда (op), потом два целых числа (a, b). Команда может быть "add", "sub", "mul", "div", "mod". Деление будет целочисленное, потому что пока мы работаем с int.
Базовая заготовка чтения:
package main
import "fmt"
func main() {
var op string
var a, b int
fmt.Scan(&op, &a, &b)
fmt.Println("op =", op, "a =", a, "b =", b) // пример отладочной печати
}
Теперь добавим switch и вычисление результата. Сделаем так: заведём переменную result, а в default скажем, что команда неизвестна.
package main
import "fmt"
func main() {
var op string
var a, b int
fmt.Scan(&op, &a, &b)
result := 0
switch op {
case "add":
result = a + b
case "sub":
result = a - b
default:
fmt.Println("unknown op")
return
}
fmt.Println(result)
}
Здесь важный момент: мы использовали return, чтобы “выйти из программы”, если команда неизвестна. Это нормально и читаемо: если дальше продолжать, то у нас будет напечатан result, который останется 0, и пользователь подумает, что 0 — это настоящий ответ.
Теперь расширим набор операций и покажем пример “несколько значений в case”: пусть "+" будет означать то же, что "add".
package main
import "fmt"
func main() {
var op string
var a, b int
fmt.Scan(&op, &a, &b)
switch op {
case "add", "+":
fmt.Println(a + b)
case "sub", "-":
fmt.Println(a - b)
case "mul", "*":
fmt.Println(a * b)
default:
fmt.Println("unknown op")
}
}
Обратите внимание: этот вариант даже без result выглядит нормально, потому что в каждой ветке мы сразу печатаем результат. Это вопрос стиля: иногда удобнее вычислить и потом печатать, иногда — печатать прямо в ветке. Главное, чтобы код оставался прозрачным.
Осталась потенциально “опасная” операция: деление. Тут есть риск деления на ноль. У нас уже есть if и логика, поэтому внутри case можно поставить маленькую проверку.
package main
import "fmt"
func main() {
var op string
var a, b int
fmt.Scan(&op, &a, &b)
switch op {
case "div", "/":
if b == 0 {
fmt.Println("division by zero")
return // завершить работу функции main()
}
fmt.Println(a / b)
default:
fmt.Println("unsupported")
}
}
Да, внутри case можно писать любой код: хоть if, хоть несколько строк вычислений. switch не ограничивает “тело” ветки.
8. Когда switch лучше, чем if/else
После нескольких примеров легко впасть в крайность: “о, switch красивый, давайте везде использовать switch”. Это нормальная стадия. Но лучше держать пару правил здравого смысла.
Если у вас два варианта, то if/else обычно проще. Он короче и не заставляет читателя “переключаться в режим switch”. Если у вас много вариантов по равенству одному значению, то switch x обычно выигрывает по читабельности. Если у вас набор диапазонов или условий, то switch {} и else if примерно равны, и выбор часто зависит от того, что лучше читается в конкретной задаче.
Чтобы было проще ориентироваться, вот компактная табличка (не как закон, а как подсказка):
| Ситуация | Обычно удобнее |
|---|---|
| 2 ветки (да/нет) | |
| 3+ вариантов по одному значению (op, role, status) | |
| Диапазоны и правила сверху вниз (оценки, категории) | или |
| Хотите обязательный “прочие случаи” | в или в |
9. Типичные ошибки при работе со switch
Ошибка №1: ожидать “проваливание” в следующий case как поведение по умолчанию.
Люди иногда приходят из C‑подобных языков и подсознательно думают: “сейчас выполнится этот case, а потом пойдёт дальше”. В Go так не работает: выполнилась одна ветка — switch закончился. Если вы рассчитывали на двойное выполнение, логика программы будет неполной, и баг будет выглядеть как “почему не печатает вторую строку”.
Ошибка №2: забыть обработать неизвестные значения (default), а потом удивляться “тишине”.
default не обязателен, и компилятор не ругается. Но пользователь вашей программы может ввести неожиданный вариант, и программа просто ничего не сделает. В консольных утилитах это особенно раздражает: человек ввёл команду — а в ответ тишина.
Ошибка №3: перепутать порядок условий в switch { case cond: ... }.
В switch без выражения порядок — это смысл. Если поставить более “широкое” условие раньше (например, score >= 60 раньше, чем score >= 90), то “высокие” случаи никогда не сработают. Это типичный баг, который выглядит логично, пока не проговоришь: “выполняется первый true”.
Ошибка №4: дублировать одинаковые ветки вместо объединения значений в одном case.
Когда у вас "add" и "+" делают одно и то же, хочется написать два case подряд с одинаковым кодом. Это работает, но увеличивает шанс ошибки при правках: вы обновите один case и забудете второй. Гораздо аккуратнее перечислить значения через запятую в одном case.
Ошибка №5: использовать switch, где нужен алгоритм, а не выбор из вариантов.
Иногда новички пытаются через switch выразить сложную логику, особенно когда условия частично пересекаются. В результате получается “комбинаторный взрыв”: множество case, которые сложно проверить. В таких ситуациях часто проще разложить задачу на несколько if или заранее вычислить булевы переменные с говорящими именами, а затем уже выбирать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ