[TOC]
# 接口
在go中,接口是一个自定义类型
接口类型是一个抽象类型,他不会暴露他代表的对象的内部值的结构和这个对象支持的基础操作的集合,他们只会展示出自己的方法.**因此接口类型不能将他实例化**
**定义**
~~~
type Humaner interface {
sayHi()
}
~~~
* 接口命名习惯以er结尾
* 接口只有方法声明,没有实现,没有数据字段
* 接口可以匿名嵌入其他接口,或嵌入到结构中
**实现**
~~~
//定义接口类型
type Humaner interface {
sayHi()
}
type Student struct {
name string
id int
}
//Student类型实现了这个方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
func main() {
//定义接口类型的变量
var i Humaner
//只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"Mike", 666}
//引用赋值
i= s
//调用实现者的方法
i.sayHi()
}
~~~
## 多态
~~~
//定义接口类型
type Humaner interface {
sayHi()
}
type Student struct {
name string
id int
}
//Student类型实现了这个方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
type Mystr string
//MyStr实现了这个方法
func (tmp *Mystr) sayHi() {
fmt.Println(*tmp)
}
//定义一个普通函数,函数的参数为接口类型
//只有一个函数,缺有不同表现
func WhoSayHi(i Humaner) {
i.sayHi()
}
func main() {
s := &Student{"Mike", 666}
t := &Student{"sds", 6556}
var str Mystr = "HELLO"
//要传地址
WhoSayHi(s)
WhoSayHi(t)
WhoSayHi(&str)
}
~~~
## 接口的继承
~~~
//定义接口类型
type Humaner interface { //子集
sayHi()
}
type Person interface { //超集
Humaner //继承了sayHi()
sing(lrc string)
}
type Student struct {
name string
id int
}
//Student类型实现了这个方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
func (tmp *Student) sing(lrc string) {
fmt.Println(*tmp)
}
func main() {
//定义一个接口类型的变量
var i Person
s := &Student{"mike", 666}
i = s
i.sayHi() //继承过来的方法
i.sing("abc")
}
~~~
## 接口转换
超级可以转换为子集,反过来不可以
~~~
func main() {
//定义一个接口类型的变量
var i Person
var h Humaner
i = h //不可以
}
~~~
## 空接口
空接口(interface{})不包含任何方法,所有类型都实现了空接口,因此空接口可以存储任意类型的数值.有点类似于c语言的`void *`类型
~~~
//将int类型赋值给interface{}
var v1 interface{} = 1
//将string类型赋值给interface{}
var v2 interface{} = "abc"
//将*interface{]类型赋值给interface{}
var v3 interface{} = &v2
var v4 interface{} = struct {
X int
}{1}
var v5 interface{} = struct {
x int
}{1}
~~~
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子就是标准库fmt中PrintXXX系列的函数
~~~
func Printf(fmt string, args ...interface{})
//可变参数的空接口类型
func Println(args ...interface{})
~~~
~~~
func main() {
var i interface{} = 1
fmt.Println(i)
}
~~~
## 动态类型
对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征(即全部的方法),那么它就一定是这个接口的实现类型。比如下面这样:
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
~~~
怎样判定一个数据类型的某一个方法实现的就是某个接口类型中的某个方法呢?
这有两个充分必要条件,一个是“两个方法的签名需要完全一致”,另一个是“两个方法的名称要一模一样”。显然,这比判断一个函数是否实现了某个函数类型要更加严格一些。
如果你查阅了上篇文章附带的最后一个示例的话,那么就一定会知道,虽然结构体类型Cat不是Pet接口的实现类型,但它的指针类型\*Cat却是这个的实现类型。
我声明的类型Dog附带了 3 个方法。其中有 2 个值方法,分别是Name和Category,另外还有一个指针方法SetName。
这就意味着,Dog类型本身的方法集合中只包含了 2 个方法,也就是所有的值方法。而它的指针类型\*Dog方法集合却包含了 3 个方法,
也就是说,它拥有Dog类型附带的所有值方法和指针方法。又由于这 3 个方法恰恰分别是Pet接口中某个方法的实现,所以\*Dog类型就成为了Pet接口的实现类型。
~~~
dog := Dog{"little pig"}
var pet Pet = &dog
~~~
正因为如此,我可以声明并初始化一个Dog类型的变量dog,然后把它的指针值赋给类型为Pet的变量pet。
这里有几个名词需要你先记住。对于一个接口类型的变量来说,例如上面的变量pet,我们赋给它的值可以被叫做它的实际值(也称动态值),而该值的类型可以被叫做这个变量的实际类型(也称动态类型)。
比如,我们把取址表达式&dog的结果值赋给了变量pet,这时这个结果值就是变量pet的动态值,而此结果值的类型\*Dog就是该变量的动态类型。
动态类型这个叫法是相对于静态类型而言的。对于变量pet来讲,它的静态类型就是Pet,并且永远是Pet,但是它的动态类型却会随着我们赋给它的动态值而变化。
比如,只有我把一个\*Dog类型的值赋给变量pet之后,该变量的动态类型才会是\*Dog。如果还有一个Pet接口的实现类型\*Fish,并且我又把一个此类型的值赋给了pet,那么它的动态类型就会变为\*Fish。
还有,在我们给一个接口类型的变量赋予实际的值之前,它的动态类型是不存在的
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog *Dog) Name() string {
return dog.name
}
func (dog *Dog) Category() string {
return "dog"
}
func main() {
// 示例1
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog是接口Pet的实现类型吗: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog是接口Pet的实现类型吗: %v\n", ok)
fmt.Println()
// 示例2
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())
}
~~~
## 实现规则
接口变量的值并不等同于这个可被称为动态值的副本。它会包含两个指针,一个指针指向动态值,一个指针指向类型信息
规则一:如果使用指针方法来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。
规则二:如果使用值方法来实现一个接口,那么那个类型的值和指针都能够实现对应的接口
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string // 名字。
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog Dog) Name() string {
return dog.name
}
func (dog Dog) Category() string {
return "dog"
}
func main() {
// 示例1。
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog implements interface Pet: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog implements interface Pet: %v\n", ok)
fmt.Println()
// 示例2。
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
}
~~~
## Stringer
在fmt包里有一个interface叫做Stringer:
~~~
type Stringer interface {
String() string
}
~~~
作用:fmt.Println或打印一个变量的值的时候,会判断这个变量是否实现了Stringer接口,如果实现了,则调用这个变量的String()方法,并将返回值打印到屏幕上
fmt.Printf的`%v`也会读取Stringer
例子:
~~~
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("(Name: %v) (Age: %v)", p.Name, p.Age)
}
func main() {
a := Person{"benz", 21}
fmt.Println(a)
fmt.Printf("%v\n", a)
}
~~~
输出
~~~
(Name: benz) (Age: 21)
(Name: benz) (Age: 21)
~~~
可以看出,用String()修改了输出格式
再举个例子:
~~~
package main
import "fmt"
type IPAddr [4]byte
/*
func (ip IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
*/
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
~~~
输出
~~~
loopback: [127 0 0 1]
googleDNS: [8 8 8 8]
~~~
如果把上面的String的注释去掉,输出则是
~~~
loopback: 127.0.0.1
googleDNS: 8.8.8.8
~~~
## type switch
## **对接口变量hold值做类型判断**[¶](https://cyent.github.io/golang/method/interface_typeswitch/#hold "Permanent link")
* * *
当i是个接口变量的时候,可以用i.(type)来对这个接口变量hold的值类型做判断
~~~
switch v := i.(type) {
case int:
...
case string:
...
default:
...
}
~~~
注意:之前类型断言是用i.($TYPE)比如i.(int)来判断是不是int类型,但是这里用关键字type。关键字type只能用在switch语句里,如果用在switch外面会报错,比如:
~~~
a := i.(type)
fmt.Printf("%v %T\n", a, a)
~~~
报错:
~~~
use of .(type) outside type switch
~~~
除了能识别内置类型,也可以识别其他类型,比如函数类型,或者带指针的struct,比如这个例子是带指针的struct
~~~
type Foo interface {
foo() int
}
type MyStruct struct {
X, Y int
}
func (a *MyStruct) foo() int {
return a.X + a.Y
}
func main() {
var f Foo
s := MyStruct{3, 4}
f = &s
fmt.Printf("%v,%T\n", f, f)
switch v := f.(type) {
case *MyStruct:
fmt.Printf("1,%v,%T\n", v, v)
default:
fmt.Printf("2,%v,%T\n", v, v)
}
}
~~~
输出
~~~
&{3 4},*main.MyStruct
1,&{3 4},*main.MyStruct
~~~
注意:这个type用%T显示出来的是\*main.MyStruct,而case里是\*MyStruct,没有main喔。
这个例子是函数:
~~~
func main() {
pos := func () int { return 1 }
fmt.Printf("%v,%T\n", pos, pos)
var i interface{}
i = pos
fmt.Printf("%v,%T\n", i, i)
switch v := i.(type) {
case int:
fmt.Printf("1,%v,%T\n", v, v)
case func() int:
fmt.Printf("2,%v,%T\n", v, v)
case func(int) int:
fmt.Printf("3,%v,%T\n", v, v)
default:
fmt.Printf("4,%v,%T\n", v, v)
}
}
~~~
输出:
~~~
0x1088f30,func() int
0x1088f30,func() int
2,0x1088f30,func() int
~~~
可以看出:case后面跟的就是i值的类型
注意:case后面不能跟不存在的自定义类型,比如:
~~~
func main() {
var i interface{}
i = 1
fmt.Printf("%v,%T\n", i, i)
switch v := i.(type) {
case int:
fmt.Printf("1,%v,%T\n", v, v)
case func() int:
fmt.Printf("2,%v,%T\n", v, v)
case func(int) int:
fmt.Printf("3,%v,%T\n", v, v)
case *MyStruct:
fmt.Printf("4,%v,%T\n", v, v)
default:
fmt.Printf("5,%v,%T\n", v, v)
}
}
~~~
报错:
~~~
undefined: MyStruct
~~~
## **可以用是否实现接口做判断**[¶](https://cyent.github.io/golang/method/interface_typeswitch/#_1 "Permanent link")
* * *
~~~
package main
import "fmt"
type Adder interface {
Add()
}
type MyStruct struct {
X, Y int
}
func (this MyStruct) Add() {
fmt.Println(this.X + this.Y)
}
func main() {
s := MyStruct{3, 4}
//var i interface{} = s
var i Adder = s
switch v := i.(type) {
case MyStruct:
fmt.Printf("case MyStruct: %T %v\n", v, v)
case interface{}:
fmt.Printf("case interface{}: %T %v\n", v, v)
case Adder:
fmt.Printf("case Adder: %T %v\n", v, v)
default:
fmt.Printf("not case: %T %v\n", v, v)
}
}
~~~
输出
~~~
case MyStruct: main.MyStruct {3 4}
~~~
另外上面
1. `var i interface{} = s`或`var i Adder = s`用哪个都一样
2. 这3个case不管用哪个,第一个case都能匹配到
Warning
case只能用于类型判断(包括实现接口),没有办法判断是否为一个接口。就是说没有办法判断一个变量是否为接口变量,即使用反射也是无法判断的
- 基础
- 简介
- 主要特征
- 变量和常量
- 编码转换
- 数组
- byte与rune
- big
- sort接口
- 和mysql类型对应
- 函数
- 闭包
- 工作区
- 复合类型
- 指针
- 切片
- map
- 结构体
- sync.Map
- 随机数
- 面向对象
- 匿名组合
- 方法
- 接口
- 权限
- 类型查询
- 异常处理
- error
- panic
- recover
- 自定义错误
- 字符串处理
- 正则表达式
- json
- 文件操作
- os
- 文件读写
- 目录
- bufio
- ioutil
- gob
- 栈帧的内存布局
- shell
- 时间处理
- time详情
- time使用
- new和make的区别
- container
- list
- heap
- ring
- 测试
- 单元测试
- Mock依赖
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 详解
- plugin包
- 信号
- goto
- 协程
- 简介
- 创建
- 协程退出
- runtime
- channel
- select
- 死锁
- 互斥锁
- 读写锁
- 条件变量
- 嵌套
- 计算单个协程占用内存
- 执行规则
- 原子操作
- WaitGroup
- 定时器
- 对象池
- sync.once
- 网络编程
- 分层模型
- socket
- tcp
- udp
- 服务端
- 客户端
- 并发服务器
- Http
- 简介
- http服务器
- http客户端
- 爬虫
- 平滑重启
- context
- httptest
- 优雅中止
- web服务平滑重启
- beego
- 安装
- 路由器
- orm
- 单表增删改查
- 多级表
- orm使用
- 高级查询
- 关系查询
- SQL查询
- 元数据二次定义
- 控制器
- 参数解析
- 过滤器
- 数据输出
- 表单数据验证
- 错误处理
- 日志
- 模块
- cache
- task
- 调试模块
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密码
- 对称加密
- 非对称加密
- 单向散列函数
- 消息认证
- 数字签名
- mysql优化
- 常见错误
- go run的错误
- 新手常见错误
- 中级错误
- 高级错误
- 常用工具
- 协程-泄露
- go env
- gometalinter代码检查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高编译
- go get
- 代理
- 其他的知识
- go内存对齐
- 细节总结
- nginx路由匹配
- 一些博客
- redis为什么快
- cpu高速缓存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密码加密解密
- for 循环迭代变量
- 备注
- 垃圾回收
- 协程和纤程
- tar-gz
- 红包算法
- 解决golang.org/x 下载失败
- 逃逸分析
- docker
- 镜像
- 容器
- 数据卷
- 网络管理
- 网络模式
- dockerfile
- docker-composer
- 微服务
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell调用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb