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对象的信息。
posted @ 2020-07-27 19:28  菅兮徽音  阅读(308)  评论(0编辑  收藏  举报