What pointers may be used for in Go?

huangapple go评论83阅读模式
英文:

What pointers may be used for in Go?

问题

我认为我理解了指针是什么,但我不太明白何时使用它。

下面的代码片段来自《Go之旅》。

“*Vertex”和“&Vertex”的目的是什么?

我将它们替换为“Vertex”,代码仍然可以正常运行。

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 main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}
英文:

I think I understand what pointer is but I don't quite understand when to use it.<br>
The below snippet is from "A Tour of Go".<br>
What is the purpose of "*Vertex" and "&Vertex"? <br>
I replaced them with "Vertex" and it run fine.<br>

package main

import (
    &quot;fmt&quot;
    &quot;math&quot;
)

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 := &amp;Vertex{3, 4}
    fmt.Println(v.Abs())
}

答案1

得分: 5

这不是指针/值的区别的一个特别好的例子,因为在这种情况下它们是可以互换的!当你需要从另一个函数中远程改变数据时,指针是很有用的。

func (v Vertex) SetX(x int) {
    v.X = x
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v)
    v.SetX(1)
    fmt.Println(v)
}

正如你所看到的,这并没有改变任何东西(严格来说,它改变了顶点的副本,但在大多数情况下这只是语义上的问题)!v的值仍然是{3,4}。如果改用以下方式:

func (v *Vertex) SetX(x int) {
    v.X = x
}

func main() {
    v := &Vertex{3, 4}
    fmt.Println(v)
    v.SetX(1)
    fmt.Println(v)
}

突然间,它起作用了,第二次打印出{1,4}。现在,如果你好奇的话,你可以尝试将v := &Vertex{3, 4}改为v := Vertex{3, 4}。确实,上面的代码片段仍然有效。奇怪的是,如果你在第二个代码片段中更改相同的行以包含一个指针,它也会以相同的方式工作。

为什么呢?Go语言具有"透明"指针。在其他具有显式指针值的语言(如C或C++)中,你必须显式使用&*运算符来解引用指针。C和C++甚至在字段访问和方法调用上有指针追踪的特殊语法v->SetX

不管好坏,Go语言将这一点隐藏起来。如果你有一个值并且需要调用一个指针方法,Go会愉快地为你执行(&v).Method(),如果你需要解引用调用一个值方法,它会自动地执行(*v).Method()。这在大多数情况下都是正确的,但在一些像映射这样的边缘情况下可能不适用,但总体上是成立的。

那么,当涉及到在方法上使用指针接收器时,什么时候应该使用呢?答案真的是"大多数情况下"。Go风格指南通常建议在方法接收器上使用指针类型,除非接收器是mapfuncchan的直接别名,它是一个不需要重新切片的切片,或者你正在对小的、不可变的数据类型进行优化(因为指针追踪比复制稍微慢一点)。我还要补充的是,通常不应该直接使用指向指针的指针。

通常,当你不知道该使用哪个时,使用指针接收器。99%的情况下,使用指针将给你期望的行为,特别是如果你习惯于像Python或C#这样的语言。与因为你的Setter方法实际上没有设置任何东西而导致bug的概率相比,不正确地使用指针引起bug的概率相对较小。

英文:

That's not a particularly good example of the pointer/value distinction, because in that case they're interchangeable! Pointers are useful when you need to mutate data "remotely" (from another function).

func (v Vertex) SetX(x int) {
    v.X = x
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v)
    v.SetX(1)
    fmt.Println(v)
}

As you'll note, this doesn't change anything (strictly speaking, it changes a copy of the vertex, but that's just semantics in most cases)! The value of v is still {3,4}. Trying instead with:

func (v *Vertex) SetX(x int) {
    v.X = x
}

func main() {
    v := &amp;Vertex{3, 4}
    fmt.Println(v)
    v.SetX(1)
    fmt.Println(v)
}

And suddenly, it works, the second time it prints {1,4}. Now, if you're curious, you may decide to experiment and change v := &amp;Vertex{3, 4} to v := Vertex{3, 4}. Indeed, the above snippet still works. Strange. Likewise, if you change the same line in the second snippet to contain a pointer, it also works the same way.

Why? Go has "transparent" pointers. In other languages with explicit pointer values like C or C++, you have to explicitly use the operators &amp; and * to dereference a pointer. C and C++ even have special syntax for pointer chasing on field access and method calls v-&gt;SetX.

For better or worse, Go hides this from you. If you have a value and need to call a pointer method, Go will happily do (&amp;v).Method() for you, if you need to dereference to call a value method, it happily does (*v).Method() automatically. This is true in most cases, there are a few corner cases with things like maps where this doesn't apply, but in general this holds.

So, when it comes down to it, when should you use a pointer receiver on a method? The answer, really, is "most of the time." The Go Style Guide generally recommends using pointer type method receivers except when the receiver is a direct alias for a map, func, or chan, it's a slice that doesn't need reslicing, or you're doing optimizations on small, immutable data types (because pointer chasing is a little bit slower than copying). I'd add to that that you generally shouldn't use direct pointers to pointers.

Generally, when you have no idea which to use, use a pointer receiver. 99% of the time using a pointer will give you the behavior you expect, especially if you're used to languages like Python or C#. It's comparatively rare that incorrectly using a pointer causes a bug, compared the probability of getting a bug because your Setter method isn't actually setting anything.

答案2

得分: 3

这个特定的例子是不好的,因为在指针类型 *Vertex 上定义的方法没有尝试去改变(mutate)其接收者的值(方法被调用时的值)。

在Go语言中,所有的东西都是通过值传递/赋值的,包括指针。所以,当你有一个方法:

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

(注意接收者类型规范中没有 *Vertex 前面),它可以正常工作,因为当你这样做时:

v := Vertex{2, 3}
x := v.Abs()

v.Abs() 调用时的 v 的值被复制给了 Abs() 方法接收到的值。

现在假设你想要通过方法调用来改变(mutate)一些 Vertex 的变量。一种天真的方法,就像这样:

func (v Vertex) SetX(x float64) {
    v.X = x
}

v := Vertex{2, 3}
v.SetX(-5)
// 这里,v.X 仍然是 2

是行不通的,因为它会改变被调用时复制到被调用者的 X 的值 v;方法改变了副本的 X,这个改变只在方法的作用域中可见。

另一方面,如果你在指针上定义该方法(指针保存的是一个实际变量的地址,而不是值本身),那么就可以工作:

func (v *Vertex) SetX(x float64) {
    v.X = x
}

v := Vertex{2, 3}
v.SetX(-5)

在这里,编译器会在调用 SetX() 的时候取 v 的地址,并将其传递给方法。方法将使用该地址来引用调用者作用域中的值。

这种语法上的混淆是因为Go语言(在大多数情况下)允许你不使用操作符来取地址和解引用该地址。

如果你来自于像PHP、Python等流行语言之一,主要的区别在于它们中的许多对象是“特殊的”,并且总是通过引用传递。Go语言更低级,尽量不在程序员背后使用魔法,所以你必须明确地指定你想要将指针还是值的副本传递给一个方法。

请注意,这不仅仅是关于一个方法能否改变其接收者的问题;性能问题也可能在这里起作用。

英文:

This particular example is bad because the method defined on pointer type, *Vertex, does not attempt to mutate the value of its receiver (the value the method is called on).

In Go, everything is ever passed/assigned by value &mdash; including pointers. So, when you have a method

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

(notice there's no * in front of Vertex in the receiver's type specification), it works just OK because when you do

v := Vertex{2, 3}
x := v.Abs()

the value of v at the v.Abs() call site is copied to the value the Abs() method receives.

Now suppose you want to change (mutate) some of the Vertex's variables using a method call. A naive approach, like in,

func (v Vertex) SetX(x float64) {
    v.X = x
}

v := Vertex{2, 3}
v.SetX(-5)
// Here, v.X is still 2

won't work because it will change X of the value v which has been copied to the callee when the call was made; the method changed the X of the copy&mdash;a change only seen in the method's scope.

On the other hand, if you were to define that method on the pointer (which holds the address of an actual variable holding a value instead of the value itself), that would work:

func (v *Vertex) SetX(x float64) {
    v.X = x
}

v := Vertex{2, 3}
v.SetX(-5)

Here, the compiler would take the address of v at the point SetX() is called and pass it to the method. The method would then use that address to refer to the value in the caller's scope.

The syntactic confusion is because Go (in most cases) allows you to not use operators to take address of a value and dereference that address.

If you're coming from one of popular languages like PHP, Python etc the chief difference is that in many of them objects are "special" and are always passed by reference. Go is more low-level and tries not to use magic behind programmer's back, so you have to be explicit about whether you want to pass a pointer or a copy of the value to a method.

Note that this is not only about whether a method is able or is not able to mutate its receiver; performance things might also play a role here.

huangapple
  • 本文由 发表于 2014年6月9日 20:54:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/24120789.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定