Golang基础笔记五之结构体

Golang基础笔记五之结构体

本文首发于公众号:Hunter后端

原文链接:Golang基础笔记五之结构体

本篇笔记介绍 Golang 中的结构体。

在 Go 中,结构体是一种用户自定义的数据类型,可以将不同类型的数据组合在一起。

以下是本篇笔记目录:

  1. 结构体的定义和使用
  2. 嵌套结构体
  3. 创建结构体递归结构
  4. 结构体标签

1、结构体的定义和使用

结构体定义的基本语法如下:

type 结构体名称 struct{
    field1 type
    field2 type
}

我们可以先创建一个 struct:

type Person struct {
    Name string
    Age  int
}

下面介绍几种方式来实例化结构体:

1. 使用结构体字面量初始化

使用结构体字面量初始化结构体变量,在创建结构体变量时,将字段值直接赋给结构体的字段。

其语法如下:

person := Person{Name: "Alice", Age: 30}
fmt.Println(person)

在这里,如果有字段没有赋值,那么这个字段的值就是该类型的零值。

2. 按字段顺序初始化

我们可以在初始化的时候不指定字段,那么它会按照结构体中的字段顺序进行赋值:

person := Person{"Alice", 30}
fmt.Println(person)

但是这个操作有个问题,就是结构体中的全部字段都需要赋值。

3. 使用 new 函数创建结构体指针

可以使用 new() 函数创建一个结构体,但是注意,其返回值是对应结构体的指针:

var person *Person = new(Person)
person.Age = 1
person.Name = "Hunter"

2、嵌套结构体

结构体可以嵌套在另一个结构体里,这个就是嵌套结构体。

1. 匿名嵌套

在嵌套的时候如果直接嵌套不指定字段名,这个就叫匿名嵌套,比如下面的示例:

type Address struct {
    City string
}
type Person struct {
    Name string
    Age  int
    Address
}

这里没有给 Address 指定字段名,所以这个 Address 就是一个匿名字段。

以下是一个初始化的例子:

person := Person{
    Name: "Hunter",
    Age:  18,
    Address: Address{
        City: "Beijing",
    },
}

使用匿名嵌套,我们可以直接访问到嵌套的结构体的字段:

fmt.Println(person.City)

这里 City 是 Addree 结构体的字段,但因为是匿名嵌套,所以可以直接访问到。

当然,也可以通过 Address 结构体进行访问:

fmt.Println(person.Address.City)

2. 具名嵌套

如果嵌套的时候显式指定字段名,就叫具名嵌套,比如:

type Company struct {
    CompanyName string
}
type Person struct {
    Name string
    Age  int
    CompanyInfo Company
}

这里为了以示区分,给 Company 结构体的字段名设为 CompanyInfo,其初始化的方式还是类似的:

person := Person{
    Name: "Hunter",
    Age:  18,
    CompanyInfo: Company{
        CompanyName: "ACompany",
    },
}

而如果要访问到嵌套的结构体的字段,则需要通过 CompanyInfo 进行访问:

fmt.Println(person.CompanyInfo.CompanyName)

3、创建结构体递归结构

我们可以通过结构体嵌套自己来形成递归结构,但是嵌套的类型需要是自身结构体的指针。

下面介绍一下使用结构体来创建链表和二叉树。

1. 链表

链表的结构体如下:

type ListNode struct {
    Val  int
    Next *ListNode
}

接下来创建一个链表,并打印节点内容:

head := &ListNode{Val: 1}
node2 := &ListNode{Val: 2}
node3 := &ListNode{Val: 3}
head.Next = node2
node2.Next = node3
node := head
for {
    if node == nil {
        break
    } else {
        fmt.Println(node.Val)
        node = node.Next
    }
}

2. 二叉树

以下是二叉树的结构体:

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

接下来创建一个二叉树,并用前序遍历打印二叉树节点:

func preorderTraversal(root *TreeNode) {
    if root == nil {
        return
    }
    fmt.Println(root.Val)
    if root.Left != nil {
        preorderTraversal(root.Left)
    }
    if root.Right != nil {
        preorderTraversal(root.Right)
    }
}
root := &TreeNode{Val: 1}
root.Left = &TreeNode{Val: 2}
root.Right = &TreeNode{Val: 3}
root.Left.Left = &TreeNode{Val: 4}
root.Left.Right = &TreeNode{Val: 5}
preorderTraversal(root)

4、结构体标签

结构体标签是附加在结构体字段后的元数据字符串,可以用于 json 序列化和反序列化、Web 框架表单数据绑定、数据库 ORM 映射等。

下面介绍一下 json 标签的使用。

json 标签

json 标签用于 JSON 的序列化和反序列化,有以下几个功能:

  1. 字段映射:json:"field_name" 指定 JSON 字段名,如果不设置,默认为 struct 的字段名
  2. 忽略字段:json:"-" 表示该字段不参与序列化
  3. 忽略空值:json:"omitempty" 表示如果字段值为零值时,则不参与序列化
  4. 类型转换:json:"field_name,string", 表示将数值转换为字符串类型

下面是一个结构体示例:

type Person struct {
    Name        string `json:"name"`
    Age         int    `json:"age,omitempty,string"`
    Gender      string `json:""`
    Address     string `json:"address"`
    NoJsonField string `json:"-"`
}

在上面的结构体中,分别对字段实现了下面的操作:

  1. 将 Name 字段映射为 json 里的 name 字段
  2. Age 字段映射为 age 字段,如果 Age 字段值为零值,则不参与序列化,如果 Age 字段值不为零值,则将 Age 字段值转换为字符串类型
  3. Gender 字段映射到 json,但是不改变字段名
  4. Address 字段映射为 address 字段
  5. NoJsonField 字段不参与序列化

对于这些操作,我们使用下面的代码进行测试,记住,要先引入 json 模块:

import (
    "encoding/json"
)

然后进行操作:

person := Person{
    Name:        "Hunter",
    Age:         18,
    Gender:      "Male",
    Address:     "Beijing",
    NoJsonField: "NoJsonValue",
}

_person, err := json.Marshal(person)

if err != nil {
    fmt.Println("JSON 编码错误:", err)
} else {
    fmt.Println(string(_person))
}

// 测试 omitempty,为零值时忽略
person.Age = 0
_person, err = json.Marshal(person)
if err != nil {
    fmt.Println("JSON 编码错误:", err)
} else {
    fmt.Println(string(_person))
}

分别打印出两条 json 数据:

{"name":"Hunter","age":"18","Gender":"Male","address":"Beijing"}
{"name":"Hunter","Gender":"Male","address":"Beijing"}

输出的结果可以印证前面我们对字段进行的定义逻辑。

除了 json 标签,还有 form 标签用于表单数据绑定,gorm 标签用于数据库映射,validate 标签用于数据验证等,后面有用到的时候再做介绍。

原文地址:https://www.cnblogs.com/hunterxiong/p/18951127