9 struct 和interface: 结构体和接口都实现了那
方法赋值给变量称为方法表达式
age:=Age(25)
//方法赋值给变量,方法表达式
sm:=Age.String
//通过变量,要传一个接收者进行调用也就是age
sm(age)
方法String 其实没有参数的,但是通过方法表达式赋值给变量sm后,在调用的时候,必须要传一个接收者,这样sm才知道怎么调用
不管方法是否有参数,通过方法表达式调用,第一个参数必须是接收者,然后才是方法自身的参数
结构体:
结构体定义
结构体是一种聚合类型,里面可以包含任何类型的值,这些值可就是我们定义结构体的成员,也称为字段。在Go语言中,要定义一个结构体需要使用type+struct关键字组合
在下面的例子中,定义一个结构体类型,名称为person,表示一个人。这个person结构体有2个字段: name 代表这个人的名称,age代表年龄
type person struct {
name string
age uint
}
在定义结构体时,字段声明方法和平时声明一个变量是一样的,都是变量名在前,类型在后,只不过在结构体中,变量名称为成员名字段名
结构体的成员名字段并不是必须的,也可以一个字段也没有,这种结构体称为空结构体
根据以上信息,我们可以总结出结构体定义的表达式
type structName struct{
fieldName typeName
....
....
}
其中:
type和struct 是go语言的关键字,二者组合就代表要定义一个新的结构体类型
structName 是结构体类型的名字
fieldName 是结构体的字段名,而 typeName是对应字段的;类型
字段可以是零个,一个或者多个
结构体也是一种类型
定义好结构体后就可以使用了,因为他是一个聚合类型,所以比普通类型可以携带更多的数据
结构体声明使用
结构体类型和普通的字符串,整型一样,也可以使用同样的方式声明和初始化。
在下面的例子中,声明一个person类型的变量p,因为没有对变量p初始化,所以默认会使用结构体字段的零值。
var p person
当然在声明一个结构体变量的时候,也可以通过结构体字面量的方式初始化,如下面的代码所示:
p:=person{"飞雪",30}
采用简短声明发,同时采用字面量初始化的方式,把结构体变量p的name 初始化为"飞雪",age初始化为30,以逗号分隔
声明一个结构体变量后就可以使用了,下面运行以下代码,验证name和age的值是否和初始化的一样:
fmt.printIn(p.name,p.age)
在Go语言中,访问一个结构体的字段和调用一个类型的方法一样,都是使用点操作符"."
采用字面量初始化结构体时,初始化值的顺序很重要,必须和字段定义的顺序一致
在person这个结构体中,第一个字段是string类型的name,第二个字段是unit类型的age,所以在初始化的时候,初始化值的类型顺序必须一致,才能编译通过,也就是说在示例 {“飞雪无情”,30} 中,表示 name 的字符串飞雪无情必须在前,表示年龄的数字 30 必须在后。
那么是否可以不按照顺序初始化呢?当然可以,只不过需要指出子弹名称,如下所示:
p:=person{age:30,name:"飞雪无情"}
其中,第一位我放了整型的 age,也可以编译通过,因为采用了明确的 field:value 方式进行指定,这样 Go 语言编译器会清晰地知道你要初始化哪个字段的值。
有没有发现,这种方式和map类型的初始化很像,都是采用冒号分隔。Go语言尽可能的重用操作,不发明新的表达式,便于记忆和使用。
当然你也可以只初始化子字段age,字段name使用默认值的零值,如下面代码所示,仍然可以编译通过。
p:=person{age:30}
子弹结构体
结构体的字段可以是任意类型,也包括自定义的结构体类型。比如下面的代码
type person struct {
name string
age uint
addr address
}
type address struct {
province string
city string
}
在这个示例中,定义了2个结构体:person 表示人,address表示地址,在结构体person中,有一个address类型的字段addr,这就是自定义的结构体
通过这种方式,用代码描述现实中的实体会更匹配,复用程度也更高。对于嵌套结构体字段的结构体,其初始化和正常的结构体大同小异,只需要根据字段对应的类型初始化即可,如下面的代码所示:
p:=person{
age:30,
name:"飞雪无情",
addr:address{
province: "北京",
city: "北京",
},
}
如果需要访问结构体最里层的 province 字段的值,同样也可以使用点操作符,只不过需要使用两个点,如下面的代码所示:
fmt.Println(p.addr.province)
第一个点获取addr,第二个点获取addr的province
接口
接口的定义
接口是和调用方的一种约定,他是一种高度抽象的类型,不用和具体的实现细节绑定在一起。接口要做的是定义好约定,告诉调用方自己可以做点什么,但不用知道他的内部实现,这和我们见到的具体的类型如 int,map,slice等不一样
接口的定义和结构体稍微有些差别,虽然都以type关键字开始,但接口的关键字是interface,表示自定义的类型是一个接口,也就是说Stringer是一个接口,他有一个方法String()string 整体如下面的代码所示
type Stringer interface {
String() string
}
提示: Stringer 是Go SDK的一个的接口,属于fmt包
针对Stringer接口来说,他会告诉调用者可以通过他的String()方法获取一个字符串这就是接口的约定。至于这个字符串怎么获得的,长什么样,接口不关心,调用者也不用关心,因为这些是由接口实现者来做的。
接口的实现
接口的实现者必须是一个具体的类型,继续以person结构体为例,让他来实现Stringer接口,如下代码所示
func (p person) String() string {
return fmt.Sprintf("the name is %s,age is %d", p.age, p.age)
}
给结构体类型Person定义一个方法,这个方法和接口里方法的签名(名称,参数和返回值)一样,这样结构体person就实现了Stringer接口
注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。
实现了Stringer接口后就可以使用了,首先先来定义一个打印Stringer接口的函数,如下所示:
func printString(s fmt.Stringer) {
fmt.Println(s.String())
}
这个被定义的函数printString,他接收一个Stringer接口类型的参数,然后打印出Stringer接口的String方法返回的字符串
printString 这个函数的优势在于他是面向接口编程的,只要一个类型实现了Stringer接口,都可以打印出对应的字符串,都可以打印出对应的字符串,而不用管具体的类型实现
因为person 实现了 Stringer接口,所以变量p可以作为函数printString,可以用如下方式打印:
func printString(s fmt.Stringer){
fmt.Println(s.String())
}
这个被定义的函数pringString,它接收了一个Stringer接口类型的参数,然后打印出Stringer接口的String 方法返回的字符串。
printString这个函数的优势就在于他是面向接口编程的,只要一个类型实现了Stringer接口,都可以打印出对应的字符串,而不用管具体的类型实现
因为person 实现了Stringer接口,所以变量p可以作为函数printString 的参数,可以用如下方式打印:
printString(p)
结果为
the name is 飞雪无情,age is 30
现在让结构体address也实现了Stringer 接口,如下代码所示
func (addr address) String() string{
return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}
因为结构体address也实现了Stringer接口,所以pringString函数不用做任何改变,可以直接被使用,打印出地址,如下所示:
printString(p.addr)
//输出:the addr is 北京北京
这就是面向接口的好处,只要定义和调用方满足约定,就可以使用,而不用管具体的实现。接口的实现者也可以更好的升级重构,而不会有任何的影响,因为接口的约定没有变
值接收者和指针接收者
我们已经知道,如果实现一个接口,必须实现这个接口提供的所有方法,而且定义一个方法,有值类型接收者和指针类型接收者2种。二者都可以调用方法,因为Go语言编译器自动做了转换,所以值类型接收者和指针类型接收者是等价的。但是在接口的实现中,值类型接收者和指针类型接收者不一样。
已经验证了结构体类型实现了 Stringer 接口,那么结构体对应的指针是否也实现了该接口呢?我通过下面这个代码进行测试:
printString(&p)
测试后会发现,把变量 p 的指针作为实参传给 printString 函数也是可以的,编译运行都正常。这就证明了以值类型接收者实现接口的时候,不管是类型本身,还是该类型的指针类型,都实现了该接口。
示例中值接收者(p person)实现了Stringer接口,那么类型person和他的指针类型