结构体(Struct)
Go中struct的特点
-
- 用来自定义复杂数据结构
-
- struct里面可以包含多个字段(属性)
-
- struct类型可以定义方法,注意和函数的区分
-
- struct类型是值类型
-
- struct类型可以嵌套
-
- Go语言没有class类型,只有struct类型
-
- Go语言中有tag
一、struct的定义
1.struct的声明
1
2
3
4
5 1type 标识符 struct {
2 field1 type
3 field2 type
4}
5
例子
1
2
3
4
5
6 1type Student struct {
2 Name string
3 Age int
4Score int
5}
6
2. struct中的tag
Tag是结构体中某个字段别名, 可以定义多个, 空格分隔
1
2
3
4 1type Student struct {
2 Name string `ak:"av" bk:"bv" ck:"cv"`
3}
4
使用空格来区分多个tag,所以格式要尤为注意
tag的作用
tag相当于该字段的一个属性标签, 在Go语言中, 一些包通过tag来做相应的判断
举个例子, 比如我们有一个结构体
1
2
3
4 1type Student struct {
2 Name string
3}
4
然后我们将一个该结构体实例化一个 s1
1
2
3
4 1s1 := Student{
2 Name: "s1",
3 }
4
再将 s1 序列化
1
2
3
4
5
6 1v, err := json.Marshal(s1) // json.Marshal方法,json序列化,返回值和报错信息
2if err != nil { // 不为nil代表报错
3 fmt.Println(err)
4}
5fmt.Println(string(v)) // []byte转string, json
6
此时 string(v) 为
1
2
3
4 1{
2 "Name": "s1"
3}
4
因为在 Go 语言中, 结构体字段要想为外部所用就必须首字母大写, 但是如果这个 s1 是返回给前端的, 那每个字段都首字母大写就很怪, 此时我们可以给 Student 加tag解决
结构体修改为
1
2
3
4 1type Student struct {
2 Name string`json:"name"`
3}
4
序列化时, 会自己找到名为 json 的tag, 根据值来进行json后的赋值
因此 string(v) 为
1
2
3
4 1{
2 "name": "s1"
3}
4
常用tag记录
-
json json序列化或反序列化时字段的名称
-
db sqlx模块中对应的数据库字段名
-
form gin框架中对应的前端的数据字段名
-
binding 搭配 form 使用, 默认如果没查找到结构体中的某个字段则不报错值为空, binding为 required 代表没找到返回错误给前端
3. struct 中字段访问:和其他语言一样,使用点
1
2
3
4
5
6
7
8 1var stu Student
2
3stu.Name = “tony”
4stu.Age = 18
5stu.Score=20
6
7fmt.Printf(“name=%s age=%d score=%d”, stu.Name, stu.Age, stu.Score
8
4. struct定义的三种形式:
1
2
3
4 1a. var stu Student
2b. var stu *Student = new (Student)
3c. var stu *Student = &Student{}
4
其中b和c返回的都是指向结构体的指针,访问形式如下:
1
2 1stu.Name、stu.Age和stu.Score或者 (*stu).Name、(*stu).Age等
2
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 1package main
2
3import "fmt"
4
5type Student struct {
6 Name string
7 Age int32
8 score float32 // 外部的包访问不了这个字段
9}
10
11func main() {
12 // 结构体的三种定义方式
13 // 方式一
14 var stu Student
15 stu.Name = "zhangyafei"
16 stu.Age = 24
17 stu.score = 88
18
19 fmt.Printf("Name: %p\n", &stu.Name) // string占10字节
20 fmt.Printf("Age: %p\n", &stu.Age) // int占8字节 int32占4字节
21 fmt.Printf("score: %p\n", &stu.score)
22
23 // 方式二
24 var stu1 *Student = &Student{
25 Age: 20,
26 Name: "ZhangYafei",
27 }
28 fmt.Println(stu1)
29 fmt.Println(stu1.Name)
30
31 // 方式三
32 var stu2 = Student{
33 Age: 20,
34 Name: "Fei",
35 }
36 fmt.Println(stu2)
37 fmt.Println(stu2.Age)
38
39}
40
41// Name: 0xc000004460
42// Age: 0xc000004470
43// score: 0xc000004478
44
45// Age int32
46// Name: 0xc000050400
47// Age: 0xc000050410
48// score: 0xc000050414
49// &{ZhangYafei 20 0}
50// {Fei 20 0}
51
struct的定义示例
二、struct的初始化
1. struct的内存布局
struct中的所有字段在内存是连续的,布局如下:
2. 链表定义
1
2
3
4
5 1type Student struct {
2 Name string
3 Next* Student
4}
5
每个节点包含下一个节点的地址,这样把所有的节点串起来了,通常把链表中的第一个节点叫做链表头
3. 双链表定义
1
2
3
4
5
6 1type Student struct {
2 Name string
3 Next* Student
4 Prev* Student
5}
6
如果有两个指针分别指向前一个节点和后一个节点,我们叫做双链表
4. 二叉树定义
1
2
3
4
5
6 1type Student struct {
2 Name string
3 left* Student
4 right* Student
5}
6
如果每个节点有两个指针分别用来指向左子树和右子树,我们把这样的结构叫做二叉树
5. 结构体是用户单独定义的类型,不能和其他类型进行强制转换
1
2
3
4
5
6
7
8
9
10
11
12 1type Student struct {
2Number int
3}
4
5type Stu Student //alias
6
7var a Student
8a = Student(30)
9
10var b Stu
11a = b
12
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 1package main
2
3import (
4 "fmt"
5 "math/rand"
6)
7
8type Student struct {
9 Name string
10 Age int
11 Score float32
12 next *Student
13}
14
15func trans(p *Student) {
16 // 遍历链表
17 for p != nil {
18 fmt.Println(*p)
19 p = p.next
20 }
21}
22
23func insertTail(p *Student) {
24 // 尾插法
25 var tail = p
26 for i := 0; i < 10; i++ {
27 stu := &Student{
28 Name: fmt.Sprintf("stu%d", i),
29 Age: rand.Intn(100),
30 Score: rand.Float32() * 100,
31 }
32 tail.next = stu
33 tail = stu
34 }
35}
36
37func insertHead(head **Student) {
38 // 头插法
39 for i := 0; i < 10; i++ {
40 stu := &Student{
41 Name: fmt.Sprintf("stu%d", i),
42 Age: rand.Intn(100),
43 Score: rand.Float32() * 100,
44 }
45 stu.next = *head
46 *head = stu
47 }
48}
49
50func delNode(p *Student) {
51 var prev *Student = p
52 for p != nil {
53 if p.Name == "stu6" {
54 prev.next = p.next
55 break
56 }
57 prev = p
58 p = p.next
59 }
60}
61
62func addNode(p *Student, newNode *Student) {
63 for p != nil {
64 if p.Name == "stu6" {
65 newNode.next = p.next
66 p.next = newNode
67 break
68 }
69 p = p.next
70 }
71}
72
73func main() {
74 // var head *Student = &Student{}
75 var head *Student = new(Student)
76 head.Name = "ZhangYafei"
77 head.Age = 2
78 head.Score = 88
79
80 // 尾插
81 // insertTail(head)
82 // 头插
83 insertHead(&head)
84 // 遍历
85 trans(head)
86 // 删除
87 delNode(head)
88 trans(head)
89
90 // 指定位置插入节点
91 var newNode *Student = new(Student)
92 newNode.Name = "newstu"
93 newNode.Age = 34
94 newNode.Score = 100
95 addNode(head, newNod)
96}
97
链表的头插、尾插、遍历、删除和指定位置插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 1package main
2
3import "fmt"
4
5type Student struct {
6 Name string
7 Age int
8 Score float32
9 left *Student
10 right *Student
11}
12
13func PreOrdertrans(root *Student) {
14 if root == nil {
15 return
16 }
17 // 打印这棵树的节点
18 fmt.Println(root)
19 // 递归遍历左子树
20 PreOrdertrans(root.left)
21 // 递归遍历右子树
22 PreOrdertrans(root.right)
23}
24
25func InOrdertrans(root *Student) {
26 if root == nil {
27 return
28 }
29 // 递归遍历左子树
30 InOrdertrans(root.left)
31 // 打印这棵树的节点
32 fmt.Println(root)
33 // 递归遍历右子树
34 InOrdertrans(root.right)
35}
36
37func PostOrdertrans(root *Student) {
38 if root == nil {
39 return
40 }
41 // 递归遍历左子树
42 PostOrdertrans(root.left)
43 // 递归遍历右子树
44 PostOrdertrans(root.right)
45 // 打印这棵树的节点
46 fmt.Println(root)
47}
48func main() {
49 var root *Student = new(Student)
50 root.Name = "Zhangyafei"
51 root.Age = 18
52 root.Score = 88
53
54 var left1 *Student = new(Student)
55 left1.Name = "left1"
56 left1.Age = 18
57 left1.Score = 88
58
59 root.left = left1
60
61 var right1 *Student = new(Student)
62 right1.Name = "right1"
63 right1.Age = 18
64 right1.Score = 88
65
66 root.right = right1
67
68 var left2 *Student = new(Student)
69 left2.Name = "left2"
70 left2.Age = 18
71 left2.Score = 88
72
73 left1.left = left2
74
75 fmt.Println("前序遍历:")
76 PreOrdertrans(root)
77 fmt.Println("中序遍历:")
78 InOrdertrans(root)
79 fmt.Println("后序遍历:")
80 PostOrdertrans(root)
81}
82
二叉树的前、中、后序遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1package main
2
3import "fmt"
4
5type integer int
6
7type Student struct {
8 Number int
9}
10
11type Stu Student //alias 别名
12
13func main() {
14 var i integer = 1000
15 var j int = 100
16 // 变量操作必须同类型,需要强制转换类型
17 j = int(i)
18 fmt.Println(j)
19
20 var a Student
21 a = Student{30}
22
23 var b Stu
24 a = Student(b)
25 fmt.Println(a)
26}
27
变量的强制类型转换
三、工厂模式
golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1Package model
2type student struct {
3Name stirng
4Age int
5}
6
7func NewStudent(name string, age int) *student {
8return &student{
9Name:name,
10Age:age,
11}
12}
13
14Package main
15S := new (student)
16S := model.NewStudent(“tony”, 20)
17
四、struct中的tag
我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的
机制获取到,最常用的场景就是json序列化和反序列化
1
2
3
4
5 1type student struct {
2 Name stirng “this is name field”
3 Age int “this is age field”
4}
5
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 1package main
2
3import (
4 "encoding/json"
5 "fmt"
6)
7
8type Student struct {
9 Name string `json:"name"` // json打包的时候用name
10 Age int `json:"age"`
11 Score int `json:"score"`
12}
13
14func main() {
15 var stu Student = Student{
16 Name: "ZhangYafei",
17 Age: 24,
18 Score: 88,
19 }
20 data, err := json.Marshal(stu)
21 if err != nil {
22 fmt.Println("json encoder stu failed, err", err)
23 return
24 }
25 fmt.Println(string(data))
26}
27
28// {"name":"ZhangYafei","age":24,"score":88}
29
json序列化
五、匿名字段
1. 结构体中字段可以没有名字,即匿名字段
1
2
3
4
5
6
7
8
9
10
11 1type Car struct {
2 Name stirng
3 Age int
4}
5
6type Train struct {
7 Car
8 Start time.Time
9 int
10}
11
2. 匿名字段冲突处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1type Car struct {
2 Name string
3 Age int
4}
5
6type Train struct {
7 Car
8 Start time.Time
9 Age int
10}
11type A struct {
12 a int
13}
14
15type B struct {
16 a int
17 b int
18}
19
20type C struct {
21 A
22 B
23}
24
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8type Cart1 struct {
9 name string
10 age int
11}
12
13type Cart2 struct {
14 name string
15 age int
16}
17
18type Train struct {
19 Cart1
20 Cart2
21 int
22 start time.Time
23 age int
24}
25
26func main() {
27 var t Train
28 // 访问匿名字段
29 // 方式一
30 t.Cart1.name = "001"
31 t.Cart1.age = 300
32 t.Cart2.name = "002"
33 t.Cart2.age = 400
34 // 方式二
35 // t.name = "train"
36 t.age = 100
37 t.int = 200
38
39 fmt.Println(t)
40}
41
访问匿名字段
六、方法
1. Golang中的方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct
1
2 1定义:func (recevier type) methodName(参数列表)(返回值列表){}
2
2. 方法的调用
1
2
3
4
5
6
7
8
9
10 1type A struct {
2a int
3}
4func (this A) test() {
5fmt.Println(this.a)
6}
7
8var t A
9t.test()
10
3. 方法和函数的区别
1
2
3 1函数调用: function(variable, 参数列表)
2方法:variable.function(参数列表)
3
4. 指针receiver vs 值receiver
本质上和函数的值传递和地址传递是一样的
5. 方法的访问控制,通过大小写控制
6.继承
如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。
7. 组合和匿名字段
如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。
8. 多重继承
如果一个struct嵌套了多个匿名结构体,那么这个结构可以直接访问多个匿名结构体的方法,从而实现了多重继承。21. 实现String()
如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 1package main
2
3import "fmt"
4
5type integer int
6
7func (p integer) print() {
8 fmt.Println("p is", p)
9}
10
11func (p *integer) set(b integer) {
12 *p = b
13}
14
15type Student struct {
16 Name string
17 Age int
18 Score int
19 sex int
20}
21
22func (p *Student) init(name string, age int, score int) {
23 p.Name = name
24 p.Age = age
25 p.Score = score
26 fmt.Println(p)
27}
28
29func (p Student) get() Student {
30 return p
31}
32
33func main() {
34 var stu Student
35 stu.init("stu", 10, 200)
36 stu1 := stu.get()
37 fmt.Println(stu1)
38
39 var a integer
40 a = 10
41 a.print()
42
43 a.set(1000)
44 a.print()
45}
46
自定义方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 1package main
2
3import "fmt"
4
5type Car struct {
6 weight int
7 name string
8}
9
10func (self *Car) Run() {
11 fmt.Println(self, "is running")
12}
13
14type Bike struct {
15 Car
16 lunzi int
17}
18
19type Train struct {
20 c Car
21}
22
23func main() {
24 var a Bike
25 a.weight = 100
26 a.name = "bike"
27 a.lunzi = 2
28
29 fmt.Println(a)
30 a.Run()
31
32 var b Train
33 b.c.weight = 100
34 b.c.name = "train"
35 b.c.Run()
36}
37
继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 1package main
2
3import "fmt"
4
5type Car struct {
6 weight int
7 name string
8}
9
10func (self *Car) Run() {
11 fmt.Println(self, "is running")
12}
13
14type Bike struct {
15 Car
16 lunzi int
17}
18
19type Train struct {
20 c Car
21}
22
23func (self Train) String() string {
24 str := fmt.Sprintf("name=[%s] weight=[%d]", self.c.name, self.c.weight)
25 return str
26}
27
28func main() {
29 var a Bike
30 a.weight = 100
31 a.name = "bike"
32 a.lunzi = 2
33
34 fmt.Println(a)
35 a.Run()
36
37 var b Train
38 b.c.weight = 100
39 b.c.name = "train"
40 b.c.Run()
41 fmt.Printf("%s", b)
42}
43
实现String方法
接口
一、Go中的接口
1.定义
Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
1
2
3
4
5
6 1type example interface{
2 Method1(参数列表) 返回值列表
3 Method2(参数列表) 返回值列表
4…
5}
6
interface类型默认是一个指针
1
2
3
4
5
6
7
8
9
10 1type example interface{
2
3 Method1(参数列表) 返回值列表
4 Method2(参数列表) 返回值列表
5…
6}
7
8var a example
9a.Method1()
10
2. 接口实现
- a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字
- b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。
- c. 如果一个变量只含有了1个interface的方部分方法,那么这个变量没有实现这个接口。
3. 多态
一种事物的多种形态,都可以按照统一的接口进行操作
4. 接口嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1type ReadWrite interface {
2 Read(b Buffer) bool
3 Write(b Buffer) bool
4}
5type Lock interface {
6 Lock()
7 Unlock()
8}
9type File interface {
10 ReadWrite
11 Lock
12 Close()
13}
14
二、类型断言
1. 类型断言
由于接口是一般类型,不知道具体类型,如果要转成具体类型可以采用以下方法进行转换:
1
2
3
4
5
6
7
8
9 1var t int
2var x interface{}
3x = t
4y = x.(int) //转成int
5var t int
6var x interface{}
7x = t
8y, ok = x.(int) //转成int,带检查
9
2. 练习,写一个函数判断传入参数的类型
1
2
3
4
5
6
7
8
9
10
11
12 1func classifier(items ...interface{}) {
2 for i, x := range items {
3 switch x.(type) {
4 case bool: fmt.Printf(“param #%d is a bool\n”, i)
5 case float64: fmt.Printf(“param #%d is a float64\n”, i)
6 case int, int64: fmt.Printf(“param #%d is an int\n”, i)
7 case nil: fmt.Printf(“param #%d is nil\n”, i)
8 case string: fmt.Printf(“param #%d is a string\n”, i)
9 default: fmt.Printf(“param #%d’s type is unknown\n”, i)
10 }
11}
12
3. 类型断言,采用type switch方式
4.空接口
空接口没有任何方法,所以所有类型都实现了空接口。Interface{}
1
2
3
4 1var a int
2var b interface{}
3b = a
4
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 1package main
2
3import "fmt"
4
5type People struct {
6 name string
7 age int
8}
9
10type Test interface {
11 Print()
12 Sleep()
13}
14
15type Student struct {
16 name string
17 age int
18 score int
19}
20
21func (self *Student) Print() {
22 fmt.Println("name:", self.name)
23 fmt.Println("age:", self.age)
24 fmt.Println("score:", self.score)
25}
26
27func (self People) Print() {
28 fmt.Println("name:", self.name)
29 fmt.Println("age:", self.age)
30}
31
32func (self People) Sleep() {
33 fmt.Println("people is sleep")
34}
35
36func (self Student) Sleep() {
37 fmt.Println("student is sleep")
38}
39
40func main() {
41 var t Test
42 var stu Student = Student{
43 name: "Zhangyafei",
44 age: 24,
45 score: 88,
46 }
47 t = &stu
48 t.Print()
49
50 var people People = People{
51 name: "people",
52 age: 24,
53 }
54 t = people
55 t.Print()
56 t.Sleep()
57}
58
接口示例
扩展:实现一个图书管理系统,具有以下功能:
- a. 书籍录入功能,书籍信息包括书名、副本数、作者、出版日期
- b. 书籍查询功能,按照书名、作者、出版日期等条件检索
- c. 学生信息管理功能,管理每个学生的姓名、年级、身份证、性别、借了什么书等信息
- d. 借书功能,学生可以查询想要的书籍,进行借出
参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 1package model
2
3import (
4 "errors"
5 "time"
6)
7
8var (
9 ErrStockNotEnough = errors.New("stock is not enough")
10)
11
12type Book struct {
13 Name string
14 Total int
15 Author string
16 CreateTime time.Time
17}
18
19func CreateBook(name string, total int, author string, createTime time.Time) (b *Book) {
20 b = &Book{
21 Name: name,
22 Total: total,
23 Author: author,
24 CreateTime: createTime,
25 }
26 return
27}
28
29func (self *Book) canBorrow(c int) bool {
30 return self.Total >= c
31}
32
33func (self *Book) Borrow(c int) (err error) {
34 if self.canBorrow(c) == false {
35 err = ErrStockNotEnough
36 return
37 }
38 self.Total -= c
39 return
40}
41
42func (self *Book) Back(c int) (err error) {
43 self.Total += c
44 return
45}
46
book.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 1package model
2
3import (
4 "errors"
5)
6
7var (
8 ErrNotFoundBook = errors.New("not found book")
9)
10
11type Student struct {
12 Name string
13 Grade string
14 Id string
15 Sex string
16 books []*BorrowItem
17}
18
19type BorrowItem struct {
20 book *Book
21 num int
22}
23
24func CreateStudent(name, grade, id, sex string) *Student {
25 stu := &Student{
26 Name: name,
27 Grade: grade,
28 Id: id,
29 Sex: sex,
30 }
31 return stu
32}
33
34func (self *Student) AddBook(b *BorrowItem) {
35 self.books = append(self.books, b)
36}
37
38func (self *Student) DelBook(b *BorrowItem) (err error) {
39 for i := 0; i < len(self.books); i++ {
40 if self.books[i].book.Name == b.book.Name {
41 if b.num == self.books[i].num {
42 front := self.books[0:i]
43 left := self.books[i+1:]
44 front = append(front, left...)
45 self.books = front
46 return
47 }
48 self.books[i].num -= b.num
49 return
50 }
51 }
52 err = ErrNotFoundBook
53 return
54}
55
56func (self *Student) GetBookList() []*BorrowItem {
57 return self.books
58}
59
stu.go