简介
围绕具体细节构建抽象是编程语言可以给开发人员的最好工具。结构允许围棋开发人员描述围棋程序运行的世界。结构允许我们讨论Address
而不是描述Street
、City
或PostCode
的字符串。它们是documentation的天然纽带,我们努力告诉未来的开发人员(包括我们自己),哪些数据对我们的Go程序很重要,以及未来的代码应该如何适当地使用这些数据。Structs可以以几种不同的方式定义和使用。在本教程中,我们将逐一了解这些技术。
定义结构
结构的工作方式就像纸质表格,例如,你可以用来报税。纸质表单可能会有文本信息的字段,如您的名字和姓氏。除文本字段外,表单可能还具有复选框来指示布尔值,如已婚
或单身
,或出生日期的日期字段。类似地,结构将不同的数据片段收集在一起,并以不同的字段名称对其进行组织。当你用一个新的结构来初始化一个变量时,就好像你影印了一张表格并准备好填写一样。
要创建新的结构,您必须首先给Go一个描述该结构所包含的字段的蓝图。此结构定义通常以关键字type
开头,后跟结构的名称。在此之后,使用struct
关键字,后跟一对大括号{}
,在其中声明结构将包含的字段。一旦定义了结构,就可以声明使用此结构定义的变量。此示例定义一个结构并使用它:
1package main
2
3import "fmt"
4
5type Creature struct {
6 Name string
7}
8
9func main() {
10 c := Creature{
11 Name: "Sammy the Shark",
12 }
13 fmt.Println(c.Name)
14}
运行此代码时,您将看到以下输出:
1[secondary_label output]
2Sammy the Shark
在本例中,我们首先定义了一个Creature
结构,其中包含一个字符串
类型的Name
字段。在main
的主体中,我们创建了一个Creature
的实例,方法是在类型名Creature
后面放一对大括号,然后为该实例的字段指定值。c
中的实例将其Name
字段设置为鲨鱼Sammy
。在fmt.Println
函数调用中,我们通过在创建实例的变量后放置句点,后跟要访问的字段的名称来检索实例的字段值。例如,本例中的c.Name
返回Name
字段。
在声明结构的新实例时,通常会用它们的值枚举字段名,如上一个示例所示。或者,如果每个字段值都将在结构实例化期间提供,则可以省略字段名,如下例所示:
1package main
2
3import "fmt"
4
5type Creature struct {
6 Name string
7 Type string
8}
9
10func main() {
11 c := Creature{"Sammy", "Shark"}
12 fmt.Println(c.Name, "the", c.Type)
13}
输出与上一个示例相同:
1[secondary_label output]
2Sammy the Shark
我们在Creature
中添加了一个额外的字段来跟踪生物的类型
作为字符串
。在main
的主体内实例化Creature
时,我们选择使用较短的实例化形式,按顺序为每个字段提供值并省略它们的字段名。在声明Creature{
Sammy,
Shark}
中,Name
字段取值Sammy
,而Type
字段取值Shark
,因为Name
首先出现在类型声明中,然后是Type
。
这种较短的申报单有一些缺点,导致围棋界在大多数情况下更喜欢较长的申报单。在使用简短声明&mdash时,必须为结构中的每个字段提供值;不能跳过不关心的字段。这很快就会使带有许多字段的结构的简短声明变得令人困惑。因此,使用短格式声明结构通常与字段较少的结构一起使用。
到目前为止,示例中的字段名称都以大写字母开头。这比风格偏好更重要。字段名称使用大写或小写字母会影响其他包中运行的代码是否可以访问您的字段名称。
结构字段导出
结构的字段遵循与Go编程语言中的其他标识符相同的导出规则。如果字段名以大写字母开头,则它将可由定义结构的包外部的代码读取和写入。如果该字段以小写字母开头,则只有该结构包中的代码才能读写该字段。此示例定义了已导出和未导出的字段:
1package main
2
3import "fmt"
4
5type Creature struct {
6 Name string
7 Type string
8
9 password string
10}
11
12func main() {
13 c := Creature{
14 Name: "Sammy",
15 Type: "Shark",
16
17 password: "secret",
18 }
19 fmt.Println(c.Name, "the", c.Type)
20 fmt.Println("Password is", c.password)
21}
这将输出:
1[secondary_label output]
2Sammy the Shark
3Password is secret
我们在前面的示例中添加了一个额外的字段,即ici
。秘
是一个未导出的字符串
字段,这意味着任何其他试图实例化Creature
的包都将无法访问或设置它的秘
字段。在同一个包中,我们能够访问这些字段,就像本例所做的那样。因为main
也在main
包中,所以它能够引用c.password
并检索存储在那里的值。在结构中有未导出的字段并通过导出的方法进行访问是很常见的。
内联结构
除了定义表示结构的新类型外,还可以定义内联结构。在为结构类型发明新名称会白费力气的情况下,这些动态结构定义非常有用。例如,测试通常使用结构来定义组成特定测试用例的所有参数。当该结构只在一个地方使用时,想出像CreatureNamePrintingTestCase
这样的新名称会很麻烦。
内联结构定义出现在变量赋值的右侧。之后,您必须立即提供它们的实例化,方法是为您定义的每个字段提供另一对带值的大括号。下面的示例显示了内联结构定义:
1package main
2
3import "fmt"
4
5func main() {
6 c := struct {
7 Name string
8 Type string
9 }{
10 Name: "Sammy",
11 Type: "Shark",
12 }
13 fmt.Println(c.Name, "the", c.Type)
14}
此示例的输出将为:
1[secondary_label output]
2Sammy the Shark
本例没有使用type
关键字定义描述我们的结构的新类型,而是通过将struct
定义紧跟在短赋值操作符:=
之后来定义内联结构。我们像前面的示例一样定义结构的字段,但随后必须立即提供另一对大括号和每个字段将采用的值。现在使用这个结构与以前完全一样—我们可以使用点符号来引用字段名。您将看到声明的内联结构最常见的地方是在测试期间,因为一次性结构通常被定义为包含特定测试用例的数据和预期。
结论
结构是程序员为组织信息而定义的异类数据的集合。大多数程序都要处理大量数据,如果没有结构,就很难记住哪些字符串
或int
变量属于同一个变量,或者哪些是不同的变量。下一次当您发现自己在处理变量组时,问问自己,这些变量是否可以使用‘struct’组合在一起更好。这些变量可能一直在描述一些更高层次的概念。