Перейти к содержимому
Теория · Указатели · урок 1 из 8

Указатели: & и *

Чему вы научитесь

Чему вы научитесь:

  • объяснять, что такое адрес переменной и указатель;
  • читать и менять переменную через указатель с помощью & и *;
  • передавать указатель в функцию, чтобы она изменила вашу переменную;
  • отличать nil-указатель от указателя на ноль и создавать переменные через new.
Улица из одинаковых домов, бумажка с адресом &x и пунктир от неё к дому x

Память компьютера похожа на длинную улицу из одинаковых ячеек. У каждой ячейки есть номер — её адрес. Когда вы пишете x := 1, Go выбирает ячейку, кладёт туда 1 и запоминает, что имя x означает именно эту ячейку.

Представьте улицу из одинаковых домов и бумажку с адресом одного из них. Дом — это переменная со значением внутри. Бумажка — это адрес: по ней можно прийти к дому, заглянуть внутрь или что-то в нём поменять. Бумажку можно передать другому человеку, и он придёт к тому же самому дому.

Зачем нужна бумажка, если есть имя x? Посмотрим, что делает обычное присваивание:

GO
package main

import "fmt"

func main() {
    x := 1
    y := x
    y = 5
    fmt.Println(x, y) // 1 5
}

Давайте разберём пример по строкам:

  1. x := 1 строит дом x и кладёт в него 1.
  2. Строка y := x не даёт второе имя тому же дому. Она строит новый дом y и копирует в него значение.
  3. y = 5 меняет только дом y.
  4. fmt.Println(x, y) печатает 1 5: запись в y не изменила x.

Иногда нужно именно другое: второй способ добраться до самой переменной x. Для этого в Go есть указатели.

Адрес и тип *int

Оператор & перед именем переменной возвращает её адрес — ту самую бумажку. Указатель — это значение, которое хранит адрес переменной. Тип указателя на int записывают как *int:

GO
x := 1
p := &x
fmt.Println(p == &x) // true

Разберём по строкам:

  1. x := 1 — обычная переменная.
  2. p := &x кладёт в p адрес x. У p тип *int: «адрес, по которому лежит int».
  3. fmt.Println(p == &x) печатает true: в p тот же адрес, что возвращает &x.

Что ещё важно знать о типе указателя:

  • в указатель на int можно положить адрес только переменной типа int, а адрес строки — нельзя;
  • сам адрес — число, которое меняется от запуска к запуску, поэтому его почти никогда не печатают.

Указатель — такая же переменная, как другие: у неё есть имя, тип и значение. После q := p у вас две бумажки с одним и тем же адресом, и запись через q изменит тот же x.

Чтение и запись через *p

Оператор * перед указателем означает «пойти по адресу и взять саму переменную». Такая операция называется разыменованием. Давайте разберём пример по строкам:

GO
x := 1
p := &x
*p = 5
fmt.Println(x) // 5
y := *p
x = 7
fmt.Println(y, *p) // 5 7
  1. x := 1 строит дом x и кладёт в него 1.
  2. p := &x пишет адрес этого дома на бумажку p.
  3. *p = 5 идёт по адресу из p и кладёт 5 в сам x.
  4. fmt.Println(x) печатает 5.
  5. y := *p копирует то, что лежит в x в эту секунду, в новую переменную y.
  6. x = 7 меняет x, а y живёт сама по себе: в ней по-прежнему 5.
  7. fmt.Println(y, *p) печатает 5 7: *p каждый раз заново идёт к x и поэтому видит 7.

Схема показывает момент сразу после строки *p = 5.

СхемаУказатель: бумажка с адресом дома

*p идёт по адресу и берёт сам x. Адрес условный: в программе он меняется от запуска к запуску.

Бумажку можно переписать

Здесь аналогия с бумажкой заканчивается одной оговоркой: бумажку можно переписать. После p = &y тот же p указывает уже на y, и *p = 0 изменит y, а не x. Дом x при этом никуда не делся — бумажка теперь ведёт к другому дому.

Частая ошибка

Значение записали в сам указатель, забыв *:

GO
x := 1
p := &x
p = 5 // ✗
fmt.Println(x, *p)
TEXT
cannot use 5 (untyped int constant) as *int value in assignment

Программа не компилируется: у p тип *int, и в него можно положить только адрес переменной типа int, а не само число. Это всё равно что написать на бумажке с адресом «5» вместо адреса. Чтобы записать 5 в переменную, на которую указывает p, нужна запись *p = 5.

Главное

  • Указатель хранит адрес переменной; &x берёт адрес, а тип указателя на int — *int.
  • *p — это сама переменная по адресу: через неё читают и пишут.
  • y := *p копирует значение в эту секунду, а *p каждый раз читает переменную заново.
  • В сам указатель кладут адрес (p = &y), а значение — через *p = 5.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом