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

Память компьютера похожа на длинную улицу из одинаковых ячеек. У каждой ячейки есть номер — её адрес. Когда вы пишете x := 1, Go выбирает ячейку, кладёт туда 1 и запоминает, что имя x означает именно эту ячейку.
Представьте улицу из одинаковых домов и бумажку с адресом одного из них. Дом — это переменная со значением внутри. Бумажка — это адрес: по ней можно прийти к дому, заглянуть внутрь или что-то в нём поменять. Бумажку можно передать другому человеку, и он придёт к тому же самому дому.
Зачем нужна бумажка, если есть имя x? Посмотрим, что делает обычное присваивание:
package main
import "fmt"
func main() {
x := 1
y := x
y = 5
fmt.Println(x, y) // 1 5
}Давайте разберём пример по строкам:
x := 1строит домxи кладёт в него1.- Строка
y := xне даёт второе имя тому же дому. Она строит новый домyи копирует в него значение. y = 5меняет только домy.fmt.Println(x, y)печатает1 5: запись вyне изменилаx.
Иногда нужно именно другое: второй способ добраться до самой переменной x. Для этого в Go есть указатели.
Адрес и тип *int
Оператор & перед именем переменной возвращает её адрес — ту самую бумажку. Указатель — это значение, которое хранит адрес переменной. Тип указателя на int записывают как *int:
x := 1
p := &x
fmt.Println(p == &x) // trueРазберём по строкам:
x := 1— обычная переменная.p := &xкладёт вpадресx. Уpтип*int: «адрес, по которому лежитint».fmt.Println(p == &x)печатаетtrue: вpтот же адрес, что возвращает&x.
Что ещё важно знать о типе указателя:
- в указатель на
intможно положить адрес только переменной типаint, а адрес строки — нельзя; - сам адрес — число, которое меняется от запуска к запуску, поэтому его почти никогда не печатают.
Указатель — такая же переменная, как другие: у неё есть имя, тип и значение. После q := p у вас две бумажки с одним и тем же адресом, и запись через q изменит тот же x.
Чтение и запись через *p
Оператор * перед указателем означает «пойти по адресу и взять саму переменную». Такая операция называется разыменованием. Давайте разберём пример по строкам:
x := 1
p := &x
*p = 5
fmt.Println(x) // 5
y := *p
x = 7
fmt.Println(y, *p) // 5 7x := 1строит домxи кладёт в него1.p := &xпишет адрес этого дома на бумажкуp.*p = 5идёт по адресу изpи кладёт5в самx.fmt.Println(x)печатает5.y := *pкопирует то, что лежит вxв эту секунду, в новую переменнуюy.x = 7меняетx, аyживёт сама по себе: в ней по-прежнему5.fmt.Println(y, *p)печатает5 7:*pкаждый раз заново идёт кxи поэтому видит7.
Схема показывает момент сразу после строки *p = 5.
*p идёт по адресу и берёт сам x. Адрес условный: в программе он меняется от запуска к запуску.
Бумажку можно переписать
Здесь аналогия с бумажкой заканчивается одной оговоркой: бумажку можно переписать. После p = &y тот же p указывает уже на y, и *p = 0 изменит y, а не x. Дом x при этом никуда не делся — бумажка теперь ведёт к другому дому.
Частая ошибка
Значение записали в сам указатель, забыв *:
x := 1
p := &x
p = 5 // ✗
fmt.Println(x, *p)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 и подпишись на канал — откроются все уроки.