Публикации - Go

Типы данных - Основные типы данных, однотипные - Указатели

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

Указатель определяется как обычная переменная, только перед типом данных ставится символ звездочки *. Например, определение указателя на объект типа 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.

Количество комментариев: 0

Для того, чтобы оставить коментарий необходимо зарегистрироваться