ZetCode

Go struct

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何在 Golang 中使用结构体。

Struct

一个 struct 是一个用户定义的类型,它包含一组字段。它用于将相关数据组合成一个单一单元。Go 的 struct 可以与一个不包含继承特性的轻量级类进行比较。

Go struct 定义

Struct 使用 type 关键字进行定义。

type User struct {
    name       string
    occupation string
    age        int
}

使用 type 关键字创建一个新类型。后面跟着类型名称(User)。struct 关键字表示我们正在创建一个 struct。在大括号内,我们有一系列字段。每个字段都有一个名称和一个类型。

Go 初始化 struct

我们将展示如何在 Go 中初始化 struct 类型。

u := User{"John Doe", "gardener", 34}

创建了一个新的 User struct。Struct 字段使用大括号内的值进行初始化。在这种情况下,字段的顺序是相关的。

u := User{
    name:       "John Doe",
    occupation: "gardener",
    age:        34,
}

我们可以同时提供字段名和值。在这种情况下,顺序并不重要。请注意,最后的逗号是必需的。

u := User{}

如果我们省略大括号中的值,它们将被初始化为零值。

Go struct 简单示例

下面是一个 Go struct 简单示例。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u := User{"John Doe", "gardener", 34}

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

我们定义了一个包含三个字段的 User struct。

type User struct {
    name       string
    occupation string
    age        int
}

我们声明了 User struct。

u := User{"John Doe", "gardener", 34}

我们初始化了 User struct。

fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)

我们打印了 User struct 的内容。

$ go run main.go
John Doe is 34 years old and he is a gardener

Go struct 访问字段

Struct 字段使用点运算符进行访问。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u := User{}
    u.name = "John Doe"
    u.occupation = "gardener"
    u.age = 34

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

我们创建了一个空的 User struct。我们使用点运算符初始化字段并读取它们。

Go 匿名 struct

在 Go 中可以创建匿名 struct。匿名 struct 没有名称。它们只被创建一次。

main.go
package main

import "fmt"

func main() {

    u := struct {
        name       string
        occupation string
        age        int
    }{
        name:       "John Doe",
        occupation: "gardener",
        age:        34,
    }

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

匿名 struct 仅使用 struct 关键字创建。Struct 的声明后面跟着它的初始化。

Go 嵌套 struct

Go struct 可以嵌套。

main.go
package main

import "fmt"

type Address struct {
    city    string
    country string
}

type User struct {
    name    string
    age     int
    address Address
}

func main() {

    p := User{
        name: "John Doe",
        age:  34,
        address: Address{
            city:    "New York",
            country: "USA",
        },
    }

    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.address.city)
    fmt.Println("Country:", p.address.country)
}

在代码示例中,Address struct 嵌套在 User struct 中。

fmt.Println("City:", p.address.city)
fmt.Println("Country:", p.address.country)

要访问嵌套 struct 的字段,我们首先使用点运算符访问内部 struct;然后访问相应的字段。

$ go run nested.go
Name: John Doe
Age: 34
City: New York
Country: USA

Go struct 字段提升

嵌套匿名 struct 的字段会被提升;也就是说,它们在不引用嵌套 struct 的情况下即可访问。

main.go
package main

import "fmt"

type Address struct {
    city    string
    country string
}

type User struct {
    name string
    age  int
    Address
}

func main() {
    p := User{
        name: "John Doe",
        age:  34,
        Address: Address{
            city:    "New York",
            country: "USA",
        },
    }

    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.city)
    fmt.Println("Country:", p.country)
}

在代码示例中,我们有一个嵌套的 Address struct。

type User struct {
    name string
    age  int
    Address
}

User struct 包含一个嵌套的匿名 Address struct。该字段没有名称。

fmt.Println("City:", p.city)
fmt.Println("Country:", p.country)

citycountry 字段被提升。它们可以直接通过父 struct 访问。

$ go run main.go
Name: John Doe
Age: 34
City: New York
Country: USA

Go struct 函数字段

Struct 字段可以是函数。

main.go
package main

import "fmt"

type Info func(string, string, int) string

type User struct {
    name       string
    occupation string
    age        int
    info       Info
}

func main() {

    u := User{
        name:       "John Doe",
        occupation: "gardener",
        age:        34,
        info: func(name string, occupation string, age int) string {

            return fmt.Sprintf("%s is %d years old and he is a %s\n", name, age, occupation)
        },
    }

    fmt.Printf(u.info(u.name, u.occupation, u.age))
}

在代码示例中,我们有 User struct。它的 info 字段是一个名为 Info 的函数。

Go struct 指针

可以使用 & 运算符或 new 关键字创建 struct 的指针。指针使用 * 运算符解引用。

main.go
package main

import "fmt"

type Point struct {
    x int
    y int
}

func main() {

    p := Point{3, 4}

    p_p := &p

    (*p_p).x = 1
    p_p.y = 2

    fmt.Println(p)
}

在代码示例中,我们创建了一个 Point struct 的指针。

p_p := &p

& 运算符返回 Point 结构体的指针。

(*p_p).x = 1
p_p.y = 2

指针使用 * 运算符解引用。Go 也允许直接使用点运算符。

或者,我们可以使用 new 关键字创建 struct 的指针。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u := new(User)
    u.name = "Richard Roe"
    u.occupation = "driver"
    u.age = 44

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

该示例使用 new 关键字创建了一个新的 User struct 指针。

Go struct 构造函数

Go 中没有内置的构造函数。程序员有时会创建构造函数作为最佳实践。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func newUser(name string, occupation string, age int) *User {

    p := User{name, occupation, age}
    return &p
}

func main() {

    u := newUser("Richard Roe", "driver", 44)

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

在代码示例中,我们有 newUser 构造函数,它创建新的 User struct。该函数返回新创建 struct 的指针。

Go struct 是值类型

Go struct 是值类型。当我们把一个 struct 变量赋值给另一个 struct 变量时,会创建一个 struct 的新副本。同样,当我们把一个 struct 传递给另一个函数时,函数会接收到 struct 的新副本。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u1 := User{"John Doe", "gardener", 34}

    u2 := u1

    u2.name = "Richard Roe"
    u2.occupation = "driver"
    u2.age = 44

    fmt.Printf("%s is %d years old and he is a %s\n", u1.name, u1.age, u1.occupation)
    fmt.Printf("%s is %d years old and he is a %s\n", u2.name, u2.age, u2.occupation)
}

在代码示例中,我们将一个 struct 赋给了另一个 struct。更改新 struct 的字段不会影响原始 struct。

$ go run main.go
John Doe is 34 years old and he is a gardener
Richard Roe is 44 years old and he is a driver

这两个 struct 是独立的实体。

比较 Go struct

如果 Go struct 的所有对应字段都相等,那么它们就相等。

main.go
package main

import "fmt"

type Point struct {
    x int
    y int
}

func main() {

    p1 := Point{3, 4}
    p2 := Point{3, 4}

    if p1 == p2 {

        fmt.Println("The structs are equal")
    } else {

        fmt.Println("The structs are not equal")
    }
}

在代码示例中,我们比较了两个 Point struct。

$ go run main.go
The structs are equal

Go 导出 struct

以大写字母开头的命名 struct 会被导出,并且可以在其包外访问。同样,以大写字母开头的 struct 字段也会被导出。以小写字母开头的 struct 名称和字段仅在其包内可见。

$ go mod init exporting

我们使用 go mod init 命令创建一个新的 Go 模块。

go.mod
main
└── main.go
model
├── address.go
└── user.go

这是项目结构。

model/user.go
package model

type User struct {
    Name       string
    Occupation string
    age        int
}

User struct 的 NameOccupation 字段被导出,而 age 字段未被导出。

model/address.go
package model

type address struct {
    city    string
    country string
}

address struct 未被导出。我们无法在 main.go 文件中引用它。

main/main.go
package main

import (
    "exporting/model"
    "fmt"
)

func main() {

    u := model.User{Name: "John Doe", Occupation: "gardener"}

    fmt.Printf("%s is a %s\n", u.Name, u.Occupation)
}

main.go 文件中,我们从 exporting/model 包导入了 NameOccupation 字段。

Go 创建 struct 切片

在下面的示例中,我们创建了一个 struct 切片。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    country    string
}

func main() {

    users := []User{}
    users = append(users, User{"John Doe", "gardener", "USA"})
    users = append(users, User{"Roger Roe", "driver", "UK"})
    users = append(users, User{"Paul Smith", "programmer", "Canada"})
    users = append(users, User{"Lucia Mala", "teacher", "Slovakia"})
    users = append(users, User{"Patrick Connor", "shopkeeper", "USA"})
    users = append(users, User{"Tim Welson", "programmer", "Canada"})
    users = append(users, User{"Tomas Smutny", "programmer", "Slovakia"})

    for _, user := range users {

        fmt.Println(user)
    }
}

定义了一个 User 类型。然后我们创建了一个空的 User struct 切片。我们使用 append 向切片添加元素。

Go 过滤 struct 切片

在下一个示例中,我们过滤了一个 Go 结构体切片。

main.go
package main

import "fmt"

type User struct {
    name       string
    occupation string
    country    string
}

func main() {

    users := []User{

        {"John Doe", "gardener", "USA"},
        {"Roger Roe", "driver", "UK"},
        {"Paul Smith", "programmer", "Canada"},
        {"Lucia Mala", "teacher", "Slovakia"},
        {"Patrick Connor", "shopkeeper", "USA"},
        {"Tim Welson", "programmer", "Canada"},
        {"Tomas Smutny", "programmer", "Slovakia"},
    }

    var programmers []User

    for _, user := range users {

        if isProgrammer(user) {
            programmers = append(programmers, user)
        }
    }

    fmt.Println("Programmers:")
    for _, u := range programmers {

        fmt.Println(u)
    }
}

func isProgrammer(user User) bool {

    return user.occupation == "programmer"
}

在代码示例中,我们定义了一个用户切片。我们创建了一个只包含程序员的新切片。

type User struct {
    name       string
    occupation string
    country    string
}

User struct 有三个字段。

users := []User{

    {"John Doe", "gardener", "USA"},
    {"Roger Roe", "driver", "UK"},
    {"Paul Smith", "programmer", "Canada"},
    {"Lucia Mala", "teacher", "Slovakia"},
    {"Patrick Connor", "shopkeeper", "USA"},
    {"Tim Welson", "programmer", "Canada"},
    {"Tomas Smutny", "programmer", "Slovakia"},
}

这是原始的 User 结构体切片。

var programmers []User

过滤后的用户/程序员存储在 programmers 切片中。

for _, user := range users {

    if isProgrammer(user) {
        programmers = append(programmers, user)
    }
}

我们遍历 users 切片,如果用户满足 isProgrammer 断言,则将当前用户添加到 programmers 切片中。

func isProgrammer(user User) bool {

    return user.occupation == "programmer"
}

IsProgrammer 断言对于所有 occupation 字段等于 "programmer" 的用户都返回 true。

$ go run main.go
Programmers:
{Paul Smith programmer Canada}
{Tim Welson programmer Canada}
{Tomas Smutny programmer Slovakia}

来源

The Go Programming Language Specification

在本文中,我们介绍了 Golang 中的 struct。

作者

我叫 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面有超过十年的经验。

列出所有 Go 教程