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 回复
我没想到这居然能行…
如果 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不包含指针接收者方法

