Go struct 和 interface:结构体与接口都实现了哪些功能?
接口
• 接口定义一组方法集合
type IF interface {
Method1(param_list) return_type
}
定义一个统一的接口,然后用多个结构体去实现这些接口,这些结构体其实是可以加到同一个接口切片里面的,在打印的时候,也就是在调用函数的时候,go语言能够自动的判断它到底是哪一个具体的类型,然后调用其具体的实现。
注意事项
接口
接口里面其实封装着一大堆的函数,这些函数必须绑定实体比如结构体,那么其实也就是方法了。总结就是接口里面封装着一大堆的方法,这些方法都必须要有接收者。
接口的定义
接口是和调用方的一种约定,它是一个高度抽象的类型,不用和具体的实现细节绑定在一起。接口要做的是定义好约定,告诉调用方自己可以做什么,但不用知道它的内部实现,这和我们见到的具体的类型如 int、map、slice 等不一样。
接口的定义和结构体稍微有些差别,虽然都以 type 关键字开始,但接口的关键字是 interface,表示自定义的类型是一个接口。也就是说 Stringer 是一个接口,它有一个方法 String() string,整体如下面的代码所示:
type Stringer interface {
String() string
}
提示:Stringer 是 Go SDK 的一个接口,属于 fmt 包。
针对 Stringer 接口来说,它会告诉调用者可以通过它的 String() 方法获取一个字符串,这就是接口的约定。至于这个字符串怎么获得的,长什么样,接口不关心,调用者也不用关心,因为这些是由接口实现者来做的。
接口的实现
接口的实现者必须是一个具体的类型,继续以 person 结构体为例,让它来实现 Stringer 接口,如下代码所示:
func (p person) String() string{
return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
给结构体类型 person 定义一个方法,这个方法和接口里方法的签名(名称、参数和返回值)一样,这样结构体 person 就实现了 Stringer 接口。
注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。
实现了 Stringer 接口后就可以使用了。首先我先来定义一个可以打印 Stringer 接口的函数,如下所示:
func printString(s fmt.Stringer){
fmt.Println(s.String())
}
这个被定义的函数 printString,它接收一个 Stringer 接口类型的参数,然后打印出 Stringer 接口的 String 方法返回的字符串。
printString 这个函数的优势就在于它是面向接口编程的,只要一个类型实现了 Stringer 接口,都可以打印出对应的字符串,而不用管具体的类型实现。
因为 person 实现了 Stringer 接口,所以变量 p 可以作为函数 printString 的参数,可以用如下方式打印:
printString(p)
结果为:
the name is 飞雪无情,age is 30
现在让结构体 address 也实现 Stringer 接口,如下面的代码所示:
func (addr address) String() string{
return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}
因为结构体 address 也实现了 Stringer 接口,所以 printString 函数不用做任何改变,可以直接被使用,打印出地址,如下所示:
printString(addr)
//输出:the addr is 北京北京
这就是面向接口的好处,只要定义和调用双方满足约定,就可以使用,而不用管具体实现。接口的实现者也可以更好的升级重构,而不会有任何影响,因为接口约定没有变。
值接收者和指针接收者
我们已经知道,如果要实现一个接口,必须实现这个接口提供的所有方法,我们也知道定义一个方法,有值类型接收者和指针类型接收者两种。二者都可以调用方法,因为 Go 语言编译器自动做了转换,所以值类型接收者和指针类型接收者是等价的。
但是在接口的实现中,值类型接收者和指针类型接收者不一样,下面我会详细分析二者的区别。
在上一小节中,已经验证了结构体类型实现了 Stringer 接口,那么结构体对应的指针是否也实现了该接口呢?我通过下面这个代码进行测试:
printString(&p)
测试后会发现,把变量 p 的指针作为实参传给 printString 函数也是可以的,编译运行都正常。这就证明了以值类型接收者实现接口的时候,不管是类型本身,还是该类型的指针类型,都实现了该接口。
示例中值接收者(p person)实现了 Stringer 接口,那么类型 person 和它的指针类型*person就都实现了 Stringer 接口。
现在,我把接收者改成指针类型,如下代码所示:
func (p *person) String() string{
return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
修改成指针类型接收者后会发现,示例中这行 printString(p) 代码编译不通过,提示如下错误:
./main.go:17:13: cannot use p (type person) as type fmt.Stringer in argument to printString:
person does not implement fmt.Stringer (String method has pointer receiver)
意思就是类型 person 没有实现 Stringer 接口。这就证明了以指针类型接收者实现接口的时候,只有对应的指针类型才被认为实现了该接口。
我用如下表格为你总结这两种接收者类型的接口实现规则:
可以这样解读:
- 当值类型作为接收者时,person 类型和*person类型都实现了该接口。
-
当指针类型作为接收者时,只有*person类型实现了该接口。
可以发现,实现接口的类型都有*person,这也表明指针类型比较万能,不管哪一种接收者,它都能实现该接口。
---------------------------------------------------------------------
相关文章
- 化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07
- Go:一文玩转接口
- go语言学习之接口与动态类型
- 「Go工具箱」一个对语义化版本进行解析、比较的库:go-version
- 2023-02-25:请用go语言调用ffmpeg,解码mp4文件并保存为YUV420SP格式文件,YUV420P不要转换成YUV420SP。
- Docker 之父:Go、Rust 为什么会成为云原生的主导语言?
- Go-运算符-关系运算符
- Go-包管理-go clean
- Go语言函数类型实现接口——把函数作为接口来调用
- Go语言接口和类型之间的转换
- Go-连接Redis-学习go-redis包详解编程语言
- Go-json解码到接口及根据键获取值详解编程语言
- Go语言网络爬虫内部基础接口
- Go语言接口内部实现
- Go语言分布式id生成器
- 毁誉交加的14个月后,Amazon Go 终于对外开放,商业意义有多大?
- Go语言环境配置:在 Linux 下实现(go环境搭建linux)
- 关键字在SQL Server中利用GO关键字实现更优化的操作(sqlserver中go)
- 便捷使用Go编程连接Redis(go连接redis)
- Go语言操作Oracle轻松实现数据库编程(go语言访问oracle)
- 程序Go语言调用Oracle数据库驱动程序指南(go oracle驱动)
- Pokemon Go后,Niantic将重新打造Ingress,这次会有哪些不同?