Глиф Учебники

Урок 2. Переменные, := и типы

Курс «Go» · урок 2 из 10 · 4 минуты чтения

Программа из первого урока всегда печатает одно и то же. Чтобы она что-то считала, значения нужно где-то хранить. Для этого есть переменные — именованные места в памяти. У каждой переменной в Go есть тип: он задаётся один раз и потом не меняется. В переменную типа int (целое число) нельзя положить строку — компилятор не даст.

Объявление: var и :=

Полная запись — служебное слово var, имя, тип и значение:

Go
package main

import "fmt"

func main() {
    var age int = 20
    var name = "Анна"
    city := "Минск"
    fmt.Println(name, age, city)

    age = 21
    city = "Брест"
    fmt.Println(name, age, city)
}
Вывод
Анна 20 Минск
Анна 21 Брест

Три способа объявить переменную:

:= объявляет новую переменную, а = присваивает значение уже существующей. Короткое объявление работает только внутри функций; на уровне пакета, вне функций, пишут var.

var и := создают = меняет значение var age int = 20 age int 20 21 age = 21 var name = "Анна" name string "Анна" city := "Минск" city string "Минск" "Брест" city = "Брест" тип задаётся один раз: в int строку не положить
var и := создают переменную с именем, типом и значением, а = кладёт новое значение в ту же переменную: age становится 21, city — «Брест», а тип у каждой остаётся прежним.

Слева от := должна быть хотя бы одна новая переменная. Если все уже объявлены, компилятор сообщит no new variables on left side of := — значит, нужно = вместо :=.

Несколько переменных объявляются и присваиваются сразу:

Go
package main

import "fmt"

func main() {
    a, b := 1, 2
    a, b = b, a
    fmt.Println(a, b)
}
Вывод
2 1

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

Объявленная внутри функции переменная должна где-то читаться. Если переменная только объявлена или ей только присваивают значения, программа не соберётся: declared and not used: x. Подробно — в справке declared and not used.

Нулевые значения

Переменная без начального значения в Go не содержит «мусора»: она получает нулевое значение своего типа.

Go
package main

import "fmt"

func main() {
    var count int
    var price float64
    var name string
    var ready bool
    fmt.Println(count, price, ready)
    fmt.Printf("%q\n", name)
}
Вывод
0 0 false
""

У чисел нулевое значение — 0, у строки — пустая строка "", у bool — false. Пустую строку Println напечатал бы как ничто, поэтому здесь она выведена через Printf с %q — в кавычках.

Основные типы

ТипЧто хранитПример
intцелое число; на 64-битных системах — примерно ±9,2·10¹⁸n := 42
int64, int32, int8целое ровно на 64, 32, 8 битvar big int64 = 1 << 40
uint, uint8…целое без знака, от нуляvar u uint = 7
float64дробное число, около 15–16 верных знаковpi := 3.14159
stringстрока текста в двойных кавычкахs := "Go"
booltrue или falseok := true
byteто же, что uint8: один байтb := byte('A')
runeодин символ Юникода, то же, что int32r := 'ж'

Для начала хватит int, float64, string и bool. Число без точки (42) по умолчанию становится int, с точкой (2.5) — float64. Символ в одинарных кавычках ('ж') — это rune, а строка — только в двойных.

Строки в Go хранятся в кодировке UTF-8, и len считает байты, а не буквы. Русская буква занимает два байта:

Go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "привет"
    fmt.Println(len(s))
    fmt.Println(utf8.RuneCountInString(s))
    fmt.Println(len([]rune(s)))
}
Вывод
12
6
6

Количество букв дают utf8.RuneCountInString или длина среза символов []rune(s) (о срезах — в уроке Массивы, срезы и append).

s := "привет" буквы байты п 0 1 р 2 3 и 4 5 в 6 7 е 8 9 т 10 11 len(s) 12 считает байты utf8.RuneCountInString(s) 6 считает буквы len([]rune(s)) 6 длина среза символов
Каждая русская буква строки "привет" занимает в UTF-8 два байта, поэтому len(s) даёт 12 байтов, а utf8.RuneCountInString(s) и длина среза символов rune — 6 букв.

Арифметика и деление

Действия: +, -, *, / и % (остаток). Как и во многих языках, деление двух целых даёт целое: дробная часть отбрасывается.

Go
package main

import "fmt"

func main() {
    a, b := 7, 2
    fmt.Println(a / b)
    fmt.Println(a % b)
    fmt.Println(-a/b, -a%b)
    fmt.Println(float64(a) / float64(b))
}
Вывод
3
1
-3 -1
3.5

Результат деления отбрасывается к нулю, поэтому -7 / 2 — это -3; знак остатка — как у делимого.

Сокращённые записи: x += 5 значит x = x + 5, так же работают -=, *=, /=, %=. x++ и x-- увеличивают и уменьшают на единицу. В Go это отдельные команды, а не выражения: y := x++ не соберётся, и формы ++x нет вовсе.

Типы не смешиваются сами

В Go нет автоматического перевода одного числового типа в другой. Сложить int и float64 нельзя:

Go
package main

import "fmt"

func main() {
    count := 3
    price := 2.5
    // fmt.Println(count * price) — ошибка: mismatched types int and float64
    total := float64(count) * price
    fmt.Println(total)
    fmt.Println(int(total))
}
Вывод
7.5
7

float64(count) — преобразование типа: значение count берётся как дробное. int(7.5) отбрасывает дробную часть и даёт 7. Число в строку и строку в число так не переводят: string(65) даёт не "65", а букву "A". Для этого есть пакет strconv — справка Как перевести строку в число.

count int 3 * price float64 2.5 не соберётся mismatched types int and float64 float64(count) 3 * price float64 2.5 total float64 7.5 int(total) int 7 дробная часть отброшена
int и float64 напрямую не перемножаются — ошибка mismatched types; float64(count) превращает 3 в float64, total получает 7.5, а int(total) отбрасывает дробную часть — 7.

Константы

Значение, которое не меняется, объявляют через const:

Go
package main

import "fmt"

const daysInWeek = 7

func main() {
    const hoursInDay = 24
    fmt.Println(daysInWeek * hoursInDay)
}
Вывод
168

Константу нельзя изменить, и компилятор не требует, чтобы она где-то использовалась. Для нумерованных констант есть iota — счётчик, который в блоке const ( … ) начинается с нуля и растёт на единицу с каждой строкой.

Имена

Имя состоит из букв, цифр и _ и не начинается с цифры. В Go принято писать имена из нескольких слов слитно, со второго слова с большой буквы: userName, maxValue, а не user_name. Помните, что большая первая буква делает имя экспортируемым (видимым из других пакетов): внутри функций имена пишут с маленькой.

Частые ошибки

Коротко

Проверьте себя

1. Что выведет fmt.Println(9 / 4)?
2. Какое значение у переменной после var s string?
3. Почему не соберётся x := 1 и затем x := 2 в той же функции?
4. Что выведет fmt.Println(len("мир"))?
5. Как правильно умножить n int на k float64?