Перейти к содержимому
Теория · Структуры и методы · урок 3 из 11

Методы

В прошлом уроке структура была анкетой с подписанными графами. Теперь научим анкету действовать. Представьте, что внизу бланка «Прямоугольник» напечатано: «Площадь: умножьте ширину на высоту». Кто держит заполненный бланк, тот сразу знает, как посчитать его площадь. Действие привязано к виду бланка, а не лежит в общем справочнике.

Посмотрим, зачем это нужно. Площадь есть и у прямоугольника, и у квадрата. Но две функции с именем area в одном пакете объявить нельзя, даже с разными типами параметров:

GO
package main

type Rect struct{ Width, Height float64 }

func area(r Rect) float64 { return r.Width * r.Height }

type Square struct{ Side float64 }

func area(s Square) float64 { return s.Side * s.Side } // ✗

func main() {}
TEXT
area redeclared in this block

Можно назвать функции rectArea и squareArea, но тогда имя каждой операции придётся дополнять именем типа. В Go операцию привязывают к самому типу.

Объявление метода

Метод — функция с дополнительным параметром, записанным в скобках перед именем. Этот параметр называют получателем. Запись func (r Rect) Area() float64 читается слева направо:

  • func — ключевое слово, как у обычной функции;
  • (r Rect) — получатель: имя r и тип Rect;
  • Area — имя метода;
  • () — обычные параметры, здесь их нет;
  • float64 — тип результата.

Вот методы Area у обоих типов:

GO
type Rect struct{ Width, Height float64 }

func (r Rect) Area() float64 { return r.Width * r.Height }

type Square struct{ Side float64 }

func (s Square) Area() float64 { return s.Side * s.Side }

Имя метода принадлежит типу, поэтому Rect.Area и Square.Area не конфликтуют. Методы объявляют вне фигурных скобок структуры, в любом месте пакета. Имя получателя обычно короткое — первая буква типа: r для Rect, s для Square.

Здесь картинка с бланком неточна: инструкция не напечатана внутри структуры. Внутри struct { … } перечисляют только поля, а метод с типом связывает лишь получатель.

Вызов метода

Метод вызывают через точку после значения: r.Area(). Значение перед точкой становится получателем, а внутри метода r — обычный параметр. Вызов r.Area() делает то же, что делала бы функция area(r).

GO
r := Rect{Width: 2, Height: 3}
s := Square{Side: 4}
fmt.Println(r.Area(), s.Area()) // 6 16

Давайте разберём вызовы:

  1. В r.Area() получателем становится r, и метод возвращает 2 * 3, то есть 6.
  2. В s.Area() Go вызывает уже метод Square, и он возвращает 4 * 4, то есть 16.
  3. fmt.Println печатает оба числа: 6 16.

Go выбирает метод по типу значения перед точкой.

Метод и функция

Этот вопрос часто задают на собеседовании: «Чем метод отличается от функции?» Ответ собирается из трёх пунктов:

  • метод — та же функция, но с получателем перед именем;
  • получатель привязывает метод к типу, и метод вызывают через точку: r.Area();
  • функцию без получателя вызывают по имени, без значения перед точкой.

Каким типам добавляют методы

Получателем может быть любой тип, объявленный через type в этом же пакете. Вот два частых случая.

Структура

Чаще всего получатель — структура, как Rect и Square выше. Метод читает её поля через получатель: r.Width, r.Height.

Свой тип на основе другого

Подходит и тип вроде type Meters float64. Он хранит такое же число, как float64, но это уже отдельный тип вашего пакета. Поэтому у него могут быть свои методы, например func (m Meters) Km() float64.

Так добавляют действия к числу или строке, не заводя структуру с одним полем. Но метод добавляют только к своему типу: к int или к типу из чужого пакета его не добавить.

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

Метод пытаются объявить у типа int, объявленного в самом языке:

GO
func (n int) Double() int { // ✗
    return n * 2
}
TEXT
cannot define new methods on non-local type int

Программа не компилируется: int объявлен не в вашем пакете. Объявите свой тип на основе int — type Number int — и добавьте метод к нему: func (n Number) Double() Number.

Главное

  • Метод — функция с получателем: func (r Rect) Area() float64.
  • Метод вызывают через точку, r.Area(), а значение перед точкой становится получателем.
  • Методы разных типов могут называться одинаково, а две функции в одном пакете — нет.
  • Метод можно добавить только к типу из своего пакета, но не к int.

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

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

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