zoukankan      html  css  js  c++  java
  • 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的作用

    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"  
    }

    常用tag记录

    • 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
    ", &stu.Name) // string占10字节
        fmt.Printf("Age: %p
    ", &stu.Age)   // int占8字节  int32占4字节
        fmt.Printf("score: %p
    ", &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
    ”, i)
                       case float64:    fmt.Printf(“param #%d is a float64
    ”, i)
                       case int, int64: fmt.Printf(“param #%d is an int
    ”, i)
                       case nil: fmt.Printf(“param #%d is nil
    ”, i)
                       case string: fmt.Printf(“param #%d is a string
    ”, i)
                        default: fmt.Printf(“param #%d’s type is unknown
    ”, 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
  • 相关阅读:
    关于celery django djangocelery搭配报错问题及解决方法
    django 1048错误原因及解决思路
    CSS ::Selection
    Win7编程:在按钮中加入管理员权限运行盾牌图标转载
    VisualStudioVS2010统计代码行数
    在套用母版页的页面中应用input file上传图片
    Asp.Net Url 传值出现乱码的解决方法(包括js传值)
    JS验证码刷新无反应原因
    AspnetPager
    fckeditor2.6在IE9下的弹出窗口报错问题解决
  • 原文地址:https://www.cnblogs.com/zhangyafei/p/10667465.html
Copyright © 2011-2022 走看看