Некоторые операторы: инкремента/декремента и присваивания

Краткое описание

В этом уроке разбираем операторы инкремента (++) и декремента (--) в Go, и почему они радикально отличаются от C#, Java, JavaScript. Изучаем составные операторы присваивания (+=, -=, *=, /=, %=) и понимаем, почему x = x + 1 — это не математическое равенство. Кратко касаемся побитовых операций.

Ключевые концепции

Инкремент и декремент: два критических отличия

В Go операторы ++ и -- работают совершенно иначе, чем в других языках.

Отличие 1: только постфиксная форма

В Go разрешена ТОЛЬКО постфиксная форма:

counter := 5

counter++  // ✅ OK: постфиксный инкремент
counter--  // ✅ OK: постфиксный декремент

// ++counter  // ❌ ОШИБКА компиляции!
// --counter  // ❌ ОШИБКА компиляции!

В других языках (C#, Java, JS):

let x = 5;
x++;    // работает
++x;    // тоже работает

В Go префиксная форма запрещена.

Отличие 2: statement, а не expression

Критично: ++ и -- — это операторы (statements), а не выражения (expressions). Они не возвращают значение.

counter := 5

// z := counter++  // ❌ ОШИБКА! syntax error: unexpected ++

// fmt.Println(counter++)  // ❌ ОШИБКА! нельзя использовать в выражениях

В других языках (C#, Java, JS):

let counter = 5;
let z = counter++;  // работает, z = 5, counter = 6

В Go такое невозможно.

Почему Go так сделали?

Разработчики Go убрали префиксные операторы и запретили использование в выражениях, чтобы:

  1. Устранить неоднозначность
    // JavaScript - запутанный код
    let x = 5;
    let y = x++ + ++x;  // что здесь происходит?
    
  2. Избежать вопросов на собеседованиях в стиле “gotcha”
    // Java - бессмысленный код, который спрашивают на интервью
    int x = 5;
    int y = x++ + x++ + ++x;  // чему равно y?
    
  3. Сделать код проще и понятнее

В Go если нужно использовать значение и увеличить его, пишите явно:

counter := 5

// Если нужно использовать старое значение
oldValue := counter
counter++
fmt.Println(oldValue)  // 5

// Или просто
counter = counter + 1

Правильное использование инкремента/декремента

counter := 5
fmt.Println(counter)  // 5

counter++  // увеличение на 1
fmt.Println(counter)  // 6

counter--  // уменьшение на 1
fmt.Println(counter)  // 5

// Инкремент - это отдельная инструкция
counter++
// Дальше используем изменённое значение
fmt.Println(counter)  // 6

Запомните: counter++ — это полноценный оператор, занимающий отдельную строку.

Составные операторы присваивания

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

value := 10

value += 5   // value = value + 5  →  15
value -= 3   // value = value - 3  →  12
value *= 2   // value = value * 2  →  24
value /= 4   // value = value / 4  →  6
value %= 4   // value = value % 4  →  2

Эквивалентность:

  • x += yx = x + y
  • x -= yx = x - y
  • x *= yx = x * y
  • x /= yx = x / y
  • x %= yx = x % y

Присваивание ≠ математическое равенство

Важно понимать: оператор = — это не равенство, а присваивание.

value := 10

// НЕ читать как "value равно value плюс один"
// ЧИТАТЬ как "вычисли value + 1 и положи результат в value"
value = value + 1

Порядок выполнения:

  1. Вычислить правую часть (value + 110 + 111)
  2. Присвоить результат переменной слева (value = 11)

Слева от = может быть только переменная:

x := 5
x = x + 1  // ✅ OK

// x + 1 = x  // ❌ ОШИБКА! слева не переменная

Побитовые операции (краткий обзор)

Go поддерживает стандартные побитовые операции для работы с двоичным представлением чисел:

a := 5  // 0101 в двоичной системе
b := 3  // 0011

// Побитовое И (AND)
result := a & b  // 0101 & 0011 = 0001 → 1

// Побитовое ИЛИ (OR)
result = a | b   // 0101 | 0011 = 0111 → 7

// Побитовое XOR (исключающее ИЛИ)
result = a ^ b   // 0101 ^ 0011 = 0110 → 6

// Сдвиг влево (умножение на 2)
result = 5 << 1  // 0101 << 1 = 1010 → 10

// Сдвиг вправо (деление на 2)
result = 5 >> 1  // 0101 >> 1 = 0010 → 2

Составные побитовые операторы:

x &= y   // x = x & y
x |= y   // x = x | y
x ^= y   // x = x ^ y
x <<= 2  // x = x << 2
x >>= 2  // x = x >> 2

Практические примеры

Инкремент/декремент

package main

import "fmt"

func main() {
    counter := 5
    fmt.Printf("Начальное значение: %d\n", counter)  // 5

    counter++
    fmt.Printf("После counter++: %d\n", counter)     // 6

    counter--
    fmt.Printf("После counter--: %d\n", counter)     // 5

    // ОШИБКИ - так делать нельзя:
    // ++counter               // ❌ синтаксическая ошибка
    // z := counter++          // ❌ нельзя использовать в выражении
    // fmt.Println(counter++)  // ❌ нельзя передать в функцию

    // ПРАВИЛЬНО:
    counter = counter + 1
    fmt.Printf("После counter = counter + 1: %d\n", counter)  // 6

    counter += 1
    fmt.Printf("После counter += 1: %d\n", counter)  // 7
}

Составные операторы

value := 10
fmt.Printf("Начало: %d\n", value)  // 10

value += 5
fmt.Printf("После += 5: %d\n", value)  // 15

value -= 3
fmt.Printf("После -= 3: %d\n", value)  // 12

value *= 2
fmt.Printf("После *= 2: %d\n", value)  // 24

value /= 4
fmt.Printf("После /= 4: %d\n", value)  // 6

value %= 4
fmt.Printf("После %= 4: %d\n", value)  // 2

Отличие от других языков

JavaScript/Java/C# (так НЕ работает в Go):

// JavaScript - компактно, но запутанно
let x = 5;
let y = x++ + ++x;  // x=7, y=12 🤯

// Использование в функциях
console.log(x++);  // выведет 5, x станет 6

Go (явно и понятно):

// Go - многословно, но прозрачно
x := 5
oldX := x
x++
x++
y := oldX + x  // 5 + 7 = 12

// Использование в функциях
fmt.Println(x)  // выведет текущее значение
x++             // увеличение - отдельная операция

Важные моменты

1. Только постфиксная форма

x++   // ✅ работает
++x   // ❌ ошибка компиляции

2. Не является выражением

y := x++           // ❌ ошибка
fmt.Println(x++)   // ❌ ошибка
if x++ == 10 {}    // ❌ ошибка

3. Отдельная инструкция

// Правильно
x++
fmt.Println(x)

// Неправильно
fmt.Println(x++)  // ❌

4. Составные операторы — альтернатива

// Вместо x++ в выражении:
x += 1
y := x  // теперь можно использовать

5. Присваивание выполняется справа налево

x = x + 10  // сначала вычисляется x + 10, потом присваивается в x

6. Побитовые операции с составными операторами

x &= 0xFF   // x = x & 0xFF (маскирование)
x <<= 1     // x = x << 1 (умножение на 2)

Best Practices

1. Используйте ++ и -- отдельными строками

// Хорошо
counter++
processValue(counter)

// Плохо (не компилируется, но даже если бы...)
// processValue(counter++)

2. Для сложных операций используйте явное присваивание

// Хорошо - понятно
oldValue := counter
counter++
calculate(oldValue, counter)

// Плохо - так в Go нельзя
// calculate(counter++, counter)

3. Составные операторы для читаемости

// Хорошо
total += price

// Хуже
total = total + price

4. Комментируйте побитовые операции

// Устанавливаем флаг в 3-м бите
flags |= (1 << 3)

// Проверяем флаг
if flags & (1 << 3) != 0 {
    // флаг установлен
}

Что запомнить

  • В Go ++ и -- — только постфиксные (x++, а не ++x)
  • Это операторы (statements), а не выражения (expressions)
  • Нельзя использовать в выражениях: z := x++ — ошибка
  • Нельзя передавать в функции: fmt.Println(x++) — ошибка
  • Для использования в выражениях: x += 1 вместо x++
  • Составные операторы: +=, -=, *=, /=, %=
  • x = x + 1 — это присваивание, а не математическое равенство
  • Выполнение: сначала правая часть, потом присваивание в левую
  • Побитовые операции: &, |, ^, <<, >>
  • Составные побитовые: &=, |=, ^=, <<=, >>=
  • Go проще других языков — нет запутанных конструкций с ++

Полезные ссылки