JavaRush /Курсы /Go SELF /switch в Go: case/default и switch { ... }

switch в Go: case/default и switch { ... }

Go SELF
3 уровень , 4 лекция
Открыта

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 ветки (да/нет)
if/else
3+ вариантов по одному значению (op, role, status)
switch value
Диапазоны и правила сверху вниз (оценки, категории)
switch {}
или
else if
Хотите обязательный “прочие случаи”
default
в
switch
или
else
в
if

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 или заранее вычислить булевы переменные с говорящими именами, а затем уже выбирать.

1
Задача
Go SELF, 3 уровень, 4 лекция
Недоступна
Контроль прохода
Контроль прохода
1
Задача
Go SELF, 3 уровень, 4 лекция
Недоступна
Команда калькулятора
Команда калькулятора
1
Задача
Go SELF, 3 уровень, 4 лекция
Недоступна
Скидка магазина
Скидка магазина
1
Задача
Go SELF, 3 уровень, 4 лекция
Недоступна
Маршрут посылки
Маршрут посылки
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ