# 2.1 聲明和初始化
當我們第一次看見變量和聲明時,我們僅僅看見一些內置的類型,比如整型和字符串。現在我們將學習結構體,并且我們會深入學習包括指針的內容。
通過一種最簡單的方式去創建一個結構體值類型:
```go
goku := Saiyan{
Name: "Goku",
Power: 9000,
}
```
**注意**:上面的結構體中,結尾的逗號`,`是不能省的。如果沒有逗號,編譯器會給出一個錯誤。你將喜歡上這種一致性要求,特別是如果你已經使用一種相反的語言或格式。
我們不需要給結構體設置任何值甚至任何字段。這2中方式都是有效的:
```go
goku := Saiyan{}
// 或者
goku := Saiyan{Name: "Goku"}
goku.Power = 9000
```
這就像一個未賦值的變量一樣,結構體的字段也會有一個0值。
另外,你也可以省略字段的名字,按字段的順序進行聲明(盡管為了簡潔起見,你盡量在結構體只有少量字段時才使用這種方式):
`goku := Saiyan{"Goku", 9000}`
上面的例子主要是聲明了一個變量`goku`,并給它賦值。
盡管在大多數時候,我們不希望一個變量直接關聯一個值,而是希望一個指針指向變量的值。指針是一個內存地址。通過指針可以找到這個變量實際的值。這是一種間接的取值。嚴格地說,這與存在一個房子并指向另外一個房子有一些區別。
為什么我們需要一個指針指向一個值,而不需要一個實際值。這主要是因為在go語言中,函數的參數傳遞都是按值傳遞,即傳遞的是一個拷貝。了解到這點,下面程序會打印什么?
```go
func main() {
goku := Saiyan{"Goku", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s Saiyan) {
s.Power += 10000
}
```
答案是9000,不是19000。為什么?因為`Super`只是改變了`goku`的一個拷貝,所以在`Super`中的改變不會調用者中反應出來。如果你希望答案是19000,我們需要傳遞一個指向我們值的指針:
```go
func main() {
goku := &Saiyan{"Goku", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s *Saiyan) {
s.Power += 10000
}
```
我們改變了2個地方。首先是使用了`&`操作符去獲得我們值的地址(`&`叫取地址符)。接下來,我們改變了`Super`接受的參數類型。之前我們是傳遞一個`Saiyan`的值類型,現在我們傳遞了一個地址類型`*Saiyan`,這里的`*X`表示一個指向類型`X`的一個指針。顯而易見,`Saiyan`和`*Saiyan`類型之間有一定的聯系,但是它們是兩種不同的類型。
需要指出的是,我們現在傳遞給`Super`參數的仍然是`goku`的值拷貝。只是現在`goku`的值變成了一個地址。這個地址拷貝和源地址相同。可以認為它類似一個指向餐廳方向的拷貝,這就間接服務于我們。雖然是一個拷貝,但是和源地址一樣,也指向同一個餐廳。
我們能證明這是一個拷貝,通過試著去改變它指向的地方(這可能不是你想做的):
```go
func main() {
goku := &Saiyan{"Goku", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s *Saiyan) {
s = &Saiyan{"Gohan", 1000}
}
```
上面的代碼在此輸出了9000。很多語言也有類似的行為,包括ruby、python、java和c#。go某種程度上和c#一樣,只是讓事實可見。
顯而易見,復制一個指針變量的開銷比復制一個復雜的結構體小。在一個64的系統上,指針的大小只有64位。如果我們的結構體有很多字段,創建一個結構體的拷貝會有很大的性能開銷。指針的真正意義就是通過指針可以共享值。我們想通過`Super`去改變`goku`的拷貝或者改變共享的`goku`值本身?
這里不是說你需要一直使用指針。本章的結尾,在我們學到更多關于結構體使用的內容之后。我們將重新審視值類型和指針類型的問題。
## 鏈接
- 關于本書
- 引言
- 準備工作
- 安裝開發環境
- 開始使用Go
- 創建一個Go模塊
- 第1章:基礎知識
- 1.1 編譯
- 1.2 靜態類型
- 1.3 類c語法
- 1.4 垃圾回收
- 1.5 運行go代碼
- 1.6 導入包
- 1.7 變量和聲明
- 1.8 函數聲明
- 1.9 繼續之前
- 第2章:語法學習
- 2.1 聲明和初始化
- 2.2 結構體上的函數
- 2.3 構造函數
- 2.4 new
- 2.5 結構體字段
- 2.6 組合
- 2.7 指針類型和值類型
- 2.8 繼續之前
- 第3章:復雜類型
- 3.1 數組
- 3.2 切片
- 3.3 映射
- 3.4 指針類型和值類型
- 3.5 繼續之前
- 第4章:面向對象
- 4.1 包
- 4.2 接口
- 4.3 繼續之前
- 第5章:綜合知識
- 5.1 錯誤處理
- 5.2 defer
- 5.3 go語言風格
- 5.4 初始化的if
- 5.5 空接口和轉換
- 5.6 字符串和字節數組
- 5.7 函數類型
- 5.8 內存分配
- 第6章:高并發
- 6.1 go協程
- 6.2 同步
- 6.3 通道
- 6.4 繼續之前
- 第7章:工具庫
- 7.1 類型轉換
- 7.2 時間操作
- 第8章:程序測試
- 單元測試
- 性能測試
- 第9章:簡單實例
- 內存分配
- 第10章:項目實戰
- 結論
- 附錄