下面的代码是一个比较好的面试题
请问下面的代码是否能通过编译?
package mainimport "fmt"type People interface {Speak(string) string
}type Student struct{}func (stu *Student) Speak(think string) (talk string) {if think == "sb" {talk = "你是个大帅比"} else {talk = "您好"}return
}func main() {var peo People = Student{}think := "bitch"fmt.Println(peo.Speak(think))
}
编译输出
cannot use Student{} (value of type Student) as People value in variable declaration: Student does not implement People (method Speak has pointer receiver)
答案
答案是不能,提示Stduent does not implement People (Speak method has pointer receiver),
修正方法是var peo People = &Stduent{}
,赋值为指针
在Go语言中,函数和方法是不一样的,函数是没有接收者的,而方法是有接收者的,属于某个结构体。
接受者有两种:value receivers(按值传递),pointer receivers(按指针传递,可能改变传入的参数)
之前看到有地方解释道:pointer receivers的方法既能传值也能传指针,包含了value receivers的方法。
这个理解是错误的。
从这道题来看不是这样的,value receivers和pointer receivers是有明确区分的,people interface是value receivers,而student实现了pointer receivers的speak方法,所以student没有实现peple。
事实上pointer receivers的方法只是将m.speak()自动转换为(&m).speak(),这点官方的go tour中就有提到,go编译器帮你省去了取地址的一步。
Golang FAQ
Should I define methods on values or pointers?
func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct) valueMethod() { } // method on value
For programmers unaccustomed to pointers, the distinction between these two examples can be confusing, but the situation is actually very simple. When defining a method on a type, the receiver (s
in the above examples) behaves exactly as if it were an argument to the method. Whether to define the receiver as a value or as a pointer is the same question, then, as whether a function argument should be a value or a pointer. There are several considerations.
First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.) In the examples above, if pointerMethod
modifies the fields of s
, the caller will see those changes, but valueMethod
is called with a copy of the caller’s argument (that’s the definition of passing a value), so changes it makes will be invisible to the caller.
By the way, in Java method receivers have always been pointers, although their pointer nature is somewhat disguised (and recent developments are bringing value receivers to Java). It is the value receivers in Go that are unusual.
Second is the consideration of efficiency. If the receiver is large, a big struct
for instance, it may be cheaper to use a pointer receiver.
Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.
For types such as basic types, slices, and small structs
, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.
我应该使用值还是指针定义方法
func (s *MyStruct) pointerMethod() { } // 指针方法
func (s MyStruct) valueMethod() { } // 值方法
对于不熟悉指针的程序员,很难理解这两个例子的区别,实际上这很简单。为类型定义方法时,接收者(上面例子中的 s)实际扮演的是方法的一个参数。判断使用值还是指针定义方法与判断使用值还是指针作为函数参数是相同的问题。有以下几点考虑。
首先,最重要的是,这个方法是否需要改变接收者?如果是,接收者必须是指针。(切片和映射扮演引用的角色,他们的情况更微妙,但如果要在一个方法中改变切片的长度,接收者依然必须是指针。)在上面的例子中,如果指针方法改变 s 的字段,调用者可以察觉那些更改,但是值方法使用的是调用者参数的一个副本(这就是值传递的定义),所以更改对调用者是不可见的。
顺便说一下,在 Java 中,方法的接收者永远是指针,尽管它们的指针是经过包装的(也有提议加入值接受者)。所以 go 的值接收者并不常见。
其次,是性能上的考虑。如果接收者是一个很大的结构体实例,使用指针接收者的开销要小很多。
然后是一致性的考虑。如果一个类型的某些方法必须是指针接收者,那么其他方法也应该如此,这样使用者才可以一致地调用该类型的方法集。查看 方法集 获取更多细节。
对于基础类型,切片,和小的结构体,值接收者的开销更小,所以除非方法在语义上需要指针接收者,使用值接收者会更高效和明确。
参考资料
接口经典面试题
go语言中的方法和interface的实现
Frequently Asked Questions (FAQ)
FAQ中文-我应该使用值还是指针定义方法