Когда мы вызываем функцию с аргументами, аргументы копируются в функцию:
func zero(x int) {
x = 0
}
func main() {
x := 5
zero(x)
fmt.Println(x) // x всё еще равен 5
}
В этой программе функция zero
не изменяет оригинальную переменную x
из функции main
. Но что если мы хотим её изменить? Один из способов сделать это — использовать специальный тип данных — указатель:
func zero(xPtr *int) {
*xPtr = 0
}
func main() {
x := 5
zero(&x)
fmt.Println(x) // x is 0
}
Указатели указывают (прошу прощения за тавтологию) на участок в памяти, где хранится значение. Используя указатель (*int
) в функции zero
, мы можем изменить значение оригинальной переменной.
Операторы * и &
В Go указатели представлены через оператор * (звёздочка), за которым следует тип хранимого значения. В функции zero
xPtr
является указателем на int
.
*
также используется для «разыменовывания» указателей. Когда мы пишем *xPtr = 0
, то читаем это так: «Храним int
0 в памяти, на которую указывает xPtr
». Если вместо этого мы попробуем написать xPtr = 0
, то получим ошибку компиляции, потому что xPtr
имеет тип не int
, а *int
. Соответственно, ему может быть присвоен только другой *int
.
Также существует оператор &
, который используется для получения адреса переменной. &x
вернет *int
(указатель на int
) потому что x
имеет тип int
. Теперь мы можем изменять оригинальную переменную. &x
в функции main
и xPtr
в функции zero
указывают на один и тот же участок в памяти.
Оператор new
Другой способ получить указатель — использовать встроенную функцию new
:
func one(xPtr *int) {
*xPtr = 1
}
func main() {
xPtr := new(int)
one(xPtr)
fmt.Println(*xPtr) // x is 1
}
Функция new
принимает аргументом тип, выделяет для него память и возвращает указатель на эту память.
В некоторых языках программирования есть существенная разница между использованием new
и &
, и в них нужно удалять всё, что было создано с помощью new
. Go не такой — Go хороший. Go — язык с автоматической сборкой мусора. Это означает, что область памяти очищается автоматически, когда на неё не остаётся ссылок.
Указатели редко используются в Go для встроенных типов, но они будут часто фигурировать в следующей главе (они чрезвычайно полезны при работе со структурами).
Задачи
-
Как получить адрес переменной?
-
Как присвоить значение указателю?
-
Как создать новый указатель?
-
Какое будет значение у переменной
x
после выполнения программы:func square(x *float64) { *x = *x * *x } func main() { x := 1.5 square(&x) }
-
Напишите программу, которая меняет местами два числа (
x := 1; y := 2; swap(&x, &y)
должно датьx=2
иy=1
).