Голанские указатели
В настоящее время я участвую в программировании на языке Go.
У меня возникают некоторые трудности с пониманием указателей Go (и мой C/С++ уже далеко...).
Например, в Tour of Go # 52 (http://tour.golang.org/#52) я читал:
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
Но если вместо
func (v *Vertex) Abs() float64 {
[...]
v := &Vertex{3, 4}
Я написал:
func (v Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
Или даже:
func (v Vertex) Abs() float64 {
[...]
v := &Vertex{3, 4}
и наоборот:
func (v *Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
Я получил тот же результат. Есть ли разница (память и т.д.)?
Ответы
Ответ 1
Существуют два разных правила языка Go, используемые вашими примерами:
-
Можно получить метод с приемником указателя из метода с приемником значений. Таким образом, func (v Vertex) Abs() float64
автоматически сгенерирует реализацию дополнительного метода:
func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) }
func (v *Vertex) Abs() float64 { return Vertex.Abs(*v) } // GENERATED METHOD
Компилятор автоматически найдет сгенерированный метод:
v := &Vertex{3, 4}
v.Abs() // calls the generated method
-
Go может автоматически принимать адрес переменной. В следующем примере:
func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) }
func main() {
v := Vertex{3, 4}
v.Abs()
}
выражение v.Abs()
эквивалентно следующему коду:
vp := &v
vp.Abs()
Ответ 2
Есть различия. Например, форма получателя не указателя заставляет метод работать с копией. Таким образом, метод не может мутировать экземпляр, на который он был вызван, - он может получить доступ только к копии. Что может быть неэффективным с точки зрения, например, время/производительность/потребление памяти и т.д.
OTOH, указатель на экземпляры и методы с помощью указателей-указателей позволяет легко использовать общий доступ к экземплярам (и мутировать).
Подробнее здесь.
Ответ 3
Разница заключается в прохождении через реферив против пропущенного значения.
В func f(v Vertex)
аргумент копируется в параметр v
. В func f(v *Vertex)
передается указатель на существующий экземпляр Vertex
.
При использовании методов для вас может быть выполнено разделение на разы, поэтому вы можете иметь метод func (v *Vertex) f()
и вызывать его, не указав сначала: v := Vertex{...}; v.f()
. Это всего лишь зерно синтаксического сахара, AFAIK.
Ответ 4
В этих примерах есть два основных отличия:
func (v *Vertex) Abs()....
Получатель будет передан по ссылке для v, и вы сможете вызвать этот метод только по указателям:
v := Vertex{1,3}
v.Abs() // This will result in compile time error
&v.Abs() // But this will work
С другой стороны,
func (v Vertex) Abs() ....
Вы можете вызвать этот метод как для указателей, так и для структур. Приемник будет передаваться по значению, даже если вы вызываете этот метод указателями.
v := Vertex{1,3}
v.Abs() // This will work, v will be copied.
&v.Abs() // This will also work, v will also be copied.
Вы можете объявить как func (v *Vertex)
, так и func (v Vertex)
.
Ответ 5
Как указано в описании
Вызов метода x.m() действителен, если набор методов (тип) x содержит m, а список аргументов может быть назначен списку параметров m. Если x адресуется, а набор методов & x содержит m, x.m() является сокращением для (& x).m():
В вашем случае v.Abs() является сокращением для & v.Abs(), если метод адресуется.