方法集(指针接收器 vs 值接收器)

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

Method Sets (Pointer vs Value Receiver)

问题

我很难理解为什么这些规则与指针类型和值类型的方法集相关联。

可以有人从接口表的角度解释一下原因吗?

(摘自William Kennedy的博客)

值类型          方法接收者
-----------------------------------------------
T               (t T)
*T              (t T) 和 (t *T)

方法接收者    值类型
-----------------------------------------------
(t T)                 T 和 *T
(t *T)                *T

规范中的摘录

方法集

一个类型可以有一个与之关联的方法集。接口类型的方法集就是它的接口。任何其他类型T的方法集由所有声明了接收者类型为T的方法组成。相应指针类型T的方法集是所有声明了接收者类型为T或T的方法的集合(也就是说,它还包含了T的方法集)。对于包含匿名字段的结构体,还有其他规则,详见结构体类型的章节。其他任何类型都有一个空的方法集。在方法集中,每个方法必须有一个唯一的非空方法名。

类型的方法集确定了该类型实现的接口以及可以使用该类型的接收者调用的方法。

英文:

I am having a hard time understanding as to why are these rules associated with method set of pointer type .vs. value type

Can someone please explain the reason (from the interface table perspective)

(Snippet from William Kennedy's blog)

Values          Methods Receivers
-----------------------------------------------
T               (t T)
*T              (t T) and (t *T)

Methods Receivers    Values
-----------------------------------------------
(t T)                 T and *T
(t *T)                *T

Snippet from specification

Method sets

A type may have a method set associated with it. The method set of an interface type is its interface.
The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing anonymous fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

答案1

得分: 49

  1. 如果你有一个 *T,你可以调用接收者类型为 *T 的方法,也可以调用接收者类型为 T 的方法(你引用的段落,方法集)。
  2. 如果你有一个 T 并且它是可寻址的,你可以调用接收者类型为 *T 的方法,也可以调用接收者类型为 T 的方法,因为方法调用 t.Meth() 等效于 (&t).Meth()调用)。
  3. 如果你有一个 T 并且它不可寻址(例如,函数调用的结果,或者从映射中索引的结果),Go 无法获取指向它的指针,因此你只能调用接收者类型为 T 而不是 *T 的方法。
  4. 如果你有一个接口 I,并且 I 的方法集中的一些或全部方法由接收者为 *T 的方法提供(其余方法由接收者为 T 的方法提供),那么 *T 满足接口 I,但 T 不满足。这是因为 *T 的方法集包括 T 的方法集,但反过来不成立(回到第一点)。

简而言之,你可以混合使用值接收者和指针接收者的方法,并将它们与包含值和指针的变量一起使用,而不必担心它们的区别。两者都可以工作,语法相同。然而,如果需要使用指针接收者的方法来满足一个接口,那么只有指针才能赋值给该接口,值是无效的。

英文:
  1. If you have a *T you can call methods that have a receiver type of *T as well as methods that have a receiver type of T (the passage you quoted, Method Sets).
  2. If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have a receiver type of T, because the method call t.Meth() will be equivalent to (&t).Meth() (Calls).
  3. If you have a T and it isn't addressable (for instance, the result of a function call, or the result of indexing into a map), Go can't get a pointer to it, so you can only call methods that have a receiver type of T, not *T.
  4. If you have an interface I, and some or all of the methods in I's method set are provided by methods with a receiver of *T (with the remainder being provided by methods with a receiver of T), then *T satisfies the interface I, but T doesn't. That is because *T's method set includes T's, but not the other way around (back to the first point again).

In short, you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same. However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won't be valid.

答案2

得分: 7

Golang FAQ中可以得知:

根据Go语言规范,类型T的方法集包含所有接收者类型为T的方法,而相应指针类型T的方法集包含所有接收者类型为T或T的方法。这意味着*T的方法集包含T的方法集,但反过来不成立。

这种区别的原因是,如果一个接口值包含指针*T,方法调用可以通过解引用指针来获取值,但如果一个接口值包含值T,方法调用无法安全地获取指针。(这样做将允许方法修改接口内部值的内容,这是语言规范不允许的。)

即使在编译器可以获取值的地址并将其传递给方法的情况下,如果方法修改了该值,更改将在调用者中丢失。例如,如果bytes.Buffer的Write方法使用值接收者而不是指针,那么以下代码:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

将把标准输入复制到buf的副本中,而不是buf本身。这几乎永远不是期望的行为。

关于Golang接口的内部实现细节,可以参考以下链接:

英文:

From Golang FAQ:
> As the Go specification says, the method set of a type T consists of all methods with receiver type T, while that of the corresponding pointer type *T consists of all methods with receiver *T or T. That means the method set of *T includes that of T, but not the reverse.
>
> This distinction arises because if an interface value contains a pointer *T, a method call can obtain a value by dereferencing the pointer, but if an interface value contains a value T, there is no safe way for a method call to obtain a pointer. (Doing so would allow a method to modify the contents of the value inside the interface, which is not permitted by the language specification.)
>
> Even in cases where the compiler could take the address of a value to pass to the method, if the method modifies the value the changes will be lost in the caller. As an example, if the Write method of bytes.Buffer used a value receiver rather than a pointer, this code:
>go
>var buf bytes.Buffer
>io.Copy(buf, os.Stdin)
>

> would copy standard input into a copy of buf, not into buf itself. This is almost never the desired behavior.

About Golang interface under the hood.

答案3

得分: 0

  • 在Go语言中,当我们有一个类型时,我们可以在其上附加方法,这些附加到类型上的方法被称为其方法集。
  • 根据指针或非指针值的情况,它将确定附加到哪个方法。

案例1
接收器(t T) 值 T => https://go.dev/play/p/_agcEVFaySx

type square struct { 
	length int
}

type shape interface { // 将 shape 定义为接口
	area() int
}
// 接收器(t T)
func (sq square) area() int { 
	return sq.length * sq.length
}

func describe(s shape) {
	fmt.Println("area", s.area())
}

func main() {
	sq := square{
		length: 5,
	}
	describe(sq) // 值 `sq` (T)
}

案例2: 接收器(t T) 值 T

// 接收器(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(sq) // 值 sq (T)
}

案例3: 接收器(t *T) 值 T

// 接收器(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(&sq) // 值 sq (*T)
}

案例4: 接收器(t *T) 值 T
这个案例失败了

// 接收器(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(&sq) // 值 sq (T)
}

> 我们输入的是普通值而不是指针,但是方法接收器需要指针值,因此它不接受,失败了。

但是我们可以这样调用 area 方法 sq.area()// 而不是使用接口来访问它。

英文:

-In go when we have a type we can attach methods on it, those methods attached to type are known as its method set.

  • Depending on Pointer or not pointer value , it will determine which method attach to it.

Case:1
Receiver (t T) Value T => https://go.dev/play/p/_agcEVFaySx

type square struct { 
	length int
}

type shape interface { shape as an interface
	area() int
}
// receiver(t T)
func (sq square) area() int { 
	return sq.length * sq.length
}

func describe(s shape) {
	fmt.Println("area", s.area())
}

func main() {
	sq := square{
		length: 5,
	}
	describe(sq)// value `sq` (T)
}

Case 2: Receiver (t T) Value T

// receiver(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(sq)// value sq (T)
}

Case 4: Receiver (t *T) Value T

// receiver(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(&sq)// value sq (*T)
}

Case 4: Receiver (t *T) Value T
this case fails

// receiver(t *T)
func (sq *square) area() int { 
	return sq.length * sq.length
}

func main() {
	describe(&sq)// value sq (T)
}

> we input normal value rather than pointer , but method receiver takes pointer value,it will not accept ,fails.

But we call area method like this sq.area()//rather than using interface to access it.

huangapple
  • 本文由 发表于 2015年11月8日 04:09:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/33587227.html
匿名

发表评论

匿名网友

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

确定