Перейти до змісту

Оператори та вирази

Повний список

Арифметичні

+   -   *   /   %

Порівняння

==  !=  <  <=  >  >=

Порівняння завжди повертає bool.

Логічні (булеві)

&&  ||  !

Обчислюються ліниво, як у Python. Операнди мають бути bool — неявного перетворення на основі істинності немає.

Побітові

&    // AND
|    // OR
^    // XOR (також унарний NOT)
&^   // AND NOT — специфічний для Go, скидає біти
<<   // зсув ліворуч
>>   // зсув праворуч

Складені присвоєння

+=  -=  *=  /=  %=
&=  |=  ^=  &^=
<<= >>=

Отримання адреси / розіменування

&x   // вказівник на x
*p   // значення за вказівником p

Переповнення цілих чисел загортається

Переповнення цілих чисел використовує арифметику доповнення до двох — без невизначеної поведінки, без паніки.

var x uint8 = 255
x++
fmt.Println(x)   // 0  — загортається

var y int8 = 127
y++
fmt.Println(y)   // -128

Якщо потрібно виявляти переповнення, використовуйте Add64/Mul64 тощо з пакету math/bits, або перевіряйте діапазони вручну перед арифметикою.

З досвіду Python: int у Python зростає без обмежень. Цілі числа Go мають фіксовану ширину й загортаються. Для довільної точності зверніться до math/big.Int.

Ділення цілих чисел скорочує до нуля

fmt.Println(7 / 2)     // 3
fmt.Println(-7 / 2)    // -3 (до нуля, а не до -нескінченності)
fmt.Println(7 % 2)     // 1
fmt.Println(-7 % 2)    // -1

З досвіду Python: / у Python 3 завжди повертає float (7 / 2 == 3.5); для цілого ділення з округленням вниз використовується // (-7 // 2 == -4). Оператор / у Go є цілочисельним якщо обидва операнди є цілими, і скорочує до нуля — як у C.

Немає оператора піднесення до степеня

У Go немає **. Використовуйте math.Pow:

import "math"

fmt.Println(math.Pow(2, 10))    // 1024  — float64

// Для степенів двійки використовуйте зсув:
fmt.Println(1 << 10)            // 1024

Немає тернарного оператора

Спільнота вирішила, що читабельність важливіша за стислість. Використовуйте if/else або допоміжну функцію:

// Python: status := "even" if n%2 == 0 else "odd"
var status string
if n%2 == 0 {
    status = "even"
} else {
    status = "odd"
}

Якщо дуже хочеться однорядниковий варіант, напишіть крихітну узагальнену допоміжну функцію. Частина [T any] — це синтаксис узагальненого програмування (generics) у Go: параметр типу, що дозволяє одній функції працювати зі значеннями будь-якого типу. Узагальнення розглядатимуться окремо; наведений нижче приклад — лише для того, щоб показати, як такий помічник виглядає.

func If[T any](cond bool, a, b T) T {
    if cond { return a }
    return b
}

status := If(n%2 == 0, "even", "odd")

Але більшість коду на Go просто використовує чотирирядковий if/else. Не воюйте з мовою.

++ та -- — це оператори-вирази, а не вирази

i++             // ok — оператор
j--             // ok — оператор

x := i++        // compile error: i++ is not an expression
if i++ > 10 {}  // compile error

i++ ніколи не можна використовувати всередині виразу. І префіксних форм ++i / --i не існує.

&^ — AND NOT (скидання бітів)

Унікальний для Go. a &^ b еквівалентно a & (^b) — скидає в a ті біти, що встановлені в b.

const (
    Readable  = 1 << 0   // 0b001
    Writable  = 1 << 1   // 0b010
    Executable = 1 << 2  // 0b100
)

perms := Readable | Writable | Executable   // 0b111
perms = perms &^ Writable                   // 0b101 — біт запису скинуто

Конкатенація рядків

s := "hello" + ", " + "world"        // працює, але кожен + виконує виділення пам'яті
s := fmt.Sprintf("%s, %s", "hello", "world")

import "strings"
s := strings.Join([]string{"hello", "world"}, ", ")

// Для багатьох конкатенацій у циклі використовуйте Builder:
var b strings.Builder
for _, w := range words {
    b.WriteString(w)
    b.WriteString(" ")
}
result := b.String()

Конкатенація з + у щільному циклі має складність O(n²) — кожен + копіює весь попередній префікс. strings.Builder — O(n).

З досвіду Python: ≈ обговорення + проти ''.join(...). Та сама порада: для циклів використовуйте builder.

Пріоритет операторів

П'ять рівнів від найнижчого до найвищого:

||
&&
==  !=  <  <=  >  >=
+  -  |  ^
*  /  %  <<  >>  &  &^

Унарні оператори (!, -, ^, *, &, <-) мають вищий пріоритет, ніж будь-який бінарний оператор.

Якщо сумніваєтесь — ставте дужки. Рецензенти коду надають перевагу явним дужкам перед покладанням на таблицю пріоритетів.

Пастки при порівнянні

  • Числа з рухомою комою порівнюються точно, але з нюансом. 0.1 + 0.2 == 0.3, записаний як голі літерали, є true — Go обчислює нетипізовані константні вирази під час компіляції з довільною точністю, тому округлення IEEE 754 не відбувається. Як тільки ті самі числа живуть у змінних float64, округлення вступає в силу і відповідь змінюється:
fmt.Println(0.1 + 0.2 == 0.3)                                 // true  (untyped constants)
var a, b, c float64 = 0.1, 0.2, 0.3
fmt.Println(a + b == c)                                        // false (float64 variables)

Використовуйте порівняння з епсилоном щоразу, коли порівнюєте змінні float32/float64. - Рядки порівнюються байт за байтом (лексикографічно). "a" < "b" є true. - Зрізи, мапи, функції не можна порівнювати за допомогою ==/!= — лише з nil. Використовуйте reflect.DeepEqual або написану вручну перевірку.

a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(a == b)   // compile error
- Структури можна порівнювати, якщо всі їхні поля є порівнюваними. Структура з полями int/string — нормально; структура, що містить зріз — ні.

Джерела