Вы можете объявлять методы, где в качестве получателей выступают указатели.
Это означает, что получатель объявлен как *T для некоторого типа T. (И еще, T не может быть указателем сам по себе, как, например, *int.)
В следующем примере метод Scale объявлен для *Vertex.
Методы с получателями-указателями могут модифицировать значение, на которое указывает получатель (как делает Scale в примере). Т.к. методам часто необходимо модифицировать получателя, то использование указателей более частая практика, чем получатели как значения.
При использовании получателя-значения, метод Scale работает с копией оригинального значения Vertex. (Это такое же поведение, как и для любого другого аргумента функции.) Метод Scale должен иметь указатель на получателя для того, чтобы изменить значение Vertex, объявленное в функции main.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}
Вывод:
50
Указатели и функции
Здесь мы видим методы Abs и Scale переписанные в качестве функций.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}
Вывод:
50
Методы и косвенная адресация указателей
Сравнивая две предыдущие программы, вы могли обратить внимание, что функции с аргументом указателем должны принимать указатель:
var v Vertex
ScaleFunc(v) // Ошибка компиляции!
ScaleFunc(&v) // OK
тогда как методы объявленные с получателем-указателем могут принимать как значение, так и указатель при вызове:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
В выражении v.Scale(5), хотя v является значением, а не указателем, метод с получателем-указателем вызывается автоматически. Это потому, что Go в качестве удобства интерпретирует v.Scale(5) как (&v).Scale(5), т.к. метод Scale объявлен для получателя-указателя.
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)
p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)
fmt.Println(v, p)
}
Вывод:
{60 80} &{96 72}
То же самое происходит и в обратном направлении.
Функции с аргументом значением должны принимать значение конкретного типа:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Ошибка компиляции!
тогда как методы с получателем-значением могут принимать как значение, так и указатель при вызове:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
В этом случае вызов метода p.Abs() интерпретируется как (*p).Abs().
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}
Вывод:
5
5
5
5
Выбирая между получателем как значение или указателем
Есть две причины, чтобы использовать указатель для получателя.
Во-первых, метод может изменять значение, на которое ссылается указатель.
Во-вторых, тем самым можно избежать копирования значения при каждом вызове метода. Это может быть более эффективно, если получателем является большая структура, например.
В следующем примере у Scale и Abs в качестве получателя указан тип *Vertex, хотя метод Abs не имеет необходимости изменять получателя.
В общем, все методы для типа должны иметь либо значение, либо указатель в качестве получателя, но не оба сразу.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
Вывод:
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25
Читайте также:
Комментариев нет:
Отправить комментарий