Что такое указатели? Как передаются параметры в функцию по указателю или по значению? Какие типы неявно передаются как указатель? Как передать по указателю?go-54

Что такое указатели?

Указатель — это переменная, которая хранит адрес памяти другой переменной, а не её значение. В Go указатели имеют тип с префиксом *, например *int — указатель на целое число.

var x int = 10
var p *int = &x // p содержит адрес x

Основные операции с указателями

  1. Получение адреса (оператор &):
x := 42
ptr := &x // ptr теперь указывает на x
  1. Разыменование (оператор *):
fmt.Println(*ptr) // Выведет 42 (значение по адресу)
*ptr = 100       // Изменяем значение x через указатель
  1. Нулевой указатель (nil):
var p *int // nil по умолчанию
if p != nil {
    *p = 1  // Опасно! Разыменование nil указателя приведет к панике
}

Передача параметров в функции

1. По значению

Создается копия значения:

func modifyValue(val int) {
    val = 100 // Изменяется только копия
}

x := 42
modifyValue(x)
fmt.Println(x) // 42 (оригинал не изменился)

2. По указателю

Передается ссылка на оригинальную переменную:

func modifyPointer(ptr *int) {
    *ptr = 100 // Изменяем оригинальное значение
}

x := 42
modifyPointer(&x)
fmt.Println(x) // 100 (оригинал изменился)

Типы, которые неявно передаются как указатели

Некоторые типы в Go уже содержат указатели внутри и ведут себя особым образом:

  1. Срезы (slice) — хранят указатель на массив
func modifySlice(s []int) {
    s[0] = 100 // Изменяет оригинальный массив
}

nums := []int{1, 2, 3}
modifySlice(nums)
fmt.Println(nums) // [100 2 3]
  1. Карты (map) — реализованы как указатели
func modifyMap(m map[string]int) {
    m["key"] = 100
}

data := map[string]int{"key": 1}
modifyMap(data)
fmt.Println(data) // map[key:100]
  1. Каналы (chan) — ссылочный тип
  2. Функции — передаются по ссылке
  3. Интерфейсы — содержат указатель на данные

Как правильно передавать по указателю?

1. Для модификации значений

func resetToZero(ptr *int) {
    *ptr = 0
}

value := 5
resetToZero(&value)

2. Для больших структур

type BigStruct struct { /* много полей */ }

func processBig(b *BigStruct) {
    // Работаем с оригинальной структурой
}

3. Когда нужно проверить на nil

func (u *User) Save() error {
    if u == nil {
        return errors.New("nil user")
    }
    // ...
}

Особенности работы с указателями

1. Указатели и методы

Методы могут быть привязаны как к значению, так и к указателю:

type Counter struct {
    value int
}

// По значению (работает с копией)
func (c Counter) Increment() {
    c.value++ // Не изменяет оригинал
}

// По указателю (работает с оригиналом)
func (c *Counter) IncrementPtr() {
    c.value++ // Изменяет оригинал
}

2. Возврат указателей

Можно возвращать указатели на локальные переменные (Go перемещает их в кучу):

func createUser() *User {
    return &User{Name: "Alice"} // Безопасно
}

3. Указатели на базовые типы

Обычно не нужны, но могут быть полезны для optional-значений:

func printInt(ptr *int) {
    if ptr != nil {
        fmt.Println(*ptr)
    } else {
        fmt.Println("nil")
    }
}

Распространенные ошибки

  1. Разыменование nil указателя:
var p *int
*p = 1 // panic: runtime error
  1. Возврат указателя на локальную переменную в C-стиле:
// Плохо в C, но нормально в Go:
func foo() *int {
    x := 42
    return &x // В Go это безопасно
}
  1. Путаница между значением и указателем:
u := User{}
u.Increment()   // Не изменяет u
u.IncrementPtr() // Изменяет u

Резюмируем

  1. Указатель — переменная, содержащая адрес памяти
  2. По умолчанию параметры передаются по значению (копируются)
  3. Для передачи по указателю используйте & и *
  4. Срезы, карты, каналы, функции и интерфейсы уже содержат указатели
  5. Используйте указатели для:
    • Модификации аргументов
    • Работы с большими структурами
    • Опциональных параметров
  6. Методы могут быть привязаны как к значению, так и к указателю
  7. В Go есть сборщик мусора, поэтому можно безопасно возвращать указатели на локальные переменные

Правильное использование указателей — ключ к эффективному и безопасному коду на Go.