方法集:
go语言中,每个类型
都有与之相关的方法,把这个类型
的所有方法
称为类型的方法集。
package main
import (
"fmt"
)
type Student struct {
age int8
name string
}
func (s Student) showName() {
fmt.Println(s.name)
}
func (s *Student) setName(newName string) {
s.name = newName
}
类型Student方法集包含了showName()方法。
类型*Student方法集包含了showName()方法和setName()方法
因为:
类型 T 方法集包含全部 receiver T 方法。(类型Student方法集包含了receiver Student)方法
类型 *T 方法集包含全部 receiver T + *T 方法 (类型*Student方法集包含了receiver *Student)方法
为啥?
个人理解,是基于Go语言特性,Go语言在方法调用时,如果方法的接受器是一个指针,即使传的值不是指针类型的,也会自动转换为指针类型。哎?这不就是上面那两个的意思吗!!!
方法集和嵌入
嵌入,即把一个类型
作为另外一个类型
的匿名字段
。如下:
type Person struct {
age int8
name string
}
type Student struct {
// 嵌入Person类型
// 匿名字段
Person
}
package main
import "fmt"
type Person struct {
age int8
name string
}
func (p Person) showName(){
fmt.Println(p.name)
}
func (p *Person) setName(newName string){
p.name = newName
}
type Student1 struct {
// 值类型的嵌入
// Student1包含了Person,那么Student1对应的value和pointer包含了Person
Person
}
type Student2 struct {
// 指针类型的嵌入
*Person
}
func main(){
// 内嵌类型Person默认值为Person{age: 0, name:""}
s1 := Student1{}
s1.setName("student1_01")
s1.showName()
// 内嵌类型*Person默认值为nil
// Error,由于目前内嵌类型的值为nil,会触发报错
//s2 := &Student2{}
//s2.setName("student1_02")
//s2.showName()
s3 := &Student2{Person: &Person{age: 3, name: "s3"}}
s3.showName()
}
因为:
嵌入和方法集的关系
● 类型S包含匿名字段T,则S和*S方法集包含T方法。// Student1包含匿名字段Person
● 类型S包含匿名字段*T,则S和*S方法集包含T + *T方法。// Student2包含匿名字段*Person
● 不管嵌入的是T还是*T,*S方法集包含T + *T方法。
匿名字段
go支持只提供类型
而不写字段名
的方式,也就是匿名字段
,也称为嵌入字段
// 同名字段的情况
package main
import "fmt"
//人
type Person struct {
name string
sex string
age int
}
type Student struct {
// `只提供类型`而`不写字段名`
Person
id int
addr string
//同名字段
name string
}
func main() {
var s Student
// 给自己字段赋值了
s.name = "5lmh"
fmt.Println(s)
// 若给父类同名字段赋值,如下
s.Person.name = "枯藤"
fmt.Println(s)
}
// {{ 0} 0 5lmh}
// {{枯藤 0} 0 5lmh}
所有的内置类型和自定义类型都是可以作为匿名字段去使用
package main
import "fmt"
// 人
type Person struct {
name string
sex string
age int
}
// 自定义类型
type mystr string
// 学生
type Student struct {
Person
int
mystr
}
func main() {
s1 := Student{Person{"5lmh", "man", 18}, 1, "bj"}
fmt.Println(s1)
}
指针类型匿名字段
// 人
type Person struct {
name string
sex string
age int
}
// 学生
type Student struct {
*Person
id int
addr string
}
func main() {
s1 := Student{&Person{"5lmh", "man", 18}, 1, "bj"}
fmt.Println(s1)
fmt.Println(s1.name)
fmt.Println(s1.Person.name)
}
// {0xc00007e4b0 1 bj}
// 5lmh
// 5lmh
接口
接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
1.1 接口
1.1.1. 接口类型
在Go语言中接口(interface)是一种类型,一种抽象的类型(多抽象?)。
interface是一组method的集合,是duck-type programming的一种体现。
接口做的事情就像是定义一个协议(规则):只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。---- 只要能干活,我就要,怎么干,我不管。
牢记接口(interface)是一种类型。
1.1.2 为啥使用接口?
type Cat struct{}
func (c Cat) Say() string { return "喵喵喵" }
type Dog struct{}
func (d Dog) Say() string { return "汪汪汪" }
func main() {
c := Cat{}
fmt.Println("猫:", c.Say())
d := Dog{}
fmt.Println("狗:", d.Say())
}
上面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码,如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢?
像类似的例子在我们编程过程中会经常遇到:
比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理呢?
比如三角形,四边形,圆形都能计算周长和面积,我们能不能把它们当成“图形”来处理呢?
比如销售、行政、程序员都能计算月薪,我们能不能把他们当成“员工”来处理呢?
Go语言中为了解决类似上面的问题,就设计了接口这个概念。
接口区别于我们之前所有的具体类型,接口是一种抽象的类型。当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。
再看定义:
接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
1.1.3 接口的定义
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
1.接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,
如,有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
2.方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
type writer interface{
Write([]byte) error
}
1.1.4. 实现接口的条件
一个对象只要全部实现
了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表
啥意思?一个程序还必须把接口中的方法用完?
我们来定义一个Sayer接口:
package main
import "fmt"
type Sayer interface {
say()
}
type dog struct {
}
type cat struct {
}
// 因为Sayer接口里只有一个say方法,所以我们只需要给dog和cat 分别实现say方法就可以实现Sayer接口了。
// Dog实现say方法
func (d dog) say() {
fmt.Println("汪汪汪")
}
// cat实现say方法
func (c cat) say() {
fmt.Println("喵喵喵")
}
// 接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dog和cat类型的变量。
func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := cat{} // 实例化一个cat
b := dog{} // 实例化一个dog
// 还是得赋值啊,让接口统一管理
x = a // 可以把cat实例直接赋值给x
x.say() // 喵喵喵
x = b // 可以把dog实例直接赋值给x
x.say() // 汪汪汪
}