## 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`指針類型的成員，這可以讓我們創建遞歸的數據結構，比如鏈表和樹結構等。在下面的代碼中，我們使用一個二叉樹來實現一個插入排序：

```Go
gopl.io/ch4/treesort

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" %}
