Указатели — одна из тех тем, которая часто пугает новичков, но в Go они реализованы максимально логично и безопасно. В отличие от C++, здесь нет «арифметики указателей» (вы не можете прибавить единицу к адресу), что исключает целый класс ошибок с памятью.
По умолчанию Go передает аргументы в функции по значению. Это значит, что функция получает копию данных. Если вы измените копию, оригинал останется прежним.
Рассмотрим пример:
func zero(x int) {
x = 0
}
func main() {
x := 5
zero(x)
fmt.Println(x) // Выведет 5
}
Чтобы функция могла изменить оригинал, нам нужно передать ей не само значение, а адрес того места в памяти, где это значение лежит. Здесь на сцену выходят указатели.
Что такое указатель?
Указатель — это переменная, которая хранит адрес другой переменной. Вместо того чтобы нести «коробку с данными», указатель просто говорит: «данные лежат в коробке по адресу №123».
Операторы & и *
Для работы с указателями используются два символа:
&(адрес): Возвращает адрес переменной.*(разыменование): Позволяет получить доступ к значению, лежащему по адресу.
Перепишем нашу программу:
func zero(xPtr *int) {
*xPtr = 0 // Переходим по адресу и меняем значение на 0
}
func main() {
x := 5
zero(&x) // Передаем адрес переменной x
fmt.Println(x) // Теперь выведет 0
}
*intв сигнатуре функции — это тип «указатель на целое число».*xPtr = 0— это действие. Мы говорим: «Возьми адрес, хранящийся вxPtr, и запиши туда 0».
Оператор new
Еще один способ создать указатель — использовать встроенную функцию new. Она выделяет память под тип и возвращает указатель на «нулевое значение» этого типа.
xPtr := new(int) // Создает в памяти int (равный 0) и дает нам адрес
*xPtr = 10
fmt.Println(*xPtr) // 10
В Go вам не нужно вручную удалять объекты из памяти (как в C++). Сборщик мусора (Garbage Collector) автоматически очистит память, как только поймет, что на этот участок больше никто не ссылается.
Когда использовать указатели?
Как эксперт, дам совет: не используйте указатели везде подряд. Копирование маленьких переменных (как int или float64) происходит очень быстро.
Указатели нужны в двух случаях:
- Мутация: Когда функции необходимо изменить состояние передаваемого объекта.
- Эффективность: Когда вы передаете огромную структуру (например, массив на миллион элементов). Передать один адрес (8 байт) гораздо быстрее, чем копировать все данные.
Задачи
- Адрес: С помощью какого оператора можно получить адрес существующей переменной?
- Значение: С помощью какого оператора можно получить значение, на которое указывает указатель?
- Квадрат: Какое значение будет у переменной после выполнения этого кода? ```go func square(x *float64) { *x = *x * *x } func main() { x := 1.5 square(&x) }
* [ ] **Обмен (Swap):** Напишите функцию `swap(x, y *int)`, которая меняет значения двух переменных местами.
* *Подсказка:* Вам понадобится временная переменная внутри функции.
```go
x := 1
y := 2
swap(&x, &y)
// Должно стать: x = 2, y = 1
Полезные ссылки
- Go by Example: Pointers — лаконичные примеры.
- A Tour of Go: Pointers — интерактивная практика.
- Language Mechanics On Pointers — глубокий разбор того, как указатели работают со стеком и кучей (на английском).