---
title: "Указатели по-человечески"
description: "Пока что значения в наших программах жили каждое в своей коробке. Передали int в функцию — функция получила копию, поковырялась в ней, а ваша исходная переменная осталась…"
canonical: https://graphlms.ru/go-basics/book/pointers
course: "Основы Go"
updated: 2026-10-12
lang: ru
publisher: GraphLMS
---

# Указатели по-человечески

> Источник: https://graphlms.ru/go-basics/book/pointers — курс «Основы Go», GraphLMS.

## О чём глава

Пока что значения в наших программах жили каждое в своей коробке. Передали `int`
в функцию — функция получила копию, поковырялась в ней, а ваша исходная
переменная осталась нетронутой. Часто это именно то, что нужно: меньше сюрпризов,
никто исподтишка не меняет ваши данные.

Но иногда копия мешает. Вы хотите, чтобы функция **изменила** вашу переменную,
а не её слепок: пополнила баланс счёта, отметила задачу выполненной. Или данные
большие, и копировать их на каждый вызов жалко. Для этого есть указатель —
значение, которое хранит не сами данные, а **адрес**, по которому они лежат в
памяти.

## Сначала — почему копия мешает

Возьмём функцию, которая «должна» удвоить число. Наивная попытка:

```go play
package main

import "fmt"

func doubleCopy(n int) {
	n = n * 2 // меняем локальную копию
}

func main() {
	x := 21
	doubleCopy(x)
	fmt.Println("после doubleCopy:", x) // 21 — ничего не изменилось
}
```

Функция получила копию `x` в параметре `n`. Удвоила копию. Вернулась — копия
исчезла, а `x` как был `21`, так и остался. Это не баг, а правило Go: **аргументы передаются по значению**, то есть копированием. Чтобы
функция дотянулась до самой переменной вызывающего, ей нужен её адрес.

## Что такое указатель

Указатель — это значение, чей тип записывается как `*T` (читается «указатель на
`T`»). Внутри лежит адрес памяти, по которому находится значение типа `T`.

С указателями работают два оператора:

- `&x` — **взять адрес** переменной `x`. Результат имеет тип `*T`.
- `*p` — **разыменовать** указатель `p`: добраться до значения, на которое он
  показывает. Это работает и на чтение, и на запись.

Запомнить легко: `&` упаковывает переменную в адрес, `*` распаковывает адрес
обратно в переменную. Они обратны друг другу.

```go
x := 10
p := &x      // p имеет тип *int, хранит адрес x
fmt.Println(*p) // 10 — читаем значение по адресу
*p = 20      // пишем по адресу
fmt.Println(x)  // 20 — изменили саму x, не копию
```

## Мутация вызывающего: чиним удвоение

Теперь та же функция, но принимающая адрес. Внутри мы разыменовываем указатель и
пишем по нему — а значит, дотягиваемся до настоящей переменной `main`.

```go play
package main

import "fmt"

func double(n *int) {
	*n = *n * 2 // читаем по адресу, удваиваем, пишем обратно по адресу
}

func main() {
	x := 21
	fmt.Println("до:   ", x)
	double(&x) // передаём адрес x, а не саму x
	fmt.Println("после:", x) // 42 — изменилась исходная переменная
}
```

Вывод:

```
до:    21
после: 42
```

Обратите внимание: в `double` мы пишем `*n = ...`, а не `n = ...`. Присвоить
самому `n` новое значение было бы бессмысленно — мы бы поменяли локальную копию
адреса. Нам нужно то, что **по** этому адресу, отсюда звёздочка.

## Общая память без копирования

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

```go play
package main

import "fmt"

func main() {
	score := 100
	a := &score
	b := &score // оба указателя смотрят на одну переменную

	*a += 50 // меняем через a
	*b += 10 // меняем через b

	fmt.Println("score =", score) // 160 — обе правки попали в одну ячейку
	fmt.Println("через a:", *a, "через b:", *b)
}
```

`a` и `b` — разные переменные-указатели, но внутри у них один и тот же адрес.
Поэтому правка через любой из них видна через другой и через саму `score`. Это
ровно то, что делает функция, принимающая `*int`: она получает доступ к ячейке
вызывающего.

## nil — указатель в никуда

Нулевое значение для любого указателя — `nil`. Это «адрес, которого нет»: такой
указатель ни на что не показывает. Объявленный, но не инициализированный
указатель равен `nil`:

```go
var p *int       // p == nil
fmt.Println(p == nil) // true
```

Попытка разыменовать `nil` — `*p`, когда `p == nil` — это паника во время
выполнения (*nil pointer dereference*), и программа падает. Поэтому, если есть
сомнение, пришёл ли указатель «настоящим», проверяют его перед разыменованием:

```go play
package main

import "fmt"

func describe(p *int) {
	if p == nil {
		fmt.Println("указателя нет (nil)")
		return
	}
	fmt.Println("значение по адресу:", *p)
}

func main() {
	x := 7
	describe(&x)  // значение по адресу: 7
	describe(nil) // указателя нет (nil)
}
```

Без проверки программа упала бы с сообщением
`panic: runtime error: invalid memory address or nil pointer dereference`. Его
стоит запомнить в лицо: это одна из самых частых аварий в программах на Go.

`nil`-указатель сам по себе не ошибка; ошибка — попытаться достать значение
там, где его нет. `nil` часто используют осознанно: например, как «значение
отсутствует». Но за каждым разыменованием стоит молчаливое предположение, что
указатель не `nil`.

## Указатели на структуры

Чаще всего указатели в Go встречаются со *структурами*. Подробно структуры
разобраны в [седьмой главе](/go-basics/book/structs), а здесь хватит минимума.
Структура собирает несколько значений в одно: объявление
`type Account struct { Owner string; Balance int }` вводит новый тип «счёт» с
двумя полями, запись `Account{Owner: "Ира", Balance: 100}` создаёт значение
этого типа, а `acc.Balance` обращается к полю.

Указатель на структуру нужен по тем же двум причинам: чтобы функция меняла поля
исходной структуры и чтобы не копировать большую структуру на каждый вызов.

Приятная мелочь: чтобы добраться до поля через указатель, **не нужно** писать
`(*p).Field`. Go разрешает короткую форму `p.Field` — он сам разыменует указатель
за вас. Обе записи означают одно и то же, но короткая читается человечнее.

```go play
package main

import "fmt"

type Account struct {
	Owner   string
	Balance int
}

func deposit(a *Account, amount int) {
	a.Balance += amount // то же, что (*a).Balance += amount
}

func main() {
	acc := Account{Owner: "Ира", Balance: 100}
	deposit(&acc, 250)
	deposit(&acc, 50)
	fmt.Printf("%s: %d\n", acc.Owner, acc.Balance) // Ира: 400
}
```

Если бы `deposit` принимал `Account` по значению, каждый вызов работал бы с
копией, и баланс в `main` остался бы `100`. Указатель даёт функции доступ к
настоящему счёту.

Запись `&Account{...}` сразу создаёт структуру и берёт её адрес: получается
`*Account` без промежуточной переменной:

```go play
package main

import "fmt"

type Point struct{ X, Y int }

func main() {
	p := &Point{X: 3, Y: 4} // p имеет тип *Point
	p.X = 10                 // короткая форма разыменования
	fmt.Println(p.X, p.Y)    // 10 4
}
```

## Можно ли вернуть адрес локальной переменной

Если вы писали на C, следующий код вас насторожит: функция создаёт переменную и
возвращает её адрес. Переменная ведь «умрёт», когда функция завершится?

```go play
package main

import "fmt"

type Point struct{ X, Y int }

func newPoint(x, y int) *Point {
	p := Point{X: x, Y: y}
	return &p // в Go это безопасно
}

func main() {
	a := newPoint(1, 2)
	a.X = 5
	fmt.Println(*a)
}
```

Вывод: `{5 2}`. В Go это безопасно и так пишут постоянно. Компилятор видит, что
адрес `p` уходит за пределы функции, и размещает переменную там, где она
проживёт сколько нужно. Сборщик мусора освободит её, когда на неё не останется
указателей. Думать о том, «где лежит» переменная, вам не нужно.

Ещё один способ получить указатель — встроенная функция `new(T)`. Она создаёт
переменную типа `T` с нулевым значением и возвращает её адрес: `n := new(int)`
даёт `*int`, а `*n` равно `0`. На практике чаще пишут `&T{...}`, но `new`
встретится в чужом коде.

## Когда передавать по указателю

Не нужно превращать всё подряд в указатели «на всякий случай». Простое правило:

- **Передавайте по указателю**, если функция должна *изменить* аргумент так,
  чтобы изменение увидел вызывающий.
- **Передавайте по указателю**, если значение большое (крупная структура) и
  копировать его на каждый вызов накладно.
- **Передавайте по значению** во всех остальных случаях — для маленьких значений
  (`int`, `bool`, небольшие структуры) это проще, безопаснее и часто не медленнее.

Отдельно держите в голове: срезы (slices), отображения (maps) и каналы уже внутри
себя содержат ссылку на общие данные. Передавать `map` по указателю обычно не
нужно — функция и так меняет тот же самый map. Но это тема следующих глав; здесь
важно лишь не удивляться, что не каждый «изменяемый» тип требует `*`.

## Частые ошибки

- **Меняют сам указатель вместо значения по нему.** Внутри функции `n = &other`
  (где `n` имеет тип `*int`) меняет локальную копию адреса и не меняет указатель
  вызывающего. Запись `n = 42` вообще не компилируется: число не является указателем. Чтобы поменять данные, нужно `*n = 42`.
- **Разыменовывают `nil`.** `*p` при `p == nil` — это паника. Если указатель
  может быть «пустым», проверяйте `if p != nil` перед разыменованием.
- **Делают указатель там, где он не нужен.** `*int` для функции, которая просто
  читает число и ничего не меняет, только усложняет код и плодит лишние `&`/`*`.
  Нужна мутация или большая структура — да; иначе по значению чище.
- **Сравнивают адреса вместо значений.** `p == q` для двух указателей проверяет,
  смотрят ли они на одну и ту же переменную, а не равны ли значения. Две разные
  переменные со значением `5` дадут `false`; чтобы сравнить значения, пишите
  `*p == *q`.

## Попробуйте сами

Напишите `increase(p *int)`, которая прибавляет 3 к исходному числу. В `main` создайте `x := 7`, передайте `&x` и напечатайте `x`. Затем добавьте проверку, позволяющую безопасно вызвать `increase(nil)`.

### Проверка

После первого вызова `x` равен `10`; вызов с `nil` ничего не меняет и не вызывает панику:

```go
func increase(p *int) {
    if p == nil {
        return
    }
    *p += 3
}
```

`&x` передаёт адрес, `*p` обращается к значению по адресу. Сам `p` тоже копируется при вызове. Присвоение другого адреса локальному `p` не изменило бы `x`.

## Что дальше

В следующей главе, [«Срезы, карты, строки»](/go-basics/book/collections), указатели появятся неявно: срез внутри себя хранит адрес массива с данными. Это объяснит, почему функция, получившая копию среза, всё же может изменить его элементы. Затем [структуры и методы](/go-basics/book/structs) покажут, как применять получатель-указатель к собственному типу. К модели памяти нескольких горутин перейдём уже в продвинутом курсе, после освоения обычной передачи данных.
