【Go语言入门系列】前面的文章:算法
若是你使用过Java等面向对象语言,那么确定对接口这个概念并不陌生。简单地来讲,接口就是规范,若是你的类实现了接口,那么该类就必须具备接口所要求的一切功能、行为。接口中一般定义的都是方法。编程
就像玩具工厂要生产玩具,生产前确定要先拿到一个生产规范,该规范要求了玩具的颜色、尺寸和功能,工人就按照这个规范来生产玩具,若是有一项要求没完成,那就是不合格的玩具。数据结构
若是你以前还没用过面向对象语言,那也不要紧,由于Go的接口和Java的接口有区别。直接看下面一个实例代码,来感觉什么是Go的接口,后面也围绕该例代码来介绍。app
package main import "fmt" type people struct { name string age int } type student struct { people //"继承"people subject string school string } type programmer struct { people //"继承"people language string company string } type human interface { //定义human接口 say() eat() } type adult interface { //定义adult接口 say() eat() drink() work() } type teenager interface { //定义teenager接口 say() eat() learn() } func (p people) say() { //people实现say()方法 fmt.Printf("我是%s,今年%d。\n", p.name, p.age) } func (p people) eat() { //people实现eat()方法 fmt.Printf("我是%s,在吃饭。\n", p.name) } func (s student) learn() { //student实现learn()方法 fmt.Printf("我在%s学习%s。\n", s.school, s.subject) } func (s student) eat() { //student重写eat()方法 fmt.Printf("我是%s,在%s学校食堂吃饭。\n", s.name, s.school) } func (pr programmer) work() { //programmer实现work()方法 fmt.Printf("我在%s用%s工做。\n", pr.company, pr.language) } func (pr programmer) drink() {//programmer实现drink()方法 fmt.Printf("我是成年人了,能大口喝酒。\n") } func (pr programmer) eat() { //programmer重写eat()方法 fmt.Printf("我是%s,在%s公司餐厅吃饭。\n", pr.name, pr.company) } func main() { xiaoguan := people{"行小观", 20} zhangsan := student{people{"张三", 20}, "数学", "银河大学"} lisi := programmer{people{"李四", 21},"Go", "火星有限公司"} var h human h = xiaoguan h.say() h.eat() fmt.Println("------------") var a adult a = lisi a.say() a.eat() a.work() fmt.Println("------------") var t teenager t = zhangsan t.say() t.eat() t.learn() }
运行:数据结构和算法
我是行小观,今年20。 我是行小观,在吃饭。 ------------ 我是李四,今年21。 我是李四,在火星有限公司公司餐厅吃饭。 我在火星有限公司用Go工做。 ------------ 我是张三,今年20。 我是张三,在银河大学学校食堂吃饭。 我在银河大学学习数学。
这段代码比较长,你能够直接复制粘贴运行一下,下面好好地解释一下。函数
上例中,咱们声明了三个接口human
、adult
、teenager
:学习
type human interface { //定义human接口 say() eat() } type adult interface { //定义adult接口 say() eat() drink() work() } type teenager interface { //定义teenager接口 say() eat() learn() }
例子摆在这里了,能够很容易总结出它的特色。code
interface
和结构体strcut
的声明相似:type interface_name interface { }
type interface_name interface { 方法签名1 方法签名2 ... }
先说一下上例代码的具体内容。对象
有三个接口分别是:继承
human
接口:有say()
、eat()
方法签名。
adult
接口:有say()
、eat()
、drink()
、work()
方法签名。
teenager
接口:有say()
、eat()
、learn()
方法签名。
有三个结构体分别是:
people
结构体:有say()
、eat()
方法。student
结构体:有匿名字段people
,因此能够说student
“继承”了people
。有learn()
方法,并“重写”了eat()
方法。programmer
结构体:有匿名字段people
,因此能够说programmer
“继承”了people
。有work()
、drink()
方法,并“重写”了eat()
方法。前面说过,接口就是规范,要想实现接口就必须遵照并具有接口所要求的一切。如今好好看看上面三个结构体和三个接口之间的关系:
people
结构体有human
接口要求的say()
、eat()
方法。
student
结构体有teenager
接口要求的say()
、eat()
、learn()
方法。
programmer
结构体有adult
接口要求的say()
、eat()
、drink()
、work()
方法。
虽然student
和programmer
都重写了say()
方法,即内部实现和接收者不一样,但这不要紧,由于接口中只是一组方法签名(无论内部实现和接收者)。
因此咱们如今能够说:people
实现了human
接口,student
实现了human
、teenager
接口,programmer
实现了human
、adult
接口。
是否是感受很巧妙?不须要像Java同样使用implements
关键字来显式地实现接口,只要类型实现了接口中定义的全部方法签名,就能够说该类型实现了该接口。(前面都是用结构体举例,结构体就是一个类型)。
换句话说:接口负责指定一个类型应该具备的方法,该类型负责决定这些方法如何实现。
在Go中,实现接口能够这样理解:programmer
说话像adult
、吃饭像adult
、喝酒像adult
、工做像adult
,因此programmer
是adult
。
接口也是值,这就意味着接口能像值同样进行传递,并能够做为函数的参数和返回值。
func main() { xiaoguan := people{"行小观", 20} zhangsan := student{people{"张三", 20}, "数学", "银河大学"} lisi := programmer{people{"李四", 21},"Go", "火星有限公司"} var h human //定义human类型变量 h = xiaoguan var a adult //定义adult类型变量 a = lisi var t teenager //定义teenager类型变量 t = zhangsan }
若是定义了一个接口类型变量,那么该变量中能够存储实现了该接口的任意类型值:
func main() { //这三我的都实现了human接口 xiaoguan := people{"行小观", 20} zhangsan := student{people{"张三", 20}, "数学", "银河大学"} lisi := programmer{people{"李四", 21},"Go", "火星有限公司"} var h human //定义human类型变量 //因此h变量能够存这三我的 h = xiaoguan h = zhangsan h = lisi }
不能存储未实现该interface
接口的类型值:
func main() { xiaoguan := people{"行小观", 20} //实现human接口 zhangsan := student{people{"张三", 20}, "数学", "银河大学"} //实现teenager接口 lisi := programmer{people{"李四", 21},"Go", "火星有限公司"} //实现adult接口 var a adult //定义adult类型变量 //但zhangsan没实现adult接口 a = zhangsan //因此a不能存zhangsan,会报错 }
不然会相似这样报错:
cannot use zhangsan (type student) as type adult in assignment: student does not implement adult (missing drink method)
也能够定义接口类型切片:
func main() { var sli = make([]human, 3) sli[0] = xiaoguan sli[1] = zhangsan sli[2] = lisi for _, v := range sli { v.say() } }
所谓空接口,即定义了零个方法签名的接口。
空接口能够用来保存任何类型的值,由于空接口中定义了零个方法签名,这就至关于每一个类型都会实现实现空接口。
空接口长这样:
interface {}
下例代码展现了空接口能够保存任何类型的值:
package main import "fmt" type people struct { name string age int } func main() { xiaoguan := people{"行小观", 20} var ept interface{} //定义一个空接口变量 ept = 10 //能够存整数 ept = xiaoguan //能够存结构体 ept = make([]int, 3) //能够存切片 }
看下例:
package main import "fmt" type sayer interface {//接口 say() } func foo(a sayer) { //函数的参数是接口值 a.say() } type people struct { //结构体类型 name string age int } func (p people) say() { //people实现了接口sayer fmt.Printf("我是%s,今年%d岁。", p.name, p.age) } type MyInt int //MyInt类型 func (m MyInt) say() { //MyInt实现了接口sayer fmt.Printf("我是%d。\n", m) } func main() { xiaoguan := people{"行小观", 20} foo(xiaoguan) //结构体类型做为参数 i := MyInt(5) foo(i) //MyInt类型做为参数 }
运行:
我是行小观,今年20岁。 我是5。
因为people
和MyInt
都实现了sayer
接口,因此它们都能做为foo
函数的参数。
上一小节说过,interface类型变量中能够存储实现了该interface接口的任意类型值。
那么给你一个接口类型的变量,你怎么知道该变量中存储的是什么类型的值呢?这时就须要使用类型断言了。类型断言是这样使用的:
t := var_interface.(val_type)
var_interface
:一个接口类型的变量。
val_type
:该变量中存储的值的类型。
你可能会问:个人目的就是要知道接口变量中存储的值的类型,你这里还让我提供值的类型?
注意:这是类型断言,你得有个假设(猜)才行,而后去验证猜对得对不对。
若是正确,则会返回该值,你能够用t
去接收;若是不正确,则会报panic
。
话说多了容易迷糊,直接看代码。仍是用本章一开始举的那个例子:
func main() { zhangsan := student{people{"张三", 20}, "数学", "银河大学"} var x interface{} = zhangsan //x接口变量中存了一个student类型结构体 var y interface{} = "HelloWorld" //y接口变量中存了一个string类型的字符串 /*如今假设你不知道x、y中存的是什么类型的值*/ //如今使用类型断言去验证 //a := x.(people) //报panic //fmt.Println(a) //panic: interface conversion: interface {} is main.student, not main.people a := x.(student) fmt.Println(a) //打印{{张三 20} 数学 银河大学} b := y.(string) fmt.Println(b) //打印 HelloWorld }
第一次,咱们断言x
中存储的变量是people
类型,但其实是student
类型,因此报panic。
第二次,咱们断言x
中存储的变量是student
类型,断言对了,因此会把x
的值赋给a
。
第三次,咱们断言y
中存储的变量是string
类型,也断言对了。
有时候咱们并不须要值,只想知道接口变量中是否存储了某类型的值,类型断言能够返回两个值:
t, ok := var_interface.(val_type)
ok
是个布尔值,若是断言对了,为true;若是断言错了,为false且不报panic
,但t
会被置为“零值”。
//断言错误 value, ok := x.(people) fmt.Println(value, ok) //打印{ 0} false //断言正确 _, ok := y.(string) fmt.Println(ok) //true
类型断言其实就是在猜接口变量中存储的值的类型。
由于咱们并不肯定该接口变量中存储的是什么类型的值,因此确定会考虑足够多的状况:当是int
类型的值时,采起这种操做,当是string
类型的值时,采起那种操做等。这时你可能会采用if...else...
来实现:
func main() { xiaoguan := people{"行小观", 20} var x interface{} = 12 if value, ok := x.(string); ok { //x的值是string类型 fmt.Printf("%s是个字符串。开心", value) } else if value, ok := x.(int); ok { //x的值是int类型 value *= 2 fmt.Printf("翻倍了,%d是个整数。哈哈", value) } else if value, ok := x.(people); ok { //x的值是people类型 fmt.Println("这是个结构体。", value) } }
这样显得有点啰嗦,使用switch...case...
会更加简洁。
switch value := x.(type) { case string: fmt.Printf("%s是个字符串。开心", value) case int: value *= 2 fmt.Printf("翻倍了,%d是个整数。哈哈", value) case human: fmt.Println("这是个结构体。", value) default: fmt.Printf("前面的case都没猜对,x是%T类型", value) fmt.Println("x的值为", value) }
这就是类型选择,看起来和普通的 switch 语句类似,但不一样的是 case 是类型而不是值。
当接口变量x
中存储的值和某个case的类型匹配,便执行该case。若是全部case都不匹配,则执行 default,而且此时value
的类型和值会和x
中存储的值相同。
这里的“继承”并非面向对象的继承,只是借用该词表达意思。
咱们已经在【Go语言入门系列】(八)Go语言是否是面向对象语言?一文中使用结构体时已经体验了匿名字段(嵌入字段)的好处,这样能够复用许多代码,好比字段和方法。若是你对经过匿名字段“继承”获得的字段和方法不满意,还能够“重写”它们。
对于接口来讲,也能够经过“继承”来复用代码,实际上就是把一个接口当作匿名字段嵌入另外一个接口中。下面是一个实例:
package main import "fmt" type animal struct { //结构体animal name string age int } type dog struct { //结构体dog animal //“继承”animal address string } type runner interface { //runner接口 run() } type watcher interface { //watcher接口 runner //“继承”runner接口 watch() } func (a animal) run() { //animal实现runner接口 fmt.Printf("%s会跑\n", a.name) } func (d dog) watch() { //dog实现watcher接口 fmt.Printf("%s在%s看门\n", d.name, d.address) } func main() { a := animal{"小动物", 12} d := dog{animal{"哮天犬", 13}, "天庭"} a.run() d.run() //哮天犬能够调用“继承”获得的接口中的方法 d.watch() }
运行:
小动物会跑 哮天犬会跑 哮天犬在天庭看门
【做者】:行小观
【公众号】:行人观学
【简介】:一个面向学习的帐号,用有趣的语言写系列文章。包括Java、Go、数据结构和算法、计算机基础等相关文章。
本文章属于系列文章「Go语言入门系列」,本系列从Go语言基础开始介绍,适合从零开始的初学者。
欢迎关注,咱们一块儿踏上编程的行程。
若有错误,还请指正。