结构体(Struct)
Go中struct的特点
一、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) 为
因为在 Go 语言中, 结构体字段要想为外部所用就必须首字母大写, 但是如果这个 s1 是返回给前端的, 那每个字段都首字母大写就很怪, 此时我们可以给 Student 加tag解决
结构体修改为
type Student struct {
Name string`json:"name"`
}
序列化时, 会自己找到名为 json 的tag, 根据值来进行json后的赋值
因此 string(v) 为
- 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的定义示例
二、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}
json序列化
五、匿名字段
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)
}
实现String方法
接口
一、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
}
book.go
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
}
stu.go
使用空格来区分多个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) 为
因为在 Go 语言中, 结构体字段要想为外部所用就必须首字母大写, 但是如果这个 s1 是返回给前端的, 那每个字段都首字母大写就很怪, 此时我们可以给 Student 加tag解决
结构体修改为
type Student struct {
Name string`json:"name"`
}
序列化时, 会自己找到名为 json 的tag, 根据值来进行json后的赋值
因此 string(v) 为
- binding 搭配 form 使用, 默认如果没查找到结构体中的某个字段则不报错值为空, binding为 required 代表没找到返回错误给前端
|
请发表评论