Указатели представляют собой объекты, значением которых служат адреса других объектов (например, переменных).
Указатель определяется как обычная переменная, только перед типом данных ставится символ звездочки *
. Например, определение указателя на объект типа int:
var p *int
Схематично разницу между указателем на int и переменной int можно представить так:
Если переменная-указатель указывает на существующую обычную переменную, то любые изменения, которые вы вносите в сохраненное значение с помощью переменной-указателя, изменят обычную переменную.
Указателю можно присвоить адрес переменной типа int. Для получения адреса применяется операция &
, после которой указывается имя переменной (&x
).
package main import "fmt" func main() { var x int = 4 // определяем переменную var p *int // определяем указатель p = &x // указатель получает адрес переменной fmt.Println(p) // значение самого указателя - адрес переменной x }
Здесь указатель p хранит адрес переменной x. Что важно, переменная x имеет тип int, и указатель p указывает именно на объект типа int. То есть должно быть соответствие по типу. И если мы попробуем вывести адрес переменной на консоль, то увидим, что он представляет шестнадцатеричное значение:
0xc0420120a0
В каждом отдельном случае адрес может отличаться, но к примеру, в моем случае машинный адрес переменной x - 0xc0420120a0. То есть в памяти компьютера есть адрес 0xc0420120a0, по которому располагается переменная x.
По адресу, который хранит указатель, мы получить значение переменной x. Для этого применяется операция * или операция разыменования. Результатом этой операции является значение переменной, на которую указывает указатель. Применим данную операцию и получим значение переменной x:
package main import "fmt" func main() { var x int = 4 var p *int = &x // указатель получает адрес переменной fmt.Println("Address:", p) // значение указателя - адрес переменной x fmt.Println("Value:", *p) // значение переменной x }
Консольный вывод
Address: 0xc0420c058 Value: 4
И также используя указатель, мы можем менять значение по адресу, который хранится в указателе:
var x int = 4 var p *int = &x *p = 25 fmt.Println(x) // 25
Для определения указателей можно использовать также сокращенную форму:
f := 2.3 pf := &f fmt.Println("Address:", pf) fmt.Println("Value:", *pf)
Пустой указатель
Если указателю не присвоен адрес какого-либо объекта, то такой указатель по умолчанию имеет значение nil (по сути отстутствие значения). Если мы попробуем получить значение по такому пустому указателю, то мы столкнемся с ошибкой:
var pf *float64 fmt.Println("Value:", *pf) // ! ошибка, указатель не указывает на какой-либо объект
Поэтому при работе с указателями иногда бывает целесообразано проверять на значение nil:
var pf *float64 if pf != nil{ fmt.Println("Value:", *pf) }
Функция new
Переменная представляет именованный объект в памяти. Язык Go также позволяет создавать безымянные объекты - они также размещаются в памяти, но не имеют имени как переменные. Для этого применяется функция new(type)
. В эту функцию передается тип, объект которого надо создать. Функция возвращает указатель на созданный объект:
package main import "fmt" func main() { p := new(int) fmt.Println("Value:", *p) // Value: 0 - значение по умолчанию *p = 8 // изменяем значение fmt.Println("Value:", *p) // Value: 8 }
В данном случае указатель p будет иметь тип *int, поскольку он указывает на объект типа int. Создаваемый объект имеет значение по умолчанию (для типа int это число 0).
Объект, созданный с помощью функции new, ничем не отличается от обычной переменной. Единственное что, чтобы обратиться к этому объекту - получить или изменить его адрес, необходимо использовать указатель.
Указатели как параметры функции
По умолчанию все параметры передаются в функцию по значению. Например:
package main import "fmt" func changeValue(x int){ x = x * x } func main() { d := 5 fmt.Println("d before:", d) // 5 changeValue(d) // изменяем значение fmt.Println("d after:", d) // 5 - значение не изменилось }
Функция changeValue изменяет значение параметра, возводя его в квадрат. Но после вызова этой функции мы видим, что значение переменной d, которая передается в changeValue, не изменилось. Ведь функция получает копию данной переменной и работает с ней независимо от оригинальной переменной d. Поэтому d никак не изменяется.
Однако что, если нам все таки надо менять значение передаваемой переменной? И в этом случае мы можем использовать указатели:
package main import "fmt" func changeValue(x *int){ *x = (*x) * (*x) } func main() { d := 5 fmt.Println("d before:", d) // 5 changeValue(&d) // изменяем значение fmt.Println("d after:", d) // 25 - значение изменилось! }
Теперь функция changeValue принимает в качестве параметра указатель на объект типа int. При вызове функции changeValue в нее передается адрес переменной d (changeValue(&d)). И после ее выполнения мы видим, что значение переменной d изменилось.
Указатель как результат функции
Функция может возвращать указатель:
package main import "fmt" func createPointer(x int) *int{ p := new(int) *p = x return p } func main() { p1 := createPointer(7) fmt.Println("p1:", *p1) // p1: 7 p2 := createPointer(10) fmt.Println("p2:", *p2) // p2: 10 p3 := createPointer(28) fmt.Println("p3:", *p3) // p3: 28 }
В данном случае функция createPointer возвращает указатель на объект int.