Go语言学习笔记part4
接口(interface)
接口是一种抽象的类型。
接口的定义
每个接口由数个方法组成,接口的定义格式如下:
type 接口类型名 interface{方法名1( 参数列表1 ) 返回值列表1方法名2( 参数列表2 ) 返回值列表2…}
其中:
- 接口名:使用
type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。 - 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
type speaker interface{speak()}
接口的实现
一个变量如果实现了接口中规定的所有类型,所有的方法,那么这个变量就实现了这个接口(这个变量可以成为这个接口类型)。
例子:
// Sayer 接口type Sayer interface {say()}//定义dog和cat两个结构体:type dog struct {}type cat struct {}// dog实现了Sayer接口func (d dog) say() {fmt.Println("汪汪汪")}// cat实现了Sayer接口func (c cat) say() {fmt.Println("喵喵喵")}
这里dog和cat实现了Sayer接口,dog、cat就成为了Sayer类型,可以使用Sayer类型变量调用对应的方法
func main(){var s Sayerd := dog{}c := cat{}s = ds.say()s = cs.say()}
func(s Sayer)say{s.say()}func main(){d := dog{}say(d)c := cat{}say(c)}
Q:是先定义接口,再实现结构体,还是先实现结构体再抽象出接口?
值接收者和指针接收者的区别
使用值接收者实现接口,那么不管是结构体还是结构体指针都可以赋给该接口变量;
使用指针接收者实现接口,只有结构体指针能赋给该接口变量。
eg.值接收者
type Mover interface {move()}type dog struct {}func (d dog) move() {fmt.Println("狗会动")}func main() {var x Movervar wangcai = dog{} // 旺财是dog类型x = wangcai // x可以接收dog类型var fugui = &dog{} // 富贵是*dog类型x = fugui // x可以接收*dog类型x.move()}
eg.指针接收者
func (d *dog) move() {fmt.Println("狗会动")}func main() {var x Movervar wangcai = dog{} // 旺财是dog类型x = wangcai // x不可以接收dog类型var fugui = &dog{} // 富贵是*dog类型x = fugui // x可以接收*dog类型}
实现多个接口和接口嵌套
略
空接口
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {// 定义一个空接口xvar x interface{}s := "Hello 沙河"x = sfmt.Printf("type:%T value:%v\n", x, x)i := 100x = ifmt.Printf("type:%T value:%v\n", x, x)b := truex = bfmt.Printf("type:%T value:%v\n", x, x)}
空接口应用
- 空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数func show(a interface{}) {fmt.Printf("type:%T value:%v\n", a, a)}
- 空接口作为map的值
使用空接口实现可以保存任意值的字典。
// 空接口作为map值var studentInfo = make(map[string]interface{})studentInfo["name"] = "沙河娜扎"studentInfo["age"] = 18studentInfo["married"] = falsefmt.Println(studentInfo)
类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
我们来看一个具体的例子:
var w io.Writerw = os.Stdoutw = new(bytes.Buffer)w = nil

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
- x:表示类型为
interface{}的变量 - T:表示断言
x可能是的类型。
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
包
包(package)是多个Go源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmt、os、io等。
我们还可以根据自己的需要创建自己的包。一个包可以简单理解为一个存放.go文件的文件夹。 该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包。
package 包名
注意事项:
- 一个文件夹下面直接包含的文件只能归属一个
package,同样一个package的文件不能在多个文件夹下。 - 包名可以不和文件夹的名字一样,包名不能包含
-符号。 - 包名为
main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
init()函数
在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用。需要注意的是: init()函数没有参数也没有返回值。 init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。
包初始化执行的顺序如下图所示:

Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入了其他的包。Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。
在运行时,被最后导入的包会最先初始化并调用其init()函数, 如下图示:

