指针新手 – 纠正我

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

Pointer newbie - Correct me

问题

我还在学习Go语言,需要帮助来理清思路。

以下程序在每个Println中输出Power值为1。我期望第一个输出为1,第二个输出为2
我的假设是Change函数用一个新地址覆盖了s的地址,并且这个改变会反映到调用者(main函数)中。在这种情况下,当第二个Println调用时,原始地址应该指向新创建的地址。
我的假设是错误的,但我无法弄清楚为什么。

package main

import (
	"fmt"
)

type Pod struct{
	Power int
}

func main() {
	pod := &Pod{1}
	fmt.Println(pod.Power)
	Change(pod)
	fmt.Println(pod.Power)
}

func Change(s *Pod) {
	s = &Pod{2}
}

为了进一步探索发生了什么,我尝试打印地址,结果如下:

import (
	"fmt"
)

type Pod struct{
	Power int
}

func main() {
	pod := &Pod{1}
	fmt.Println(&pod) //0xc04202c020
	Change(pod)
	fmt.Println(&pod) //0xc04202c020
}

func Change(s *Pod) {
	fmt.Println(&s) //0xc04202c030(我期望这里是0xc04202c020)
	s = &Pod{2}
	fmt.Println(&s) //0xc04202c030
}

代码

英文:

I am still learning go and need a hand to clear my head.

Following program output Power value as 1 in each Println. I was expecting 1 as first output and 2 as second output.
My assumption was the Change func overwrite the address of s with a new address and this change will reflect back to the caller (main func). In such case, the original address would be pointing to newly created address when it call the second Println.
My assumption is wrong but I can't figure out why.

package main

import (
	"fmt"
	
)
type Pod struct{
	Power int
}
func main() {
	pod := &Pod{1}
	fmt.Println(pod.Power)
	Change(pod)
	fmt.Println(pod.Power)
}
func Change(s *Pod) {
	s = &Pod{2}
}

Code

To explore further on what happens under cover, I did tried to print addresses this time and it looks like below;

import (
	"fmt"
)

type Pod struct{
	Power int
}
func main() {
	pod := &Pod{ 1}
	fmt.Println(&pod) //0xc04202c020
	Change(pod)
	fmt.Println(&pod) //0xc04202c020
}
func Change(s *Pod) {
	fmt.Println(&s) //0xc04202c030 ( I was expecting 0xc04202c020 here)
	s = &Pod{ 2}
	fmt.Println(&s) //0xc04202c030
}

答案1

得分: 4

这是因为当你将参数传递给函数时,它们总是按值传递的。

换句话说,即使在函数参数中接受一个指针,结构体的地址也会被复制。

当你尝试给s赋值来改变地址时,它只会改变本地副本,而不是函数外部的那个。

要在函数内部改变地址,你需要传入一个指针指针,然后对解引用的s进行赋值,例如:

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(&pod)
    fmt.Println(pod.Power)
}

func Change(s **Pod) {
    *s = &Pod{2}
}

在这种情况下,函数仍然传递了一个地址的副本,但因为它是指向指针的指针,所以当你将s解引用为*s时,你将得到函数外部结构体的地址。这意味着如果你对*s进行赋值,你可以改变函数外部指针的地址。

当然,就像Andy Schweig所说的那样,你可能真的不想这样做,而是使用普通指针版本的函数根据需要更改各个字段。

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(pod)
    fmt.Println(pod.Power)
}

func Change(s *Pod) {
    s.Power = 2
}

这个例子可以工作,因为当你输入s.Power = 2时,Go实际上会为你执行类似(*s).Power = 2的操作。所以它会自动解引用s,给你一个实际的Pod结构体来使用。

在这个普通指针的例子中,你不能执行*s = &Pod{2},因为在这种情况下,*s实际上将等于类型Pod,而不是*Pod

因此,如果你想使用&Pod{2}语法来分配一个地址,你需要传递一个指向指针的指针。在s **Pod的情况下,解引用的*s将指向Pod的地址,而不是实际的Pod,所以*s的类型将是*Pod,允许你分配&Pod{2}

话虽如此,只有当你想要使用&Pod{2}语法来分配一个地址时,才需要**Pod

如果你不需要使用&Pod{2}语法,你可以直接解引用s并使用普通的Pod{2}进行赋值。

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(pod)
    fmt.Println(pod.Power)
}

func Change(s *Pod) {
    *s = Pod{2}
}

这也可以工作,因为现在s是一个地址的副本,当你解引用它时,你得到的是函数外部的值,它的类型是Pod,而不是*Pod

这意味着你可以通过去掉&来对它进行赋值。

基本上,如果你使用&,它意味着你想要分配一个地址,而不是实际的值。

希望这个解释不会太令人困惑。我使用**Pod进行解释,因为我以为你想要使用&Pod{2}语法,而不是Pod{2},但如果不是这样的话,我的s.Power = 2*s = Pod{2}的例子可能更有意义。

英文:

It's because when you pass arguments to functions etc, they are always passed by value.

In other words, even though you are accepting a pointer in the function parameter, the address of the struct will be copied.

Then when you assign to s to try and change the address it only changes that local copy, not the one outside the function.

To change the address from within the function you would need to pass in a pointer pointer, then assign to the dereferenced s, for example:

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(&pod)
    fmt.Println(pod.Power)
}

func Change(s **Pod) {
    *s = &Pod{2}
}

In this case a copy of an address is still being passed into the function, but because it's a pointer to a pointer it means that when you dereference s as *s you will get the address of the struct outside of the function. This means if you assign to *s you can change the address of the pointer outside the function.

Of course, like Andy Schweig says, you probably wouldn't really want to do that though, and would probably just change individual fields as needed with the normal pointer version of the function.

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(pod)
    fmt.Println(pod.Power)
}

func Change(s *Pod) {
    s.Power = 2
}

This works because when you type s.Power = 2 Go will actually do something like (*s).Power = 2 for you. So it automatically dereferences s for you which gives you the actual Pod struct to work with.

You can't do *s = &Pod{2} in this normal pointer example because in that case *s will actually equal type Pod, not *Pod.

Because of that if you want to use the &Pod{2} syntax to assign an address you need to pass a pointer to the pointer. In the case of s **Pod the dereferenced *s will point to the address of the Pod instead of the actual Pod, so *s will be of type *Pod which allows you to assign &Pod{2}.

Having said all of that, a **Pod is only required if you want to assign an address with the &Pod{2} syntax.

If you don't need to use the &Pod{2} syntax you can just dereference s and assign with a normal Pod{2}.

package main

import (
    "fmt"
)

type Pod struct {
    Power int
}

func main() {
    pod := &Pod{1}
    fmt.Println(pod.Power)
    Change(pod)
    fmt.Println(pod.Power)
}

func Change(s *Pod) {
    *s = Pod{2}
}

This also works, because now s is a copy of an address, and when you dereference you get to the value outside the function which is type Pod, not *Pod.

That means you can just assign to it by removing the &.

Basically if you use & it means you want to assign an address rather than the actual value.

I hope this explanation isn't too confusing. I explained using a **Pod because I thought you wanted to use the &Pod{2} syntax rather than Pod{2}, but if that's not the case then my s.Power = 2 or *s = Pod{2} examples may make more sense.

答案2

得分: 2

将函数内部参数的值更改为函数本身永远不会对调用者传递的参数产生影响。所有参数都是按值传递的。如果你想在指向s所指向的对象内部更改Power的值(在调用者中为pod),可以使用s.Power = 2。如果你真的想将调用者中的指针变量设置为不同的Pod对象,你需要将Change函数的参数声明为s **pos,将函数中的赋值改为*s = &Pod{2},并调用Change(&pod)。不过,这可能不是你想要做的。

英文:

Changing the value of a parameter to a function inside the function never has an effect on the parameter passed by the caller. All parameters are passed by value. If you want to change the value of Power inside the object pointed to by s (pod in the caller), use s.Power = 2. If you actually want to set the pointer variable in the caller to a different Pod object, you need to declare the parameter to Change as s **pos, change the assignment in the function to *s = &Pod{2}, and call Change(&pod). That's probably not what you want to do, though.

huangapple
  • 本文由 发表于 2016年11月23日 08:21:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/40754061.html
匿名

发表评论

匿名网友

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

确定