Переменные — это именованные значения, которые хранятся в определенных областях оперативной памяти и используются во время выполнения программы.
Язык программирования Go (или Golang) основан на статической типизации. Это значит, что тип уже объявленной переменной константен и изменить его нельзя.
Сами переменные могут быть различных типов — каждый имеет свое предназначение и особенности.
В Golang есть несколько базовых типов данных, на основе которых строится логика работы программы:
Также есть и составные типы данных:
А еще несколько вспомогательных типов:
Помимо этого Golang (как, например, и C++) имеет «Стандартную библиотеку» (std), в которой содержится множество пользовательских типов.
Более подробно о типах переменных в Go можно узнать из отдельной статьи в официальном блоге Timeweb Cloud.
О том, как установить Golang на операционные системы Linux, Windows или macOS, можно почитать в отдельных руководствах Timeweb Cloud. Во всех показанных примерах использовался интерпретатор Golang версии 1.21.3.
cloud
Все примеры кода, показанные в этом руководстве, запускаются в отдельных файлах с расширением .go
.
Сперва создается:
sudo nano example.go
Далее наполняется содержимым в виде кода внутри функции main() с подключением всех необходимых модулей:
package main
import "fmt"
func main() {
// начало примера
var number int = 10
fmt.Println(number)
// конец примера
}
После чего запускается:
go run example.go
Существует градация вариантов того, как объявить переменную в Go, прежде чем ее использовать — от полной формы с явным указанием параметров переменной (или нескольких переменных) до краткой формы с автоопределением объявления и автовыводом типа.
Использование каждой из форм объявления переменной зависит от контекста. Тем не менее, наиболее краткую и наиболее автоматическую запись рекомендуется использовать как можно чаще — она позволяет переложить часть ответственности на интерпретатор языка, тем самым снизив количество возможных ошибок по вине программиста.
Наиболее явная форма объявления переменной в языке программирования Go предполагает указание ключевого слова var
, имени переменной, ее типа и значения:
var some_variable int = 5
Однако при указании значения переменной явное указание типа можно опустить:
var some_variable = 5
При этом переменную можно объявить без присваивания значения, но в этом случае требуется указать тип:
var some_variable int
some_variable = 5
Во всех этих примерах:
Например, вот так можно объявлять переменные строкового типа:
var some_name string = "Ivan"
А вот такое объявление переменной вызовет ошибку:
// ОШИБКА: при объявлении не указано ни значение переменной, ни ее тип
var some_name
some_name = "Ivan"
Важно помнить, что автовывод типа возможен только в момент объявления переменной, когда интерпретатор выделяет соответствующий объем оперативной памяти под ее значения.
Несмотря на строгую статическую типизацию, Golang позволяет объявлять переменные в более лаконичной форме без явного указания ее параметров:
some_variable := 5
В этом случае интерпретатор понимает, что необходимо автоматически вывести тип переменной исходя из указанного значения.
Тем не менее, подобное объявление возможно только внутри функции (в том числе main()
) — вне функции его использовать нельзя:
package main
// ОШИБКА: короткая форма объявления вне функции
some_variable := 5
func main() {
// ОК: короткая форма объявления внутри функции
other_variable := 10
}
При этом важно понимать отличие объявление переменной (вместе с инициализацией) от присвоения значения:
package main
func main() {
some_variable := 5 // это объявление и инициализация (есть двоеточие)
some_variable = 50 // это присвоение (нет двоеточия)
other_variable = 7 // ОШИБКА: это присвоение (нет двоеточия) необъявленной переменной
}
Например, можно последовательно объявить (и инициализировать) несколько переменных:
age := 50 // переменная типа int
name := "Ivan" // переменная типа string
occupation := "Just a guy" // переменная типа string
height := 190.5 // переменная типа float32
При этом оператор :=
нельзя использовать вместе с ключевым словом var
. Если это сделать, возникнет ошибка:
var someVariable int := 5 // ОШИБКА
var someVariable := 5 // ОШИБКА
Исключение ключевого слова var
, но с явным указанием типа по прежнему вызывает ошибку:
someVariable int := 5 // ОШИБКА
В языке программирования Go можно объявить сразу несколько переменных в одной строке или блоке.
Например, можно использовать ключевое слово var
с указанием единого типа для всех объявляемых переменных:
var width, height, depth int = 100, 200, 300
Можно также разделить объявление переменных и присваивание значений:
var width, height, depth int
width, height, depth = 100, 200, 300
Если типы переменных разные, то интерпретатор может вывести их типы автоматически:
var name, age, fired = "Ivan", 50, false
Аналогично можно использовать краткую форму для нескольких переменных:
name, age, fired := "Ivan", 50, false
В этом случае нет ни ключевого слова var
, не указанных типов переменных.
Еще один способ множественного объявления переменных — использование блока:
var (
name string = "Ivan"
age int = 50
height float64 = 190
fired bool = false
)
Кстати, блочное объявление можно отформатировать с помощью пробелов таким образом, чтобы имена, типы и значения выстраивались в несколько колонок, тем самым улучшая читаемость кода:
var (
name string = "Ivan"
age int = 50
height float64 = 190.5
fired bool = false
)
У блочного объявления переменных нет какого-либо утилитарного смысла. Это просто синтаксический сахар, который:
Таким образом, объявление в блоке оправдано только в тех случаях, когда необходимо сгруппировать несколько ключевых переменных, упростив их визуальное восприятие в текстовом редакторе кода.
Создание переменной Go возможно без ее инициализации. В этом случае переменной будет присвоено нулевое значение, соответствующее указанному типу:
int
, float32
, float64
— 0, 0.0, 0.0bool
— falsestring
— ""Эту особенность того, как Go обрабатывает объявление и инициализацию переменных, можно рассмотреть на примере реального скрипта:
package main
import "fmt"
func main() {
// целое число
var numberInt int
fmt.Println("Целое число:", numberInt)
// вещественное число
var numberFloat float32
fmt.Println("Вещественное число:", numberFloat)
// строка
var text string
fmt.Println("Строка:", text)
// булев
var condition bool
fmt.Println("Булев:", condition)
// массив
var array [5]int
fmt.Println("Массив:", array)
// срез
var cut []int
fmt.Println("Срез:", cut)
// структура
type S struct {
name string
size int
address string
}
var structure S
fmt.Println("Структура:", structure)
// карта
var dictionary map[int]int
fmt.Println("Карта:", dictionary)
// указатель
var pointer *int
fmt.Println("Указатель:", pointer)
}
Консольный вывод будет таким:
Целое число: 0
Вещественное число: 0
Строка:
Булев: false
Массив: [0 0 0 0 0]
Срез: []
Структура: { 0 }
Карта: map[]
Указатель: <nil>
Как видно, переменные разных типов автоматически инициализируются нулевыми (или пустыми) значениями там, где это возможно.
В языке программирования Go имена переменных могут начинаться либо с латинской буквы, либо с символа нижнего подчеркивания (_
):
onething := 123 // ОК
Onething := 123 // ОК
_onething := 123 // ОК
__onething := 123 // ОК
1thing := 123 // ОШИБКА
При этом у имени переменной есть одна функциональная особенность: имена, начинающиеся с заглавной буквы, видны в других пакетах, а начинающиеся с маленькой буквы — нет.
Существует также несколько способов именования переменных, которые можно считать общими для всех языков программирования, в том числе и для Go:
При использовании Snake Case имя переменной выглядит так:
some_random_variable := 123 // нижний регистр
SOME_RANDOM_VARIABLE := 123 // верхний регистр
При использовании Camel Case имя переменной выглядит так:
someRandomVariable := 123
При использовании Pascal Case имя переменной выглядит так:
SomeRandomVariable := 123
При использовании Kebab Case имя переменной выглядит так:
// ОШИБКА
some-random-variable := 123 // нижний регистр
SOME-RANDOM-VARIABLE := 123 // верхний регистр
Однако последний стиль именования переменных (Kebab Case) не поддерживается в языке Go из-за символа тире, который зарезервирован под операцию вычитания.
Все показанные выше способы объявления переменных в языке программирования Go можно рассмотреть конкретнее на примере еще одного скрипта:
package main
import "fmt"
func main() {
// явное объявление с указанием типа
var age int = 50
fmt.Println("Возраст:", age)
// явное объявление с автоматическим выводом типа
var height = 190.5
fmt.Println("Рост:", height)
// краткое объявление
name := "Ivan"
fmt.Println("Имя:", name)
// явное объявление нескольких переменных
var width, depth int = 100, 200
fmt.Println("Ширина:", width, "Глубина:", depth)
// явное объявление без инициализации
var distance int
fmt.Println("Дистанция:", distance)
// блочное объявление нескольких переменных
var (
occupation string = "Сварщик"
category float32 = 3.4
license bool
)
fmt.Println("Профессия:", occupation, "Разряд:", category, "Лицензия:", license)
}
Результатом работы этого кода станет следующий вывод в консольном терминале:
Возраст: 50
Рост: 190.5
Имя: Ivan
Ширина: 100 Глубина: 200
Дистанция: 0
Профессия: Сварщик Разряд: 3.4 Лицензия: false
Таким образом:
var
необходимо для явного объявления переменной, особенно в глобальной области видимости.:=
необходимо для краткого объявления переменной, особенно внутри функций.()
необходимо для читабельного объявления целого множества переменных.При этом надо помнить, что в языке программирования Go предпочтительнее минимализм и лаконичность форм записи. Поэтому наиболее краткая форма записи должна использоваться везде, где это возможно.
Все это позволяет снизить количество ошибок и сбоев, а также помогает поддерживать чистоту и читаемость кода.
Как правило во время объявления переменной она вручную инициализируется каким-либо значением. При этом инициализация разных типов имеет синтаксические различия.
Числовые переменные инициализируются присваиванием числового значения, что синтаксически выглядит тривиально:
// int
var someNumber int = 5
// float32
otherNumber := 10.0
Число можно инициализировать другим числом:
// int
var someNumber int = 5
var otherNumber int = someNumber
// int
oneMoreNumber := someNumber
Строковые переменные инициализируются присваиванием последовательности символов, заключенных в двойные кавычки:
// string
var someString string = "Здесь был какой-то программист"
Строку можно также инициализировать другой строкой:
// string
var someString string = "Здесь был какой-то программист"
var otherString string = someString
// string
oneMoreString := someString
Инициализация булевых переменных ничем не отличается от инициализации числовых и строковых переменных за исключением того, что в качестве значения используется ключевое слово:
// bool
var someBool bool = true
Аналогично, булевые переменные можно инициализировать другими булевыми переменными
// bool
var someBool bool = true
var otherBool bool = someBool
// bool
oneMoreBool := someBool
Есть несколько способов инициализации массива. Самый простой — с помощью поочередного доступа к элементам:
// массив
var languages [3]string
planets[0] = "Golang"
planets[1] = "Python"
planets[2] = "Rust"
Более сложный — через так называемый композитный литерал.
Композитный литерал — компактная синтаксическая конструкция для инициализации любого композитного (составного) типа, исключающая последовательное присваивание каждого элемента.
Таким образом массив можно инициализировать целиком за один шаг:
var languages = [3]string{"Golang", "Python", "Rust"}
Или использовать краткую форму:
languages := [3]string{"Golang", "Python", "Rust"}
Также инициализировать элементы массива можно частично:
// размер массива 5, а инициализировано элементов 3
languages := [5]string{"Golang", "Python", "Rust"}
languages[3] = "Java"
languages[4] = "C++"
А с помощью такой записи инициализацию большого массива можно сделать более читабельной:
languages := [5]string{
"Golang",
"Python",
"Rust",
"Java",
"C++", // запятая в конце ОБЯЗАТЕЛЬНА
}
Кстати, массив можно инициализировать другим массивом, скопировав все его элементы:
languages := [3]string{"Golang", "Python", "Rust"}
otherLanguages := languages
При этом важно понимать, что копирование массива также выполняется при его передаче в функцию:
package main
import "fmt"
func change(languages [5]string) {
for i := range languages {
languages[i] = "[" + languages[i] + "]"
}
}
func main() {
languages := [5]string{
"Golang",
"Python",
"Rust",
"Java",
"C++",
}
change(languages)
fmt.Println(languages)
}
Вывод в консольный терминал будет следующим:
[Golang Python Rust Java C++]
Таким образом изменениям подверглась только копия массива внутри функции change()
, а не оригинальный массив из функции main()
.
Тем не менее, явная инициализация массива другим массивом возможна только в том случае, если оба массива имеют одинаковую длину и тип:
languages := [3]string{"Golang", "Python", "Rust"}
var otherLanguages [3]string = languages // ОК
var oneMoreLanguages [4]string = languages // ОШИБКА
Помимо этого, в языке Go можно создавать массивы из произвольного числа других массивов. При этом, инициализировать элементы таких массивов можно как последовательно:
var matrix [2][2]string
matrix[0][0] = "a"
matrix[0][1] = "b"
matrix[1][0] = "c"
matrix[1][1] = "d"
Так и с помощью композитного литерала:
var matrix = [2][2][2]string{{{"a", "b"}, {"c", "d"}}, {{"e", "f"}, {"g", "h"}}}
Как видно, второй вариант занимает меньше места, но синтаксически более сложен.
Срез инициализируется также, как и массив:
var languages = []string{"Golang", "Python", "Rust"}
Однако в отличие от массива, срез можно инициализировать другим срезом произвольной длины:
var languages = []string{"Golang", "Python", "Rust"}
var otherLanguages []string = languages
Карты инициализируются через композитный литерал с указанием типа ключа и значения. Содержимое же перечисляется через запятую, разделяясь двоеточием:
var languages = map[string]string{"first": "Golang", "second": "Python", "third": "Rust"}
Можно также использовать короткую форму объявления и более читабельный вид инициализации:
languages := map[string]string{
"first": "Golang",
"second": "Python",
"third": "Rust", // запятая в конце ОБЯЗАТЕЛЬНА
}
При этом инициализация карты другой картой не копирует элементы, а делает их общими:
package main
import "fmt"
func main() {
languages := map[string]string{"first": "Golang", "second": "Python", "third": "Rust"}
otherLanguages := languages
fmt.Println(languages)
fmt.Println(otherLanguages)
otherLanguages["first"] = "C++"
fmt.Println(languages)
fmt.Println(otherLanguages)
delete(otherLanguages, "second")
fmt.Println(languages)
fmt.Println(otherLanguages)
}
Консольный вывод этого примера будет таким:
map[first:Golang second:Python third:Rust]
map[first:Golang second:Python third:Rust]
map[first:C++ second:Python third:Rust]
map[first:C++ second:Python third:Rust]
map[first:C++ third:Rust]
map[first:C++ third:Rust]
Как можно заметить, любое изменение в одной из карт влияет также на другую.
Указатели можно инициализировать только адресом переменной такого же типа:
var variable int = 15
var pointer *int = &variable
Символ амперсанда (&
) позволяет получить адрес любой переменной:
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
fmt.Println(pointer)
}
Консольный вывод этого примера будет примерно таким:
0xc000104040
Для инициализации указателей можно также использовать краткую запись:
variable := 15
pointer := &variable
Для того, чтобы получить значение по адресу указателя, его нужно разыменовать с помощью символа звездочки (*
):
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
fmt.Println(*pointer)
}
В этом случае в терминале консоли появится такой вывод:
15
Таким образом можно присваивать новые значения переменной, расположенной по адресу указателя:
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
*pointer = 5
fmt.Println(*pointer)
}
В консоли появится такой вывод:
5
Наконец, указатель можно инициализировать безымянным объектом в памяти. Для этого используется функция new()
, которая возвращает адрес выделенной области памяти:
variable := new(int)
*variable = 15
Удалять же выделенную область памяти вручную не надо — этим автоматически занимается сборщик мусора.
Структуру можно инициализировать либо последовательно указанными значениями:
type something struct {
first string
second int
}
var structure something = something{"Ivan", 15}
Либо значениями, указанными явно по ключам:
type something struct {
first string
second int
}
var structure something = something{second: 15, first: "Ivan"}
А можно вообще не указывать никаких значений, тем самым автоматически инициализировать все нулями:
package main
import "fmt"
type something struct {
first string
second int
}
func main() {
var structure something = something{}
fmt.Println(structure)
structure.first = "Ivan"
structure.second = 15
fmt.Println(structure)
}
В этом случае консольный вывод будет таким:
{ 0}
{Ivan 15}
Переменные занимают центральное место в ветвлениях. На основе их значений (по условию) выполняются различные участки кода программы.
Самая базовая условная конструкция создается операторами if/else
. Так выглядит наиболее простое условие:
a := 5
b := 10
if a < b {
fmt.Println("A меньше B")
}
Например, с помощью простого условия можно проверить указатель:
var pointer *int
if pointer == nil {
fmt.Println("Нет адреса")
}
Более сложная форма описывается так:
a := 10
b := 5
if a < b {
fmt.Println("A меньше B")
} else {
fmt.Println("A больше B")
}
А с помощью комбинации else
и if
можно создавать еще более сложные конструкции:
a := 10
b := 5
if a < b {
fmt.Println("A меньше B")
} else if a > b {
fmt.Println("A больше B")
} else {
fmt.Println("A равно B")
}
При этом выражений if/else
может быть несколько:
a := 12
if a < 5 {
fmt.Println("A меньше 5")
} else if a < 10 {
fmt.Println("A меньше 10")
} else if a < 20{
fmt.Println("A меньше 20")
} else {
fmt.Println("A в суперпозиции")
}
Еще один способ ветвления — конструкция switch
, внутри которой задаются возможные значения переменной и действия, которые нужно выполнить, если есть совпадение:
a := 1
switch(a) {
case 0:
fmt.Println("A равно 0")
case 1:
fmt.Println("A равно 1")
case 2:
fmt.Println("A равно 2")
}
С помощью секции default
можно задать действие, выполняющееся при отсутствии совпадений:
a := 3
switch(a) {
case 0:
fmt.Println("A равно 0")
case 1:
fmt.Println("A равно 1")
case 2:
fmt.Println("A равно 2")
default:
fmt.Println("A в суперпозиции")
}
Можно также объединить несколько возможных совпадений внутри одной секции:
a := 1
switch(a) {
case 0, 1, 2:
fmt.Println("A равно либо 0, либо 1, либо 2")
default:
fmt.Println("A в суперпозиции")
}
В языке Go есть множество дополнительных функций для работы с переменными. В этом руководстве мы рассмотрим лишь основные из них.
В языке Go есть специальные системные функции, позволяющие как установить переменную окружения, так и получить ее значение:
package main
import (
"fmt"
"os"
"strings"
)
func main() {
os.Setenv("SOMEVAR", "1") // записываем переменную окружения
fmt.Println("SOMEVAR:", os.Getenv("SOMEVAR")) // читаем переменную окружения
}
Часто логика программы требует измерения времени. В языке Go для этого есть соответствующий инструмент — переменная типа time
.
Надо сказать, что время — это отдельная обширная тема. Узнать же больше о пакете time
можно в официальной документации.
В этом же руководстве будет показано, как узнать текущее время в различных форматах:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Текущее время:", time.Now())
fmt.Println("Текущее время (UTC):", time.Now().UTC())
fmt.Println("Текущее время (Unix):", time.Now().Unix())
}
В консольном терминале появится следующий вывод:
Текущее время: 2009-11-10 23:00:00 +0000 UTC m=+0.000000001
Текущее время (UTC): 2009-11-10 23:00:00 +0000 UTC
Текущее время (Unix): 1257894000
Можно также уточнить конкретный параметр времени:
package main
import (
"fmt"
"time"
)
func main() {
timeNow := time.Now()
fmt.Println("Общее время:", timeNow)
fmt.Println("Год:", timeNow.Year())
fmt.Println("Месяц:", timeNow.Month())
fmt.Println("День:", timeNow.Day())
fmt.Println("Часы:", timeNow.Hour())
fmt.Println("Минуты:", timeNow.Hour())
fmt.Println("Секунды", timeNow.Second())
}
В этом случае консольный вывод будет таким:
Общее время: 2024-11-15 23:46:09.157929822 +0000 UTC m=+0.000031801
Год: 2024
Месяц: November
День: 15
Часы: 23
Минуты: 23
Секунды 9
К срезам можно добавлять элементы:
var languages = []string{"Golang", "Python", "Rust"}
languages = append(languages, "Java", "C++")
fmt.Println(languages)
Или удалять элементы из них:
var languages = []string{"Golang", "Python", "Rust"}
// удаление 2-го элемента
n := 1
languages = append(languages[:n], languages[n+1:]...)
fmt.Println(languages)
Удаление в данном случае выполняется с помощью оператора среза, который создает из одной последовательности другую.
package main
import "fmt"
func main() {
var sequence = []string{"One", "Two", "Three", "Four", "Five"}
newSequence := sequence[1:4] // элементы в диапазоне от 1 до 3 станут содержимым нового среза
fmt.Println(newSequence)
}
В консольном терминале будет следующий вывод:
[Two Three Four]
Тип переменной можно узнать с помощью функции TypeOf()
, которая содержится в модуле reflect
:
package main
import (
"fmt"
"reflect" // модуль, позволяющий узнать тип
)
func main() {
variableString := "строка"
variableInt := 5
variableFloat64 := 1.5
variableBool := true
fmt.Println(reflect.TypeOf(variableString))
fmt.Println(reflect.TypeOf(variableInt))
fmt.Println(reflect.TypeOf(variableFloat64))
fmt.Println(reflect.TypeOf(variableBool))
}
Консольный вывод этого примера будет следующим:
string
int
float64
bool
Часто необходимо вставить переменную в строку. Сделать это можно несколькими способами:
package main
import "fmt"
func main() {
// СПОСОБ 1
stringPre := "человекочитаемая"
stringEnd1 := fmt.Sprintf("Это %s строка", stringPre)
fmt.Println(stringEnd1)
// СПОСОБ 2
stringEnd2 := "Это " + stringPre + " строка"
fmt.Println(stringEnd2)
}
В консоли появится следующий вывод:
Это человекочитаемая строка
Это человекочитаемая строка
Также можно комбинировать числовые переменные со строковыми:
package main
import "fmt"
func main() {
name := "Иван"
age := 50
fmt.Printf("Привет, меня зовут %v и мне %v лет.\n", name, age)
}
В этом случае вывод будет таким:
Привет, меня зовут Иван и мне 50 лет.
Подготовили для вас выгодные тарифы на облачные серверы
Переменные в Go, как и в большинстве других языков программирования, необходимы для хранения данных.
Так как данные отличаются друг от друга, переменные Go обладают несколькими базовыми типами — каждый из них имеет особое представление в оперативной памяти компьютера.
В этом руководстве мы рассмотрели только базовые способы работы с переменными. Более подробную (и исчерпывающую) информацию о типах и их особенностях можно узнать в официальной документации Golang.
Дополнительно, в официальном каталоге пакетного менеджера Go можно найти информацию о множестве полезных модулей, доступных для импорта в проект. Одним из таких модулей, например, является «Стандартная библиотека»