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