Skip to content

Указатели в Go

Go сохранил указатели, что在一定程度上 гарантирует производительность, и в то же время ограничил их использование для лучшего GC и безопасности.

Создание

С указателями связаны две основные операции: оператор взятия адреса & и оператор разыменования *. Взятие адреса переменной возвращает указатель соответствующего типа:

go
func main() {
   num := 2
   p := &num
   fmt.Println(p)
}

Указатель хранит адрес переменной num:

0xc00001c088

Оператор разыменования имеет два назначения. Первое — доступ к элементу, на который указывает указатель:

go
func main() {
  num := 2
  p := &num
  rawNum := *p
  fmt.Println(rawNum)
}

p — указатель, разыменование позволяет получить доступ к элементу.

Второе назначение — объявление указателя:

go
func main() {
   var numPtr *int
   fmt.Println(numPtr)
}
<nil>

*int означает, что переменная является указателем на int. Указатель должен быть инициализирован — выделена память, иначе это будет пустой указатель. Можно присвоить адрес другой переменной или использовать new:

go
func main() {
   var numPtr *int
   numPtr = new(int)
   fmt.Println(numPtr)
}

Чаще используется короткое объявление:

go
func main() {
   numPtr := new(int)
   fmt.Println(numPtr)
}

Функция new принимает тип и возвращает указатель, выделяя память и инициализируя нулевым значением:

go
func main() {
   fmt.Println(*new(string))
   fmt.Println(*new(int))
   fmt.Println(*new([5]int))
   fmt.Println(*new([]float64))
}

Вывод:


0
[0 0 0 0 0]
[]

Запрет арифметики указателей

В Go не поддерживается арифметика указателей — указатель не может смещаться. Пример на C++:

cpp
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = &arr[0];
    cout << &arr << endl
         << p << endl
         << p + 1 << endl
         << &arr[1] << endl;
}

Вывод:

0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884

Адрес массива совпадает с адресом первого элемента, и при инкременте указатель смещается ко второму элементу. В Go массивы аналогичны, но указатели не могут смещаться:

go
func main() {
   arr := [5]int{0, 1, 2, 3, 4}
   p := &arr
   println(&arr[0])
   println(p)
   // Попытка арифметики указателей
   p++
   fmt.Println(p)
}

Код не скомпилируется:

main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)

TIP

Стандартная библиотека unsafe предоставляет операции для низкоуровневого программирования, включая арифметику указателей. Подробнее: Стандартная библиотека - unsafe.

new и make

Функции new и make похожи, но различаются:

go
func new(Type) *Type
  • Возвращает указатель типа
  • Принимает тип
  • Предназначена для выделения памяти указателям
go
func make(t Type, size ...IntegerType) Type
  • Возвращает значение, не указатель
  • Принимает тип и переменное число параметров
  • Предназначена для выделения памяти срезам, отображениям, каналам

Примеры:

go
new(int) // указатель на int
new(string) // указатель на string
new([]int) // указатель на срез int
make([]int, 10, 100) // срез int длиной 10, ёмкостью 100
make(map[string]int, 10) // отображение ёмкостью 10
make(chan int, 10) // канал с буфером 10

Golang by www.golangdev.cn edit