Go语言学习笔记part4

接口(interface)

接口是一种抽象的类型。

接口的定义

每个接口由数个方法组成,接口的定义格式如下:

  1. type 接口类型名 interface{
  2. 方法名1( 参数列表1 ) 返回值列表1
  3. 方法名2( 参数列表2 ) 返回值列表2
  4. }

其中:

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
  1. type speaker interface{
  2. speak()
  3. }

接口的实现

一个变量如果实现了接口中规定的所有类型,所有的方法,那么这个变量就实现了这个接口(这个变量可以成为这个接口类型)。

例子:

  1. // Sayer 接口
  2. type Sayer interface {
  3. say()
  4. }
  5. //定义dog和cat两个结构体:
  6. type dog struct {}
  7. type cat struct {}
  8. // dog实现了Sayer接口
  9. func (d dog) say() {
  10. fmt.Println("汪汪汪")
  11. }
  12. // cat实现了Sayer接口
  13. func (c cat) say() {
  14. fmt.Println("喵喵喵")
  15. }

这里dogcat实现了Sayer接口,dogcat就成为了Sayer类型,可以使用Sayer类型变量调用对应的方法

  1. func main(){
  2. var s Sayer
  3. d := dog{}
  4. c := cat{}
  5. s = d
  6. s.say()
  7. s = c
  8. s.say()
  9. }
  1. func(s Sayer)say{
  2. s.say()
  3. }
  4. func main(){
  5. d := dog{}
  6. say(d)
  7. c := cat{}
  8. say(c)
  9. }

Q:是先定义接口,再实现结构体,还是先实现结构体再抽象出接口?

值接收者和指针接收者的区别

使用值接收者实现接口,那么不管是结构体还是结构体指针都可以赋给该接口变量;

使用指针接收者实现接口,只有结构体指针能赋给该接口变量。

eg.值接收者

  1. type Mover interface {
  2. move()
  3. }
  4. type dog struct {}
  5. func (d dog) move() {
  6. fmt.Println("狗会动")
  7. }
  8. func main() {
  9. var x Mover
  10. var wangcai = dog{} // 旺财是dog类型
  11. x = wangcai // x可以接收dog类型
  12. var fugui = &dog{} // 富贵是*dog类型
  13. x = fugui // x可以接收*dog类型
  14. x.move()
  15. }

eg.指针接收者

  1. func (d *dog) move() {
  2. fmt.Println("狗会动")
  3. }
  4. func main() {
  5. var x Mover
  6. var wangcai = dog{} // 旺财是dog类型
  7. x = wangcai // x不可以接收dog类型
  8. var fugui = &dog{} // 富贵是*dog类型
  9. x = fugui // x可以接收*dog类型
  10. }

实现多个接口和接口嵌套

空接口

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

  1. func main() {
  2. // 定义一个空接口x
  3. var x interface{}
  4. s := "Hello 沙河"
  5. x = s
  6. fmt.Printf("type:%T value:%v\n", x, x)
  7. i := 100
  8. x = i
  9. fmt.Printf("type:%T value:%v\n", x, x)
  10. b := true
  11. x = b
  12. fmt.Printf("type:%T value:%v\n", x, x)
  13. }

空接口应用

  1. 空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

  1. // 空接口作为函数参数
  2. func show(a interface{}) {
  3. fmt.Printf("type:%T value:%v\n", a, a)
  4. }
  1. 空接口作为map的值

使用空接口实现可以保存任意值的字典。

  1. // 空接口作为map值
  2. var studentInfo = make(map[string]interface{})
  3. studentInfo["name"] = "沙河娜扎"
  4. studentInfo["age"] = 18
  5. studentInfo["married"] = false
  6. fmt.Println(studentInfo)

类型断言

空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?

一个接口的值(简称接口值)是由一个具体类型具体类型的值两部分组成的。这两部分分别称为接口的动态类型动态值

我们来看一个具体的例子:

  1. var w io.Writer
  2. w = os.Stdout
  3. w = new(bytes.Buffer)
  4. w = nil

Go_part04 - 图1

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

  1. x.(T)

其中:

  • x:表示类型为interface{}的变量
  • T:表示断言x可能是的类型。

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

包(package)是多个Go源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmtosio等。

我们还可以根据自己的需要创建自己的包。一个包可以简单理解为一个存放.go文件的文件夹。 该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包。

  1. package 包名

注意事项:

  • 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
  • 包名可以不和文件夹的名字一样,包名不能包含 - 符号。
  • 包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。

init()函数

在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用。需要注意的是: init()函数没有参数也没有返回值。 init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。

包初始化执行的顺序如下图所示:

Go_part04 - 图2

Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入了其他的包。Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。

在运行时,被最后导入的包会最先初始化并调用其init()函数, 如下图示:

Go_part04 - 图3