Golang中的方法集与方法调用问题

问题提要

之前写代码的时候遇到了一个问题:自己编写了一个接口,然后又写了一个结构体实现这个接口,在通过函数调用接口方法时出现了问题。
代码如下:

type Validator interface  {
  Valid() bool
}

type LoginInput struct {
  Username string
  Password string
}

func (input *LoginInput) Valid() bool {
  // 一些检验逻辑
  // 返回校验结果
}

func Handle(v Validator) {
  res := v.Valid()
  // 根据校验结果做一些逻辑处理
}

func main() {
  // 对具体过程做了提炼,最终逻辑一致
  input := LoginInput{Username: "XXX", Password: "YYY"}
  Handle(input)
}

main中调用Handle()时传参失败,Goland提示消息如下:Cannot use 'input' (type LoginInput) as type ValidatorType does not implement 'Validator' as 'Valid' method has a pointer receiver

解决方法其实很简单,就是调用Handle()不要传值,要传指针。把调用改成这样就行:Handle(&input)

但这是为什么呢?回去翻了翻书,发现是因为方法集

什么是方法集

我们先来看看Golang官方对它的描述:

https://golang.google.cn/ref/spec#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 embedded 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.
一个类型会有一个与它关联的方法集。interface类型的方法集就是接口本身。其他任意类型T的方法集由接收者为T类型的全部方法构成。对应的指针类型*T的方法集是由接收者为T*T的全部方法构成的(也就是说,它也包含了T的方法集)。更多的规则应用在包含嵌入字段的结构体上,就像struct types章节中描述的一样。任何其他类型都有一个空的方法集。在方法集中,每个方法必须具有唯一的非空方法名。
类型的方法集确定类型实现的接口以及可以使用该类型的接收器调用的方法。

总结一下官方文档表述的意思,我们得到如下一张表:

变量类型方法接收器类型
T(t T)
*T(t T) + (t *T)

对于T类型,它的方法集只包含接收者类型是T的方法;而对于*T类型,它的方法集则包含接收者为T*T类型的方法,也就是全部方法
只有一个类型的方法集完全涵盖了接口的方法集后,这个类型才会被认为是接口的实现类型。
从这里可以看出来,我们最开始的代码就是因为LoginInput类型的方法集中没有notify方法,所以函数调用失败了。

结构体的方法调用与方法集之间的关系

其实到这里就会有个疑问:平时调用方法时,无论变量类型是值类型还是指针类型都能调用成功,也没出过问题啊。
这里其实是Golang的一个语法糖:在使用选择器(Selectors)调用方法时,编译器会帮你做好取址或取值的操作的。
下面通过代码说明一下这个关系:

type StructA struct {}

func (s StructA) ValueReceiver () {}

func (s *StructA) PointReceiver () {}

func main() {
  value := StructA{}
  point := &value
  // 编译器不做处理,就是value.ValueReceiver()
  value.ValueReceiver()
  // 其实是(&value).ValueReceiver()的简便写法
  value.PointReceiver()
  // 其实是(*point).ValueReceiver()的简便写法
  point.ValueReceiver()
  // 编译器不做处理,就是point.ValueReceiver()
  point.PointReceiver()
}

 

转载:
作者:黑色小核
链接:https://www.jianshu.com/p/d93656cdce0a
来源:简书
 

<think>好的,我现在需要回答用户关于如何在Golang中正确引用方法问题,特别是示例和语法。首先,我得回忆一下Golang方法的基本概念。 Golang中的方法是在函数定义前加上一个接收器(receiver),也就是将函数绑定到某个类型上。接收器可以是值类型或者指针类型,这会影响方法如何被调用以及是否能修改接收器的数据。 用户提到“引用方法”,可能指的是通过指针接收器来调用方法,或者是正确地在不同的变量类型上调用方法。根据引用[4],当接收器既有值类型又有指针类型时,需要注意可寻址性的问题。例如,不能在一个不可寻址的值类型上调用指针接收器的方法。 接下来,我需要整理一下基本的语法结构。比如,声明一个方法时,接收器的位置是在func关键字和方法名之间。如果是值接收器,直接写类型;如果是指针接收器,则用*T。然后,调用方法的时候,无论是值还是指针,Golang会自动转换,但需要注意某些情况下需要变量是可寻址的。 然后,用户需要示例。我应该分别给出值接收器和指针接收器的例子,并说明它们的行为差异。例如,值接收器的方法不能修改原对象,而指针接收器可以。同时,还要展示如何正确调用这些方法,包括直接通过值或指针调用,以及当变量是可寻址时的自动转换。 另外,引用[4]中提到的情况需要举例说明。比如,结构体B,如果有一个指针接收器的方法,当使用B{}这样的不可寻址值时,无法直接调用方法。但如果是变量b := B{},则b是可寻址的,可以调用指针方法。这部分需要明确说明。 还需要注意,当使用指针作为接收器时,方法内部对结构体的修改会影响到原变量,而值接收器不会。这一点在示例中要体现出来,比如修改某个字段的值,并展示结果。 可能还需要提到方法集的概念,即类型T的方法集包含所有值接收器的方法,而类型*T的方法集包含值接收器和指针接收器的方法。因此,当使用接口时,指针类型变量可以同时实现值接收器和指针接收器的方法,而值类型只能实现值接收器的方法。不过用户的问题可能不需要深入接口,但可以稍微提及。 最后,总结一下正确引用方法的要点:注意接收器类型,值类型和指针类型的区别,调用时的自动转换规则,以及可寻址性的限制。确保示例代码清晰,语法正确,并解释每个例子中的关键点。 现在需要检查是否有遗漏,比如是否正确处理了接收器的声明和调用,是否覆盖了用户可能的疑问点。同时要确保引用了提供的参考文献,特别是引用[4]中的内容,以支持关于可寻址性的部分。</think>在Go语言中,正确引用方法需要明确接收器(Receiver)的类型(值或指针),并遵循方法调用的规则。以下是具体示例和语法说明: --- ### **1. 方法声明语法** #### **值接收器(Value Receiver)** 方法通过值接收器操作**副本**,不会修改原实例: ```go type User struct { Name string } // 值接收器方法 func (u User) GetName() string { return u.Name } ``` #### **指针接收器(Pointer Receiver)** 方法通过指针接收器操作**原实例**,可直接修改数据: ```go // 指针接收器方法 func (u *User) SetName(name string) { u.Name = name // 修改原实例的字段 } ``` --- ### **2. 方法调用规则** #### **通过值或指针调用** - **值类型实例**可调用**值接收器方法**和**指针接收器方法**(Go自动转换): ```go u := User{Name: "Alice"} fmt.Println(u.GetName()) // 值接收器方法 u.SetName("Bob") // 指针接收器方法(自动转换为 &u) ``` - **指针类型实例**也可调用**值接收器方法**和**指针接收器方法**: ```go p := &User{Name: "Charlie"} fmt.Println(p.GetName()) // 值接收器方法(自动转换为 *p) p.SetName("David") // 指针接收器方法 ``` #### **不可寻址值的限制** 若值不可寻址(如直接使用结构体字面量),则**不能调用指针接收器方法**: ```go // 错误:B{} 不可寻址,无法调用指针接收器方法 B{}.SetName("Error") // 正确:变量 b 可寻址 b := B{} b.SetName("OK") ``` 引用[^4]对此规则有详细说明。 --- ### **3. 完整示例** ```go package main import "fmt" type Book struct { Title string } // 值接收器方法 func (b Book) PrintTitle() { fmt.Println("Title:", b.Title) } // 指针接收器方法 func (b *Book) UpdateTitle(title string) { b.Title = title } func main() { // 值类型实例 book1 := Book{Title: "Go Basics"} book1.PrintTitle() // 输出: Title: Go Basics book1.UpdateTitle("Go Advanced") // 自动转换为 (&book1).UpdateTitle(...) fmt.Println(book1.Title) // 输出: Go Advanced // 指针类型实例 book2 := &Book{Title: "Web Development"} book2.PrintTitle() // 自动转换为 (*book2).PrintTitle() book2.UpdateTitle("Web Security") fmt.Println(book2.Title) // 输出: Web Security } ``` --- ### **关键总结** 1. **接收器类型决定行为**:值接收器操作副本,指针接收器操作原实例。 2. **自动转换机制**:Go会根据需要自动转换值↔指针(如`u.SetName()`等价于`(&u).SetName()`)。 3. **可寻址性限制**:直接结构体字面量(如`Book{}`)不可寻址,需通过变量调用指针方法[^4]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值