# 最新消息

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2Fxa1zWgNjvPMthGcfzkgU%2Fgo1.png?alt=media&amp;token=ead5f61e-9bae-44c2-b6fa-015a7a0538a8" alt=""><figcaption></figcaption></figure>

說明：An Introduction to Programming in Go 的正體中文版，內容淺顯易懂，適合做為 Go 的入門資料。

譯者：[Aaron Ming-Yi Liao](http://aaron.netdpi.net/)、Marcus Hsieh

網址：<https://go.netdpi.net>

Email：<myliao2007@acm.org>

感謝「國立成功大學／電機工程學系」協助

感謝 Robert Lee 幫忙審閱中文翻譯

原著：Caleb Doxsey

網址：<http://www.golang-book.com/>

下載 PDF：

{% file src="/files/zEdfBflGGcLl7xLtLp34" %}

版權聲明：

© 2014 Caleb Doxsey. Cover Art: © 2012 Abigail Doxsey Anderson. All Rights Reserved.

Portions of this page may contain modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.

本著作係採用[創用姓名標示 3.0 台灣 授權條款](http://www.google.com/url?q=http%3A%2F%2Fcreativecommons.org%2Flicenses%2Fby%2F3.0%2Ftw%2F\&sa=D\&sntz=1\&usg=AOvVaw0kKB19u22X4HWDL19ITbto).


# 中文版更新日誌

* 2022: migrate site from google site to gitbook.com, gitbook supports code format.
* 2016: initial version: publish in google site.


# 譯者誌謝

感謝「國立成功大學／電機工程學系」協助

感謝 Robert Lee 幫忙審閱中文翻譯


# 01-起步走

電腦程式設計是藝術、工藝及定義電腦如何運作的程式設計科學。本書將教你如何使用 Google 設計的 Go 程式語言來設計電腦程式。\
Go 是一個通用目的程式語言，有先進的功能及精簡的語法。由於 Go 支援多種平台、可靠並有完整說明通用函式庫的文件，而且 Go 重視良好的軟體工程原則，所以讓 Go 做為第一個學習的程式語言是很理想的。\
我們用 Go（及多數的程式語言）來寫軟體的過程是相當直覺的：<br>

* 收集需求
* 尋求解決方案
* 寫程式碼實做方案
* 將程式碼編譯為可執行檔
* 執行並測試程式，以確認它能運作

\
這個過程是迭代的（iterative，表示需要做很多次），而步驟通常會重疊（overlap）。但是在我們開始用 Go 寫第一個程式以前，我們需要去了解一些預備概念。


# 01.01-檔案與資料夾

檔案具有名字、將一批資料以一個單位儲存。現代的作業系統（如 Windows 或 Mac OSX）有幾百萬個檔案，儲存了大量的不同類型資訊，從文字檔、執行檔、到多媒體檔都有。

全部的檔案以同樣的方式儲存在電腦上：它們都有名字、明確的大小（以 byte 為單位），以及相關的類型。通常檔案的類型可以清楚的從副檔名得知，副檔名是檔名中「 . 」之後的部分。例如：一個 hello.txt 檔名，其副檔名 txt 是用來表示它是個文字資料。

資料夾（也稱為目錄）是用來將檔案分類，資料夾中也可以有其它資料夾。在 Windows 檔案及資料夾路徑（位置）中，用 '\\' 字元區隔，例如：C:\Users\john\example.txt 的 example.txt 就是檔名，它位在 john 資料夾裡，而 john 資料夾位在 Users 資料夾中，Users 則位在 C 磁碟（這是 Windows 中主要的實體硬碟）。在 OSX 裡（以及多數其它的作業系統），檔案與資料夾路徑是用 '/' 字元隔開，例如：/Users/john/example.txt。類似在 Windows 中，example.txt 是檔名，它也是位在 john 資料夾中，而 john 位在 Users 資料夾中，與 Windows 不一樣的是，OSX 沒有標示儲存檔案的磁碟字母代號。

#### Windows <a href="#h.p_id_38_l" id="h.p_id_38_l"></a>

在 Windows 裡，檔案與資料夾可以透過檔案管理員（Windows Explorer）瀏覽（透過滑鼠連擊我的電腦或按著 win + e）：

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2FN1YVl1w1kSFAQrCOPw6r%2F11.png?alt=media&amp;token=97014bda-ca58-4b05-b825-d39d30f21eb6" alt=""><figcaption></figcaption></figure>

#### OSX <a href="#h.p_id_63_l" id="h.p_id_63_l"></a>

在 OSX 系統，檔案與資料夾可以使用 Finder 瀏覽（透過滑鼠連擊 Finder 圖形，在下方左邊有個臉的圖案）：

<img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2F5ejHIsKnKwH9Yg7NX5DC%2F01-01-osx.png?alt=media&amp;token=79c48254-c7fb-4405-a26a-41934db8ef4e" alt="" data-size="original"><br>


# 01.02-終端機（Terminal）

### The Terminal <a href="#h.p_id_32_l" id="h.p_id_32_l"></a>

今日我們大多透過圖形使用者介面（GUI）與電腦互動，我們用鍵盤、滑鼠、觸控螢幕與虛擬按鍵或其它在螢幕上呈現的控制元件來與電腦互動。

但不是本來就用這個方法，我們在 GUI 出現以前是用終端機（terminal），這是個簡化的文字介面，螢幕上沒有控制的按鍵，而我們只是透過下指令與接收回應，與電腦對話。

雖然終端機已經從大部分的電腦世界消失，恍如過去的遺跡，然而事實的真相是終端機仍然是大多數電腦上最多程式語言使用的基本使用者介面。Go 程式語言也一樣，所以在我們開始用 Go 寫程式之前，我們要先對終端機的運作有所了解。

**Windows**

在 Windows 系統裡，終端機（所謂的命令提示字元）可以透過按下 windows 鍵 + r（一直按著 windows 鍵，然後按 r），並接著輸入 cmd.exe，然後按下 Enter。你應該會看到一個黑色的視窗出現，類似這樣：

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2F8QSNfz9QlMDYtP9KqoZo%2F13.png?alt=media&amp;token=fcd3ec4b-9ea0-4051-95c7-d235168e5f69" alt=""><figcaption></figcaption></figure>

預設的情況，命令提示字元會從你的個人資料夾開始（以我而言，個人資料夾在 C:\Users\caleb），你可以輸入指令並按下 Enter 來執行。試著輸入 dir 指令，這個會列出資料夾的內容，你應該可以看到類似這樣的東西：

C:\Users\caleb>dir Volume in drive C has no label. Volume Serial Number is B2F5-F125

你的個人資料夾裡有一堆檔案與資料夾，你可以使用 cd 指令切換資料夾，例如：你可能會有個資料夾叫做 Desktop，你可以透過輸入 cd Desktop 進入資料夾，並輸入 dir 看到資料夾的內容。如果要退回到你的個人資料夾，可以用特別的資料夾名稱 ..（兩個句號）： cd ..，單個句號表示目前的資料夾（也是所謂的工作資料夾），所以 cd . 不會發生什麼事情。還有很多指令可以讓你使用，不過剛開始這些應該就夠用了。

#### OSX <a href="#h.p_id_77_l" id="h.p_id_77_l"></a>

在 OSX 裡，終端機可以透過 Finder → Applications → Utilities → Terminal 找到，你應該可以看到一個如下的視窗：

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2FUkUbn7oj41oiopBL2APl%2F14.png?alt=media&amp;token=ce4f6559-eea8-46a5-8974-ec68e7e22a19" alt=""><figcaption></figcaption></figure>

預設的情況，命令提示字元會從你的個人資料夾開始（以我而言，個人資料夾在 C:\Users\caleb），你可以輸入指令並按下 Enter 來執行。試著輸入 ls 指令，這個會列出資料夾的內容，你應該可以看到類似這樣的東西：

caleb-min:\~ caleb$ ls Desktop Downloads Movies Pictures Documents Library Music Public

你的個人資料夾裡有一堆檔案與資料夾，你可以使用 cd 指令切換資料夾，例如：你可能會有個資料夾叫做 Desktop，你可以透過輸入 cd Desktop 進入資料夾，並輸入 dir 看到資料夾的內容。如果要退回到你的個人資料夾，可以用特別的資料夾名稱 ..（兩個句號）： cd ..，單個句號表示目前的資料夾（也是所謂的工作資料夾），所以 cd . 不會發生什麼事情。還有很多指令可以讓你使用，不過剛開始這些應該就夠用了。


# 01.03-文字編輯器

### Text Editors <a href="#h.p_id_32_l" id="h.p_id_32_l"></a>

程式設計師用來寫程式的主要工具是文字編輯器，類似文書處理程式（Microsoft Word、Open Office，...），但是與文書程式不同的是文字編輯器無法指定格式（沒有粗體、斜體、...），而是只能以純文字呈現。OSX 與 Windows 系統都有文字編輯器，但是功能太過侷限，所以我建議要另外安裝比較好用的。

為了讓這個軟體的安裝可以更簡單，安裝程式可以從本書的網站上取得：[http://www.golang-book.com/](http://www.google.com/url?q=http%3A%2F%2Fwww.golang-book.com%2F\&sa=D\&sntz=1\&usg=AOvVaw2ofwBpJf-k2BO_lOQOMg9I)。這個安裝程式也會安裝 Go 工具套件（Go tool suite）、設定環境變數，以及安裝文字編輯器。

#### Windows <a href="#h.p_id_38_l" id="h.p_id_38_l"></a>

在 Windows 系統上，安裝程式會裝上 Scite 文字編輯器，你可以到開始選單 → 所有程式 → Go → Scite 執行。 你應該會看到如下畫面：

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2FaijPAdluhgqkcGFUmuNj%2F15.png?alt=media&amp;token=ad884509-781c-4950-9d66-35c698d3feef" alt=""><figcaption></figcaption></figure>

文字編輯器有一個大的空白文字區域，這裡可以輸入文字。文字區域的左方可以看到行號，視窗的下面是狀態列，它會顯示檔案的資訊，以及你目前在檔案裡的位置（像圖中它就告訴我們現在位於第一列、第一行、文字正以一般方式插入、而我們正使用 Windows-style 的換行符號）。

開啟檔案的方式可以到 File → Open 並瀏覽你所需要的檔案。儲存檔案的方式是到 File → Save 或 File → Save As。

當你操作文字編輯器時，學會一些鍵盤的熱鍵是很好用的。選單將熱鍵在右邊列出，這裡有些常用的熱鍵：

* * Ctrl + S – 儲存目前的檔案
  * Ctrl + X – 剪下選擇的文字（移除文字並將文字放到剪貼簿裡，之後可以用來貼上）
  * Ctrl + C – 複製目前選擇的文字
  * Ctrl + V – 將剪貼簿中的文字貼上
  * 使用箭頭方向鍵來導航、Home 是到行的開頭、End 到行尾
  * 使用箭頭方向鍵（或 Home 或 End）時，持續按著 shift 可以不用滑鼠就選擇文字
  * Ctrl + F – 帶出一個 find 對話框，你可以用來搜尋檔案的內容

#### OSX <a href="#h.p_id_86_l" id="h.p_id_86_l"></a>

OSX 安裝程式會安裝 Text Wrangler 文字編輯器：

<figure><img src="https://2569945419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSnARg1R0IghdjA6R1UDf%2Fuploads%2FQ1NaLDm4DbvRbVWLjKCf%2F16.png?alt=media&amp;token=b27970c5-403b-4c5b-a65e-1665f48038d5" alt=""><figcaption></figcaption></figure>

類似 Windows 系統的 Scite，Text Wrangler 有個大的空白區域可以輸入文字。檔案可以到 File → Open 開啟，檔案可以到 File → Save 或 File → Save As 存檔。這裡有些好用的鍵盤熱鍵：（指令是 ⌘ 鍵）

* * Ctrl + S – 儲存目前的檔案
  * Ctrl + X – 剪下選擇的文字（移除文字並將文字放到剪貼簿裡，之後可以用來貼上）
  * Ctrl + C – 複製目前選擇的文字
  * Ctrl + V – 將剪貼簿中的文字貼上
  * 使用箭頭方向鍵來導航
  * Ctrl + F – 帶出一個 find 對話框，你可以用來搜尋檔案的內容


# 01.04-Go 的工具

### Go Tools <a href="#h.p_id_32_l" id="h.p_id_32_l"></a>

Go 是編譯式程式語言，意思是已經將原始碼（你寫的程式碼）轉換為電腦可以理解的語言，因此，在我們開始寫 Go 程式以前，我們需要有 Go 的編譯器（compiler）。

安裝程式會自動幫你設定 Go，我們會用第一版的語言（更多資訊可以在 [http://www.golang.org](http://www.golang.org/) 找到）。

我們來確認每件事情都能正常運作，開啟終端機並輸入下列的指令：

go version

你應該會看到下列的結果：

go version go1.0.2

你的版本編號可能會有點不太一樣，如果你在執行指令時發生了無法識別的錯誤，先試著重新啟動電腦。

Go 工具集是由許多不同的指令與子指令所組成，這些指令清單可以透過輸入下列的指令取得：

go help

我們在後續的章節可以看到如何使用這些指令。


# 02-你的第一個程式

## Your First Program

不管你用哪個程式語言，通常第一個寫的程式是名為 "Hello World" 的程式： 一個單純將 `Hello World` 輸出到終端機的程式。我們用 Go 來寫一個。

你在第一章使用的安裝程式，會在你的個人資料夾建立一個名為 `Go` 的資料夾。我們這裡要再建立一個新的資料夾，用來儲存程式的地方，資料夾是 `~/Go/src/golang-book/chapter2`（這裡的 `~` 表示你的個人資料夾）你可以從終端機輸入下列的指令來完成這件工作：

```
mkdir Go/src/golang-book
mkdir Go/src/golang-book/chapter2
```

使用你的文字編輯器輸入下列的程式碼：

```go
package main

import "fmt"

// 這是註解

func main() {
    fmt.Println("Hello World")
}
```

確定你的檔案內容與上述相同，並將檔案 `main.go` 儲存在我們剛建立的資料夾中。開啟一個新的終端機，並輸入下列的指令：

```
cd Go/src/golang-book/chapter2
go run main.go
```

你應該會看到 `Hello World` 顯示在你的終端機。`go run` 指令會將後面的幾個（以空格隔開的）檔案編譯為一個可執行檔並存放在一個暫存資料夾中，然後執行程式。若你沒有看到 `Hello World` 顯示出來，可能是輸入的程式碼有錯誤的地方。Go 編譯器會給你一些提示，跟你說哪裡有錯誤。Go 編譯器跟多數的編譯器一樣，很嚴謹而且無法容忍有錯誤存在。


# 02.01-如何讀 Go 程式

### How to Read a Go Program

我們先好好研究這個程式，由上而下、由左而右來讀 Go 程式，（跟讀英文書一樣），第一行這樣寫：

```
package main
```

這是所謂的 “package declaration”（套件宣告），每個 Go 程式都得用個 package declaration 當作開頭。Packages 是 Go 用來組成與重複使用程式碼的方式。有兩種 Go 程式：執行檔與函式庫。我們可以直接從終端機執行的應用程式（在 Windows 系統是用 `.exe` 結尾），函式庫（library）是我們將程式碼打包在一起的彙整，因而我們可以在其它程式中使用這些程式碼。我們之後會詳細的探討函式庫，現在只是要確定你寫的任何程式都有引用這行。

下一行是空白行，電腦以特殊字元來表示換行（或幾個字元）。換行、空格及 tab 都是所謂的空白（whitespace，因為我們看不到空白）。Go 大部分不會管空白，我們只用空白來排版，讓程式碼更好讀。（你可以移除這行，而程式也會以原本的行為正常運作）。

接著我們看看這行：

```
import "fmt"
```

關鍵字 `import` 用來將其它 packages 中的程式碼引用（include）到我們的程式裡。`fmt` package（format 的縮寫）實做了格式化輸入及輸出。用我們剛學到的 packages 概念，你認為 `fmt` package 的檔案會包含在它們之上嗎？

要注意到，上面的 `fmt` 有雙引號括起來，雙引號的用途類似所謂的 "string literal"，這是一種 "expression" 型別。在 Go 字串中表示有明確大小的一串字元（字母、數字、符號、...） 。字串在下一章會有詳細的說明，現在心裡記得的重要事項是：`"` 字元必須成對使用，而且它們中間只能放字串，而 `"` 字元本身不是字串。

以 `//` 開頭的是註解，Go 編譯器會忽略註解，註解存在的原因是為了你（或者是某個讀你程式碼的人）。Go 支援兩個不同類型的註解：`//` 表示在 `//` 之後與該行結尾以間的文字都是註解，而 `/* */` 註解表示在兩個 `*` 之間的部分都是註解（而且可能包含多行）。

之後你會看到一個函式宣告：

```go
func main() {
    fmt.Println("Hello World")
}
```

函式在一個 Go 程式中的定位類似積木，有輸入、輸出、以及依序執行一連串稱為 陳述句（statement）的步驟。全部的函式以 `func` 關鍵字起始，並接著函式名稱（此例為 `main`），可能沒有參數或是有一些由括號包圍起來的參數（parameter）、一個選配式的傳回型別（return type）、以及一個由大括弧包圍住的本體（body）。現在這個函式沒有參數，沒有傳回任何資料，只有一些陳述句。`main` 名字是很特別的，因為這是你執行程式時先被呼叫的函式。

我們的程式最後一段是這行：

```
    fmt.Println("Hello World")
```

這個 陳述句由三個元件所組成，我們先存取 `fmt` package 中的另一個名為 `Println` 的函式，（即 `fmt.Println` 這段，`Println` 表示印出這行），接著我們建立一個包含 `Hello World` 的新字串，並在呼叫這個函式時將這個字串做為它的參數。

此時，我們已經看過許多新名詞，而你可能有點無法負荷。有時刻意將程式唸出來是很有幫助的，現在將我們剛寫好的程式唸出來，大約類似這樣：

建立一個新的執行檔，它引用了 `fmt` 函式庫及包含一個名為 `main` 的函式，這個函式沒有參數，不會傳回任何值，且進行下列的工作：存取 `fmt` package 裡的 `Println` 函式，並搭配  `Hello World` 字串做為參數來呼叫這個函式。

`Println` 函式執行這個程式真正的工作，你可以在終端機輸入下列指令來查詢更多的資訊：

```
godoc fmt Println
```

在其它部分，你應該會看到這些：

```
Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.
```

Go 程式語言有很完整的文件，但是不好理解，除非你曾經學過其它程式語言。不過 `godoc` 指令相當好用，而且在你有問題時可以先從這裡查起。

回到我們手邊的這個函式，Go 文件告訴我們 `Println` 函式會將你給它的任何東西送到標準輸出（standard output，你正在使用的終端機輸出的名稱）。這個函式就是輸出 `Hello World` 的函式。

我們在下一章透過瞭解型別來探討 Go 如何儲存並表示如  `Hello World` 這類的資料。


# 02.02-問題

* 何謂空白呢？
* 何謂註解？註解有哪兩種寫法呢？
* 我們的程式以 `package main` 開始，在 `fmt` package 中的檔案以什麼做為開始呢？
* 我們使用定義於 `fmt` package 裡的 `Println` 函式，如果我們想要使用  `os` package 中的 `Exit` 函式，我們需要做什麼事呢？
* 修改我們寫的程式，讓原本印出的 `Hello World` 改成印出 `Hello, my name is` ，後面再接上你的名字。


# 03-型別

## Types

我們在上一章用字串資料型別儲存 `Hello World`，資料型別將相關值的集合分類為一組，說明可進行的資料型別操作，以及定義儲存資料的方式。因為型別的概念不好掌握，所以在我們開始研究如何用 Go 實做以前，要先從幾個不同的觀點來看。

哲學家有時會對型別與 token 進行區分，比如假設你有一條叫做 Max 的狗，Max 就是 token（一個特殊的實體「instance」或成員），而 dog 就是型別（通用概念）。"Dog" 或 "dogness" 表示每條狗都有的一組屬性（property）。雖然過於簡化，不過我們可以這樣解釋：因為全部的狗都有 4 條腿，而 Max 是條狗，所以 Max 有 4 條腿。在程式語言中的型別以簡單的方式運作：全部的字串都有長度，而 x 是個字串，因此 x 有長度。

我們在數學經常談論集合，比如：ℝ（全部實數的集合）或 ℕ（全部自然數的集合）。這些集合的每個數與集合裡的其它成員共享屬性。比如全部的自然數都相關的：＂對於全部的自然數 a、b 及 c，a + (b + c) = (a + b) + c，而 a × ( b × c ) = (a × b) × c＂。在這個方式，集合就類似是程式語言的型別，因為一個屬性型別的每個值都彼此共用某些屬性。

Go 是個靜態型別的（statically typed）程式語言，這表示變數永遠有特定的型別，而且該型別無法改變。靜態型別或許一開始看起來很討人厭，你在最後編譯程式時會耗費大量的時間在修正程式，可是型別讓我們可以合理的解釋程式在做什麼，並找出各種常見的錯誤。

我們將詳細的探討幾個 Go 內建的資料型別。


# 03.01-數字

Go 有幾種表示數字的型別，通常我們將數字分成整數與浮點數兩種。

#### 整數（Integers） <a href="#toc-integers" id="toc-integers"></a>

整數（類似整數在數學上的對應）是沒有小數點的數字（..., -3, -2, -1, 0, 1, ...）。電腦使用 2 進位的系統，與我們習慣使用的 10 進位的系統不同。

我們的數學系統由 10 個不同的數字組成，一旦耗盡了可用的數字，我們用 2 位數表達更大的數（再來是 3 位數、4 位數、5 位數、...）。比如，9 之後是 10、99 之後是 100，依此類推。電腦也是一樣，可是電腦只有 2 個數字而不是 10 個，所以算數就會像這樣，0, 1,, 10, 11, 100, 101, 110, 111 等。我們用的數字系統與電腦用的數字系統的另一個差別是全部的整數型別都有定義的大小，它們只對特定位數的數字提供空間，所以，一個 4 bit 的整數看起來會像這樣：0000, 0001, 0010, 0011, 0100。即使我們用完了空間，多數的電腦只會再從頭開始（這會導致一些很詭異的行為）。

Go 的整數型別是：`uint8`, `uint16`, `uint32`, `uint64`, `int8`, `int16`, `int32` 與 `int64`。8, 16, 32 與 64 讓我們知道這些型別使用了多少個 bits。`uint` 表示 "無號整數（unsigned integer）"，而 `int` 表示 "有號整數（signed integer）"。無號整數只包含正整數（與零）。此外，還有兩個別名型別（alias types）：`byte` 與 `uint8` 一樣，而 `rune` 與 `int32` 一樣。Bytes 在電腦上是很常見的量測單位（1 byte = 8 bits, 1024 bytes = 1 kilobyte, 1024 kilobytes = 1 megabyte, …），因此 Go 的 `byte` 資料型別通常用在其它型別的定義。還有三種與電腦架構有關的整數型別：`uint`、`int` 與 `uintptr`。它們都視電腦架構而定，因為它們的大小取決於你使用的架構類型。

一般而言，如果你用到整數，應該只會用到 `int` 型別。

#### 浮點數（Floating Point Numbers） <a href="#toc-floating-point-numbers" id="toc-floating-point-numbers"></a>

浮點數包含一個小數點（實數），（1.234, 123.4, 0.00001234, 12340000）。它們實際在電腦上的表示方式是相當複雜的，而且沒必要去瞭解它們的表示式。所以目前我們只需記得下列的事情：

* 浮點數是不精確的，有時候會無法表示一個數字，比如：`1.01 - 0.99` 的運算結果會得到 `0.020000000000000018`，一個相當接近我們期待的數字，但是不完全相同。
* 浮點數與整數類似，都有特定的大小（32 bit 或 64 bit）。使用一個大尺寸的浮點數可以增加它的精確度（可以表達多少位數）。
* 除了數字，還有幾個其它的值可以表示："非數字"（`NaN`，類似 `0/0` 的東西）、正無窮大及負無窮大（`+∞` 及 `−∞`）。

Go 有兩個浮點數型別：`float32` 與 `float64` （分別是單精度與雙精度），還有兩個額外的複數（有虛部的數）：`complex64` 與 `complex128`。我們一般在用浮點數時，應該無法擺脫 `float64`。

#### 範例 <a href="#toc" id="toc"></a>

我們寫個程式來示範如何使用數字，首先建立一個名為 `chapter3` 的資料夾，並建立一個名為 `main.go` 的檔案，內容如下：

```go
package main

import "fmt"

func main() {
    fmt.Println("1 + 1 =", 1 + 1)
}
```

若你執行了程式，可以看到執行結果如下：

```
$ go run main.go
1 + 1 = 2
```

注意，這個程式相當類似我們在第二章寫的程式，有一樣的 package、相同的 import、同樣的函式宣告，並使用一樣的 `Println` 函式。只是此時是將原本的 `Hello World` 字串改成印 `1 + 1 =` 及其計算結果的字串。這個運算式有三個部分：數字 `1`（型別是 `int`）、`+` 運算符（表示加法），以及另一個數值 `1`。我們試著用浮點數看看：

```
    fmt.Println("1 + 1 =", 1.0 + 1.0)
```

注意，我們用 `.0` 來讓 Go 知道這是個浮點數而不是整數，執行這個程式會得到跟之前一樣的結果。

此外，Go 還有其它的運算符：

| +  | addition             |
| -- | -------------------- |
| -  | subtraction          |
| \* | multiplication       |
| /  | division             |
| %  | <p>remainder<br></p> |


# 03.02-字串

如我們在第2章所見，字串是一連串用來表示文字的字元，有固定的長度。Go 的字串是由個別的 byte 組成，通常一個字元是一個 byte（其它語言的字元，如中文，是用一個以上的 byte 來表示）。

可以用兩個雙括號或單括號來建立字串，如：`"Hello World"` 或 `` `Hello World` ``，差異在於雙括號不能有換行符號，但是雙括號可以有特殊的跳脫字元，比如： 表示換行符號，而  表示 tab 字元。

有幾個常見的字串操作，包含找出字串長度：`len("Hello World")`、存取字串中的特定字元：`"Hello World"[1]`，以及兩個字串的連接符號：`"Hello " + "World"`。讓我們來修改之前建立的程式來測試這些功能：

```go
package main

import "fmt"

func main() {
    fmt.Println(len("Hello World"))
    fmt.Println("Hello World"[1])
    fmt.Println("Hello " + "World")
}
```

有些要注意的事情：

* 空格也是字元，所以 "Hello World" 的字串長度是 11，而不是 10，且第三行是 `"Hello "` 而不是 `"Hello"`。
* 字串從 0 開始進行索引（index）而不是 1。`[1]` 表示第二個元素（element），而不是第一個。還要注意，你在執行程式時看到 "Hello World"\[1] 印出的是 `101` 而不是 `e`。這是因為字元是用一個 byte 來表示（記得一個 byte 就是一個整數）。可以將索引想成這樣：`"Hello World"1`，可以讀成 "Hello World 字串的子項目 1"、"在 Hello World 字串索引 1 的位置" 或者 "Hello World 字串的第二個字元"。
* 連接字串的符號跟加法一樣，Go 編譯器認為依據參數決定型別，因為 `+` 的左右兩邊都是字串，所以編譯器認為你的意思是要連接字串，而不是加法（對字串做加法運算是沒有意義的）。


# 03.03-問題

* 電腦如何儲存整數？
* 我們知道（10進位）最大的個位數是 9，而最大的兩位數是 99。若二進位最大的兩位數是 11 (3)，最大的三位數是 111 (7)，且最大的四位數是 1111 (15)，那麼最大的八位數是多少？（提示：101-1 = 9 且 102-1 = 99）
* 雖然是殺雞用牛刀，不過你可以把 Go 當作計算機，寫個程式計算 `32132 × 42452`，並在終端機印出來。（使用 `*` 運算符相乘）
* 何謂字串？你如何得到字串的長度？
* 這個運算式結果的值是多少呢？ `(true && false) || (false && true) || !(false && false)`


# 04-變數

到目前為止，我們只看過程式使用固定的值（數字、字串等），但是這樣的程式並不實用，所以如果要寫實用的程式，我們需要學習兩個新的概念：變數與控制流程。本章將詳細的介紹變數。

變數是一個儲存位置，有特定的型別及相關的名字，我們來改一下第二章寫的程式，讓它使用變數：

```
package main

import "fmt"

func main() {
    var x string = "Hello World"
    fmt.Println(x)
}
```

注意原本程式的字串值在這個程式裡依然會出現，但不是直接將字串送給 `Println` 函式，而是將字串指派給變數。Go 的變數是透過 `var` 關鍵字所建立的，接著指定變數名稱（`x`）為字串型別（`string`），最後將字串值 （`Hello World`）指定給變數，最後一個非必要的步驟，寫這個程式的另一種方式如下：

```
package main

import "fmt"

func main() {
    var x string
    x = "Hello World"
    fmt.Println(x)
}
```

在 Go 裡的變數類似代數的變數，但是有些微的差異：

首先，當我們看到 `=` 符號時，我們直覺會讀成 “x 等於 Hello World 字串”，用這個方法讀程式不會有什麼問題，但是更好的方式是讀成 “將 Hello World 字串代入 x” 或者 “將 Hello World 字串指定給 x”。這樣的區分是很重要的，因為變數值會隨著程式的執行而變動（如其名所示），請試試看執行下列的程式：

```
package main

import "fmt"

func main() {
    var x string
    x = "first"
    fmt.Println(x)
    x = "second"
    fmt.Println(x)
}
```

實際上你甚至可以這麼做：

```
var x string
x = "first "
fmt.Println(x)
x = x + "second"
fmt.Println(x)
```

如果你用代數定理來讀這個程式是不合理的，不過如果你將這個程式看成一連串的指令來讀，就可以說的通了。在我們看到 `x = x + "second"` 時，我們應該將它讀成 “將變數 x 的值與字串值 second 連接在一起”。`=` 右邊會先運算，再來才將結果指定給 `=` 左邊的變數。

`x = x + y` 的形式在程式語言是很常見的，不過 Go 有個特殊的指派符號：`+=`，我們可以將 `x = x + "second"` 寫成 `x += "second"`，這樣做的事情是相同的（別的運算符號用法也是一樣）。

Go 與代數的差異在於等式判斷的符號不同：`==`（兩個等號），`==` 跟 `+` 運算符類似，而且會傳回布林值（boolean），例如：

```
    var x string = "hello"
    var y string = "world"
    fmt.Println(x == y)
```

這個程式應該會印出 `false`，因為 `hello` 不等於 `world`，另一方面：

```
    var x string = "hello"
    var y string = "hello"
    fmt.Println(x == y)
```

這樣會印出 `true`，因為兩個字串一樣。

由於建立新變數時順便賦予初始值，在 Go 是很常見的，所以 Go 也支援了較短的寫法：

```
    x := "Hello World"
```

請注意，在 `=` 之前有 `:`，而且沒有指定型別。不需要型別的理由是因為 Go 編譯器可以依據你指定給變數的數值來推論出型別。（因為你指定字串數值，所以 `x` 的型別就是 `string`）。編譯器也可以推論使用 `var` 的句子：

```
    var x = "Hello World"
```

也適用於其它的型別：

```
    x := 5
    fmt.Println(x)
```

如果可以，你通常該用簡寫的格式。


# 04.01-如何命名變數

變數命名是軟體開發很重要的一部分，命名必須以字母開頭，而且有可能會包含字母、數字或 `_` （底線）符號。Go 編譯器不會管你如何命名變數，所以為了你好（及別人），應該要使用能清楚表達變數用途的名稱，假設如下：

```go
    x := "Max"
    fmt.Println("My dog's name is", x)
```

在此例子中，x 變數名稱就不是很好，好的命名應該如下所示：

```go
    name := "Max"
    fmt.Println("My dog's name is", name)
```

或者：

```go
    dogsName := "Max"
    fmt.Println("My dog's name is", dogsName)
```

在上例中，我們使用特殊的方式來表達變數名稱，即所謂的 lower camel case（也是所謂的 mixed case、bumpy caps、camel back 或 hump back）。字首的第一個字母是小寫的，接下來的文字其第一個字母是大寫的，而其它全部的字母都是小寫的。


# 04.02-Scope（變數的作用範圍）

回到我們在本章一開始所討論的程式：

```go
package main

import "fmt"

func main() {
    var x string = "Hello World"
    fmt.Println(x)
}
```

用另一種方式來寫這個程式，類似這樣：

```go
package main

import "fmt"

var x string = "Hello World"

func main() {
    fmt.Println(x)
}
```

注意，我們將變數移到 main 函式的外部，這表示其它函式都可以存取這個變數：

```go
var x string = "Hello World"

func main() {
    fmt.Println(x)
}

func f() {
    fmt.Println(x)
}
```

`f` 函式現在可以存取 `x` 變數了，現在我們改成這樣：

```go
func main() {
    var x string = "Hello World"
    fmt.Println(x)
}

func f() {
    fmt.Println(x)
}
```

如果你執行這個程式，你應該會看到一個錯誤：

```
.\main.go:11: undefined: x
```

編譯器告訴你 `f` 函式裡的 `x` 變數不存在，它只有在 `main` 函式裡面，你可以使用 `x` 的範圍就稱為這個變數的 scope。依據語言的規範，Go 是以區塊範圍的區隔（“Go is lexically scoped using blocks”）。基本上，這表示變數存在於最接近的大括號裡 `{` `}` （一個區塊），包含任何巢狀的大括號，但不在大括號外面。一開始會對 scope 有點困惑，不過只要我們多看些 Go 的範例，應該就會比較清楚了。


# 04.03-常數（Constant）

Go 也支援常數，常數基本上是值無法更動的變數。建立常數的方式與變數一樣，但是不需使用 `var` 關鍵字，而是使用 `const` 關鍵字：

```go
package main

import "fmt"

func main() {
    const x string = "Hello World"
    fmt.Println(x)
}
```

這個：

```
    const x string = "Hello World"
    x = "Some other string"
```

結果在編譯時會發生錯誤：

```
.\main.go:7: cannot assign to x
```

在程式裡，常數是重新使用一般值的一種好方法，因為可以不用每次都將值寫出來，例如：在 `math` package 的 `Pi` 就是定義為常數。


# 04.04-定義多個變數

在定義多個參數時，Go 還有另一種簡寫的方式：

```go
var (
    a = 5
    b = 10
    c = 15
)
```

使用關鍵字 var（或 const），後面接著一對小括號，每一行定義一個變數。


# 04.05-範例程式

這裡有個範例程式，由使用者輸入一個數字，並將這個數乘以 2：

```go
package main

import "fmt"

func main() {
    fmt.Print("Enter a number: ")
    var input float64
    fmt.Scanf("%f", &input)
    
    output := input * 2
    
    fmt.Println(output)    
}
```

我們用 `fmt` package 裡的另一個函式來讀取使用者的輸入（`Scanf`），`&input` 在之後我們將會說明，目前我們只需要知道 `Scanf` 會將我們輸入的數字填在 input。


# 04.06-問題

* 建立新變數的兩種方式為何？
* 執行後的 `x` 值是多少：\
  `x := 5; x += 1`?
* Scope 是什麼？你如何確定 Go 語言變數的 scope 呢？
* `var` 與 `const` 的差異為何？
* 使用範例程式做為一個起點，寫個程式將華氏轉為攝氏 (`C = (F - 32) * 5/9`)。
* 另外寫個程式將英尺 (ft) 轉換為公尺 (m) (`1 ft = 0.3048 m`)。


# 05-控制結構

我們目前知道如何使用變數了，所以該來寫點有用的程式。首先我們來寫個可以從 1 算到 10 的程式，我們用之前學到的語法，應該會這樣寫：

```go
package main

import "fmt"

func main() {
    fmt.Println(1)
    fmt.Println(2)
    fmt.Println(3)
    fmt.Println(4)
    fmt.Println(5)
    fmt.Println(6)
    fmt.Println(7)
    fmt.Println(8)
    fmt.Println(9)
    fmt.Println(10)
}
```

或是這樣：

```go
package main
import "fmt"

func main() {
    fmt.Println(`1
2
3
4
5
6
7
8
9
10`)
}
```

不過這兩個程式寫起來都很乏味，有什麼東西可以讓我們處理重複的事情呢？


# 05.01-For

&#x20;`for` statement 讓我們將一連串的 statements（一段程式區塊）重複很多次，我們用 `for` 重寫之前的程式，類似這樣：

```go
package main

import "fmt"

func main() {
    i := 1
    for i <= 10 {
        fmt.Println(i)
        i = i + 1
    }
}
```

一開始我們先建立一個命名為 `i` 的變數，用來儲存我們要印出的數字。接著我們用關鍵字 `for`  建立一個 `for` 迴圈，提供一個條件判斷式，判斷的結果是 `true` 或 `false`，最後提供一段要執行的程式碼。for 迴圈的運作方式類似這樣：

* 我們評估（執行）表示式 `i <= 10`（“i 小於或等於 10”）。 若此表示式的結果為真，則執行區塊內的程式碼；否則就跳躍至我們程式的區塊外第一行程式。（以此例而言，區塊之外沒有程式碼，而只是離開程式）。
* 在我們執行區塊內的 statements 之後，迴圈會回到區塊的起點，並重複上個步驟。

`i = i + 1` 這行相當重要，因為如果沒有這一行，那麼 `i <= 10` 就恆為真 `true`，因而我們的程式將不會停止（這種情況稱為無窮迴圈）。

當作是個習題，我們以電腦的角度來看這整個程式：

* 建立一個名為 `i` 的變數，初值設定為 1
* `i <= 10` 是否成立？是的。
* 印出 `i`
* 將 `i` 設定為 `i + 1`（`i` 目前等於 2）
* `i <= 10` 是否成立？是的。
* 印出 `i`
* 將 `i` 設定為 `i + 1`（`i` 現在等於 3）
* …
* 將 `i` 設定為 `i + 1`（`i` 現在等於 11）
* `i <= 10` 是否成立？否
* 沒事了，所以離開

別的程式語言有蠻多種迴圈類型（while、do、until、foreach、 …）但是 Go 只有一個能有各種用法的迴圈，之前的程式可以寫成這樣：

```go
func main() {
    for i := 1; i <= 10; i++ {
        fmt.Println(i)
    }
}
```

現在條件判斷式也包含了兩個 statements，在它們之間有分號隔開。首先我們初始化變數，接著每次都要檢查條件，最後將變數加 1。（用特殊運算符  `++` 將變數加 1 是很普遍的，同樣地，減  1 可以用  `--`）

我們在後面的章節將會看到更多迴圈的用法。


# 05.02-If

讓我們來修改之前寫的程式，改為在每行印出數字 1 到 10，並指出該數字是奇數（odd）或偶數（even），像是這樣：

```go
1 odd
2 even
3 odd
4 even
5 odd
6 even
7 odd
8 even
9 odd
10 even
```

首先，我們需要一種方法來定義數字是奇數還是偶數，一個簡單的方式是將這個數字除以 2，如果餘數是 0，則為偶數；否則就是奇數。因此我們如何用 Go 找出除法的餘數呢？我們會用 `%` 運算符，`1 % 2` 等於 `1`、`2 % 2` 等於 `0`、 `3 % 2` 等於 `1` 諸如此類。

接著我們需要一種方法，可以透過條件選擇不同的事物，我們可以用 `if` statement：

```go
if i % 2 == 0 {
  // even
} else {
  // odd
}
```

`if` statement 類似 `for` statement，裡頭會有一個條件判斷式，並接著一個程式區塊。If statements 也有一個選配的 `else`。如果條件判斷式為 `true`，那麼會執行條件判斷式後面的程式碼，否則會跳過該程式碼區塊。若有 `else` 的話，則會執行 else 後面的程式碼區塊。

If statements 也可以有 `else if`：

```go
if i % 2 == 0 {
    // divisible by 2
} else if i % 3 == 0 {
    // divisible by 3
} else if i % 4 == 0 {
    // divisible by 4
}
```

條件判斷式是由上而下進行檢查的，若第一個結果為真，則會執行相對應的程式碼區塊，而其它的區塊則都不會執行，即使那些區塊的條件判斷式也會成立。（比如：4 跟 2 都可以整除 8，但是 `// divisible by 4` 這個區塊絕對不會執行，原因是會先執行 `// divisible by 2`）

將這些串在一起，我們會得到：

```go
func main() {
    for i := 1; i <= 10; i++ {
       if i % 2 == 0 {
            fmt.Println(i, "even")
        } else {
            fmt.Println(i, "odd")
        }
    }
}
```

我們從新看一次這整個程式：

* 建立一個型別為 int 的變數 `i`，並給定一個值 `1`
* `i` 是否小於或等於 `10` 呢？是的：進入該程式碼區塊。
* `i` ÷ `2` 的餘數是否等於 `0` 呢？否：進入 `else` 程式碼區塊。
* 在 `i` 後面印出 `odd`
* 加上 `i` （在條件式之後的 statement）
* `i` 是否小於或等於 `10` 呢？是的：進入該程式區塊。
* `i` ÷ `2` 的餘數是否等於 `0` 呢？是的：進入 `if` 程式碼區塊。
* 在 `i` 後面印出 `even`
* …<br>

餘數運算符，國小畢業之後就很少看到了，結果在程式設計時真的很有幫助，你從 zebra striping tables 到 partitioning data sets 裡到處都可以看到它。


# 05.03-switch

假設我們想要寫一個程式，印出數字的英文，我們可以用較早之前學到的方法處理：

```go
if i == 0 { 
    fmt.Println("Zero") 
} else if i == 1 {
    fmt.Println("One")
} else if i == 2 {
    fmt.Println("Two")
} else if i == 3 {
    fmt.Println("Three")
} else if i == 4 { 
    fmt.Println("Four")
} else if i == 5 {
    fmt.Println("Five")
}
```

因為用這個方式來寫程式有點乏味，所以 Go 提供另一種比較簡單的寫法：`switch` statement，我們可以像這樣來重寫程式：

```go
switch i {
case 0: fmt.Println("Zero")
case 1: fmt.Println("One")
case 2: fmt.Println("Two")
case 3: fmt.Println("Three")
case 4: fmt.Println("Four")
case 5: fmt.Println("Five")
default: fmt.Println("Unknown Number")
}
```

switch statement 以 `switch` 為開頭，並接著一個表示式（expression）【本例為 `i`】，並接著一連串的 `case`。表示式的值會與每個關鍵字 `case` 後頭的表示式比較，如果相等，則會執行 `:` 後面的 statements。

與 if statement 類似，由上而下檢查每個 case，並選擇第一個成功的 case。switch 也有提供 default（預設的）case，若在 default 之前的 cases 都沒有符合條件，則會執行這個 default case（類似 if statement 裡的 else）。

以上是主要的流程控制 statements，其它部分將在後續章節探討。


# 05.04-問題

* 下列的程式會印出什麼：

  ```
  i := 10
  if i > 10 {
      fmt.Println("Big")
  } else {
      fmt.Println("Small")
  }
  ```
* 寫一個程式，將 1 到 100 之間全部可以被 3 除的全部數字印出來（3, 6, 9 等）。
* 寫一個程式，印出 1 到 100 的數字，不過對於 3 的倍數要改成印出 "Fizz"，而 5 的倍數改成印 "Buzz"，對於 3 與 5 的公倍數則印出 "FizzBuzz"。


# 06-Array、Slice 與 Map

我們在第三章探討了 Go 的基本型別，本章將探討三個內建的型別：分別是陣列（array）、slice、以及 map。


# 06.01-陣列（Array）

陣列是一個有著編號的序列，裡頭的每個元素（element）都有相同的單一型別（type）。在 Go 語言中，陣列的樣式看起來如下所示：

```
var x [5]int
```

`x` 是一個陣列的範例，由 5 個 `int` 型別組成，我們試著執行下列的程式：

```go
package main

import "fmt"

func main() {
    var x [5]int
    x[4] = 100
    fmt.Println(x)
}
```

你應該會看到這樣的執行結果：

```
[0 0 0 0 100]
```

`x[4] = 100` 應該讀為「將陣列的第 5 個元素設定為 100」。可能會覺得奇怪， `x[4]` 為什麼代表第 5 個元素，而不是如字面所示的第 4 個元素呢？因為陣列的索引值是從 0 起算的，所以陣列的存取方式也是如此。我們可以將 `fmt.Println(x)` 改成 `fmt.Println(x[4])`，這樣我們會得到 100。

這個程式示範如何使用陣列：

```go
func main() {
    var x [5]float64
    x[0] = 98
    x[1] = 93
    x[2] = 77
    x[3] = 82
    x[4] = 83
    
    var total float64 = 0
    for i := 0; i < 5; i++ {
        total += x[i]
    }
    fmt.Println(total / 5)
}
```

此程式會計算一串測驗成績的平均值，執行程式之後應該會看到結果是 `86.6`。我們來看看這段程式碼：

* 我們先建立一個長度為 5 的陣列來儲存這些測驗成績，接著將分數填入每個元素中。
* 再來使用一個迴圈來計算成績的總和。
* 最後我們將成績的總和除以元素的數量，以取得平均值。

此程式可以正常運作，但是 Go 有提供一些功能可以讓我們來改進這個程式。在一開始的兩個地方：`i < 5` 與 `total / 5` ，若我們想要將成績的數量從 5 個改成 6個，則會需要同時修改這兩個地方，因此建議可以改成使用陣列長度：

```go
    var total float64 = 0
    for i := 0; i < len(x); i++ {
        total += x[i]
    }
    fmt.Println(total / len(x))
```

修改並執行程式，你會看到發生一個錯誤：

```
$ go run tmp.go
# command-line-arguments
.\tmp.go:19: invalid operation: total / 5 (mismatched types float64 and int)
```

問題在於 `len(x)` 與 `total` 的型別不同，`total` 的型別是 `float64`，而 `len(x)` 的型別是 `int`，所以我們需要將 `len(x)` 強制轉型為 `float64`：

```
fmt.Println(total / float64(len(x)))
```

這是一個型別轉換的範例，通常使用型別的名稱來進行型別轉換，就像是函式一樣。

另一個可以改的地方是使用特殊的 for 迴圈格式：

```go
var total float64 = 0
for i, value := range x {
    total += value
}
fmt.Println(total / float64(len(x)))
```

這個 for 迴圈中的 `i` 表示目前正在處理的陣列元素，而 `value` 則與 `x[i]` 相同，我們使用 `range` 這個關鍵字，並接著一個變數名稱（迴圈的終止條件）。

執行此程式會產生另一個錯誤：

```
$ go run tmp.go
# command-line-arguments
.\tmp.go:16: i declared and not used
```

Go 編譯器（compiler）不允許你建立用不到的變數，由於我們在迴圈中沒有用到 `i`，所以我們要稍微修改程式碼：

```go
var total float64 = 0
for _, value := range x {
    total += value
}
fmt.Println(total / float64(len(x)))
```

可以用單個 `_`（底線）來告知編譯器我們不需要這個變數（在此範例中，我們不需要 iterator 變數）

在 Go 可以用另一個較簡短的語法來建立陣列：

```
x := [5]float64{ 98, 93, 77, 82, 83 }
```

我們不用指定型別，因為 Go 自己知道。像這樣的陣列有時可能會太長而無法用一行來表示，所以 Go 也可以讓我們用下列方式來分行：

```go
x := [5]float64{ 
    98, 
    93, 
    77, 
    82, 
    83,
}
```

要注意在 `83` 後面有一個結尾的  `,`  符號，在 Go 語言必須要這樣寫，因為這樣就能透過在前頭加上註解，隨意移除陣列元素：

```go
x := [4]float64{ 
    98, 
    93, 
    77, 
    82, 
    // 83,
}
```


# 06.02-Slice

Slice 是在一個陣列中的一個區段（segment），與陣列一樣，slice 是可以透過索引存取的，而且也有長度。但與陣列不同的是，slide 的長度是可以改變的，下列是一個 slice 範例：

```
 var x []float64
```

&#x20;Slice 與陣列的唯一差別是，在中括號之間沒有表示長度的數字。以此例而言，`x` 的長度為 `0`。

若你想建立一個 slice，則你應該使用內建的  `make` 函式：

```
x := make([]float64, 5)
```

這樣會建立一個 slice（這個 slice 與底層長度為 5 的 `float64` 陣列有關連），slices 必定會與某個陣列有關聯，而且它們的長度不僅不會超過陣列長度，還可以更小。`make` 函式可以使用第三個參數：

```
x := make([]float64, 5, 10)
```

10 代表 slice 所指向的底層陣列之空間：

<br>

<figure><img src="https://web.archive.org/web/20200228061932im_/https://sites.google.com/site/applezugointroduction/_/rsrc/1401643397770/06-arrays-slices-and-maps/06-01.png" alt=""><figcaption></figcaption></figure>

另一個建立 slice 的方式是使用 `[low : high]` ：

```go
arr := [5]float64{1,2,3,4,5}
x := arr[0:5]
```

`low` 是 slice 的起點之索引值，而 `high` 是 slice 的結束點（但是 slice 並不包含這個結束點的索引值），例如：在 `arr[0:5]` 會傳回 `[1,2,3,4,5]`，而 `arr[1:4]` 則傳回 `[2,3,4]`。

為了便利，我們也可以忽略 `low`、`high`，甚至可以同時忽略 `low` 與 `high`。`arr[0:]` 等同於 `arr[0:len(arr)]`、 `arr[:5]` 等同於 `[0:5]`、以及 `arr[:]` 等同於 `arr[0:len(arr)]`。

#### Slice 函式 <a href="#toc-slice-functions" id="toc-slice-functions"></a>

Go 語言有兩個內建的 slice 函式：`append` 與 `copy`。下列是一個 `append` 範例：

```go
func main() {
    slice1 := []int{1,2,3}
    slice2 := append(slice1, 4, 5)
    fmt.Println(slice1, slice2)
}
```

在執行程式之後，`slice1` 是 `[1,2,3]`，而 `slice2` 則是 `[1,2,3,4,5]`。`append` 利用現有的 slice（在第一個參數）建立一個新的 slice，並將後續的參數附加在 這個 slice 之後。

下列是一個 copy 範例：

```go
func main() {
    slice1 := []int{1,2,3}
    slice2 := make([]int, 2)
    copy(slice2, slice1)
    fmt.Println(slice1, slice2)
}
```

在執行此程式之後，`slice1` 是 `[1,2,3]`，而 `slice2` 是 `[1,2]`，將 `slice1` 的內容複製到 `slice2`，不過因為 `slice2` 的空間只能容納兩個元素，所以只有複製 `slice1` 的前兩個元素。


# 06.03-Map

Map 是一個無排序的 key-value 配對集合，也是所謂的一個有關聯的陣列、一個雜湊表或是一個目錄。map 可以使用一個關聯的 key 來查值，下列是一個 Go 語言的 map 範例：

```go
var x map[string]int
```

Map 的型別是透過 `map` 關鍵字表示，後面接著中括號框著的 key 型別，最後則是 value 的型別。可以這樣念，「`x` 是一個 `string` 對 `int` 的 map」。

如同陣列與 slice，可以使用中括號來存取 map，請試著執行下列的程式：

```go
var x map[string]int
x["key"] = 10
fmt.Println(x)
```

你應該會看到類似如下的錯誤訊息：

```go
panic: runtime error: assignment to entry in nil map

goroutine 1 [running]:
main.main()
  main.go:7 +0x4d

goroutine 2 [syscall]:
created by runtime.main
        C:/Users/ADMINI~1/AppData/Local/Temp/2/bindi
t269497170/go/src/pkg/runtime/proc.c:221
exit status 2
```

到目前為止，我們只看到編譯期的錯誤。這是一個執行期的錯誤訊息，如其名所示，執行期的錯誤會在執行程式期間發生，而編譯期的錯誤則是在編譯程式期間發生的。

我們程式的問題是，必須在使用 map 之前先進行初始化，我們應該改成下面的寫法：

```go
x := make(map[string]int)
x["key"] = 10
fmt.Println(x["key"])
```

你執行此程式之後應該會看到有 `10` 出現，陳述句 `x["key"] = 10` 跟我們在使用陣列時類似，只是這裡是 key 而不是整數，而且是一個字串，因為這裡 map 的 key 型別是 `string`。我們也可以使用 `int` 型別做為 key 來建立 map：

```go
x := make(map[int]int)
x[1] = 10
fmt.Println(x[1])
```

這樣看起來很像是一個陣列，不過還是有些不同之處。首先，map 的長度（透過 `len(x)` 取得）是可以改變的，只要我們新增項目（item）就會改變。起初 map 建立時的長度為 0，而在我們執行 `x[1] = 10` 之後，長度就變成 1 了。第二個與陣列的差異是，map 並非循序的，例如在陣列中，當看到 `x[1]` 代表的是一定有 `x[0]` 的存在不過 map 卻不一定如此。

我們也可以使用內建的 `delete` 函式來刪除 map 中的項目：

```go
delete(x, 1)
```

接著來看一個 map 範例程式：

```go
package main

import "fmt"

func main() {
    elements := make(map[string]string)
    elements["H"] = "Hydrogen"
    elements["He"] = "Helium"
    elements["Li"] = "Lithium"
    elements["Be"] = "Beryllium"
    elements["B"] = "Boron"
    elements["C"] = "Carbon"
    elements["N"] = "Nitrogen"
    elements["O"] = "Oxygen"
    elements["F"] = "Fluorine"
    elements["Ne"] = "Neon"
    
    fmt.Println(elements["Li"])
}
```

`elements` 是一個 map，使用化學元素表的前十個元素符號做為 elaments 的索引值，這是常見的 map 用法：如同查表或是查字典。假設我們查詢了一個不存在的元素：

```
fmt.Println(elements["Un"])
```

若是執行上面這行程式，應該不會傳回任何資料。技術上，一個 map 會依據 value 的型別而傳回相對應的零值（比如字串就是傳回空字串）。雖然我們可以透過條件式來檢查零值（`elements["Un"] == ""`），不過 Go 語言有提供一個更好的方法：

```go
name, ok := elements["Un"]
fmt.Println(name, ok)
```

存取一個 map 中的一個元素可以傳回兩個值，第一個值是查詢的結果，而第二個值是告訴我們查詢是否成功。在 Go 語言中，我們通常會看到下列這樣的程式碼：

```go
if name, ok := elements["Un"]; ok {    
    fmt.Println(name, ok)
}
```

首先，我們試著存取 map 的值，若成功了，則執行區塊中的程式碼：

如同我們在介紹陣列時所見，也有比較簡潔的方式可以建立 map：

```go
elements := map[string]string{
    "H": "Hydrogen",
    "He": "Helium",
    "Li": "Lithium",
    "Be": "Beryllium",
    "B": "Boron",
    "C": "Carbon",
    "N": "Nitrogen",
    "O": "Oxygen",
    "F": "Fluorine",
    "Ne": "Neon",
}
```

一般會將 map 用來儲存通用的資訊，現在來修改我們的程式，使得元素不僅可以儲存名稱，還可以儲存其標準狀態（室溫狀態）：

```go
func main() {
    elements := map[string]map[string]string{
        "H": map[string]string{
            "name":"Hydrogen", 
            "state":"gas",
        },
        "He": map[string]string{
            "name":"Helium", 
            "state":"gas",
        },
        "Li": map[string]string{
            "name":"Lithium", 
            "state":"solid",
        },
        "Be": map[string]string{
            "name":"Beryllium", 
            "state":"solid",
        },
        "B":  map[string]string{
            "name":"Boron",
            "state":"solid",
        },
        "C":  map[string]string{
            "name":"Carbon",
            "state":"solid",
        },
        "N":  map[string]string{
            "name":"Nitrogen",
            "state":"gas",
        },
        "O":  map[string]string{
            "name":"Oxygen",
            "state":"gas",
        },
        "F":  map[string]string{
            "name":"Fluorine",
            "state":"gas",
        },
        "Ne":  map[string]string{
            "name":"Neon",
            "state":"gas",
        },
    }

    if el, ok := elements["Li"]; ok {    
        fmt.Println(el["name"], el["state"])
    }
}
```

要注意，我們的 map 型別已經從 `map[string]string` 改為 `map[string]map[string]string`。現在我們有一個兩層的字串 map ，外部的 map 用來查表（基於元素的符號），而內部的 map 則用來儲存與元素相關的通用資訊，雖然通常都會這樣使用 map，不過我們在第9章會再探討儲存結構化資訊的較好方法。


# 06.04-問題

* 如何存取陣列或 slice 的第四個元素？
* 使用 `make([]int, 3, 9)` 建立的 slice 長度是多少呢？
* 給定如下的陣列：

  ```go
  x := [6]string{"a","b","c","d","e","f"}
  ```

  `x[2:5]` 會取得哪個值呢？
* 寫個程式找出串列中最小的數字：

  ```go
  x := []int{
      48,96,86,68,
      57,82,63,70,
      37,34,83,27,
      19,97, 9,17,
  }
  ```


# 07-函式

函式就是一段獨立的程式碼，有零個到多個輸入參數，而且也有零個到多個輸出參數。函式（也稱為 procedure 或 subroutine）通常使用一個黑盒子來表示：

<br>

<figure><img src="https://web.archive.org/web/20200228045708im_/http://golang-zhtw.netdpi.net/_/rsrc/1401643334480/07-han-shi/07-01.png" alt=""><figcaption></figcaption></figure>

<br>

到目前為止，我們在 Go 程式裡面只有用到一個函式：

```
func main() {}
```

現在我們會開始用多個函式來寫程式。


# 07.01-你的第二個函式

這是第6章的程式：

```go
func main() {
    xs := []float64{98,93,77,82,83}
    
    total := 0.0
    for _, v := range xs {
        total += v
    }
    fmt.Println(total / float64(len(xs)))
}
```

這個程式計算一串數字的平均值，找出這類平均值是很常見的問題，所以比較理想的方式是將這段程式定義為函式。

`average` 函式需要使用一個 `float64` 型別的 slice，並能夠傳回 `float64`，我們在 `main` 函式前面增加這段程式碼：

```go
func average(xs []float64) float64 {
    panic("Not Implemented")
}
```

函式的開頭是 `func` 關鍵字，後面跟著函式的名稱。函式的參數（輸入）可像這樣定義： `name type, name type, …`。我們這個函式有一個參數（一串分數），我們將它命名為 `xs`。在此參數之後的是傳回值的型別。函式的參數與傳回值型別則是所謂函式的特徵值（signature）。

最後，在大括號之間的函式的主體中會有一些陳述句，我們在函式的主體中呼叫一個內建的 `panic` 函式，這會產生一個執行期的錯誤訊息（本章稍後會再介紹 panic）。寫一個大函式可能會比較難，所以比較好的方式是將程式流程分成幾段比較好管理的小區塊，而不是直接寫成一大段程式碼。

現在我們將 main 函式中的一些程式碼搬到 average 函式中：

```go
func average(xs []float64) float64 {    
    total := 0.0
    for _, v := range xs {
        total += v
    }
    return total / float64(len(xs))
}
```

要注意的是，我們將 `fmt.Println` 改成了 `return`，return 會讓函式立即停止，並將傳回值送給它的呼叫者，將 `main` 修改如下：

```go
func main() {
    xs := []float64{98,93,77,82,83}
    fmt.Println(average(xs))
}
```

執行此程式應該會讓你得到與原本程式相同的結果，有些事要記得：

* 參數的名稱不用與原本呼叫函式代入的變數名稱相同，例如下列方式：

```go
func main() {
    someOtherName := []float64{98,93,77,82,83}
    fmt.Println(average(someOtherName))
}
```

而且我們的程式仍然可以運作。

* 函式無法存取呼叫者的任何變數（除非透過指標），像下列的方式無法正常運作：

```go
func f() {
    fmt.Println(x)
}
func main() {
    x := 5
    f()
}
```

我們也可以這樣寫：

```go
func f(x int) {
    fmt.Println(x)
}
func main() {
    x := 5
    f(x)
}
```

譯者註：特別注意，func main() 中的 x 與 func f()中的 x ，雖然變數名稱相同，但是它們是分別屬於 main() 與 f() 的區域變數（local variable，或稱自動變數，auto variable），在main() 中的 x 變數之可視範圍（scope）僅限於 main() 函式中，而 f() 中的 x 變數可視範圍也僅限於 func() 函式中。

或是這樣：

```go
var x int = 5
func f() {
    fmt.Println(x)
}
func main() {
    f()
}
```

函式是在堆疊（stack）中建立的，以下列的程式為例：

```go
func main() {
    fmt.Println(f1())
}
func f1() int {
    return f2()
}
func f2() int {
    return 1
}
```

我們可以將上述的程式碼以具體的方式呈現如下：

<figure><img src="https://web.archive.org/web/20200228061942im_/https://sites.google.com/site/applezugointroduction/_/rsrc/1401643358907/07-han-shi/07-02.png" alt=""><figcaption></figcaption></figure>

我們每次呼叫函式時，我們就會將函式置入堆疊（push），而從函式返回時，則將函式從堆疊中移出（pop）。

我們也可以明確指定回傳值的資料型別與名稱：

* ```go
  func f2() (r int) {
      r = 1
      return
  }
  ```


# 07.02-回傳多個值

Go 語言的函式可以傳回多個值：

```go
func f() (int, int) {
    return 5, 6
}

func main() {
    x, y := f()
}
```

不過需要有三個改變：傳回值的型別要改成放多個型別，而且每個型別之間要使用 `,` 隔開，在 return 之後的傳回值也要調整，改成使用 `,` 將傳回值隔開。最後一點是，接收函式多個傳回值的變數會在函式的左邊（`:=` 或 `=`），並且一樣分別用 `,` 隔開。

通常需要傳回多個傳回值的時機是：需要同時傳回錯誤碼與結果（`x, err := f()`），或者是用一個布林值（boolean）來代表成功時 （`x, ok := f()`）。


# 07.03-參數個數可變的函式（Variadic Function）

在 Go 語言的函式中，有一種特殊格式可以使用：

```go
func add(args ...int) int {
    total := 0
    for _, v := range args {
        total += v
    }
    return total
}
func main() {
    fmt.Println(add(1,2,3))
}
```

在函式的最後一個參數的型別之前，使用 `...` 表示這個函式可以接受零個或零個以上的參數，依此規則，我們可以使用零個或零個以上的 `int` 參數，呼叫這種函式的方式與其它函式一樣，差異在於這類函式可以傳遞任何我們想傳遞的參數數量。

下列為 `fmt.Println` 函式的實作方式：

```go
func Println(a ...interface{}) (n int, err error)
```

`Println` 函式可接受任意型別、任意數量的參數值（後續會在第九章討論特殊的 `interface{}` 型別）

我們也可以傳遞一個 `int` 的 slice，只要在 slice 之後接著 `...`：

```go
func main() {
    xs := []int{1,2,3}
    fmt.Println(add(xs...))
}
```


# 07.04-Closure

可以在函式中建立函式：

```go
func main() {
    add := func(x, y int) int {
        return x + y
    }
    fmt.Println(add(1,1))    
}
```

`add` 是一個區域變數，型別是 `func(int, int) int`（一個需要帶入兩個 `int` 型別以及傳回一個 `int` 型別的函式）當我們建立一個區域函式時，此函式只能存取其它區域變數（如第四章的 scope 所述）。

```go
func main() {
    x := 0
    increment := func() int {
        x++
        return x
    }
    fmt.Println(increment())
    fmt.Println(increment())    
}
```

`increment` 將 1 加到 `x` 變數，此變數定義於 `main` 函式的 scope，increment 函式可以存取與修改 `x` 變數，這就是為何我們在第一次呼叫 `increment` 時會看到顯示為 1，而第二次會看到顯示 2。

類似的函式與函式所參照的非區域變數合稱為所謂的 closure，以同樣的例子， `increment` 與  `x` 變數形成了一個 closure。

Closure 的一種使用方式是，透過設計一個函式，此函式會傳回另一個函式的結果（被呼叫時可以產生一串號碼），例如，下列會產生全部的偶數：

```go
func makeEvenGenerator() func() uint {
    i := uint(0)
    return func() (ret uint) {
        ret = i
        i += 2
        return
    }
}
func main() {
    nextEven := makeEvenGenerator()
    fmt.Println(nextEven()) // 0
    fmt.Println(nextEven()) // 2
    fmt.Println(nextEven()) // 4
}
```

`makeEvenGenerator` 會傳回一個函式（此函式會產生偶數），每次呼叫此函式時，它會將 2 加到區域變數 `i`，與一般區域變數的差異是：此變數存在於兩個呼叫之間。


# 07.05-遞迴（Recursive）

函式可以呼叫它自己，這裡是一個計算費伯納係數（factorial of a number）的方式：

```go
func factorial(x uint) uint {
    if x == 0 {
        return 1
    }

    return x * factorial(x-1)
}
```

`factorial` 呼叫它自己，使得函式進行遞迴（recursive），為了更了解此函式的運作，我們剖析 `factorial(2)` 的運作步驟：

* `x == 0` 成立嗎？否，（`x` 為 2）
* 找出 (2-1) 的費伯納係數
  * `x == 0` 成立嗎？否，（`x` 為 1）
  * 找出 (1-1) 的費伯納係數
    * `x == 0` 成立嗎？是，傳回 1。
  * 傳回 `1 * 1`
* 傳回 `2 * 1`

Closure 與遞迴是強大的程式設計技術，是所謂函式化程式設計（functional programming）的範例基礎。大多數的人會覺得函式化的程式設計比較難以理解，而覺得使用迴圈、if 陳述句、變數與簡易的函式會比較容易。


# 07.06-Defer, Panic & Recover

Go 有一個特殊的陳述句，稱為 `defer` 會在一個函式執行完畢之後，將該函式呼叫加入排班，請見下列的範例：

```go
package main

import "fmt"

func first() {
    fmt.Println("1st")
}
func second() {
    fmt.Println("2nd")
}
func main() {
    defer second()
    first()
}
```

此程式會先印出 `1st`，並接著印出 `2nd`，基本上 defer 會將呼叫移到函式尾端的 `second`。

```go
func main() {
    first()
    second()
}
```

`defer` 通常用於需要以某種方式釋放資源時，例如：當我們開啟一個檔案時，我們需要能夠確定之後會關閉檔案。使用 `defer`：

```go
f, _ := os.Open(filename)
defer f.Close()
```

這樣有三項優點：(1) 讓我們的 `Close` 呼叫可以靠近我們的 `Open` 呼叫，使得易於了解呼叫的用處 (2) 若我們的函式有多個 return 陳述句時（或許 `if` 裡面有一個，而 `else` 裡面也有一個） `Close` 會在這兩個 return 之前執行 (3) 受到 defer 的函式即使在執行期當掉了，也能運作。


# 07.07-Panic & Recover

我們之前已經建立了一個名為 `panic` 的函式，此函式會產生執行期（run-time）的錯誤。我們也能使用內建的 `recover`  函式來處理執行期的 panic。`recover` 會停止 panic 並將傳遞給  `panic` 呼叫的參數值傳回。我們可以透過如下的方式來使用它：

```go
package main

import "fmt"

func main() {
    panic("PANIC")
    str := recover()
    fmt.Println(str)
}
```

可是在這個案例中， `recover` 絕對不會執行到，因為在呼叫 `panic` 時已經暫停執行函式。所以我們必須搭配使用  `defer`：

```go
package main

import "fmt"

func main() {
    defer func() {    
        str := recover()
        fmt.Println(str)
    }()
    panic("PANIC")
}
```

通常一個 panic 代表的是一個程式設計師的錯誤（例如想要存取超出陣列邊界的索引值、忘記對一個 map 進行初始化之類），或是有難以還原的例外情況發生（所以才稱之為 panic）。


# 07.08-問題

* `sum` 函式可以取得一個 slice 的數字，並且將這些數字加總。在 Go 語言中，它的函式特徵值（function signature）看起來像什麼呢？
* 設計一個函式，傳遞一個整數型別的數值給它，讓它將這個整數除以二（平分），若結果是偶數則傳回 true，若結果是奇數則傳回 false。例如：`half(1)` 應該要傳回 `(0, false)` 而 `half(2)` 則應該傳回 `(1, true)`。
* 設計一個函式，參數的個數是可變的，並找出這串輸入的參數值之最大值。
* 以 `makeEvenGenerator` 為例，設計一個 `makeOddGenerator` 函式，用以產生奇數。
* 費伯納數列（Fibonacci sequence）的定義是：`fib(0) = 0`, `fib(1) = 1`, `fib(n) = fib(n-1) + fib(n-2)`，請設計一個遞迴函式來取得 `fib(n)`。
* 何謂 defer、panic 及 recover？你要如何從一個執行期的 panic 中進行 recover 呢？


# 08-指標

當我們在呼叫一個函式時帶入一個參數，則這個參數會被複製到函式中：

```go
func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x is still 5
}
```

在這個程式中，`zero` 這個函式不會動到 `main` 函式中的 `x` 變數值，可是如果我們想要改，該怎麼做呢？一種方式是使用一個特殊的資料型別，即所謂的指標（pointer）：

```go
func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x is 0
}
```

指標可以取得儲存變數值的記憶體位置，而不是變數值本身（或是指向某物的指標值）。透過指標 (`*int`)，`zero` 函式可以修改原本的變數值。


# 08.01-「\*」與「&」運算符號

在 Go 語言中，指標是使用星號字元「\*」表示，後面接著儲值的資料型別。在 `zero` 函式中， `xPtr` 是一個指向 `int` 型別的指標。

`*` 是用來提取（dereference）指標變數的值，提取一個指標可以存取指標所指向的內容值。當我們寫 `*xPtr = 0`，這表示我們將 `int` 型別的整數值 0 儲存在 `xPtr` 所參照的記憶體位置中。若我們試著改用 `xPtr = 0`，則我們在編譯程式時會發生錯誤，因為 `xPtr` 的型別不是 `int`，而是 `*int`，這個型別只能存入 `*int` 型別。

最後，我們使用 `&` 運算符來取得變數本身的位址，`&x` 會傳回一個 `*int` 型別（指向一個 int 整數），由於 `x` 的型別是 `int`，透過指標可以修改原本的變數內容。在 `main` 函式的 \&x 與在 `zero` 函式的  `xPtr`  都參照到相同的記憶體位址。


# 08.02-new

使用內建的 `new` 函式也會取得一個指標：

```go
func one(xPtr *int) {
    *xPtr = 1
}
func main() {
    xPtr := new(int)
    one(xPtr)
    fmt.Println(*xPtr) // x is 1
}
```

`new` 函式的參數是一個型別，會配置符合該型別數量的記憶體空間，並傳回這塊記憶體的起始位址。

在一些程式設計語言中，使用 `new` 與 `&` 時會有些差異，在使用完 `new` 所建立的記憶體時，需要記得刪除這塊記憶體。然而，Go 語言不用，因為它是具備垃圾收集的（garbage collected）程式設計語言，這表示配置的記憶體會在沒有任何人參照到它時自動收回。

在 Go 語言，很少使用內建的型別來配置記憶體，不過我們在下一章會看到，指標搭配 struct 使用時是很有幫助的。


# 08.03-問題

* 如何取得變數的記憶體位址？
* 如何將數值賦予一個指標？
* 如何建立一個新的指標？
* 執行下列程式之後，x 的值為何？

  ```go
  func square(x *float64) {
      *x = *x * *x
  }
  func main() {
      x := 1.5
      square(&x)
  }
  ```
* 設計一個可以置換兩個整數的程式，(`x := 1; y := 2; swap(&x, &y)` 應該會變成 `x=2` 與 `y=1`)。


# 09-Struct 與 Interface

雖然我們可以只用 Go 語言內建的資料型別來設計程式，不過這樣有時會覺得相當單調。假設有一個需要處理形狀的程式：

```go
package main

import ("fmt"; "math")

func distance(x1, y1, x2, y2 float64) float64 {
    a := x2 – x1
    b := y2 – y1
    return math.Sqrt(a*a + b*b)
}
func rectangleArea(x1, y1, x2, y2 float64) float64 {
    l := distance(x1, y1, x1, y2)
    w := distance(x1, y1, x2, y1)
    return l * w
}
func circleArea(x, y, r float64) float64 {
    return math.Pi * r*r
}
func main() {
    var rx1, ry1 float64 = 0, 0
    var rx2, ry2 float64 = 10, 10
    var cx, cy, cr float64 = 0, 0, 5

    fmt.Println(rectangleArea(rx1, ry1, rx2, ry2))
    fmt.Println(circleArea(cx, cy, cr))
}
```

持續追蹤全部的座標會很難看出程式做了什麼事情，而且容易出錯。


# 09.01-struct

要讓一個使程式變好，最簡單的方式是使用 struct，struct 是一種型別，包含多個有名稱的欄位。例如，我們可以用如下方式表示一個圓圈：

```go
type Circle struct {
    x float64
    y float64
    r float64
}
```

關鍵字 `type` 指出了一個新的型別，後面跟著型別的名稱（`Circle`），`struct` 關鍵字表示我們定義了一個 `struct` 型別，大括號裡面有一些欄位。每個欄位都有一個名稱與型別，如同函式那樣，我們也可以將相同型別的變數欄位放在一起：

```go
type Circle struct {
    x, y, r float64
}
```

#### 初始化 <a href="#toc-initialization" id="toc-initialization"></a>

有許多方式可以讓我們建立新的 Circle 型別之實體（instance）：

```go
var c Circle
```

如同其它的資料型別，這樣可以建立一個 Circle 區域變數，預設值為零。對於一個 `struct` 而言，設定為零表示將 struct 的每個欄位都設定為相對應的零值（`int` 型別設定為 0, `float` 型別設定為 0.0, `string` 型別設定為 "", 指標型別設定為 `nil` , …）。我們也可以使用 new 函式來建立：

```go
c := new(Circle)
```

這樣會為每個欄位配置記憶體空間，並將每個欄位設定為零，接著傳回一個指標（`*Circle`）。通常我們會比較想要為每個欄位設定一個值，有下列兩種方式：

```go
c := Circle{x: 0, y: 0, r: 5}
```

或是我們知道欄位名稱的定義順序，也可以直接賦值：

```go
c := Circle{0, 0, 5}
```

#### 欄位 <a href="#toc-fields" id="toc-fields"></a>

我們可以使用 `.` 運算符來存取這些欄位：

```go
fmt.Println(c.x, c.y, c.r)
c.x = 10
c.y = 5
```

再來修改 `circleArea` 函式，讓它可以使用一個 `Circle`：

```go
func circleArea(c Circle) float64 {
    return math.Pi * c.r*c.r
}
```

我們在 main 函式是這樣：

```go
c := Circle{0, 0, 5}
fmt.Println(circleArea(c))
```

要記得一件事情，就是 Go 語言一定會複製參數。如果我們修改 `circleArea` 函式中的欄位值，則不會更動到原本的變數。因此我們通常會以下列方式設計函式：

```go
func circleArea(c *Circle) float64 {
    return math.Pi * c.r*c.r
}
```

並修改 main 函式：

```go
c := Circle{0, 0, 5}
fmt.Println(circleArea(&c))
```

<br>


# 09.02-Method

雖然現在的程式碼比初版好了許多，不過我們還可以用一個所謂的 method 特殊型別來大幅改善程式碼：

```go
func (c *Circle) area() float64 {
    return math.Pi * c.r*c.r
}
```

在 `func` 關鍵字與函式名稱之間，我們已經新增了一個 “receiver”，receiver 就像是一個參數，具有名稱與型別，但透過此方式建立函式可讓我們使用 `.` 運算符來呼叫函式：

```go
fmt.Println(c.area())
```

這樣很易於閱讀，我們不再需要 `&` 運算符（Go 語言能自動知道要傳遞一個指標給此 method 的 circle），而且因為此函式只能用在 `Circle`，所以我們可以重新將函式只命名為 `area`。

現在替 rectangle 做同樣的設計：

```go
type Rectangle struct {
    x1, y1, x2, y2 float64
}
func (r *Rectangle) area() float64 {
    l := distance(r.x1, r.y1, r.x1, r.y2)
    w := distance(r.x1, r.y1, r.x2, r.y1)
    return l * w
}
```

`main` 具有：

```go
r := Rectangle{0, 0, 10, 10}
fmt.Println(r.area())
```

#### 嵌入式型別 <a href="#toc-embedded-types" id="toc-embedded-types"></a>

一個 struct 的欄位通常代表有一個關係，例如：一個 `Circle` 有一個 `radius`，假設我們有一個 person 這樣的 struct：

```go
type Person struct {
    Name string
}
func (p *Person) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}
```

而且我們想要建立一個新的 `Android` struct，我們可以這樣寫：

```go
type Android struct {
    Person Person
    Model string
}
```

這是可以運作的，而我們會說這樣宣告的意思是：Android 「是」一個人，而不是說 Android「含有」(「擁有」)一個人。Go 語言透過嵌入式型別（embedded type）提供這類關係，也就是所謂的匿名欄位（anonymous field），嵌入式型別看起來類似這樣：

```go
type Android struct {
    Person
    Model string
}
```

我們使用（`Person`）型別，而且不幫它取名字。當以這個方式定義時，就可以使用這個型別名稱來存取 `Person` struct：

```go
a := new(Android)
a.Person.Talk()
```

不過我們也可以直接在 `Android` 上呼叫任何的 `Person` method：

```go
a := new(Android)
a.Talk()
```

這是一個直觀的關係：人可以說話，而一個 android 是一個人，因此 android 可以說話。


# 09.03-Interface

你可能已經注意到，我們可以將 `Rectangle` 的 `area` method 命名為 `Circle` 的 `area` method。這並非意外。在真實世界設計程式時，像這類的相似關係，Go 語言提供了一種所謂的 Interface 型別，這裡有一個 `Shape` 介面的範例：

```go
type Shape interface {
    area() float64
}
```

Interface 與 struct 一樣，都是使用 `type` 關鍵字建立的，後面跟著一個名稱，以及關鍵字 `interface`。不過這裡不定義欄位，而是定義一組 method，一組 method 指的是一串 methods，為了要實做 interface，所以這些 methods 都要有一個型別。

在我們的 `Rectangle` 與 `Circle` 範例中，都有 area method，而且會傳回 `float64`，所以這兩個型別都有實做 `Shape` interface。對它自己本身而言，這樣做並不會有什麼好處，不過我們可以使用 interface 型別做為函式的參數：

```go
func totalArea(shapes ...Shape) float64 {
    var area float64
    for _, s := range shapes {
        area += s.area()
    }
    return area
}
```

我們會像這樣呼叫這個函式：

```go
fmt.Println(totalArea(&c, &r))
```

也可以將 interface 做為欄位使用：

```go
type MultiShape struct {
    shapes []Shape
}
```

我們甚至可以透過提供 MultiShape 一個 area method，將它自己轉為 `Shape`：

```go
func (m *MultiShape) area() float64 {
    var area float64
    for _, s := range m.shapes {
        area += s.area()
    }
    return area
}
```

現在，一個 `MultiShape` 就包含了 `Circle`、`Rectangle`、或甚至是其它的 `MultiShape`。


# 09.04-問題

* 一個 method 與函式的差異為何？
* 為何你要使用一個嵌入式匿名欄位，而不使用一般的命名欄位呢？
* 在 `Shape` interface 新增一個稱為 `perimeter` 的新 method，`perimeter` 可以計算 shape 的週長，請為 `Circle` 與 `Rectangle` 實做 method。


# 10-Concurrency（平行處理）

大型程式通常是由許多小程式組成的。例如，一個網頁伺服器會處理來自瀏覽器的請求，並提供 HTML 格式的網頁做為回應。每個請求的處理都像是執行一個小程式。

比較理想的方式是，讓這類程式可以同時執行它們的小元件（以網頁伺服器需要處理多個請求為例）。可以同步處理多個任務的能力即是所謂的並行處理。Go 語言透過 goroutine 與 channel 提供了相當多的並行處理功能。


# 10.01-Goroutine

Goroutine 是一個函式，可以與其它函式同步執行，我們可以使用 go 關鍵字建立一個 goroutine，並接著一個函式呼叫：

```go
package main

import "fmt"

func f(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
    }
}

func main() {
    go f(0)
    var input string
    fmt.Scanln(&input)
}
```

這個程式有兩個 goroutines，第一個 goroutine 是隱含的（implicit），即是 main 函式自己，第二個 goroutine 是我們呼叫 `go f(0)` 時建立的。通常當我們呼叫一個函式時，我們的程式會執行函式中的每一行程式碼，並回到原本呼叫函式的地方。但是透過 goroutine，我們可以在呼叫函式之後，直接執行下一行程式，而不用等待函式完成。這就是為什麼需要呼叫 `Scanln` 函式，如果沒有這個函式，程式在還沒來得及印出全部的數字以前就會結束了。

Goroutines 是輕量級的，而且我們可以輕易的建議數千個，現在我們如下修改程式，讓它可以執行 10 個 goroutines：

```go
func main() {
    for i := 0; i < 10; i++ {
        go f(i)
    }
    var input string
    fmt.Scanln(&input)
}
```

你可能注意到了，當你執行這個程式時，程式似乎是依序執行 goroutine，而不是同步執行。我們現在使用 `time.Sleep` 與 `rand.Intn` 在函式中新增一些延遲時間：

```go
package main

import (
    "fmt"
    "time"
    "math/rand"
)

func f(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
        amt := time.Duration(rand.Intn(250))
        time.Sleep(time.Millisecond * amt)
    }
}
func main() {
    for i := 0; i < 10; i++ {
        go f(i)
    }
    var input string
    fmt.Scanln(&input)
}
```

`f` 會印出 0 到 10 的數字，每輸出一個數字就隨機等待一段時間（0 \~ 250 ms），這些 routines 現在應該會同步執行了。


# 10.02-Channel

Channel (頻道) 提供一種方式，供兩個 goroutines 可以互相通信，並彼此同步執行。下列是一個使用 channel 的範例程式：

```go
package main

import (
    "fmt"
    "time"
)

func pinger(c chan string) {
    for i := 0; ; i++ {
        c <- "ping"
    }
}
func printer(c chan string) {
    for {
        msg := <- c
        fmt.Println(msg)
        time.Sleep(time.Second * 1)
    }
}
func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}
```

此程式將會持續印出 "ping"（直到按下 Enter 為止）。一個 channel type (頻道型別) 的表示方式是使用關鍵字 `chan` 以及接著要傳遞給 channel 的資料之型別（在此例中，我們傳遞字串）。

而 `<-` （左箭頭）運算符則用於傳送與接收 channel 的訊息，`c <- "ping"` 表示送出 `"ping"`，而 `msg := <- c` 表示接收一個訊息，並將訊息儲存於 `msg`。`fmt` 這行也可以寫成：`fmt.Println(<-c)`，在此例我們可以移除前面那行。

像這樣使用一個 channel 來同步兩個 two goroutines。當 `pinger` 試圖想要送出該 channel 上的一筆訊息時，它會持續等待（即所謂的阻塞），直到 `printer` 已經準備好要接收訊息為止。我們將另一個傳送端加到程式中，看會發生什麼事情。新增如下的函式：

```go
func ponger(c chan string) {
    for i := 0; ; i++ {
        c <- "pong"
    }
}
```

再修改 `main`：

```go
func main() {
    var c chan string = make(chan string)
    
    go pinger(c)
    go ponger(c)
    go printer(c)
    
    var input string
    fmt.Scanln(&input)
}
```

這個程式現在會輪流印出 “ping” 跟 “pong”。

#### Channel Direction <a href="#toc-channel-direction" id="toc-channel-direction"></a>

我們可以對一個 channel 型別指定 channel direction 來限制它是要做為傳送或是接收。例如：會將 pinger 的函式特徵值修改如下：

```go
func pinger(c chan<- string)
```

現在 `c` 只能用來傳送，若企圖對 c 進行接收資料將會導致一個編譯的錯誤，我們一樣可以將 printer 改為如下：

```go
func printer(c <-chan string)
```

若 channel 沒有方向限制，則是所謂的雙向（bi-directional），可以將一個雙向的 channel 傳遞給一個函式做為只能傳送或只能接收的 channel 使用，反之則不可。

#### Select <a href="#toc-select" id="toc-select"></a>

Go 有一個特殊的陳述句，稱為 `select`，它的作用與 `switch` 類似，但只能用在 channels：

```go
func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        for {
            c1 <- "from 1"
            time.Sleep(time.Second * 2)
        }
    }()
    go func() {
        for {
            c2 <- "from 2"
            time.Sleep(time.Second * 3)
        }
    }()
    go func() {
        for {
            select {
            case msg1 := <- c1:
                fmt.Println(msg1)
            case msg2 := <- c2:
                fmt.Println(msg2)
            }
        }
    }()
    
    var input string
    fmt.Scanln(&input)
}
```

這個程式每隔兩秒會輸出一次 “from 1”，而每隔三秒會印出一次 “from 2”。`select` 會選取第一個已經就緒的 channel 來接收（或傳送）資料。若同時有多個 channels 已經就緒，則隨機選取一個 channel 來進行接收。若沒有任何 channel 已經就緒，則會發生阻塞，直到有任何一個 channel 已經就緒為止。

`select` 陳述句也很常用來實做計時工作（timeout）：

```go
select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
}
```

`time.After` 會建立一個 channel，並在指定的時間週期之後送出它所在的目前時間（我們用不到這個時間，所以這裡不會把時間存到變數）。我們也能指定一個 `default` 的情況：

```go
select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nothing ready")
}
```

若沒有任何 channel 已經就緒，則會進去 default 這個情況。

#### Buffered Channel <a href="#toc-buffered-channels" id="toc-buffered-channels"></a>

在建立一個 channel 時，也可以傳遞第二個參數給 make 函式：

```go
c := make(chan int, 1)
```

這樣會建立一個容量為1的 buffered channel（有緩衝的頻道），通常 channel 是同步的，channel 兩端會互相等待彼此已經就緒。不過一個有緩衝的 channel 則是非同步的（asynchronous），在傳送或接收一筆訊息時都不用等待，除非 channel 已經滿了。


# 10.03-問題

* 如何指定一個 channel 型別的 direction 呢？
* 使用 `time.After` 設計一個你自己的 `Sleep` 函式。
* 何謂 buffered channel 呢？你要如何建立一個容量為 20 的一個 buffered channel 呢？


# 11-Packages

Go 語言的設計是要鼓勵軟體工程師實作。高品質軟體的其中一個要素是重複使用程式碼（精神是不要重複做一樣的事情）。

如我們在第七章所示，函式是重複使用程式碼的第一層。此外，Go 語言還提供另一個重複使用程式碼的機制：即 package。幾乎我們看到的每個程式都有這一行：

```
import "fmt"
```

`fmt` 是 package 的名稱，包含各種螢幕輸出與格式的相關函式，用這個方式打包程式碼有三個目的：

* 可以減少命名重疊的機會，這樣可以讓我們的程式碼簡潔明瞭。
* 可以組織程式碼，容易找出我們想要重複使用的程式碼。
* 可以加速編譯器，只需要重新編譯程式的一小部份。雖然我們一直用到 `fmt` package，不過我們不用在每次修改程式時就重新編譯這個 package。


# 11.01-建立 Package

只在另一個程式的上下文（context）使用 packages 是比較合理的。若沒有這個獨立的程式，我們就無法使用我們建立的 package 了。現在我們建立一個應用程式來使用我們撰寫的 package。在資料夾 `~/Go/src/golang-book` 中建立一個名為 `chapter11` 的資料夾，並在該資料夾中建立一個名為 `main.go` 的檔案，其內容如下：

```go
package main

import "fmt"
import "golang-book/chapter11/math"

func main() {
    xs := []float64{1,2,3,4}
    avg := math.Average(xs)
    fmt.Println(avg)
}
```

現在我們在 `chapter11` 資料夾中建立另一個名為 `math` 的資料夾，並在該資料夾中建立一個 `math.go` 的檔案，其內容如下：

```go
package math

func Average(xs []float64) float64 {
    total := float64(0)
    for _, x := range xs {
        total += x
    }
    return total / float64(len(xs))
}
```

開啟終端機，在 `math` 資料夾中執行 `go install`，這樣會編譯 `math.go` 程式，並建立一個可連結的物件檔（object file）：`~/Go/pkg/os_arch/golang-book/chapter11/math.a`（這裡的 `os` 類似 `windows`，而 `arch` 則類似 `amd64`）

現在我們回到 `chapter11` 資料夾，並執行 `go run main.go`，現在應該會得到 `2.5`。有幾點要注意的事項：

* `math` 是一個 package 的名稱，這是 Go 語言在標準發行版本的一部分內容，只是因為 Go 語言的 packages 可以是階層式的，所以我們能安全的在我們自己的 package 中使用同樣的名稱（原本的 `math` package 就是 `math`，而我們的則是 `golang-book/chapter11/math`）
* 當我們匯入（import）我們自己的 math 函式庫時，我們會使用它的全名（`import "golang-book/chapter11/math"`），只是在 `math.go` 檔案中，我們只會使用最後一部分的名稱（`package math`）。
* 當從我們的函式庫中參照到函式時，也可以只用 `math` 這個名稱。如果我們想要在同樣的程式中，同時使用這兩個函式庫時，Go 語言可以使用別名：

  ```go
  import m "golang-book/chapter11/math"

  func main() {
      xs := []float64{1,2,3,4}
      avg := m.Average(xs)
      fmt.Println(avg)
  }
  ```

  `m` 就是別名。
* 你可能注意到了，package 中的每個函式都是以大寫字母開頭的，這表示其它的 packages （以及程式）都可以看到它。如果我們將函式命名為 `average` 而不是 `Average`，則我們的 `main` 程式就無法看見這個函式。比較好的方式是只揭露我們 package 的一部份內容（我們想要開放給其它 package 使用的），而將其它內容隱藏起來。我們之後可以隨意更改，而不用擔心會破壞到其它程式，而且這樣可以讓我們的 package 易於使用。
* Package 的名稱會與它們所在的資料夾相同，還有一些方法，不過這個方式是最簡單的。


# 11.02-文件

Go 語言可以自動產生 package 的文件，類似標準的 package 文件。可以在終端機中執行下列指令：

```go
godoc golang-book/chapter11/math Average
```

應該會看到我們所寫的該函式相關資訊，我們可以在函式前面新增一些註解來改善此文件：

```
// Finds the average of a series of numbers
func Average(xs []float64) float64 {
```

若你在 `math` 資料夾中執行 `go install`，接著重新執行 `godoc` 指令，你應該會在函式的定義下方看到我們的註解。此文件也可以透過執行下列指令取得：

```
godoc -http=":6060"
```

並將此 URL 輸入在你們瀏覽器上：

```
http://localhost:6060/pkg/
```

你應該可以瀏覽你系統安裝的每個 package。


# 11.03-問題

* 為何要使用 packages？
* 開頭使用大小寫字母的識別碼差異？（如 `Average` 與 `average`）
* 何為 package 的別名？如何產生一個別名呢？
* 我們將第七章的 average 函式複製到我們的 package，建立 `Min` 與 `Max` 函式，在一個 `float64` 型別的 slice 中找出最小值與最大值。
* 如何幫 #3 中建立的函式產生文件？


# 12-測試

程式設計並不容易，即使是最好的程式設計師，也無法在想寫程式時就可以寫出來。因此，軟體開發過程中的一個重點就是測試。為自己的程式碼設計測試程式在確保程式的品質與可靠度上，是一個很好的方式。

Go 語言有一個特殊的程式，可以比較容易地設計測試程式。所以我們在上一章用這個 package 建立了一些測試程式。在 `math` 資料夾中，`chapter11` 建立了一個檔名為 `math_test.go` 的新檔案，內容如下：

```go
package math

import "testing"

func TestAverage(t *testing.T) {
    var v float64
    v = Average([]float64{1,2})
    if v != 1.5 {
        t.Error("Expected 1.5, got ", v)
    }
}
```

執行這個指令：

```
go test
```

你應該會看到下列的執行結果：

```
$ go test
PASS
ok      golang-book/chapter11/math      0.032s
```

&#x20;`go test` 指令會尋找目前資料夾中的任何測試程式，並且執行它們。測試程式的辨別方式是有一個 `Test` 起始函式，並有一個  `*testing.T` 型別的參數。在我們的範例中，因為我們正在測試 `Average` 函式，所以我們將測試函式命名為 `TestAverage`。

只要我們完成了測試函式的設定，我們就可以設計測試程式來測試程式碼。以此例而言，我們知道 `[1,2]` 的平均值應該是 `1.5`，所以我們就可以這樣檢查。測試不同的數字組合可能是個好主意，所以我們稍微修改我們的測試程式：

```go
package math

import "testing"

type testpair struct {
    values []float64
    average float64
}

var tests = []testpair{
    { []float64{1,2}, 1.5 },
    { []float64{1,1,1,1,1,1}, 1 },
    { []float64{-1,1}, 0 },
}

func TestAverage(t *testing.T) {
    for _, pair := range tests {
        v := Average(pair.values)
        if v != pair.average {
            t.Error(
                "For", pair.values, 
                "expected", pair.average,
                "got", v,
            )
        }
    }
}
```

這是常見的建立測試方法（可以在 Go 語言的 packages 程式原始碼中找到大量的範例）。我們建立一個 `struct` 來儲存函式的輸入與輸出。然後我們建立一串 `struct` (成對的)。再來我們透過迴圈處理每一組，並執行函式。

#### 問題 <a href="#toc" id="toc"></a>

* 設計一個好的測試套件並不是每次都很簡單，不過設計測試程式的過程中，通常會遭遇比初次了解時更多的問題。例如：在我們的 `Average` 函式中，若你傳遞一個空的串列 (`[]float64{}`) 會發生什麼事呢？就這個案例來說，我們如何修改函式，使得函式會傳回 0 呢？
* 幫你在前幾章寫的 `Min` 與 `Max` 函式設計一些測試程式。


# 13-The Core Packages

其實不用自己克難的拼湊出程式的每個功能，在真實世界中，大多數的程式設計工作都是利用現有的函式庫。本章會探討一些 Go 常用的 packages。

一開始我們要先提醒大家：雖然這些函式庫的功能非常明確了（或是在前述章節介紹過了），不過 Go 語言引用的多數函式庫都需要特定領域的知識背景（例如：密碼學），這些技術都已經超出本書的討論範疇。


# 13.01-字串

Go 語言在 `strings` package 提供大量的字串處理函式：

```go
package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(    
        // true
        strings.Contains("test", "es"), 

        // 2
        strings.Count("test", "t"),

        // true
        strings.HasPrefix("test", "te"), 

        // true
        strings.HasSuffix("test", "st"), 

        // 1
        strings.Index("test", "e"), 

        // "a-b"
        strings.Join([]string{"a","b"}, "-"),

        // == "aaaaa"
        strings.Repeat("a", 5), 

        // "bbaa"
        strings.Replace("aaaa", "a", "b", 2),

        // []string{"a","b","c","d","e"}
        strings.Split("a-b-c-d-e", "-"), 

        // "test"
        strings.ToLower("TEST"), 

        // "TEST"
        strings.ToUpper("test"), 

    )
}
```

我們有時會需要將字串做為二進位資料使用，為了將字串轉換為 slice 的資料，需要這麼做（反之亦然）：

```go
arr := []byte("test")
str := string([]byte{'t','e','s','t'})
```

<br>


# 13.02-輸入與輸出

在我們開始探討檔案處理之前，我們需要先了解 Go 語言的  `io` package。`io` package 有一些函式，不過多數的介面在其它 packages 也都會用到。兩個主要的介面是 `Reader` 與 `Writer`，`Reader` 提供的方法是透過 `Read` method，而 `Writer` 則提供透過 `Write` method 進行寫入的方式。Go 語言中的多數函式以 `Reader` 或 `Writer` 為參數。例如：`io` package 就有一個 `Copy` 函式，可以將資料從一個 `Reader` 複製到一個 `Writer`：

```go
func Copy(dst Writer, src Reader) (written int64, err error)
```

若要讀取或是寫入一個 `[]byte` / `string` 的資料，你需要用到使用在 `bytes` package 中找到的 `Buffer` struct。

```go
var buf bytes.Buffer
buf.Write([]byte("test"))
```

不需要對 `Buffer` 進行初始化以及 `Reader`／`Writer` 這兩個介面，我們可以透過呼叫 `buf.Bytes()` 來將它轉換為 `[]byte`。若你只需要讀取一個字串，則也可以使用 `strings.NewReader` 函式，這樣會比使用一個 buffer（緩衝區）更有效率。


# 13.03-檔案與資料夾

為了使用 Go 語言開啟一個檔案，我們可以使用 `os` package 的 `Open` 函式，下列是一個範例，引導我們如何讀取一個檔案的內容，並將檔案內容呈現在終端機：

```go
package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("test.txt")
    if err != nil {
        // handle the error here
        return
    }
    defer file.Close()
    
    // get the file size
    stat, err := file.Stat()
    if err != nil {
        return
    }
    // read the file
    bs := make([]byte, stat.Size())
    _, err = file.Read(bs)
    if err != nil {
        return
    }

    str := string(bs)
    fmt.Println(str)
}
```

我們在開啟檔案之後要立刻使用 `defer file.Close()`，以確定檔案會在函式完成時盡快關閉。讀取檔案是常做的工作，因此有比較簡潔的方式來讀取檔案：

```go
package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    bs, err := ioutil.ReadFile("test.txt")
    if err != nil {
        return
    }
    str := string(bs)
    fmt.Println(str)
}
```

這裡示範如何建立一個檔案：

```go
package main

import (
    "os"
)

func main() {
    file, err := os.Create("test.txt")
    if err != nil {
        // handle the error here
        return
    }
    defer file.Close()

    file.WriteString("test")
}
```

為了取得目錄的內容，我們使用相同的 `os.Open` function，不過這次是提供一個目錄的路徑，而不是檔案名稱。接著我們呼叫 `Readdir` method：

```go
package main

import (
    "fmt"
    "os"
)

func main() {
    dir, err := os.Open(".")
    if err != nil {
        return
    }
    defer dir.Close()

    fileInfos, err := dir.Readdir(-1)
    if err != nil {
        return
    }
    for _, fi := range fileInfos {
        fmt.Println(fi.Name())
    }
}
```

通常我們會用遞迴的方式來遍尋一個資料夾的內容（讀取資料夾的內容、全部的子資料夾、全部子資料夾的子資料夾、…）。為了更方便達成這項工作，我們可以使用　`path/filepath` package 中的 `Walk` 函式：

```go
package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
        fmt.Println(path)
        return nil
    })
}
```

會對根目錄（root，此例為 .）中的每個檔案與資料夾呼叫傳遞給 `Walk` 的函式。


# 13.04-錯誤訊息

Go 語言有一個內建型別可供錯誤訊息使用（`error`型別），我們可以使用 `errors` package 中的 `New` 函式建立自己的錯誤訊息：

```go
package main

import "errors"

func main() {
    err := errors.New("error message")
}
```

<br>


# 13.05-Container 與 Sort

在 Go 語言中，除了 list 與 map，在 container package 中還有幾個可用的套件。我們會先以 `container/list` 為例進行探討。

List

&#x20;`container/list` package 實做了一個雙向鏈結串列（doubly-linked list），一個鏈結串列是一種如下所示的資料結構：<br>

<figure><img src="https://web.archive.org/web/20200228062138im_/https://sites.google.com/site/applezugointroduction/_/rsrc/1401643502089/13-the-core-packages/13-01.png" alt=""><figcaption></figcaption></figure>

串列中的每個節點都會包含一個值（此例為1、2或3）以及一個指向下一個節點的指標。因為這是一個雙向鏈結串列，所以每一個節點還會使用一個指標來指向前一個節點，可使用下列的程式建立一個這樣的串列：

```go
package main

import ("fmt" ; "container/list")

func main() {
    var x list.List
    x.PushBack(1)
    x.PushBack(2)
    x.PushBack(3)

    for e := x.Front(); e != nil; e=e.Next() {
        fmt.Println(e.Value.(int))
    }
}
```

數值為零的 `List` 是一個空的串列（而 `*List` 也能使用 `list.New` 來建立）。可以使用 `PushBack` 將值附加到串列後方，我們使用迴圈從串列的第一個元素（element）開始，依序取得串列中的每個節點值，直到遇到結尾的 nil 為止。&#x20;

#### Sort <a href="#toc-sort" id="toc-sort"></a>

Sort package 有一些能夠排序任意資料的函式。有幾個預先定義好的排序函式（供 int 與 float 的 slice 使用），此處有一個範例，示範如何排序你自的資料：

```go
package main

import ("fmt" ; "sort")

type Person struct { 
    Name string
    Age int
}

type ByName []Person

func (this ByName) Len() int {
    return len(this)
}
func (this ByName) Less(i, j int) bool {
    return this[i].Name < this[j].Name
}
func (this ByName) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

func main() {
    kids := []Person{
        {"Jill",9},
        {"Jack",10},
    }
    sort.Sort(ByName(kids))
    fmt.Println(kids)
}
```

在 `sort` 中的 Sort 函式接收一個 `sort.Interface`，並對它進行排序，`sort.Interface` 需要三個 methods：分別是 `Len`、`Less` 與 `Swap`。為了定義我們自己的排序，我們會建立一個新的型別（`ByName`），並且讓它等於我們想要排序的一個 slice。接著我們定義這三個 methods。

接著要將我們的 people 串列進行排序就如同要將串列強制轉型為我們的新型別一樣簡單，我們可以這樣來依據年齡（age）進行排序：

```go
type ByAge []Person
func (this ByAge) Len() int {
    return len(this)
}
func (this ByAge) Less(i, j int) bool {
    return this[i].Age < this[j].Age
}
func (this ByAge) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}
```

<br>


# 13.06-雜湊＆加解密

雜湊函式（hash function）採用一組資料，並將資料縮減為較小的固定尺寸。通常會將雜湊應用於設計查詢資料上，以能容易地偵測變動。Go 語言中的雜湊函式可分為兩類：

加密與非加密。

非加密式雜湊函式可以在 hash package 中找到，包含 `adler32`、`crc32`、`crc64` 與 `fnv`。這裡是一個使用 `crc32` 的範例：

```go
package main

import (
    "fmt"
    "hash/crc32"
)

func main() {
    h := crc32.NewIEEE()
    h.Write([]byte("test"))
    v := h.Sum32()
    fmt.Println(v)
}
```

`crc32` 雜湊物件實做了 `Writer` 介面，所以我們可以使用與其它資料相同的方式，透過 `Writer` 寫入資料。只要我們將資料完全寫入，我們就可以呼叫 `Sum32()` 來傳回一個 `uint32` 型別的值。`crc32` 的常用方式是用來比較兩個檔案，若兩個檔案的 `Sum32` 值都相同，若兩個檔案的 `Sum32` 值都相同，則表示這兩個檔案高度相似（不一定 100% 相同）。若兩個檔案計算的值不同，則可以確定檔案的內容不同：

```go
package main

import (
    "fmt"
    "hash/crc32"
    "io/ioutil"
)

func getHash(filename string) (uint32, error) {
    bs, err := ioutil.ReadFile(filename)
    if err != nil {
        return 0, err
    }
    h := crc32.NewIEEE()
    h.Write(bs)
    return h.Sum32(), nil
}

func main() {
    h1, err := getHash("test1.txt")
    if err != nil {
        return
    }
    h2, err := getHash("test2.txt")
    if err != nil {
        return
    }
    fmt.Println(h1, h2, h1 == h2)
}
```

加密的雜湊函式與其相對應的非加密雜湊函式類似，不過加密的雜湊函式有增加一些難以逆轉的屬性。一組經過雜湊加密的資料是很難得知使用哪種雜湊方式。這些雜湊通常是在安全性相關的應用程式上使用。

其中一種常見的加密雜湊函式是所謂的 SHA-1，下列是它的用法：

```go
package main

import (
    "fmt"
    "crypto/sha1"
)

func main() {
    h := sha1.New()
    h.Write([]byte("test"))
    bs := h.Sum([]byte{})
    fmt.Println(bs)
}
```

這個範例與 `crc32` 的範例類似，因為 `crc32` 與 `sha1` 都有實做 `hash.Hash` 介面，主要的差異在於：`crc32` 是計算一個 32 位元的雜湊，而 `sha1` 是 160 個位元。由於沒有原生的型別可以表示 160 個位元的數字，所以我們改成使用一個 20 bytes 的 slice 來代替。


# 13.07-伺服器

使用 Go 語言來設計網路伺服器是很簡單的，我們先看如何建立一個 TCP 伺服器：

```go
package main

import (
    "encoding/gob"
    "fmt"
    "net"
)

func server() {
    // listen on a port
    ln, err := net.Listen("tcp", ":9999")
    if err != nil {
        fmt.Println(err)
        return
    }
    for {
        // accept a connection
        c, err := ln.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }
        // handle the connection
        go handleServerConnection(c)
    }
}

func handleServerConnection(c net.Conn) {
    // receive the message
    var msg string
    err := gob.NewDecoder(c).Decode(&msg)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Received", msg)
    }
    
    c.Close()
}

func client() {
    // connect to the server
    c, err := net.Dial("tcp", "127.0.0.1:9999")
    if err != nil {
        fmt.Println(err)
        return
    }

    // send the message
    msg := "Hello World"
    fmt.Println("Sending", msg)
    err = gob.NewEncoder(c).Encode(msg)
    if err != nil {
        fmt.Println(err)
    }

    c.Close()
}

func main() {
    go server()
    go client()
    
    var input string
    fmt.Scanln(&input)
}
```

這個範例使用了  `encoding/gob` package，這個 package 可以很容易的對 Go 值進行編碼，使得其它 Go 程式（或是本例中的 Go 程式）可以讀值。其它的編碼方式可取自 `encoding` （如 `encoding/json`）以及第三方的 packages（例如：我們可以使用  `labix.org/v2/mgo/bson` 取得 bson 的支援）。

<br>

#### HTTP <a href="#toc-http" id="toc-http"></a>

HTTP 伺服器更易設定於與使用：

```go
package main

import ("net/http" ; "io")

func hello(res http.ResponseWriter, req *http.Request) {
    res.Header().Set(
        "Content-Type", 
        "text/html",
    )
    io.WriteString(
        res, 
        `<doctype html>
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        Hello World!
    </body>
</html>`,
    )
}
func main() {
    http.HandleFunc("/hello", hello)
    http.ListenAndServe(":9000", nil)
}
```

`HandleFunc` 透過呼叫提供的函式來處理一個 URL route (`/hello`)，我們也可以使用 `FileServer` 處理靜態檔案：

```
http.Handle(
    "/assets/", 
    http.StripPrefix(
        "/assets/", 
        http.FileServer(http.Dir("assets")),
    ),
)
```

#### RPC <a href="#toc-rpc" id="toc-rpc"></a>

`net/rpc`（remote procedure call）以及 `net/rpc/jsonrpc` packages 提供了一個簡單的方式可以揭露 methods 的存在，使得能夠透過網路執行呼叫（並非只是在呼叫它們的程式中執行）

```go
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

type Server struct {}
func (this *Server) Negate(i int64, reply *int64) error {
    *reply = -i
    return nil
}

func server() {
    rpc.Register(new(Server))
    ln, err := net.Listen("tcp", ":9999")
    if err != nil {
        fmt.Println(err)
        return
    }
    for {
        c, err := ln.Accept()
        if err != nil {
            continue
        }
        go rpc.ServeConn(c)
    }
}
func client() {
    c, err := rpc.Dial("tcp", "127.0.0.1:9999")
    if err != nil {
        fmt.Println(err)
        return
    }
    var result int64
    err = c.Call("Server.Negate", int64(999), &result)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Server.Negate(999) =", result)
    }
}
func main() {
    go server()
    go client()
    
    var input string
    fmt.Scanln(&input)
}
```

此程式與 TCP 範例類似，只是我們現在有建立一個物件（object）來承載我們想要揭露的每個 method 與在客戶端（client）呼叫的  `Negate` method，細節請參考 `net/rpc` 中的文件。


# 13.08-解析命令列參數

當我們在終端機執行指令時，可能需要傳遞參數給指令，如下列的 `go` 指令所示：

```
go run myfile.go
```

run 與 myfile.go 都是參數，我們也可以將旗標(flag)傳遞給參數：

```
go run -v myfile.go
```

flag package 可以讓我們的程式解析參數與旗標，這裡有一個範例程式，可以產生一個 0 \~ 6 的數字，我們透過將旗標（-max=100）傳遞給程式，以更改最大值（max value）：

```go
package main

import ("fmt";"flag";"math/rand")

func main() {
    // Define flags
    maxp := flag.Int("max", 6, "the max value")
    // Parse
    flag.Parse()
    // Generate a number between 0 and max
    fmt.Println(rand.Intn(*maxp))
}
```

不屬於旗標的那些額外參數可以使用 `flag.Args()` 進行解析，而這個函式會傳回一個 `[]string`。


# 13.09-Synchronization Primitives（同步處理原始物件）

在 Go 語言中，在並行（concurrency）與同步（synchronization）方面的處理上，建議使用使用第10章討論的 Goroutine 與 channel 方式。然而，Go 語言在 `sync` 與 `sync/atomic` package 中也有提供了較為傳統的多執行緒常式（multithreading routine）。

Mutexes（互斥鎖）

一個 mutex (mutal exclusive lock) 會將一段程式碼一次只鎖定給單個執行緒，可保護共用的資源在非原子式操作出現競速條件（race condition）。下列是一個 mutex 的範例：

```go
package main

import (
    "fmt"
    "sync"
    "time"
)
func main() {
    m := new(sync.Mutex)
    
    for i := 0; i < 10; i++ {
        go func(i int) {
            m.Lock()
            fmt.Println(i, "start")
            time.Sleep(time.Second)
            fmt.Println(i, "end")
            m.Unlock()
        }(i)
    }

    var input string
    fmt.Scanln(&input)
}
```

在 mutex (`m`) 處於上鎖狀態時，其他想要對此 mutex 進行上鎖的動作都會受到阻塞（block），直到此 mutex 已經被解鎖為止。在使用 `sync/atomic` package 中的 mutex 或同步處理原始物件（Synchronization Primitives ）時要非常謹慎。

以往要設計多執行緒程式是有難度的，因為很容易產生難以發現的錯誤。由於這些錯誤可能取決於非常特定的情況、極少發生，而且難以完成重現這些情況的條件。而 Go 語言最強大之處在於，它提供的並行功能比執行緒與互斥鎖更易於理解與使用。


# 14-下一步

我們現在已經具備設計多數 Go 程式的全部資訊了，只是有能力的程式設計師不該僅止於此。程式設計是需要許多知識的工藝，本章將提供一些建議，關於如何成為程式設計工藝大師。


# 14.01-閱讀大師作品

若要成為一個好的藝術者或作家，至少都要研究大師的作品。這點在程式設計也是一樣的。要成為技術高超的程式設計師，其中一個最佳的方式就是研究別人撰寫的程式原始碼。Go 語言非常適任，因為整個專案（project）的程式原始碼都可以自由免費取得。

例如，我們可能想要看看 `io/ioutil` 函式庫的程式碼，則可以參考：

[http://golang.org/src/pkg/io/ioutil/ioutil.go](https://web.archive.org/web/20200228062158/http://golang.org/src/pkg/io/ioutil/ioutil.go)

慢慢地謹慎閱讀程式碼，並試著了解每一行程式的意義並閱讀註解。例如，在 `ReadFile` method 中，有一行註解的內容如下所示：

```
// It's a good but not certain bet that FileInfo
// will tell us exactly how much to read, so
// let's try it but be prepared for the answer
// to be wrong.
```

這個 method 或許起初設計時比現在簡單許多。這是一個很棒的範例，可以知道程式如何在經過測試之後改進，以及為何對那些改變撰寫註解是很重要的。每個 packages 的程式碼都可以在此網址取得：

[http://golang.org/src/pkg/](https://web.archive.org/web/20200228062158/http://golang.org/src/pkg/)


# 14.02-動手實作

要磨練技術，其中一個最好的方法就是直接練習寫程式，有許多方法都可以辦到：你可以著手挑戰一些程式設計的問題，如 Euler 專案([http://projecteuler.net/](https://web.archive.org/web/20200228062203/http://projecteuler.net/)) 這類網站，或試著參與大型專案。或許可以實做一個網站伺服器或寫一個簡單的遊戲。


# 14.03-團隊合作

在現實生活中，多數的軟體專案都是以團隊方式進行的，因此學習如何進行團隊合作是很重要的。若可以找一個朋友（如同學之類）共同合作一個專案，學習如何對一個專案進行分工，讓你們可以同時作業。

另一個選項是參與一個開放原始碼專案，找一個第三方的函式庫，寫一些程式碼（或者是修正一個 bug），並將成果上傳給維護人員。Go 語言有一個正在壯大的社群，可以透過下列的 mailing list 加入：

([http://groups.google.com/group/golang-nuts](https://web.archive.org/web/20200228062209/http://groups.google.com/group/golang-nuts)).


# 15. 附錄-額外資源

### Additional Resources <a href="#h.p_id_32_l" id="h.p_id_32_l"></a>

* [影片簡介如何以 Go 建立 tries](http://youtu.be/9HqbKLcxQmo)


