Модуль
1Указатели: основы← вы здесь2Value и pointer receivers3Устройство типов в памяти4Стек, куча и escape analysis
Урок 1~12 минут

Указатели: основы

Адрес — это тоже данные

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

go
x := 42
p := &x   // p хранит адрес переменной x
 
fmt.Println(x)   // 42       — само значение
fmt.Println(&x)  // 0xc000018090 — адрес x в памяти
fmt.Println(p)   // 0xc000018090 — p хранит тот же адрес
fmt.Println(*p)  // 42       — значение по адресу (разыменование)

Два оператора:

  • &x — взять адрес переменной x
  • *p — получить значение по адресу p

Тип указателя на int записывается как *int. Указатель на string*string. И так для любого типа.


Изменение значения через указатель

Разыменование работает в обе стороны — можно не только читать, но и писать по адресу:

go
x := 10
p := &x
 
*p = 99   // записываем 99 по адресу, который хранит p
 
fmt.Println(x)  // 99 — оригинальная переменная изменилась

p и x — разные переменные, но *p и x обращаются к одному месту в памяти.


Почему это важно: передача по значению

В Go все аргументы функции передаются по значению — функция получает копию. Это значит, что изменения внутри не затрагивают оригинал:

go
func double(n int) {
    n = n * 2
    fmt.Println("внутри:", n) // 20
}
 
func main() {
    x := 10
    double(x)
    fmt.Println("снаружи:", x) // 10 — не изменился
}

Чтобы функция могла изменить переменную снаружи, передайте указатель:

go
func double(n *int) {
    *n = *n * 2
}
 
func main() {
    x := 10
    double(&x)              // передаём адрес x
    fmt.Println(x)          // 20 — изменился!
}

Функция получила адрес x и записала новое значение прямо туда.


Функция new

Стандартная функция new(T) выделяет память под тип T, инициализирует нулём и возвращает *T:

go
p := new(int)     // *int, значение = 0
*p = 55
fmt.Println(*p)   // 55
 
s := new(string)  // *string, значение = ""
*s = "hello"
fmt.Println(*s)   // hello

На практике new используется редко. Чаще берут адрес уже существующей переменной (&x) или создают структуру через &MyStruct{...}.


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

Указатели особенно часто встречаются со структурами. Go позволяет не писать (*p).Field — точка автоматически разыменовывает:

go
type Point struct {
    X, Y int
}
 
p := &Point{X: 3, Y: 4}
 
// Два эквивалентных способа
fmt.Println((*p).X)  // 3 — явное разыменование
fmt.Println(p.X)     // 3 — автоматическое (удобнее)
 
// Изменение через указатель
p.X = 10
fmt.Println(p.X)     // 10

Когда метод структуры объявлен с pointer receiver (func (p *Point) Scale(f int)), он получает указатель и может изменять поля — это именно этот механизм.


Nil-указатель

Указатель, которому ничего не присвоили, равен nil. Разыменовать его нельзя — будет паника:

go
var p *int
fmt.Println(p)       // <nil>
fmt.Println(p == nil) // true
 
// Паника: runtime error: invalid memory address or nil pointer dereference
// fmt.Println(*p)

Правило простое: перед разыменованием проверяй nil, если указатель пришёл извне (аргумент функции, поле структуры):

go
func printValue(p *int) {
    if p == nil {
        fmt.Println("указатель nil")
        return
    }
    fmt.Println(*p)
}

Когда использовать указатели

СитуацияРекомендация
Функция должна изменить аргумент*T
Большая структура (копирование дорого)*T
Маленькое значение (int, bool, small struct)по значению
Нужно выразить «отсутствие значения»*T (nil = нет)

В отличие от C, в Go нет арифметики указателей — нельзя написать p++ или p + 2. Указатель либо указывает на конкретную переменную, либо nil. Это сознательное ограничение для безопасности.

В Go нет арифметики указателей как в C. Указатель — это просто адрес: & берёт адрес, * разыменовывает. Передача по указателю позволяет функции изменить оригинал.

Оператор & возвращает адрес переменной. Оператор * разыменовывает указатель — читает или изменяет значение по адресу.

x := 42
p := &x      // p содержит адрес x

fmt.Println(x)    // 42
fmt.Println(&x)   // 0xc0000180a8
fmt.Println(p)    // 0xc0000180a8  (тот же адрес)
fmt.Println(*p)   // 42            (значение по адресу)
переменная x
420xc0000180a8
int
p = &x
--->
указатель p
0xc0000180a80xc0000180b0
*int
x
42
&x
0xc0000180a8
p
0xc0000180a8
*p
42
&x == p
YES
🎯
Миссия 1 из 4
Какой оператор возвращает адрес переменной?