$ sudo teach IT
МОДУЛЬ 2 · УРОК 2.3

Операторы и выражения

Учимся складывать, сравнивать и строить логику — три кита любой программы

В предыдущем уроке мы научились общаться с пользователем: выводить текст на экран и считывать то, что он вводит с клавиатуры. Но программа, которая только читает и печатает, не очень полезна. Настоящая сила программирования начинается тогда, когда мы начинаем вычислять, сравнивать и принимать решения. Именно для этого нужны операторы. Оператор — это символ или комбинация символов, которые говорят компьютеру: «Возьми эти данные и сделай с ними что-то». Сложи два числа, проверь, равны ли они, определи, выполняются ли оба условия одновременно. Сегодня мы разберём три большие группы операторов: арифметические, сравнения и логические. А в конце урока напишем полноценный калькулятор с выбором операции.

🔢 Арифметические операторы

Начнём с самого знакомого — математики. Арифметические операторы работают точно так же, как в школьной тетради, только записываются символами на клавиатуре. В Go их пять:

+ — сложение

- — вычитание

* — умножение

/ — деление

% — остаток от деления (модуль)

Давайте посмотрим, как это выглядит в коде:

package main

import "fmt"

func main() {
    a := 10
    b := 3

    fmt.Println("a + b =", a+b) // 13
    fmt.Println("a - b =", a-b) // 7
    fmt.Println("a * b =", a*b) // 30
    fmt.Println("a / b =", a/b) // 3
    fmt.Println("a % b =", a%b) // 1
}

Всё очевидно, кроме двух моментов. Первый — деление. Мы разделили 10 на 3 и получили 3, а не 3.333... Почему? Потому что обе переменные — целые числа (int). Когда Go делит целое на целое, он отбрасывает дробную часть. Это называется целочисленное деление.

Если вам нужен точный результат с дробной частью, используйте float64:

package main

import "fmt"

func main() {
    a := 10.0
    b := 3.0

    fmt.Println("a / b =", a/b) // 3.3333333333333335
}

Второй момент — оператор % (остаток от деления). Он показывает, сколько «осталось» после деления. Например, 10 делим на 3: получается 3 целых и 1 в остатке. Значит, 10 % 3 = 1. Ещё примеры:

fmt.Println(15 % 5) // 0  — 15 делится на 5 без остатка
fmt.Println(17 % 5) // 2  — 17 = 5*3 + 2
fmt.Println(7 % 2)  // 1  — 7 = 2*3 + 1
fmt.Println(8 % 2)  // 0  — 8 делится на 2 без остатка

💡 Подсказка: оператор % невероятно полезен на практике. Хотите проверить, чётное ли число? Просто посмотрите: n % 2 == 0. Если остаток от деления на 2 равен нулю — число чётное. Этот приём используется постоянно.

Помимо базовых операторов, в Go есть составные операторы присваивания. Они позволяют записать изменение переменной короче:

package main

import "fmt"

func main() {
    x := 10

    x += 5  // то же самое, что x = x + 5  →  x теперь 15
    fmt.Println(x)

    x -= 3  // то же самое, что x = x - 3  →  x теперь 12
    fmt.Println(x)

    x *= 2  // то же самое, что x = x * 2  →  x теперь 24
    fmt.Println(x)

    x /= 4  // то же самое, что x = x / 4  →  x теперь 6
    fmt.Println(x)

    x %= 4  // то же самое, что x = x % 4  →  x теперь 2
    fmt.Println(x)
}

Запись x += 5 и x = x + 5 делают абсолютно одно и то же. Составные операторы — просто более короткая и привычная для программистов форма записи. Используйте то, что вам понятнее.

📐 Приоритет операций

Как и в обычной математике, в Go операции выполняются не слева направо, а по приоритету. Умножение и деление выполняются раньше сложения и вычитания:

package main

import "fmt"

func main() {
    result := 2 + 3*4
    fmt.Println(result) // 14, а не 20
}

Сначала Go вычислил 3 * 4 = 12, а потом прибавил 2. Если вы хотите изменить порядок, используйте скобки, точно как в школе:

result := (2 + 3) * 4
fmt.Println(result) // 20

💡 Подсказка: если вы сомневаетесь в порядке операций — ставьте скобки. Даже если они не обязательны, скобки делают код понятнее. Лучше написать (a * b) + c, чем заставлять читателя вспоминать приоритеты.

⚖️ Операторы сравнения

Арифметические операторы дают нам числа. Операторы сравнения дают нам ответ на вопрос: правда или ложь? Результат сравнения — это всегда bool: либо true (правда), либо false (ложь).

== — равно

!= — не равно

< — меньше

> — больше

<= — меньше или равно

>= — больше или равно

Посмотрим на примере:

package main

import "fmt"

func main() {
    a := 10
    b := 20

    fmt.Println("a == b:", a == b) // false — 10 не равно 20
    fmt.Println("a != b:", a != b) // true  — 10 действительно не равно 20
    fmt.Println("a < b:",  a < b)  // true  — 10 меньше 20
    fmt.Println("a > b:",  a > b)  // false — 10 не больше 20
    fmt.Println("a <= b:", a <= b) // true  — 10 меньше или равно 20
    fmt.Println("a >= b:", a >= b) // false — 10 не больше и не равно 20
}

Обратите внимание на важнейшее различие: одно равно = — это присваивание (записать значение в переменную), а два равно == — это сравнение (проверить, равны ли значения). Это одна из самых частых ошибок у новичков:

x := 5   // присваивание: x теперь равно 5
x == 5   // сравнение: "x равно 5?" — вернёт true

Сравнивать можно не только числа. Строки тоже поддерживают операторы сравнения:

package main

import "fmt"

func main() {
    name := "Go"

    fmt.Println(name == "Go")     // true
    fmt.Println(name == "Python") // false
    fmt.Println(name != "Java")   // true

    // Строки сравниваются по алфавиту (лексикографически)
    fmt.Println("apple" < "banana") // true — "a" идёт раньше "b"
    fmt.Println("go" < "Go")       // false — маленькие буквы "больше" заглавных
}

Пока что результаты сравнений мы просто печатаем. Но уже в следующем уроке мы будем использовать их в условиях if, чтобы программа могла принимать решения: «если число больше нуля — сделай одно, иначе — другое».

🧠 Логические операторы

Логические операторы работают с bool-значениями (true и false). Они позволяют комбинировать несколько условий вместе. В Go их три:

&& — логическое И (AND). Результат true, только если оба условия истинны.

|| — логическое ИЛИ (OR). Результат true, если хотя бы одно условие истинно.

! — логическое НЕ (NOT). Переворачивает значение: true становится false, и наоборот.

Давайте разберём каждый с помощью жизненных аналогий.

Оператор && (И)

Представьте, что вы хотите поехать на пляж. Для этого нужно, чтобы и погода была хорошая, и у вас был выходной. Если хотя бы одно условие не выполняется — поездка срывается.

package main

import "fmt"

func main() {
    goodWeather := true
    dayOff := true

    canGoToBeach := goodWeather && dayOff
    fmt.Println("Едем на пляж?", canGoToBeach) // true

    // Если погода плохая — не едем, даже если выходной
    goodWeather = false
    canGoToBeach = goodWeather && dayOff
    fmt.Println("Едем на пляж?", canGoToBeach) // false
}

Оператор || (ИЛИ)

Вы голодны и думаете, где поесть. Вы будете сыты, если или приготовите дома, или закажете доставку. Достаточно одного из двух.

package main

import "fmt"

func main() {
    cookAtHome := false
    orderDelivery := true

    willEat := cookAtHome || orderDelivery
    fmt.Println("Буду сыт?", willEat) // true — доставку заказали

    // Если ни то, ни другое...
    cookAtHome = false
    orderDelivery = false
    willEat = cookAtHome || orderDelivery
    fmt.Println("Буду сыт?", willEat) // false — голодаем
}

Оператор ! (НЕ)

Самый простой: он переворачивает значение на противоположное.

package main

import "fmt"

func main() {
    isRaining := true

    fmt.Println("Идёт дождь?", isRaining)    // true
    fmt.Println("Нет дождя?", !isRaining)     // false

    isLoggedIn := false
    fmt.Println("Не авторизован?", !isLoggedIn) // true
}

Теперь самое интересное — комбинирование. В реальных программах логические операторы часто используются вместе с операторами сравнения:

package main

import "fmt"

func main() {
    age := 25
    hasLicense := true

    // Можно ли водить? Нужно быть старше 18 И иметь права
    canDrive := age >= 18 && hasLicense
    fmt.Println("Может водить?", canDrive) // true

    temperature := 36.6

    // Температура нормальная? Между 36.0 и 37.0
    isNormal := temperature >= 36.0 && temperature <= 37.0
    fmt.Println("Температура в норме?", isNormal) // true

    score := 85

    // Отличник? Оценка от 90 и выше
    // Хорошист? Оценка от 70 и выше, но ниже 90
    isExcellent := score >= 90
    isGood := score >= 70 && score < 90
    fmt.Println("Отличник?", isExcellent) // false
    fmt.Println("Хорошист?", isGood)      // true
}

💡 Подсказка: приоритет логических операторов: сначала !, потом &&, потом ||. То есть a || b && c означает a || (b && c), а не (a || b) && c. Если сомневаетесь — ставьте скобки, это всегда безопаснее и понятнее.

📊 Таблицы истинности

Чтобы вы полностью поняли, как работают логические операторы, вот полные таблицы всех возможных комбинаций. Запоминать их наизусть не нужно — через пару дней практики вы начнёте «чувствовать» их интуитивно.

Давайте проверим это кодом:

package main

import "fmt"

func main() {
    fmt.Println("=== Оператор && (И) ===")
    fmt.Println("true  && true  =", true && true)   // true
    fmt.Println("true  && false =", true && false)   // false
    fmt.Println("false && true  =", false && true)   // false
    fmt.Println("false && false =", false && false)   // false

    fmt.Println()

    fmt.Println("=== Оператор || (ИЛИ) ===")
    fmt.Println("true  || true  =", true || true)   // true
    fmt.Println("true  || false =", true || false)   // true
    fmt.Println("false || true  =", false || true)   // true
    fmt.Println("false || false =", false || false)   // false

    fmt.Println()

    fmt.Println("=== Оператор ! (НЕ) ===")
    fmt.Println("!true  =", !true)   // false
    fmt.Println("!false =", !false)  // true
}

Запомните простое правило: && требует, чтобы все были true. || довольствуется тем, что хотя бы один true. Один «строгий», другой «лояльный».

🔗 Оператор + для строк

Интересный факт: оператор + в Go умеет не только складывать числа, но и склеивать строки. Это называется конкатенация:

package main

import "fmt"

func main() {
    firstName := "Иван"
    lastName := "Петров"

    fullName := firstName + " " + lastName
    fmt.Println(fullName) // Иван Петров

    greeting := "Привет, " + fullName + "!"
    fmt.Println(greeting) // Привет, Иван Петров!
}

Обратите внимание: мы складываем строку firstName, строку " " (пробел) и строку lastName. Результат — новая строка, в которой все части «склеены» вместе.

💡 Подсказка: нельзя складывать строку с числом: "Возраст: " + 25 вызовет ошибку. Для этого используйте fmt.Sprintf: fmt.Sprintf("Возраст: %d", 25). Мы подробно разобрали это в предыдущем уроке про форматирование.

🔄 Инкремент и декремент

В Go есть два специальных оператора для увеличения и уменьшения числа на единицу:

package main

import "fmt"

func main() {
    counter := 0

    counter++ // увеличиваем на 1 (то же, что counter = counter + 1)
    fmt.Println(counter) // 1

    counter++ // ещё раз на 1
    fmt.Println(counter) // 2

    counter-- // уменьшаем на 1 (то же, что counter = counter - 1)
    fmt.Println(counter) // 1
}

Важный момент: в Go ++ и -- — это инструкции, а не выражения. Это значит, что нельзя написать y := x++ или fmt.Println(x++). Можно только x++ как отдельную строку. Go намеренно сделан так, чтобы код был простым и однозначным.

🛠 Практика: программа-калькулятор

Пришло время собрать всё вместе. Мы напишем программу-калькулятор, которая спрашивает у пользователя два числа и операцию, а затем выводит результат. Эта программа использует почти всё, что мы изучили: переменные, ввод/вывод и арифметические операторы.

Для начала создайте папку и файл для проекта:

mkdir calculator
cd calculator
go mod init calculator

Создайте файл main.go и напишите в нём:

package main

import "fmt"

func main() {
    var a, b float64
    var operator string

    // Просим ввести первое число
    fmt.Print("Введите первое число: ")
    fmt.Scan(&a)

    // Просим выбрать операцию
    fmt.Print("Введите операцию (+, -, *, /): ")
    fmt.Scan(&operator)

    // Просим ввести второе число
    fmt.Print("Введите второе число: ")
    fmt.Scan(&b)

    // Вычисляем результат
    var result float64
    var validOperation bool

    validOperation = true

    if operator == "+" {
        result = a + b
    } else if operator == "-" {
        result = a - b
    } else if operator == "*" {
        result = a * b
    } else if operator == "/" {
        if b == 0 {
            fmt.Println("Ошибка: деление на ноль невозможно!")
            validOperation = false
        } else {
            result = a / b
        }
    } else {
        fmt.Println("Неизвестная операция:", operator)
        validOperation = false
    }

    // Выводим результат
    if validOperation {
        fmt.Printf("%.2f %s %.2f = %.2f\n", a, operator, b, result)
    }
}

Запустите программу:

go run main.go

Пример работы:

Введите первое число: 15.5
Введите операцию (+, -, *, /): *
Введите второе число: 3
15.50 * 3.00 = 46.50

Разберём ключевые моменты программы:

1. Мы используем float64 для чисел, чтобы калькулятор мог работать с дробями. Если бы мы выбрали int, то 10 / 3 давало бы 3, а не 3.33.

2. Переменная operator имеет тип string — пользователь вводит символ операции как текст.

3. Мы проверяем деление на ноль: если пользователь попытается разделить на 0, программа выдаст понятную ошибку вместо некорректного результата.

4. Формат %.2f выводит число с двумя знаками после запятой. Мы изучили это в предыдущем уроке.

💡 Подсказка: в этой программе мы используем конструкцию if / else if / else, которую мы ещё не изучали формально. Пока просто прочитайте код — он интуитивно понятен: «если оператор это плюс — складываем, иначе если минус — вычитаем» и так далее. В следующем уроке мы разберём условия подробно.

А вот как можно расширить наш калькулятор, добавив оператор остатка от деления и возведение в квадрат:

package main

import "fmt"

func main() {
    var a, b float64
    var operator string

    fmt.Println("===== КАЛЬКУЛЯТОР =====")
    fmt.Println("Доступные операции: +  -  *  /  %  sq")
    fmt.Println("sq — возведение первого числа в квадрат")
    fmt.Println("=======================")

    fmt.Print("\nВведите первое число: ")
    fmt.Scan(&a)

    fmt.Print("Введите операцию: ")
    fmt.Scan(&operator)

    // Для операции "sq" второе число не нужно
    if operator != "sq" {
        fmt.Print("Введите второе число: ")
        fmt.Scan(&b)
    }

    var result float64
    valid := true

    if operator == "+" {
        result = a + b
    } else if operator == "-" {
        result = a - b
    } else if operator == "*" {
        result = a * b
    } else if operator == "/" {
        if b == 0 {
            fmt.Println("Ошибка: деление на ноль!")
            valid = false
        } else {
            result = a / b
        }
    } else if operator == "%" {
        if b == 0 {
            fmt.Println("Ошибка: деление на ноль!")
            valid = false
        } else {
            result = float64(int(a) % int(b))
        }
    } else if operator == "sq" {
        result = a * a
    } else {
        fmt.Println("Неизвестная операция:", operator)
        valid = false
    }

    if valid {
        if operator == "sq" {
            fmt.Printf("%.2f в квадрате = %.2f\n", a, result)
        } else {
            fmt.Printf("%.2f %s %.2f = %.2f\n", a, operator, b, result)
        }
    }
}

Попробуйте запустить эту версию и поэкспериментировать. Введите разные числа, попробуйте деление на ноль, попробуйте несуществующую операцию. Видите, как программа обрабатывает каждый случай? Это и есть программирование — предусматривать все возможные ситуации.

⚠️ Типичные ошибки новичков

Разберём ошибки, с которыми вы наверняка столкнётесь:

Путаница между = и ==

В Go компилятор не даст вам использовать = вместо == в условиях — это просто не скомпилируется. Но путаница в голове всё равно бывает. Запомните: = — «записать», == — «сравнить».

Целочисленное деление

Если оба числа — int, результат деления тоже будет int (дробная часть отбрасывается). 7 / 2 = 3, а не 3.5. Если нужен точный результат, используйте float64.

Сложение разных типов

Go — строго типизированный язык. Нельзя сложить int и float64 напрямую. Нужно привести к одному типу: float64(myInt) + myFloat.

Деление на ноль

Деление целого числа на ноль вызовет панику (аварийное завершение программы). Для float64 деление на ноль даст +Inf (бесконечность). В обоих случаях это не то, чего хочет пользователь — поэтому всегда проверяйте делитель перед делением.

📋 Итоги урока

  • Изучили арифметические операторы: +, -, *, /, % и составные варианты +=, -=, *=, /=, %=
  • Разобрались с целочисленным делением: int / int отбрасывает дробную часть
  • Узнали про операторы сравнения: ==, !=, <, >, <=, >= — они всегда возвращают bool
  • Освоили логические операторы: && (оба истинны), || (хотя бы один истинен), ! (отрицание)
  • Научились использовать + для склеивания строк (конкатенация)
  • Познакомились с инкрементом ++ и декрементом --
  • Написали полноценную программу-калькулятор с проверкой деления на ноль

В следующем уроке мы разберём условия: if, else, else if и switch. Вы узнаете, как заставить программу принимать решения и выполнять разный код в зависимости от ситуации. Мы также познакомимся с крутой фишкой Go — коротким if с инициализацией и switch без break. Операторы сравнения и логики, которые мы сегодня изучили, станут основой для написания условий.

Операторы и выражения — Язык Go – для начинающих! | $ sudo teach IT