8 struct类型
九路 507 13

struct

Go 语言中,也和 C 或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。 例如,我们可以创建一个自定义类型 Person 代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之 struct。 如下代码所示:

type Person struct {
    name string
    age int
}

到了吗?声明一个 struct 如此简单,上面的类型包含有两个字段

  • 一个 string 类型的字段 name,用来保存用户名称这个属性
  • 一个 int 类型的字段 age,用来保存用户年龄这个属性

如何使用 struct 呢?请看下面的代码

type Person struct {
    name string
    age int
}
var p Person        // p 现在就是 Person 类型的变量了
p.name = "tom"      // 赋值"tom"给 P 的 name 属性.
p.age = 25         // 赋值"25"给变量 P 的 age 属性

fmt.Printf("The person's name is %s", P.name) // 访问 P 的 name 属性

除了上面这种 P 的声明使用之外,还有两种声明使用方式

  • 按照顺序提供初始化值 p := Person{"Tom", 25}
  • 通过 field:value 的方式初始化,这样可以任意顺序 p := Person{age:24, name:"Tom"}

下面我们看一个完整的使用 struct 的例子

package main
import "fmt"

// 声明一个新的类型
type Person struct {
    name string
    age int
}

// 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
// struct 也是传值的
func Older(p1, p2 person) (person, int) {
    if p1.age>p2.age { // 比较 p1 和 p2 这两个人的年龄
        return p1, p1.age - p2.age
    }
    return p2, p2.age - p1.age
}

func main(){
    var tom person
    // 赋值初始化
    tom.name, tom.age = "Tom", 18
    // 两个字段都写清楚的初始化
    bob := person{age:25, name:"Bob"}
    // 按照 struct 定义顺序初始化值
    paul := person{"Paul", 43}
    tb_Older, tb_diff := Older(tom, bob)
    tp_Older, tp_diff := Older(tom, paul)
    bp_Older, bp_diff := Older(bob, paul)
    fmt.Printf("Of %s and %s, %s is older by %d years
",
    tom.name, bob.name, tb_Older.name, tb_diff)
    fmt.Printf("Of %s and %s, %s is older by %d     years
",
    tom.name, paul.name, tp_Older.name, tp_diff)
    fmt.Printf("Of %s and %s, %s is older by %d years
",
    bob.name, paul.name, bp_Older.name, bp_diff)
}

struct 的匿名字段

我们上面介绍了如何定义一个 struct,定义的时候是字段名与其类型一一对应,实际上 Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

当匿名字段是一个 struct 的时候,那么这个 struct 所拥有的全部字段都被隐式地引入了当

前定义的这个 struct。让我们来看一个例子,让上面说的这些更具体化

package main

import "fmt"

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human // 匿名字段,那么默认 Student 就包含了 Human 的所有字段
    speciality string
}

func main()  {
    // 我们初始化一个学生
    mark := Student{Human{"Mark", 25, 120}, "Computer Science"}

    // 我们访问相应的字段
    fmt.Println("His name is ", mark.name)
    fmt.Println("His age is ", mark.age)
    fmt.Println("His weight is ", mark.weight)
    fmt.Println("His speciality is ", mark.speciality)

    // 修改对应的备注信息
    mark.speciality = "AI"
    fmt.Println("Mark changed his speciality")
    fmt.Println("His speciality is ", mark.speciality)

    // 修改他的年龄信息
    fmt.Println("Mark become old")
    mark.age = 46
    fmt.Println("His age is", mark.age)

    // 修改他的体重信息
    fmt.Println("Mark is not an athlet anymore")
    mark.weight += 60
    fmt.Println("His weight is", mark.weight)
}

打印如下:

His name is  Mark
His age is  25
His weight is  120
His speciality is  Computer Science
Mark changed his speciality
His speciality is  AI
Mark become old
His age is 46
Mark is not an athlet anymore
His weight is 180

8 struct类型

我们看到 Student 访问属性 age 和 name 的时候,就像访问自己所有用的字段一样,对, 匿名字段就是这样,能够实现字段的继承。是不是很酷啊?还有比这个更酷的呢,那就是 student 还能访问 Human 这个字段作为字段名。请看下面的代码,是不是更酷了。

mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1

通过匿名访问和修改字段相当的有用,但是不仅仅是 struct 字段哦 所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子

package main

import "fmt"

type Skills []string


type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human // 匿名字段,那么默认 Student 就包含了 Human 的所有字段
    speciality string
    Skills // 匿名字段,自定义的类型 string slice
    int // 内置类型作为匿名字段
}

func main()  {
    // 初始化学生 Jane
    jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
    // 现在我们来访问相应的字段
    fmt.Println("Her name is ", jane.name)
    fmt.Println("Her age is ", jane.age)
    fmt.Println("Her weight is ", jane.weight)
    fmt.Println("Her speciality is ", jane.speciality)

    // 我们来修改他的 skill 技能字段
    jane.Skills = []string{"anatomy"}
    fmt.Println("Her skills are ", jane.Skills)
    fmt.Println("She acquired two new ones ")
    jane.Skills = append(jane.Skills, "physics", "golang")
    fmt.Println("Her skills now are ", jane.Skills)

    // 修改匿名内置类型字段
    jane.int = 3
    fmt.Println("Her preferred number is", jane.int)
}

打印如下

Her name is  Jane
Her age is  35
Her weight is  100
Her speciality is  Biology
Her skills are  [anatomy]
She acquired two new ones 
Her skills now are  [anatomy physics golang]
Her preferred number is 3

从 上面例子我们看出来 struct 不仅仅能够将 struct 作为匿名字段、自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作(如例子中的 append)。

这里有一个问题:如果 human 里面有一个字段叫做 phone,而 student 也有一个字段叫做phone,那么该怎么办呢? Go 里面很简单的解决了这个问题,最外层的优先访问,也就是当你通过 student.phone 访问的时候,是访问 student 里面的字段,而不是 human 里面的字段。

这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。 请看下面的例子

package main

import "fmt"

type Skills []string


type Human struct {
    name string
    age int
    phone string // Human 类型拥有的字段
}

type Employee struct {
    Human                 // 匿名字段 Human
    speciality string
    phone string         // 雇员的 phone 字段
}

func main()  {
    Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
    fmt.Println("Bob's work phone is:", Bob.phone)
    // 如果我们要访问 Human 的 phone 字段
    fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}
评论区

索引目录