Указатели

Когда мы вызываем функцию с аргументами, аргументы копируются в функцию:

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).

Fork me on GitHub