跳转到主要内容

介绍


围绕具体细节构建抽象是编程语言可以为开发人员提供的最大工具。结构允许 Go 开发人员描述 Go 程序运行的世界。结构体允许我们讨论地址,而不是推理描述街道、城市或邮政编码的字符串。在我们努力告诉未来的开发人员(包括我们自己)哪些数据对我们的 Go 程序很重要以及未来的代码应该如何适当地使用这些数据时,它们充当了文档的天然纽带。结构可以以几种不同的方式定义和使用。在本教程中,我们将了解这些技术中的每一种。

定义结构


结构就像您可能用来报税的纸质表格一样工作。纸质表格可能包含文本信息字段,例如您的名字和姓氏。除了文本字段,表单可能还有复选框来指示布尔值,例如“已婚”或“单身”,或用于出生日期的日期字段。类似地,结构将不同的数据块收集在一起,并将它们组织在不同的字段名称下。当您使用新结构初始化变量时,就好像您已经复印了一个表格并准备好填写。

要创建一个新结构,你必须首先给 Go 一个蓝图,描述结构包含的字段。此结构定义通常以关键字类型开头,后跟结构名称。在此之后,使用 struct 关键字后跟一对大括号 {},您可以在其中声明 struct 将包含的字段。一旦定义了结构,就可以声明使用该结构定义的变量。这个例子定义了一个结构并使用它:

package main

import "fmt"

type Creature struct {
    Name string
}

func main() {
    c := Creature{
        Name: "Sammy the Shark",
    }
    fmt.Println(c.Name)
}

运行此代码时,您将看到以下输出:

output
Sammy the Shark


在这个例子中,我们首先定义了一个 Creature 结构体,它包含一个字符串类型的 Name 字段。 在 main 的主体中,我们通过在类型名称 Creature 之后放置一对大括号来创建 Creature 的实例,然后为该实例的字段指定值。 c 中的实例将其名称字段设置为“Sammy the Shark”。 在 fmt.Println 函数调用中,我们通过在创建实例的变量后面放置一个句点来检索实例字段的值,然后是我们想要访问的字段的名称。 例如,本例中的 c.Name 返回名称字段。

当您声明一个结构的新实例时,您通常会枚举字段名称及其值,如上一个示例所示。 或者,如果在结构实例化期间将提供每个字段值,则可以省略字段名称,如下例所示:

package main

import "fmt"

type Creature struct {
    Name string
    Type string
}

func main() {
    c := Creature{"Sammy", "Shark"}
    fmt.Println(c.Name, "the", c.Type)
}

输出与上一个示例相同:

output
Sammy the Shark


我们向 Creature 添加了一个额外字段,以将生物类型作为字符串进行跟踪。在 main 的主体中实例化 Creature 时,我们选择使用较短的实例化形式,按顺序为每个字段提供值并省略其字段名称。在声明 Creature{"Sammy", "Shark"} 中,Name 字段取值 Sammy,Type 字段取值 Shark,因为 Name 在类型声明中首先出现,然后是 Type。

这种较短的声明形式有一些缺点,导致 Go 社区在大多数情况下更喜欢较长的形式。使用简短声明时,您必须为结构中的每个字段提供值——您不能跳过您不关心的字段。这很快就会导致具有许多字段的结构的简短声明变得混乱。出于这个原因,使用简短形式声明结构通常用于具有很少字段的结构。

到目前为止,示例中的字段名称都以大写字母开头。这比风格偏好更重要。字段名称使用大写或小写字母会影响您的字段名称是否可以被其他包中运行的代码访问。

结构字段导出


结构的字段遵循与 Go 编程语言中的其他标识符相同的导出规则。如果字段名称以大写字母开头,则它可以被定义结构的包之外的代码读取和写入。如果该字段以小写字母开头,则只有该结构包中的代码才能读取和写入该字段。此示例定义了已导出和未导出的字段:

package main

import "fmt"

type Creature struct {
    Name string
    Type string

    password string
}

func main() {
    c := Creature{
        Name: "Sammy",
        Type: "Shark",

        password: "secret",
    }
    fmt.Println(c.Name, "the", c.Type)
    fmt.Println("Password is", c.password)
}

这将输出:

output
Sammy the Shark
Password is secret


我们在前面的示例中添加了一个额外的字段,secret。 secret 是一个未导出的字符串字段,这意味着任何其他尝试实例化 Creature 的包都将无法访问或设置其 secret 字段。在同一个包中,我们可以访问这些字段,就像这个例子所做的那样。由于 main 也在 main 包中,它能够引用 c.password 并检索存储在那里的值。结构中通常有未导出的字段,通过导出的方法可以访问它们。

内联结构


除了定义新类型来表示结构外,您还可以定义内联结构。这些动态结构定义在为结构类型发明新名称会浪费精力的情况下很有用。例如,测试通常使用结构来定义构成特定测试用例的所有参数。当仅在一个地方使用该结构时,想出像 CreatureNamePrintingTestCase 这样的新名称会很麻烦。

内联结构定义出现在变量赋值的右侧。您必须通过为您定义的每个字段提供额外的一对带有值的大括号来立即提供它们的实例化。下面的示例显示了一个内联结构定义:

package main

import "fmt"

func main() {
	c := struct {
		Name string
		Type string
	}{
		Name: "Sammy",
		Type: "Shark",
	}
	fmt.Println(c.Name, "the", c.Type)
}

 

此示例的输出将是:

output
Sammy the Shark


这个例子不是用 type 关键字定义一个描述我们的结构的新类型,而是通过将结构定义紧跟在短赋值运算符 := 之后来定义一个内联结构。我们像前面的例子一样定义结构的字段,但是我们必须立即提供另一对大括号和每个字段将采用的值。现在使用这个结构和以前完全一样——我们可以使用点表示法来引用字段名。您将看到声明的内联结构最常见的地方是在测试期间,因为一次性结构经常被定义为包含特定测试用例的数据和期望。

结论


结构是程序员为组织信息而定义的异构数据的集合。大多数程序都处理大量数据,如果没有结构,就很难记住哪些字符串或 int 变量属于一起或哪些不同。下次当你发现自己在处理变量组时,问问自己这些变量是否可以更好地使用结构组合在一起。这些变量可能一直在描述一些更高层次的概念。

文章链接