В Go объявление переменной имеет вид: var name type = expression. Можно не указывать тип или оператор присваивания с выражением, но один из них должен присутствовать. Кроме того, существует краткая форма объявления переменной. Рассмотрим все эти варианты:

  • Объявление без явного указания типа: var name = expression.
  • Длинное объявление с указанием типа: var name type и var name type = expression.
  • Короткая нотация: name := expression.

Правил, которые регламентировали бы использование той или иной формы, в Go нет. Можно исходить из личных предпочтений или придерживаться стиля, принятого в вашей компании.

Объявление без указания типа

Тип переменной можно опускать, если при объявлении происходит инициализация. Тогда тип переменной будет равен типу выражения. Если переменной присваивается число, то компилятор установит тип int или float64.

Опишем в таблице инициализирующие значения и типы, которые компилятор присваивает объявленным переменным:

Объявление переменнойТип переменной
var i = 10int
var f = 5.0float64
var s = "Hello, world!"string
var r = 'Щ'rune (int32)
var b = truebool

Множество переменных можно инициализировать через запятую или с помощью вызова функции, которая возвращает несколько значений.

var now = time.Now()  // now равно текущему времени и имеет тип time.Time
var pi, e = 3.1415, 2.7183
var f, err = os.Open("myfile.txt") // os.Open возвращает два значения

Длинное объявление

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

var i int // i будет присвоено значение по умолчанию — 0
var s string // s будет равна пустой строке
 
// определяем три строковых переменных
var name, company, country string 

Полная запись с указанием типа и начального значения, как правило, используется тогда, когда тип переменной должен отличаться от типа, который присваивается компилятором по умолчанию.

var id uint32 = 77 
var pi float32 = 3.1415 

Можно не указывать var перед каждой переменной, а объединять переменные в блоки var (...). Это удобно, когда нужно обозначить схожие по смыслу сущности.

var height int
var length int
var weight float64
var name   string
var company = "Рога и копыта"
 
// эквивалентно
 
var (
    height, length int
    weight float64
    name   string
    company = "Рога и копыта"
) 

Короткая нотация

Конструкция с ключевым словом var используется не всегда. В Go есть форма объявления переменной в теле функции с инициализацией. Эту форму принято называть короткой нотацией. Ключевое слово var и тип не указываются, а вместо символа = пишется :=.

i := 10
f := 5.1
doublef := 2*f // doublef имеет тип float64 и равно 10.2
 
// эквивалентно
 
var i = 10 
var f = 5.1
var doublef = 2*f  

По умолчанию тип переменной равен типу присваиваемого выражения. В случае числовых типов компилятор сам выбирает размерность типа. Это может быть неудобно, когда нужно, например, объявить короткой нотацией переменную типа int64, а не int. Аналогично и с uint.

Проблему решает операция приведения типов:

int64Var := int64(5)
float32Var := float32(101.3)
 
// эквивалентно
 
var int64Var int64 = 5 
var floatVar float32 = 101.3  

Короткая нотация прекрасно работает со множественным объявлением. Но в этом случае хотя бы одна переменная в выражении должна быть новой. В противном случае возникнет ошибка компиляции:

pi, e := 3.1415, 2.7183
// при уточнении значений нельзя использовать :=, так как 
// обе переменных уже определены
pi, e = 3.14159, 2.71828
 
f, err := os.Open("myfile.txt")

Константы

Константа — это типизированное значение, которое вычисляется на этапе компиляции и известно компилятору. В отличие от переменной, значение константы не может быть изменено во время работы программы. Вы уже сталкивались с константами ранее — это строковые, булевы и числовые литералы.

var i int = 5 // здесь 5 — безымянная целочисленная константа
s := "Hello " // здесь "Hello" — строковая константа, значением которой
              // инициализирована переменная s 

Если в коде постоянно используется какое-то число или строка, можно присвоить это значение переменной, но тогда оно не будет защищено от случайного изменения. Для решения этой проблемы Go даёт возможность давать константам имена.

Именованные константы

Ключевое слово const определяет именованную константу, при этом константе можно присвоить результат некоторого выражения. В одном объявлении const можно определить несколько констант.

Именованные константы можно инициализировать выражениями, состоящими из констант или литералов следующих типов:

  • числа;
  • строки;
  • символы (руны);
  • булевы значения.
const pi = 3.14159
const doublePi = pi * 2
const version = "1.0.0"
 
// эквивалентно
 
const (
   pi = 3.14159
   doublePi = pi * 2
   version = "1.0.0"
)
 
func main() {
    fmt.Println(version, pi, doublePi)
} 

Именованные константы позволяют изменять значение только в одном месте кода. Например, в приведённом примере легко увеличить точность числа пи или изменить номер версии.

Нетипизированные константы

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

const intConst = 5 
const floatConst = 5.0
const runeConst = 'A'
const strConst = "Hello, world!"
const boolConst = true 

Может показаться, что если опустить тип при объявлении константы, то компилятор выберет его сам — как в случае с короткой формой объявления переменных. Это так лишь отчасти. В случае с константами отсутствие явного указания типа имеет большее значение.

Например, если вы объявляете константу intConst и присваиваете ей значение 5, то получаете целочисленную константу с неопределённым типом (untyped int). Конкретный тип значения этой константы ещё не определён и в разных контекстах будет интерпретироваться компилятором по-разному. Это позволяет ослабить типизацию для констант, не отказываясь от сильной типизации глобально.

Благодаря этому подходу будет работать следующий пример:

package main
 
import (
    "fmt"
)
 
const id = 100
 
func main() {
    var i int64 = id
    var f float64 = id
 
    fmt.Println("i=", i, "f=", f)
}

Если определить id как переменную var id = 100, то возникнут ошибки компиляции при определении переменных i и f.

Если бы константы, как и переменные в Go, всегда имели конкретный тип, то работать с ними было бы сложнее. Более того, Go позволяет смешивать числовые литералы разных типов (untyped int, untyped float), поэтому корректно следующее выражение:

var a float64
a = 5 + 5.0 

Константы, как и переменные, можно группировать.

const Program = "Моя программа"
const Version = "1.0.0"
 
// эквивалентно
 
const (
   Program = "Моя программа"
   Version = "1.0.0"
) 

Если в группе у константы не указано значение, то оно равно значению предыдущей константы.

const (
    pi = 3.1415
    e
    name = "John Doe"
    fullName
)
 
func main() {
    fmt.Println("pi =", pi, "e =", e)
    fmt.Println("name =", name, "fullName =", fullName)
} 

Результатом работы программы будет:

pi = 3.1415 e = 3.1415
name = John Doe fullName = John Doe 

Типизированные константы

Если при объявлении вы указываете тип константы явным образом, она становится типизированной и подчиняется правилам сильной типизации Go. В этом случае вы работаете с константой как с неизменяемой переменной:

const flag uint8 = 128
 
func main() {
    var i int = flag
    fmt.Println(i)
} 

При компиляции этого примера возникнет ошибка cannot use flag (constant 128 of type uint8) as type int in variable declaration, так как у константы flag тип uint8, а у переменной i тип int.

Ключевое слово iota

🔗 Where and When to use Iota in Go

Что, если в коде нужно реализовать перечисление (enum)? В Go для этого нет встроенной синтаксической конструкции или специального типа. Однако можно просто объявить ряд констант и работать с ними:

const (
    Black = "black"
    Gray = "gray"
    White = "white"
)
 
func main() {
    fmt.Println(Black != Gray) // true
} 

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

const (
    Black = 0
    Gray = 1
    White = 2
)
 
func main() {
    fmt.Println(Black != Gray) // тоже true
} 

При таком перечислении вручную есть вероятность получить неожиданное поведение — например, если по ошибке присвоить двум константам в перечислении одинаковое целочисленное значение:

const (
    Black = 0
    Gray = 0
)
 
func main() {
    fmt.Println(Black != Gray) // false
} 

К тому же при таком подходе не очень удобно объявлять длинные перечисления.

Для удобного объявления и инициализации блоков констант в Go есть автоматический инкремент iota. При объявлении каждого блока const значение iota равно 0 и увеличивается на 1 для каждого следующего элемента:

const (
    Black = iota
    Gray
    White
)
 
// счётчик обнуляется
const (
    Yellow = iota
    Red
    Green = iota // это присваивание не обнулит iota
    Blue
)
 
func main() {
    fmt.Println(Black, Gray, White) 
    fmt.Println(Yellow, Red, Green, Blue)
} 

Данную конструкцию применяют не только для перечислений. Ключевое слово iota можно также использовать в арифметических выражениях, чтобы быстро объявить ряд значений с прогрессией. Следует помнить, что iota увеличивается на единицу для каждой строки, где указано имя константы, даже если той было присвоено конкретное значение.

const (
    _ = iota*10  // обратите внимание, что можно пропускать константы 
    ten
    twenty
    thirty
)
 
const (
    hello = "Hello, world!"  // iota равна 0
    one = 1                  // iota равна 1
 
    black = iota   // iota равна 2
    gray
)
 
func main() {
    fmt.Println(ten, twenty, thirty)
    fmt.Println(black, gray)
} 

Пользовательские типы в константах

Предположим, нужно определить константы для дней недели.

const (
    Monday = iota + 1
    Tuesday
    //...
    Sunday
) 

Если перечислить их так, то все константы будут иметь нетипизированный числовой тип и могут использоваться в любых выражениях, что может вносить путаницу: var i int = Monday + 1. В подобных случаях стоит определить пользовательский тип и указать его при определении констант.

type Weekday int
 
const (
    Monday Weekday = iota + 1
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
)
 
func NextDay(day Weekday) Weekday {
    return (day % 7) + 1
}
 
func main() {
    var today Weekday = Sunday
    tomorrow := NextDay(today)
    fmt.Println("today =", today, "tomorrow =", tomorrow)
}

Программа выведет:

today = 7 tomorrow = 1 

Литералы

В Go можно использовать различные представления строковых и числовых литералов. Проиллюстрируем на примере целого числа 1000:

1000
1000.0
1_000 // можно разделять части числа символом '_' для удобства восприятия
01750 // восьмеричное представление, начинается с 0
0x3e8 // шестнадцатеричное представление
0b001111101000 // бинарное представление 

Любой из этих литералов может быть использован в выражениях и даст одно и то же значение.


📂 Go | Последнее изменение: 15.08.2024 14:55