Перейти к основному содержимому
Модуль 5 · Урок 2 из 522/50 в курсе~14 min
Уроки модуля (2/5)

Методы: значение и приемник указателя

Метод в Go — это функция с приемником: специальным параметр, объявленный между func и именем метода, который связывает метод к типу. Классов нет: методы можно определить на любой именованный тип, объявленный в текущем пакете.

Синтаксис

Go
type Person struct {
    Name string
}

func (p Person) Greet() string {
    return "ciao " + p.Name
}

p := Person{Name: "Ada"}
fmt.Println(p.Greet())   // "ciao Ada"

Получатель p Person превращает Greet в метод Person. Внутри метода, p — это переменная, как и любая другая.

Получатель значений и приемник указателей

Go
// VALUE receiver: operates on a COPY of the value
func (p Person) NameUpper() string {
    return strings.ToUpper(p.Name)
}

// POINTER receiver: operates on the original value, can mutate it
func (p *Person) Rename(n string) {
    p.Name = n
}

С помощью приемника указателя вы можете модифицировать приемник и вносить изменения. видны вне метода. С получателем значений вы не можете — вы бы изменяйте только локальную копию.

Go
p := Person{Name: "Ada"}
p.Rename("Grace")
fmt.Println(p.Name)   // "Grace" — change is visible

Когда использовать указательный приемник

Практические правила в порядке приоритета:

  1. Необходимо изменить приемник → приемник указателя. Обязательный.
  2. Структура большая (десятки полей, массивы внутри) → указатель приемник, чтобы избежать дорогостоящих копий.
  3. Согласованность: если ПО МИНИМУМ один метод типа имеет приемник указателя, используйте приемник указателя и для остальных тоже. Очень важная конвенция для интерфейсы (Модуль 6).
  4. Маленькие, неизменяемые, «значительные» типы (время.Время, комплексные числа, небольшие структуры) → приемник значений, более читабельный.

Методы для неструктурных типов

Вы можете определить методы для любого типа NAMED, объявленного в вашем пакете:

Go
type Celsius float64

func (c Celsius) ToFahrenheit() float64 {
    return float64(c)*9/5 + 32
}

t := Celsius(20)
fmt.Println(t.ToFahrenheit())  // 68

Вы не можете определить их для типов из других пакетов (например, int, string): вы должны сначала создайте локальный именованный тип.

Попробуйте это

Упражнение#go.m5.l2.e1
Попыток: 0Загрузка…

Добавьте к Person строковый метод Greet() (приемник значений), который возвращает «ciao <Name>».

Загрузка редактора…
Показать подсказку

Получатель в скобках ПЕРЕД именем метода.

Решение доступно после 3 попыток

Упражнение#go.m5.l2.e2
Попыток: 0Загрузка…

Добавьте метод Rename(n string) с приемником POINTER, который изменяет p.Name.

Загрузка редактора…
Показать подсказку

Приемник указателя: `(p *Person)`.

Решение доступно после 3 попыток

Quiz#go.m5.l2.e3
Готово

Когда следует использовать приемник указателя (p *T)?

Go
func (t ?T) M() { ... }
Варианты ответа

Резюме

  • Метод = функция с получателем: func (r T) Name(...) ... { ... }.
  • Получатель значений: работает с копией; Приемник указателя: работает по оригиналу.
  • Go автоматически выполняет преобразование T ↔ *T на месте вызова. — Приемник указателя при: мутации + большой структуре + согласованности с другими методами типа.
  • Никогда не смешивайте приемники значений и указателей одного типа. — Методы относятся к типам NAMED в текущем пакете (включая неструктурные).