Лекция 3. Операторы и управление потоком выполнения
Цели лекции
После лекции студент должен:
- использовать арифметические, сравнительные и логические операторы;
- писать условия через
if,else ifиelse; - применять короткую инструкцию перед условием;
- создавать циклы всех основных форм с помощью
for; - перебирать строки и коллекции через
range; - использовать выражения и конструкции
switch; - управлять выполнением через
breakиcontinue; - избегать бесконечных циклов и ошибок границ.
1. Арифметические операторы
Основные операции:
| Оператор | Значение | Пример |
|---|---|---|
+ | сложение | a + b |
- | вычитание | a - b |
* | умножение | a * b |
/ | деление | a / b |
% | остаток | a % b |
Пример:
a := 17b := 5
fmt.Println(a + b) // 22fmt.Println(a - b) // 12fmt.Println(a * b) // 85fmt.Println(a / b) // 3fmt.Println(a % b) // 2При делении двух целых чисел результат также целый. Дробная часть отбрасывается:
fmt.Println(10 / 4) // 2fmt.Println(float64(10) / float64(4)) // 2.5Деление целой переменной на ноль вызывает panic во время выполнения. Деление
целой константы на ноль обнаруживается компилятором.
2. Составные присваивания
count := 10count += 5count -= 2count *= 3count /= 2count %= 4Инкремент и декремент существуют только как отдельные инструкции:
count++count--Их нельзя использовать внутри выражения:
// total := count++ // ошибкаcount++total := countПрефиксных форм ++count и --count в Go нет.
3. Операторы сравнения
a == b // равноa != b // не равноa < ba <= ba > ba >= bРезультат сравнения имеет тип bool:
age := 19isAdult := age >= 18fmt.Println(isAdult)Строки сравниваются лексикографически по байтовому представлению UTF-8:
fmt.Println("Go" < "Python") // trueНе все типы можно сравнивать через ==. Например, slices и maps сравнимы только
с nil. Правила сравнения коллекций будут рассмотрены позже.
4. Логические операторы
| Оператор | Значение |
|---|---|
&& | логическое И |
| ` | |
! | отрицание |
age := 20hasTicket := true
canEnter := age >= 18 && hasTicketfmt.Println(canEnter)Операторы && и || используют короткое вычисление:
denominator := 0
if denominator != 0 && 100/denominator > 2 { fmt.Println("Условие выполнено")}Правая часть не вычисляется, если результат уже известен. Поэтому деление на ноль в примере не выполняется.
5. Условие if
Скобки вокруг условия не нужны, фигурные скобки обязательны:
temperature := 18
if temperature < 0 { fmt.Println("Мороз")} else if temperature < 15 { fmt.Println("Прохладно")} else { fmt.Println("Тепло")}Открывающая фигурная скобка находится на той же строке, что и условие. Это требование синтаксиса, а не только стиля.
Сравнение с Python
Python:
if temperature < 0: print("Мороз")elif temperature < 15: print("Прохладно")else: print("Тепло")Go:
if temperature < 0 { fmt.Println("Мороз")} else if temperature < 15 { fmt.Println("Прохладно")} else { fmt.Println("Тепло")}Go не преобразует значение к bool автоматически:
name := "Анна"// if name { } // ошибка
if name != "" { fmt.Println(name)}6. Короткая инструкция в if
Перед условием можно выполнить короткую инструкцию. Она отделяется точкой с запятой:
if length := len("Go"); length > 0 { fmt.Println("Длина:", length)}Переменная length видна во всех ветках этого if, но недоступна после него:
if score := 82; score >= 90 { fmt.Println("Отлично")} else if score >= 75 { fmt.Println("Хорошо")} else { fmt.Println("Нужно повторить тему")}
// fmt.Println(score) // ошибка: score вне области видимостиНаиболее распространённый вариант — получение результата и ошибки:
if value, err := strconv.Atoi("42"); err != nil { fmt.Println("Ошибка:", err)} else { fmt.Println("Число:", value)}7. Тернарного оператора нет
В Go нет выражения condition ? a : b и аналога Python a if condition else b.
Используется обычное условие:
age := 17status := "несовершеннолетний"
if age >= 18 { status = "совершеннолетний"}Такое решение занимает больше строк, но сохраняет один очевидный способ записи условной логики.
8. Единственный цикл for
В Go есть только ключевое слово for, но оно поддерживает несколько форм.
Классический счётчик
for i := 0; i < 5; i++ { fmt.Println(i)}Три части:
i := 0выполняется один раз;i < 5проверяется перед каждой итерацией;i++выполняется после тела.
Аналог while
count := 3
for count > 0 { fmt.Println(count) count--}Бесконечный цикл
for { // цикл продолжается до break, return или завершения программы}Бесконечные циклы используются в серверах, обработчиках очередей и интерактивных программах. У цикла должен быть понятный механизм завершения.
9. range
range перебирает элементы строки или коллекции.
Перебор диапазона целых чисел
В современной версии Go можно перебирать целое число:
for i := range 5 { fmt.Println(i) // 0, 1, 2, 3, 4}Если нужен диапазон от 1 до 5:
for i := range 5 { fmt.Println(i + 1)}Для произвольных начала и шага используется классическая форма for.
Перебор slice
languages := []string{"Go", "Python", "Swift"}
for index, language := range languages { fmt.Println(index, language)}Если индекс не нужен:
for _, language := range languages { fmt.Println(language)}Если значение не нужно:
for index := range languages { fmt.Println(index)}Перебор строки
for index, symbol := range "Go, мир!" { fmt.Printf("байтовый индекс=%d символ=%c\n", index, symbol)}index — смещение в байтах, а symbol имеет тип rune. Для многобайтных
символов индексы могут увеличиваться не на единицу.
Перебор map
scores := map[string]int{ "Анна": 90, "Борис": 82,}
for name, score := range scores { fmt.Println(name, score)}Порядок обхода map не определён. Нельзя строить логику или тесты, предполагая конкретный порядок.
10. break и continue
break немедленно завершает ближайший цикл:
for i := 1; i <= 100; i++ { if i == 6 { break } fmt.Println(i)}continue переходит к следующей итерации:
for i := 1; i <= 10; i++ { if i%2 != 0 { continue } fmt.Println(i)}Частое применение continue — раннее исключение неподходящих данных:
values := []int{10, -3, 0, 7}
for _, value := range values { if value <= 0 { continue }
fmt.Println("Положительное значение:", value)}11. Метки
Вложенный break завершает только ближайшую конструкцию. Метка позволяет выйти
из внешнего цикла:
search: for row := 0; row < 3; row++ { for column := 0; column < 3; column++ { if row == 1 && column == 2 { fmt.Println("Найдено:", row, column) break search } } }Метки следует применять редко. Если вложенная логика становится сложной, её
лучше вынести в функцию и завершать через return.
12. switch по значению
day := 3
switch day {case 1: fmt.Println("Понедельник")case 2: fmt.Println("Вторник")case 3: fmt.Println("Среда")default: fmt.Println("Неизвестный день")}В Go не требуется писать break после каждой ветки: переход к следующему case
автоматически не происходит.
Несколько значений объединяются:
switch day {case 6, 7: fmt.Println("Выходной")default: fmt.Println("Рабочий день")}13. switch без выражения
switch без выражения эквивалентен последовательности логических условий:
score := 84
switch {case score < 0 || score > 100: fmt.Println("Некорректный балл")case score >= 90: fmt.Println("Отлично")case score >= 75: fmt.Println("Хорошо")case score >= 60: fmt.Println("Удовлетворительно")default: fmt.Println("Не зачтено")}Выполняется первая подходящая ветка, поэтому порядок условий важен.
14. Короткая инструкция в switch
switch length := len("Go"); length {case 0: fmt.Println("Пустая строка")case 1: fmt.Println("Один байт")default: fmt.Println("Байтов:", length)}Переменная length доступна только внутри switch.
15. fallthrough
fallthrough принудительно переходит к следующей ветке, не проверяя её условие:
level := 2
switch level {case 1: fmt.Println("Базовый доступ") fallthroughcase 2: fmt.Println("Расширенный доступ")}Обычно fallthrough не нужен. Общие варианты лучше объединять через запятую или
выносить повторяющийся код в функцию.
16. Type switch
Интерфейсы будут подробно изучены позже. Сейчас важно узнать форму конструкции:
func describe(value any) { switch v := value.(type) { case int: fmt.Println("Целое число:", v) case string: fmt.Println("Строка:", v) case bool: fmt.Println("Логическое значение:", v) default: fmt.Printf("Неизвестный тип %T\n", v) }}Type switch выбирает ветку по динамическому типу значения. Использовать any
вместо нормальной типизации только ради такой проверки не следует.
17. Ранний выход
В Go нет отдельного оператора guard, но применяется тот же принцип: ошибочная
или граничная ситуация обрабатывается первой.
func printDiscount(age int) { if age < 0 { fmt.Println("Возраст не может быть отрицательным") return }
if age < 18 { fmt.Println("Скидка: 20%") return }
fmt.Println("Скидка: 5%")}Ранний выход уменьшает вложенность и делает основной сценарий заметнее.
18. Практический пример: анализ результатов
package main
import "fmt"
func main() { scores := []int{95, 82, 74, 61, 48, 103, -5} validCount := 0 total := 0
for index, score := range scores { if score < 0 || score > 100 { fmt.Printf("Результат %d пропущен: %d\n", index, score) continue }
validCount++ total += score
switch { case score >= 90: fmt.Printf("%d: отлично\n", score) case score >= 75: fmt.Printf("%d: хорошо\n", score) case score >= 60: fmt.Printf("%d: удовлетворительно\n", score) default: fmt.Printf("%d: не зачтено\n", score) } }
if validCount == 0 { fmt.Println("Нет корректных результатов") return }
average := float64(total) / float64(validCount) fmt.Printf("Средний балл: %.2f\n", average)}Здесь объединены:
- перебор slice;
- проверка корректности;
- ранний
continue; - классификация через
switch; - накопление суммы;
- защита от деления на ноль;
- явное преобразование к
float64.
19. Типичные ошибки
Ошибка границы
for i := 0; i <= len(values); i++ { // последний индекс лишний fmt.Println(values[i])}Правильно:
for i := 0; i < len(values); i++ { fmt.Println(values[i])}Или проще:
for _, value := range values { fmt.Println(value)}Бесконечный цикл
count := 5for count > 0 { fmt.Println(count) // забыли count--}Неправильный порядок условий
switch {case score >= 60: fmt.Println("Зачтено")case score >= 90: fmt.Println("Отлично") // недостижимо для 90 и выше}Сначала проверяются более узкие и строгие условия.
Ожидание упорядоченного map
Порядок элементов map не гарантирован. Для стабильного вывода ключи сначала собирают в slice и сортируют.
Избыточная вложенность
Вместо нескольких вложенных условий полезно проверять ошибки и граничные случаи
в начале, используя continue или return.
20. Вопросы для самопроверки
- Чем целочисленное деление отличается от вещественного?
- Почему
count++нельзя поместить внутрь выражения? - Какие значения можно использовать в условии
if? - Где видна переменная из короткой инструкции
if? - Как записать цикл, аналогичный
while? - Что возвращает
rangeпри переборе slice? - Что возвращает
rangeпри переборе строки? - Гарантирован ли порядок обхода map?
- Почему после
caseобычно не нуженbreak? - Для чего применяется
switchбез выражения? - Чем отличаются
breakиcontinue? - Когда ранний выход делает код понятнее?
Итоги
Go использует небольшой набор конструкций управления: if, универсальный цикл
for, range и switch. Условия всегда имеют тип bool, автоматический переход
между ветками switch отсутствует, а ранние return и continue помогают
сохранять основной путь выполнения линейным и читаемым.