『Go』方法和接口
一、方法
1.方法是什么
你可以为某个类型声明它的专属函数,如为 A 类型声明函数 f
函数 f 就附加在了类型 A 上,使用 A.f() 来调用
这时 f 就称为 A 的 「方法」
类型 A 就称为方法 f 的 「接收者」
方法就是一种带「接收者」参数的函数
2.为什么要使用方法
方法使我们能在Go中运用面向对象的思想来编程(就像C++中的类一样)
3.如何使用方法
3.1 方法的声明
首先复习一下函数的声明格式
1 | func 函数名 (参数列表) (返回值列表){ |
方法无疑就是多了个接收者参数,位置在 func 和函数名之间
1 | func (接收者) 函数名 (参数列表) (返回值列表){ |
注意:
-
你不能在 A 包内为 B 包内的一个类型定义方法,也就是说方法和接收者必须在同一个包里
-
内建类型无法定义方法,因为这些类型不是你定义的,
你也不知道它是在哪个包里定义的
例子:声明一个 point 类型,用于表示一个二维坐标的点,再为它声明一个方法 dis ,用于获取该点到原点的距离。
1 | package main |
3.2 值接收者与指针接收者
如为 A 类型声明方法 f,那么接收者就是值,每次传入的都是拷贝的一个副本,方法内操作的是原变量的副本
如为 A 类型的指针声明方法 f,那么接收者就是指针,每次传入的都是一个指针,方法内操作的是原变量
现在新声明一个方法 add,实现对坐标的加法功能
1 | package main |
3.3 指针重定向
刚才为 Point 的指针声明了方法 add,但实际上使用值也可以成功调用,Go可以为你自动转换(重定向)
1 | func main() { |
也就是说,如果方法的接收者为指针,传入时无论是值或者指针都会被自动转换为指针
对立情况也是如此,如果方法的接收者为值,传入时无论是值或者指针都会被自动转换为值
1 | fmt.Println((&a).dis()) //会自动转换,没有问题 |
二、接口
首先感谢这个视频,教会了我接口
1.为什么要用接口
在讲接口是什么的时候,有必要先弄清楚为什么要用接口
接口同方法一样,也是服务于面向对象思想的
比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理呢?
比如三角形,四边形,圆形都能计算周长和面积,我们能不能把它们当成“图形”来处理呢?
比如销售、行政、程序员都能计算月薪,我们能不能把他们当成“员工”来处理呢?
Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一种抽象的类型。
当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。
2.接口是什么
接口是一种数据类型 ,它的作用是 保存 一类符合条件的数据类型
“符合条件”具体地说就是看那些数据类型有没有实现某个接口所规定的方法,凡是实现了这些方法的类型就称实现了这个接口,实现了这个接口,这个接口就能保存那些类型
接口不管你的方法怎么实现的,只要你有这个方法就行。也就是说,你拿到一个接口,只知道它能实现什么(有什么方法),而不知道它具体是怎么完成的
3.接口怎么用
3.1 接口的声明
接口的声明格式如下:
1 | type 接口类型名 interface{ |
注意:接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer 等。接口名最好要能突出该接口的类型含义
3.2 接口的基础用法
我们来定义一个 Sayer 接口:
1 | // Sayer 接口 |
再定义 dog 和 cat 两个结构体:
1 | type dog struct {} |
因为 Sayer 接口里只有一个 say 方法,所以我们只需要给 dog 和 cat 分别实现 say 方法就可以实现 Sayer 接口了
1 | // dog实现了Sayer接口 |
接口类型变量能够存储所有实现了该接口的实例, 例如上面的示例中,Sayer 类型的变量能够存储 dog 和 cat 类型的变量
1 | func main() { |
3.3 结合函数的用法
先假设我们摸一只猫的时候,猫会喵喵喵地叫,摸一条狗的时候,它会汪汪汪地叫
现在我们写一个”摸“函数,要求来什么都会摸一下,并且不管是猫还是狗都可以传入
这时我们就可以用接口来作为参数
1 | func touch(x Sayer) { |
这便是结合函数的用法
3.4 值接收者和指针接收者实现接口的区别
先来说值接收者实现接口
在上面的例子中,无论是猫还是狗的 say 方法用的都是值接收者
拿狗举例子,我们分别尝试把 dog 类型和 *dog 类型赋给一个 Sayer 接口
第一条(dog类型)我们取名为 旺财,第二条(*dog类型)我们取名为 富贵
1 | func main() { |
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是 值(dog) 还是 指针(*dog) 类型的变量都可以赋值给该接口变量,如果是指针的话会先自动求它的值
而指针接收者实现接口则不同:
如果我们把狗的 say 方法改为指针接收者,则会有错误:
1 | func (d *dog) say() { //接收者改为了指针类型 |
1 | func main() { |
此时实现 Sayer 接口的是 *dog 类型,所以不能给 x 传入 dog 类型的 wangcai,此时 x 只能存储 *dog 类型的值
3.5 接口嵌套
接口与接口间可以通过嵌套创造出新的接口
1 | // Sayer 接口 |
嵌套得到的接口的使用与普通接口一样,只是一种偷懒的方法,相当于把其他接口的内容复制过来
3.6 空接口
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口
这不就很好玩了吗?空接口可以存储任意类型的变量!
1 | func main() { |
空接口有两个主要应用:
作为函数的参数
使用空接口实现可以接收任意类型的函数参数
1 | // 空接口作为函数参数 |
作为map的值
使用空接口实现可以保存任意值的字典
1 | // 空接口作为map值 |
3.7 类型断言
想判断接口中保存的类型可以用类型断言(其实就是猜测)来完成,格式为:
1 | x.(T) |
其中 T 表示断言 x 可能是的类型
该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言(猜测)成功,为 false 则表示断言(猜测)失败。
1 | func main() { |
如果要断言多次就需要写多个 if 判断,这个时候我们可以使用 switch 语句来实现:
1 | func justifyType(x interface{}) { |