Указатели в Go
Go сохранил указатели, что在一定程度上 гарантирует производительность, и в то же время ограничил их использование для лучшего GC и безопасности.
Создание
С указателями связаны две основные операции: оператор взятия адреса & и оператор разыменования *. Взятие адреса переменной возвращает указатель соответствующего типа:
func main() {
num := 2
p := &num
fmt.Println(p)
}Указатель хранит адрес переменной num:
0xc00001c088Оператор разыменования имеет два назначения. Первое — доступ к элементу, на который указывает указатель:
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p — указатель, разыменование позволяет получить доступ к элементу.
Второе назначение — объявление указателя:
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int означает, что переменная является указателем на int. Указатель должен быть инициализирован — выделена память, иначе это будет пустой указатель. Можно присвоить адрес другой переменной или использовать new:
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}Чаще используется короткое объявление:
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}Функция new принимает тип и возвращает указатель, выделяя память и инициализируя нулевым значением:
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++:
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 массивы аналогичны, но указатели не могут смещаться:
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 похожи, но различаются:
func new(Type) *Type- Возвращает указатель типа
- Принимает тип
- Предназначена для выделения памяти указателям
func make(t Type, size ...IntegerType) Type- Возвращает значение, не указатель
- Принимает тип и переменное число параметров
- Предназначена для выделения памяти срезам, отображениям, каналам
Примеры:
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