Golang中接口行为的困惑解析

Golang中接口行为的困惑解析 我是Go语言的新手。

1

package main

import (
	"fmt"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I
	i = T{"Hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
({Hello}, main.T)
Hello

Program exited.

2

package main

import (
	"fmt"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I
	i = &T{"Hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
(&{Hello}, *main.T)
Hello

Program exited.

3

package main

import (
	"fmt"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I
	i = &T{"Hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
(&{Hello}, *main.T)
Hello

Program exited.

4

package main

import (
	"fmt"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I
	i = T{"Hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
# example
./prog.go:21:6: cannot use T{…} (value of type T) as type I in assignment:
	T does not implement I (M method has pointer receiver)

Go build failed.

那么为什么代码2能通过编译!!! 方法和指针间接寻址? 如果代码4编译失败,代码2也应该编译失败? 发生了什么?


更多关于Golang中接口行为的困惑解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

哦…

更多关于Golang中接口行为的困惑解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我没想到这居然能行…

如果 T 实现了接口 I,那么指向 T 的指针将自动解引用,因为这样做是安全的。

然而,自动引用是安全的,因为它可能会在你意想不到的地方引入可变性。因此,如果只有指针实现了接口,你需要手动进行引用,以提醒自己可能发生的潜在可变性。

感谢您的回答。

您给出的理由是:“虽然自动引用它并不安全,因为它可能会在您意想不到的地方引入突变。”但如果没有使用接口,在我看来编译器似乎会自动引用它,如下面的代码所示。

package main

import (
	"fmt"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

func main() {
	var i T
	i = T{"Hello"}
	describe(i)
	i.M()
}

func describe(i T) {
	fmt.Printf("(%v, %T)\n", i, i)
}
({Hello}, main.T)
Hello

Program exited.

i.M() 相当于 (&i).M() 这是自动引用并且安全吗?为什么只有在涉及接口时自动引用才不起作用?

在Go语言中,接口实现的关键在于方法集规则。让我通过代码示例来解释你的困惑:

核心规则:

  • 值类型 T 的方法集包含所有值接收者方法
  • 指针类型 *T 的方法集包含所有值接收者和指针接收者方法

代码分析:

// 情况1:值接收者方法
func (t T) M() { ... }
i = T{"Hello"}  // 允许:T实现了I
i = &T{"Hello"} // 允许:*T也实现了I

// 情况2:指针接收者方法  
func (t *T) M() { ... }
i = &T{"Hello"} // 允许:*T实现了I
i = T{"Hello"}  // 错误:T没有实现I

具体解释:

代码2能通过编译是因为:

func (t T) M() { ... }  // 值接收者方法
i = &T{"Hello"}         // *main.T类型

// *T的方法集包含值接收者方法M()
// 所以 *main.T 实现了接口 I

代码4编译失败是因为:

func (t *T) M() { ... }  // 指针接收者方法
i = T{"Hello"}           // main.T类型

// T的方法集不包含指针接收者方法M()
// 所以 main.T 没有实现接口 I

自动解引用机制: 当调用 i.M() 时,如果 i 持有 *T 但方法定义是值接收者,Go会自动解引用:

func (t T) M() { ... }
i = &T{"Hello"}
i.M()  // 等价于 (*i).M()

验证示例:

package main

import "fmt"

type Speaker interface {
	Speak()
}

type Dog struct {
	Name string
}

// 值接收者方法
func (d Dog) Speak() {
	fmt.Println(d.Name, "says: Woof!")
}

// 指针接收者方法  
func (c *Cat) Speak() {
	fmt.Println(c.Name, "says: Meow!")
}

type Cat struct {
	Name string
}

func main() {
	var s Speaker
	
	// Dog - 值接收者
	d1 := Dog{"Buddy"}
	s = d1  // 允许
	s.Speak()
	
	d2 := &Dog{"Max"}
	s = d2  // 允许
	s.Speak()
	
	// Cat - 指针接收者
	c1 := &Cat{"Whiskers"}
	s = c1  // 允许
	s.Speak()
	
	// c2 := Cat{"Mittens"}
	// s = c2  // 编译错误:Cat没有实现Speaker
}

输出:

Buddy says: Woof!
Max says: Woof!
Whiskers says: Meow!

总结:

  • 值接收者方法:T*T 都实现接口
  • 指针接收者方法:只有 *T 实现接口
  • 代码2能编译是因为 *T 包含了值接收者方法
  • 代码4失败是因为 T 不包含指针接收者方法
回到顶部