调用带有指针接收器的函数的Go语法

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

Go Syntax of calling a function with pointer receiver

问题

在Go语言中,如果我使用指针作为接收器定义一个函数,那么它不应该只允许从指针调用该函数吗?为什么从值本身调用该函数并且产生相同的效果也是可以的。

例如,在下面的程序中:m1.reset()和m2.reset()产生相同的效果。尽管m1是一个值,m2是一个指针。

我有点困惑,因为有两种做同样事情的方式,我不确定应该遵循哪一种。虽然大部分代码都遵循使用指针字段调用函数的约定。我是否遗漏了什么?

package main

import "fmt"

type MyStruct struct {
    X int
}

func (m *MyStruct) reset() {
    m.X = 0
}

func main() {
    m1 := MyStruct{1}
    m2 := &MyStruct{1}

    fmt.Println(m1.X)
    fmt.Println(m2.X)

    m1.reset()
    m2.reset()

    fmt.Println(m1.X)
    fmt.Println(m2.X)
}
英文:

In Go, if I define a function with pointer as the receiver, shouldn't it allow call to the function from a pointer only? Why is it ok to call this function from the value itself and have the same effect.

For example, in following program: m1.reset() & m2.reset() have the same effect. Even though m1 is a value and m2 is a pointer.

I'm a bit confused as there are two ways of doing the same thing and am not sure which one to follow. Though most of the code follows the convention of calling the function using pointer field. Am I missing something?

package main
    
    import "fmt"
    
    type MyStruct struct {
        X int
    }
    
    func (m *MyStruct) reset() {
        m.X = 0
    }
    
    func main() {
        m1 := MyStruct{1}
        m2 := &MyStruct{1}
    
        fmt.Println(m1.X)
        fmt.Println(m2.X)
    
        m1.reset()
        m2.reset()
    
        fmt.Println(m1.X)
        fmt.Println(m2.X)
    }

答案1

得分: 11

@jnml提供了完美的文档规范解释,但我想根据你的代码添加一个代码示例。我认为你应该更关注于“为什么有两种方法可以做同样的事情”的问题,而不是关于何时使用其中一种的问题。一个以指针作为接收器的方法有能力修改该接收器的值,而一个以值作为接收器的方法则不能。这是因为方法接收到的是接收器的副本。当你得到一个指针的副本时,你仍然可以修改它的值。当你得到一个值的副本时,在该方法中所做的更改只会改变副本,而不会改变原始值:

package main

import "fmt"

type MyStruct struct {
    X int
}

func (m *MyStruct) resetPtr() {
    m.X = 0
}

func (m MyStruct) resetValue() {
    m.X = 0
}

func main() {
    m1 := MyStruct{1}
    m2 := &MyStruct{1}
    
    fmt.Println("Original Values:", m1.X, m2.X)

    m1.resetPtr()
    m2.resetPtr()
    
    fmt.Println("After resetPtr():", m1.X, m2.X)
    
    m1 = MyStruct{1}
    m2 = &MyStruct{1}
    
    m1.resetValue()
    m2.resetValue()
    
    fmt.Println("After resetValue():", m1.X, m2.X)
}

输出

Original Values: 1 1
After resetPtr(): 0 0
After resetValue(): 1 1

你可以看到,访问这些变量的方式并不是真正的问题。更重要的是你在方法内部可以做什么以及它们作为参数传递给其他函数或方法时的方式(被复制)。

英文:

@jnml offers the perfect doc spec explanation, but I wanted to add a code example based on yours. I think your focus should be less about "Why are there two ways to do the same thing" and more about when to use one vs the other. A method which has a pointer as the receiver has the ability to modify the values of that receiver, while a method which has a value as the receiver cannot. This is because the methods receive a copy of the receiver. When you get a copy of a pointer, you can still modify its value. When you receive a copy of the value, changes you make in that method only change the copy, and never the original:

package main

import "fmt"

type MyStruct struct {
	X int
}

func (m *MyStruct) resetPtr() {
	m.X = 0
}

func (m MyStruct) resetValue() {
	m.X = 0
}

func main() {
	m1 := MyStruct{1}
	m2 := &MyStruct{1}
	
	fmt.Println("Original Values:", m1.X, m2.X)

	m1.resetPtr()
	m2.resetPtr()
	
	fmt.Println("After resetPtr():", m1.X, m2.X)
	
	m1 = MyStruct{1}
	m2 = &MyStruct{1}
	
	m1.resetValue()
	m2.resetValue()
	
	fmt.Println("After resetValue():", m1.X, m2.X)
}

Output

Original Values: 1 1
After resetPtr(): 0 0
After resetValue(): 1 1

You can see that the way you access these variables isn't really the issue. Its more about what you can do with them inside of the method, and, how they are passed as arguments to other functions or methods (being copied).

答案2

得分: 8

Specs says:

> 相应指针类型 *T 的方法集是所有具有接收器 *T 或 T 的方法的集合(也就是说,它还包含 T 的方法集)。

关于方法调用的下一个必要信息如下:

> 如果 x 的(类型的)方法集包含 m 并且参数列表可以分配给 m 的参数列表,则方法调用 x.m() 是有效的。如果 x 是可寻址的并且 &x 的方法集包含 m,则 x.m()(&x).m() 的简写。

将上述两个内容结合起来,你就能得到你所看到的行为。

英文:

Specs says:

> The method set of the corresponding pointer type *T is the set of all methods with receiver *T or T (that is, it also contains the method set of T).

The next piece of necessary info about method calls says:

> A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m().

Put the two above things together and you get the behavior you see.

答案3

得分: 3

一个简短的解释是,Go编译器在后台自动将以下代码转换为:

(&m1).reset()
m2.reset()
英文:

A short explanation is that the Go compiler behind the scenes automatically converts:

m1.reset()
m2.reset()

into:

(&m1).reset()
m2.reset()

huangapple
  • 本文由 发表于 2012年4月1日 01:26:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/9958064.html
匿名

发表评论

匿名网友

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

确定