Методы в Go
Методы отличаются от функций тем, что имеют получателя, и только пользовательские типы могут иметь методы. Пример:
type IntSlice []int
func (i IntSlice) Get(index int) int {
return i[index]
}
func (i IntSlice) Set(index, val int) {
i[index] = val
}
func (i IntSlice) Len() int {
return len(i)
}Объявлен тип IntSlice на основе []int и три метода: Get, Set, Len. Методы похожи на функции, но имеют (i IntSlice) — получатель. i — получатель, IntSlice — его тип. Получатель аналогичен this или self в других языках, но в Go указывается явно.
func main() {
var intSlice IntSlice
intSlice = []int{1, 2, 3, 4, 5}
fmt.Println(intSlice.Get(0))
intSlice.Set(0, 2)
fmt.Println(intSlice)
fmt.Println(intSlice.Len())
}Использование методов похоже на вызов методов класса: объявление, инициализация, вызов.
Получатель по значению
Существует два типа получателей: по значению и по указателю. Пример:
type MyInt int
func (i MyInt) Set(val int) {
i = MyInt(val) // изменяется, но без эффекта
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}myInt остаётся 1. При вызове метода значение получателя передаётся в метод. Получатель по значению — формальный параметр, его изменение не влияет на внешнее значение. Что если вызвать через указатель?
func main() {
myInt := MyInt(1)
(&myInt).Set(2)
fmt.Println(myInt)
}Код не изменит значение. Go интерпретирует это как (*(&myInt)).Set(2).
Получатель по указателю
type MyInt int
func (i *MyInt) Set(val int) {
*i = MyInt(val)
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}Теперь получатель — указатель. Хотя myInt — значение, при вызове метода с получателем-указателем Go интерпретирует это как (&myInt).Set(2). Таким образом, метод с получателем-указателем может изменять значение независимо от типа вызывающего объекта.
При передаче параметров функции копируются значения. Передача указателя эффективнее передачи среза. Рекомендуется использовать получатель по указателю, но не следует смешивать оба типа.
TIP
Сначала ознакомьтесь с интерфейсами.
type Animal interface {
Run()
}
type Dog struct {
}
func (d *Dog) Run() {
fmt.Println("Run")
}
func main() {
var an Animal
an = Dog{}
// an = &Dog{} правильный способ
an.Run()
}Код не скомпилируется:
cannot use Dog{} (value of type Dog) as type Animal in assignment:
Dog does not implement Animal (Run method has pointer receiver)Run реализован для *Dog, а не для Dog. Решения:
- Изменить получатель на значение
- Использовать
&Dog{}
type Dog struct {
}
func (d Dog) Run() {
fmt.Println("Run")
}
func main() {
var an Animal
an = Dog{}
// an = &Dog{} тоже работает
an.Run()
}При использовании получателя по значению *Dog также может быть присвоен Animal, так как Go автоматически разыменовывает указатель. Однако смешивание получателей может вызвать ошибки с интерфейсами. Рекомендуется придерживаться одного стиля.
Особый случай: адресуемые значения
Для адресуемых получателей Go автоматически вставляет оператор указателя. Например, срезы адресуемы:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func main() {
s := make(Slice, 1)
s.Set(0, 1)
fmt.Println(s)
}Вывод:
[1]Но с append ситуация иная:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func (s Slice) Append(a int) {
s = append(s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}Вывод:
[1]append возвращает срез, особенно после расширения. Изменение получателя по значению не влияет на оригинал. Решение — получатель по указателю:
type Slice []int
func (s *Slice) Set(i int, v int) {
(*s)[i] = v
}
func (s *Slice) Append(a int) {
*s = append(*s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}Вывод:
[1 2]