mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-16 11:23:40 +00:00
142 lines
5.7 KiB
Markdown
142 lines
5.7 KiB
Markdown
## 4.4. 结构体
|
||
|
||
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例是处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。
|
||
|
||
下面两个语句声明了一个叫Employee的命名的结构体类型,并且声明了一个Employee类型的变量dilbert:
|
||
|
||
```Go
|
||
type Employee struct {
|
||
ID int
|
||
Name string
|
||
Address string
|
||
DoB time.Time
|
||
Position string
|
||
Salary int
|
||
ManagerID int
|
||
}
|
||
|
||
var dilbert Employee
|
||
```
|
||
|
||
dilbert结构体变量的成员可以通过点操作符访问,比如dilbert.Name和dilbert.DoB。因为dilbert是一个变量,它所有的成员也同样是变量,我们可以直接对每个成员赋值:
|
||
|
||
```Go
|
||
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
|
||
```
|
||
|
||
或者是对成员取地址,然后通过指针访问:
|
||
|
||
```Go
|
||
position := &dilbert.Position
|
||
*position = "Senior " + *position // promoted, for outsourcing to Elbonia
|
||
```
|
||
|
||
点操作符也可以和指向结构体的指针一起工作:
|
||
|
||
```Go
|
||
var employeeOfTheMonth *Employee = &dilbert
|
||
employeeOfTheMonth.Position += " (proactive team player)"
|
||
```
|
||
|
||
相当于下面语句
|
||
|
||
```Go
|
||
(*employeeOfTheMonth).Position += " (proactive team player)"
|
||
```
|
||
|
||
下面的EmployeeByID函数将根据给定的员工ID返回对应的员工信息结构体的指针。我们可以使用点操作符来访问它里面的成员:
|
||
|
||
```Go
|
||
func EmployeeByID(id int) *Employee { /* ... */ }
|
||
|
||
fmt.Println(EmployeeByID(dilbert.ManagerID).Position) // "Pointy-haired boss"
|
||
|
||
id := dilbert.ID
|
||
EmployeeByID(id).Salary = 0 // fired for... no real reason
|
||
```
|
||
|
||
后面的语句通过EmployeeByID返回的结构体指针更新了Employee结构体的成员。如果将EmployeeByID函数的返回值从`*Employee`指针类型改为Employee值类型,那么更新语句将不能编译通过,因为在赋值语句的左边并不确定是一个变量(译注:调用函数返回的是值,并不是一个可取地址的变量)。
|
||
|
||
通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行,就像下面的Name和Address成员那样:
|
||
|
||
```Go
|
||
type Employee struct {
|
||
ID int
|
||
Name, Address string
|
||
DoB time.Time
|
||
Position string
|
||
Salary int
|
||
ManagerID int
|
||
}
|
||
```
|
||
|
||
结构体成员的输入顺序也有重要的意义。我们也可以将Position成员合并(因为也是字符串类型),或者是交换Name和Address出现的先后顺序,那样的话就是定义了不同的结构体类型。通常,我们只是将相关的成员写到一起。
|
||
|
||
如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。
|
||
|
||
结构体类型往往是冗长的,因为它的每个成员可能都会占一行。虽然我们每次都可以重写整个结构体成员,但是重复会令人厌烦。因此,完整的结构体写法通常只在类型声明语句的地方出现,就像Employee类型声明语句那样。
|
||
|
||
一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适用于数组。)但是S类型的结构体可以包含`*S`指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。在下面的代码中,我们使用一个二叉树来实现一个插入排序:
|
||
|
||
<u><i>gopl.io/ch4/treesort</i></u>
|
||
```Go
|
||
type tree struct {
|
||
value int
|
||
left, right *tree
|
||
}
|
||
|
||
// Sort sorts values in place.
|
||
func Sort(values []int) {
|
||
var root *tree
|
||
for _, v := range values {
|
||
root = add(root, v)
|
||
}
|
||
appendValues(values[:0], root)
|
||
}
|
||
|
||
// appendValues appends the elements of t to values in order
|
||
// and returns the resulting slice.
|
||
func appendValues(values []int, t *tree) []int {
|
||
if t != nil {
|
||
values = appendValues(values, t.left)
|
||
values = append(values, t.value)
|
||
values = appendValues(values, t.right)
|
||
}
|
||
return values
|
||
}
|
||
|
||
func add(t *tree, value int) *tree {
|
||
if t == nil {
|
||
// Equivalent to return &tree{value: value}.
|
||
t = new(tree)
|
||
t.value = value
|
||
return t
|
||
}
|
||
if value < t.value {
|
||
t.left = add(t.left, value)
|
||
} else {
|
||
t.right = add(t.right, value)
|
||
}
|
||
return t
|
||
}
|
||
```
|
||
|
||
结构体类型的零值是每个成员都是零值。通常会将零值作为最合理的默认值。例如,对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存,还有在第9章将会讲到的sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是自然获得的,但是也有些类型需要一些额外的工作。
|
||
|
||
如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0,也不包含任何信息,但是有时候依然是有价值的。有些Go语言程序员用map来模拟set数据结构时,用它来代替map中布尔类型的value,只是强调key的重要性,但是因为节约的空间有限,而且语法比较复杂,所以我们通常会避免这样的用法。
|
||
|
||
```Go
|
||
seen := make(map[string]struct{}) // set of strings
|
||
// ...
|
||
if _, ok := seen[s]; !ok {
|
||
seen[s] = struct{}{}
|
||
// ...first time seeing s...
|
||
}
|
||
```
|
||
|
||
{% include "./ch4-04-1.md" %}
|
||
|
||
{% include "./ch4-04-2.md" %}
|
||
|
||
{% include "./ch4-04-3.md" %}
|