go语言学习——结构体,方法,接口
结构体
概念:是由一系列相同类型或不同类型的数据构成的数据集合。结构体成员是由一系列的成员变量构成。这些成员变量被称为字段。
结构体的定义:
type Person struct {
name string
age int
sex string
address string
}
初始化结构体的方法:
1.
var p1 Person
p1.name = "Lisa"
p1.age = 23
p1.sex = "女"
p1.address = "深圳"
2.
p2:= Person{}
p2.name = "Tom"
p2.age = 22
p2.sex = "男"
p2.address = "上海"
3.
p3:= Person{
name:"Daisy",
age:24,
sex:"女",
address:"北京"}
4.(3的省略写法,要按定义的顺序写)
p4:= Person{"Daisy",24,"女","北京"}
go中的数据类型分类:
值类型:int,float,bool,string,array,struct
值类型在复制的时候传递的是值,即只把数据复制了一份
引用类型:slice,map,function,pointer
引用类型在复制的时候传递的是地址
用数组和切片来直观对比一下:
var nums1 = [5] int{1,2,3,4,5}
nums2 := nums1
fmt.Println(nums1)//[1 2 3 4 5]
fmt.Println(nums2)//[1 2 3 4 5]
nums2[0]=100
fmt.Println(nums1)//[1 2 3 4 5]
fmt.Println(nums2)//[100 2 3 4 5]
fmt.Println("----------------------------")
var nums3 = [] int{1,2,3,4,5}
nums4 := nums3
fmt.Println(nums3)//[1 2 3 4 5]
fmt.Println(nums4)//[1 2 3 4 5]
nums4[0]=100
fmt.Println(nums3)//[100 2 3 4 5]
fmt.Println(nums4)//[100 2 3 4 5]
我们可以看到,数组的复制只会复制值,但是切片的复制复制的是地址。
结构体和数组一样,传递的是值
如何定义一个结构体的指针:
var pp1 *Person
pp1 = &p1
fmt.Println(pp1) //&{Lisa 23 女 深圳}
fmt.Println(*pp1) //{Lisa 23 女 深圳}
利用指针来改变结构体的值,达到浅拷贝:
pp1.name = "Mike"//这里是省略写法,完整写法为(*pp1).name = "Mike"
fmt.Println(pp1)//&{Mike 23 女 深圳}
fmt.Println(*pp1)//{Mike 23 女 深圳}
还可以用new来创建指针
pp2 := new(Person)
这句话与
var pp1 *Person
是等价的。
匿名结构体的定义
s :=struct{
name string
age int
}{
name:"Lisa",
age:20,
}
fmt.Println(s)//{Lisa 20}
结构体的匿名字段
定义方法:
type Worker struct{
string
int
}
初始化方法:
w :=Worker{"Tom",28}
fmt.Println(w)//{Lisa 20}
如果用匿名字段,字段的类型是不能重复的(好不方便啊)
结构体的嵌套
has a关系:
一个结构体的字段可以是另一个结构体,如:
type Book struct{
name string
price int
}
type Student struct{
id int
book Book
age int
}
学生结构体中有一个字段叫Book,它也是一个结构体
含嵌套的结构体的初始化:
b:=Book{}
b.name="go语言圣经"
b.price=50
fmt.Println(b)//{go语言圣经 50}
s:=Student{}
s.id=1001
s.book = b
s.age = 24
fmt.Println(s)//{1001 {go语言圣经 50} 24}
is a关系:(模拟继承)
一个结构体作为另一个结构体的匿名字段,如:
type Person struct{
name string
age int
}
type Student struct{
Person
school string
}
在这里,Person结构体中的name和age字段是Student结构体的提升字段,Student结构体的对象可以直接访问,不需要通过Person对象:
s:= Student{Person{"Lisa",24},"北京大学"}
fmt.Println(s.age)//24
如上,我们直接通过Student对象s访问了age。
方法:一个带有接受者的函数。
方法名是可以重复的,只要他们的接受者不同即可。
方法的定义:
type Person struct{
name string
age int
}
func (p Person) eat(){
fmt.Println(p.name,"吃饭")
}
方法的调用:
p1:=Person{"Lisa",20}
p1.eat()//Lisa 吃饭
方法的调用者可以是结构体的指针:
type Person struct{
name string
age int
}
func (p *Person) eat(){
fmt.Println(p.name,"吃饭")
}
调用:
p1:=&Person{"Lisa",20}
fmt.Println(p1)//&{Lisa 20}
p1.eat()//Lisa 吃饭
继承中的方法
首先定义两个结构体,Person是Student的子类,注意要使用匿名字段
type Person struct{
name string
age int
}
type Student struct{
Person
school string
}
然后我们定义一个父类的方法:
func (p Person) eat(){
fmt.Println(p.name,"父类方法,吃饭")
}
我们创建一个父类的对象,调用刚刚的父类的方法,这个当然是没有问题的:
p1:=Person{"Lisa",24}
p1.eat()//Lisa 父类方法,吃饭
然后我们试着创建一个子类的对象,它可以访问自己的属性,也可以直接访问父类的属性(因为父类的字段就是子类对象的提升字段):
s1:=Student{Person{"Tom",24},"北京大学"}
fmt.Println(s1.name,s1.age,s1.school)//Tom 24 北京大学
子类对象可以调用父类方法:
s1:=Student{Person{"Tom",24},"北京大学"}
s1.eat()//Tom 父类方法,吃饭
子类可以新增自己的方法:
func (s Student) study(){
fmt.Println(s.name,"子类新增方法,学习")
}
子类对象可以调用自己的方法:
s1:=Student{Person{"Tom",24},"北京大学"}
s1.study()//Tom 子类新增方法,学习
子类可以重写父类方法:
func (s Student) eat(){
fmt.Println(s.name,"子类重写父类方法,吃好吃的")
}
子类调用重写后的方法:
s1:=Student{Person{"Tom",24},"北京大学"}
s1.eat()//Tom 子类重写父类方法,吃好吃的
接口:
接口就是一组方法的签名(这里的方法只有声明,没有实现)
某个结构体实现了这个接口中的所有方法,那么这个结构体就是这个接口的实现。
定义一个接口:
type USB interface {
start()
end()
}
我们写一个结构体,如果它实现了这个接口中的所有方法,那么它就算这个接口的实现:
type mouse struct {
mname string
}
func (m mouse) start(){
fmt.Println("鼠标开始工作")
}
func (m mouse) end(){
fmt.Println("鼠标结束工作")
}
实现了这个接口有什么用呢?当其他函数中传入这个接口类型作为参数时(返回值也一样),我们就可以传入这个结构体的对象,这样具体实现的方法就根据实现类为准。
还有,如果一个类型被定义为接口类型,那么实际上可以传入它的实现类的对象。
m1:=mouse{"可爱鼠标"}
work(m1)//鼠标开始工作 鼠标结束工作
如果一个接口没有实现类,那么也就无法传入任何参数了。
空接口:
空接口的定义:
type A interface {
}
任何类型都实现了空接口。
我们可以定义两个结构体来测试一下:
type Cat struct{
name string
age int
}
type Student struct{
sex string
school string
}
测试代码中,我们传入了刚刚定义的两个结构体,又用了字符串类型来测试,发现他们都可以作为空接口的实现:
var a1 A = Cat{"小白",3}
fmt.Println(a1)//{小白 3}
var a2 A = Student{"女","北京大学"}
fmt.Println(a2)//{女 北京大学}
var a3 A = "我是一个字符串"
fmt.Println(a3)//我是一个字符串
接口的嵌套
接口是可以多继承的,一个接口可以继承多个接口,一个实现类也可以实现多个接口:
先写AB两个接口,C接口继承了AB两个接口,同时它还有一个自己的函数:
type A interface {
test1()
}
type B interface{
test2()
}
type C interface{
A
B
test3()
}
然后我们可以写一个实现类,它实现了3个方法,也就是说它同时实现了ABC三个接口。
type Student struct{
name string
age int
}
func (s Student) test1() {
fmt.Println("实现test1方法")
}
func (s Student) test2() {
fmt.Println("实现test1方法")
}
func (s Student) test3() {
fmt.Println("实现test1方法")
}
也就是说,当出现这三个接口的类型时,均可由Student这个结构体的对象s来代替。但是需要注意的是,把它视作不同的类型,它能够调用的方法是不一样的,如:
var a A = Student{"Lisa",24}
a.test1()
这里a只能调用test1。b跟a类似,只能调用test2。
var c C = Student{"Lisa",24}
c.test1()
c.test2()
c.test3()
这里c能调用全部的3个方法。
var s Student = Student{"Lisa",24}
s.test1()
s.test2()
s.test3()
直接定义为Student结构体类型,它也可以调用3个方法。
接口断言:
如果一个函数的形参是接口,那么我们需要在函数体中对它进行断言。
这样调用这个函数时,我们就可以知道传入的到底是哪个实现类。
首先我们定义一个接口:
type Work interface {
gowork()
offwork()
}
然后我们写两个实现类:
学生实现类:
type Student struct {
name string
age int
}
func (s Student) gowork(){
fmt.Println("学生上学")
}
func (s Student) offwork(){
fmt.Println("学生放学")
}
老师实现类:
type Teacher struct {
sex string
school string
}
func (t Teacher) gowork(){
fmt.Println("老师上班")
}
func (t Teacher) offwork(){
fmt.Println("老师下班")
}
写一个包含断言的测试方法:
func GetType(w Work){
instance,ok:=w.(Student)
if !ok{
fmt.Println(ok)
}
fmt.Println(instance)
}
我们在main函数中定义一个学生类型的对象,传入测试方法进行测试:
s1:=Student{"Lisa",24}
GetType(s1)//{Lisa 24}
断言成功,输出了s1对象的信息。