示例 4.1?[hello_world.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_4/hello_world.go)
~~~
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#421-包的概念導入與可見性)4.2.1 包的概念、導入與可見性
包是結構化代碼的一種方式:每個程序都由包(通常簡稱為 pkg)的概念組成,可以使用自身的包或者從其它包中導入內容。
如同其它一些編程語言中的類庫或命名空間的概念,每個 Go 文件都屬于且僅屬于一個包。一個包可以由許多以?`.go`?為擴展名的源文件組成,因此文件名和包名一般來說都是不相同的。
你必須在源文件中非注釋的第一行指明這個文件屬于哪個包,如:`package main`。`package main`表示一個可獨立執行的程序,每個 Go 應用程序都包含一個名為?`main`?的包。
一個應用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代碼都寫在一個巨大的文件里:你可以用一些較小的文件,并且在每個文件非注釋的第一行都使用?`package main`?來指明這些文件都屬于 main 包。如果你打算編譯包名不是為 main 的源文件,如?`pack1`,編譯后產生的對象文件將會是?`pack1.a`?而不是可執行程序。另外要注意的是,所有的包名都應該使用小寫字母。
**標準庫**
在 Go 的安裝文件里包含了一些可以直接使用的包,即標準庫。在 Windows 下,標準庫的位置在 Go 根目錄下的子目錄`pkg\windows_386`?中;在 Linux 下,標準庫在 Go 根目錄下的子目錄?`pkg\linux_amd64`?中(如果是安裝的是 32 位,則在`linux_386`?目錄中)。一般情況下,標準包會存放在?`$GOROOT/pkg/$GOOS_$GOARCH/`?目錄下。
Go 的標準庫包含了大量的包(如:fmt 和 os),但是你也可以創建自己的包(第 8 章)。
如果想要構建一個程序,則包和包內的文件都必須以正確的順序進行編譯。包的依賴關系決定了其構建順序。
屬于同一個包的源文件必須全部被一起編譯,一個包既是編譯時的一個單元,因此根據慣例,每個目錄都只包含一個包。
**如果對一個包進行更改或重新編譯,所有引用了這個包的客戶端程序都必須全部重新編譯。**
Go 中的包模型采用了顯式依賴關系的機制來達到快速編譯的目的,編譯器會從后綴名為?`.o`?的對象文件(需要且只需要這個文件)中提取傳遞依賴類型的信息。
如果?`A.go`?依賴?`B.go`,而?`B.go`?又依賴?`C.go`:
* 編譯?`C.go`,?`B.go`, 然后是?`A.go`.
* 為了編譯?`A.go`, 編譯器讀取的是?`B.o`?而不是?`C.o`.
這種機制對于編譯大型的項目時可以顯著地提升編譯速度。
**每一段代碼只會被編譯一次**
一個 Go 程序是通過?`import`?關鍵字將一組包鏈接在一起。
`import "fmt"`?告訴 Go 編譯器這個程序需要使用?`fmt`?包(的函數,或其他元素),`fmt`?包實現了格式化 IO(輸入/輸出)的函數。包名被封閉在半角雙引號?`""`?中。如果你打算從已編譯的包中導入并加載公開聲明的方法,不需要插入已編譯包的源代碼。
如果需要多個包,它們可以被分別導入:
~~~
import "fmt"
import "os"
~~~
或:
~~~
import "fmt"; import "os"
~~~
但是還有更短且更優雅的方法(被稱為因式分解關鍵字,該方法同樣適用于 const、var 和 type 的聲明或定義):
~~~
import (
"fmt"
"os"
)
~~~
它甚至還可以更短的形式,但使用 gofmt 后將會被強制換行:
~~~
import ("fmt"; "os")
~~~
當你導入多個包時,導入的順序會按照字母排序。
如果包名不是以?`.`?或?`/`?開頭,如?`"fmt"`?或者?`"container/list"`,則 Go 會在全局文件進行查找;如果包名以?`./`?開頭,則 Go 會在相對目錄中查找;如果包名以?`/`?開頭(在 Windows 下也可以這樣使用),則會在系統的絕對路徑中查找。
導入包即等同于包含了這個包的所有的代碼對象。
除了符號?`_`,包中所有代碼對象的標識符必須是唯一的,以避免名稱沖突。但是相同的標識符可以在不同的包中使用,因為可以使用包名來區分它們。
包通過下面這個被編譯器強制執行的規則來決定是否將自身的代碼對象暴露給外部文件:
**可見性規則**
當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那么使用這種形式的標識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導入這個包),這被稱為導出(像面向對象語言中的 public);標識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內部是可見并且可用的(像面向對象語言中的 private )。
(大寫字母可以使用任何 Unicode 編碼的字符,比如希臘文,不僅僅是 ASCII 碼中的大寫字母)。
因此,在導入一個外部包后,能夠且只能夠訪問該包中導出的對象。
假設在包 pack1 中我們有一個變量或函數叫做 Thing(以 T 開頭,所以它能夠被導出),那么在當前包中導入 pack1 包,Thing 就可以像面向對象語言那樣使用點標記來調用:`pack1.Thing`(pack1 在這里是不可以省略的)。
因此包也可以作為命名空間使用,幫助避免命名沖突(名稱沖突):兩個包中的同名變量的區別在于他們的包名,例如`pack1.Thing`?和?`pack2.Thing`。
你可以通過使用包的別名來解決包名之間的名稱沖突,或者說根據你的個人喜好對包名進行重新設置,如:`import fm "fmt"`。下面的代碼展示了如何使用包的別名:
示例 4.2?[alias.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_4/alias.go)
~~~
package main
import fm "fmt" // alias3
func main() {
fm.Println("hello, world")
}
~~~
**注意事項**
如果你導入了一個包卻沒有使用它,則會在構建程序時引發錯誤,如?`imported and not used: os`,這正是遵循了 Go 的格言:“沒有不必要的代碼!“。
**包的分級聲明和初始化**
你可以在使用?`import`?導入包之后定義或聲明 0 個或多個常量(const)、變量(var)和類型(type),這些對象的作用域都是全局的(在本包范圍內),所以可以被本包中所有的函數調用(如?[gotemplate.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_4/gotemplate.go)?源文件中的 c 和 v),然后聲明一個或多個函數(func)。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#422-函數)4.2.2 函數
這是定義一個函數最簡單的格式:
~~~
func functionName()
~~~
你可以在括號?`()`?中寫入 0 個或多個函數的參數(使用逗號?`,`?分隔),每個參數的名稱后面必須緊跟著該參數的類型。
main 函數是每一個可執行程序所必須包含的,一般來說都是在啟動后第一個執行的函數(如果有 init() 函數則會先執行該函數)。如果你的 main 包的源代碼沒有包含 main 函數,則會引發構建錯誤?`undefined: main.main`。main 函數即沒有參數,也沒有返回類型(與 C 家族中的其它語言恰好相反)。如果你不小心為 main 函數添加了參數或者返回類型,將會引發構建錯誤:
~~~
func main must have no arguments and no return values results.
~~~
在程序開始執行并完成初始化后,第一個調用(程序的入口點)的函數是?`main.main()`(如:C 語言),該函數一旦返回就表示程序已成功執行并立即退出。
函數里的代碼(函數體)使用大括號?`{}`?括起來。
左大括號?`{`?必須與方法的聲明放在同一行,這是編譯器的強制規定,否則你在使用 gofmt 時就會出現錯誤提示:
~~~
`build-error: syntax error: unexpected semicolon or newline before {`
~~~
(這是因為編譯器會產生?`func main() ;`?這樣的結果,很明顯這錯誤的)
**Go 語言雖然看起來不使用分號作為語句的結束,但實際上這一過程是由編譯器自動完成,因此才會引發像上面這樣的錯誤**
右大括號?`}`?需要被放在緊接著函數體的下一行。如果你的函數非常簡短,你也可以將它們放在同一行:
~~~
func Sum(a, b int) int { return a + b }
~~~
對于大括號?`{}`?的使用規則在任何時候都是相同的(如:if 語句等)。
因此符合規范的函數一般寫成如下的形式:
~~~
func functionName(parameter_list) (return_value_list) {
…
}
~~~
其中:
* parameter_list 的形式為 (param1 type1, param2 type2, …)
* return_value_list 的形式為 (ret1 type1, ret2 type2, …)
只有當某個函數需要被外部包調用的時候才使用大寫字母開頭,并遵循 Pascal 命名法;否則就遵循駱駝命名法,即第一個單詞的首字母小寫,其余單詞的首字母大寫。
下面這一行調用了?`fmt`?包中的?`Println`?函數,可以將字符串輸出到控制臺,并在最后自動增加換行字符?`\n`:
~~~
fmt.Println("hello, world")
~~~
使用?`fmt.Print("hello, world\n")`?可以得到相同的結果。
`Print`?和?`Println`?這兩個函數也支持使用變量,如:`fmt.Println(arr)`。如果沒有特別指定,它們會以默認的打印格式將變量?`arr`?輸出到控制臺。
單純地打印一個字符串或變量甚至可以使用預定義的方法來實現,如:`print`、`println:print("ABC")`、`println("ABC")`、`println(i)`(帶一個變量 i)。
這些函數只可以用于調試階段,在部署程序的時候務必將它們替換成?`fmt`?中的相關函數。
當被調用函數的代碼執行到結束符?`}`?或返回語句時就會返回,然后程序繼續執行調用該函數之后的代碼。
程序正常退出的代碼為 0 即?`Program exited with code 0`;如果程序因為異常而被終止,則會返回非零值,如:1。這個數值可以用來測試是否成功執行一個程序。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#423-注釋)4.2.3 注釋
示例 4.2?[hello_world2.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_4/hello_world2.go)
~~~
package main
import "fmt" // Package implementing formatted I/O.
func main() {
fmt.Printf("Καλημ?ρα κ?σμε; or こんにちは 世界\n")
}
~~~
上面這個例子通過打印?`Καλημ?ρα κ?σμε; or こんにちは 世界`?展示了如何在 Go 中使用國際化字符,以及如何使用注釋。
注釋不會被編譯,但可以通過 godoc 來使用(第 3.6 節)。
單行注釋是最常見的注釋形式,你可以在任何地方使用以?`//`?開頭的單行注釋。多行注釋也叫塊注釋,均已以?`/*`?開頭,并以?`*/`?結尾,且不可以嵌套使用,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段。
每一個包應該有相關注釋,在 package 語句之前的塊注釋將被默認認為是這個包的文檔說明,其中應該提供一些相關信息并對整體功能做簡要的介紹。一個包可以分散在多個文件中,但是只需要在其中一個進行注釋說明即可。當開發人員需要了解包的一些情況時,自然會用 godoc 來顯示包的文檔說明,在首行的簡要注釋之后可以用成段的注釋來進行更詳細的說明,而不必擁擠在一起。另外,在多段注釋之間應以空行分隔加以區分。
示例:
~~~
// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman
~~~
幾乎所有全局作用域的類型、常量、變量、函數和被導出的對象都應該有一個合理的注釋。如果這種注釋(稱為文檔注釋)出現在函數前面,例如函數 Abcd,則要以?`"Abcd..."`?作為開頭。
示例:
~~~
// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
...
}
~~~
godoc 工具(第 3.6 節)會收集這些注釋并產生一個技術文檔。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#424-類型)4.2.4 類型
可以包含數據的變量(或常量)可以使用不同的數據類型或類型來保存數據。使用 var 聲明的變量的值會自動初始化為該類型的零值。類型定義了某個變量的值的集合與可對其進行操作的集合。
類型可以是基本類型,如:int、float、bool、string;結構化的(復合的),如:struct、array、slice、map、channel;只描述類型的行為的,如:interface。
結構化的類型沒有真正的值,它使用 nil 作為默認值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是NULL或 0)。值得注意的是,Go 語言中不存在類型繼承。
函數也可以是一個確定的類型,就是以函數作為返回類型。這種類型的聲明要寫在函數名和可選的參數列表之后,例如:
~~~
func FunctionName (a typea, b typeb) typeFunc
~~~
你可以在函數體中的某處返回使用類型為 typeFunc 的變量 var:
~~~
return var
~~~
一個函數可以擁有多返回值,返回類型之間需要使用逗號分割,并使用小括號?`()`?將它們括起來,如:
~~~
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
~~~
示例: 函數 Atoi (第 4.7 節):`func Atoi(s string) (i int, err error)`
返回的形式:
~~~
return var1, var2
~~~
這種多返回值一般用于判斷某個函數是否執行成功(true/false)或與其它返回值一同返回錯誤消息(詳見之后的并行賦值)。
使用 type 關鍵字可以定義你自己的類型,你可能想要定義一個結構體(第 10 章),但是也可以定義一個已經存在的類型的別名,如:
~~~
type IZ int
~~~
**這里并不是真正意義上的別名,因為使用這種方法定義之后的類型可以擁有更多的特性,且在類型轉換時必須顯式轉換。**
然后我們可以使用下面的方式聲明變量:
~~~
var a IZ = 5
~~~
這里我們可以看到 int 是變量 a 的底層類型,這也使得它們之間存在相互轉換的可能(第 4.2.6 節)。
如果你有多個類型需要定義,可以使用因式分解關鍵字的方式,例如:
~~~
type (
IZ int
FZ float64
STR string
)
~~~
每個值都必須在經過編譯后屬于某個類型(編譯器必須能夠推斷出所有值的類型),因為 Go 語言是一種靜態類型語言。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#425-go-程序的一般結構)4.2.5 Go 程序的一般結構
下面的程序可以被順利編譯但什么都做不了,不過這很好地展示了一個 Go 程序的首選結構。這種結構并沒有被強制要求,編譯器也不關心 main 函數在前還是變量的聲明在前,但使用統一的結構能夠在從上至下閱讀 Go 代碼時有更好的體驗。
所有的結構將在這一章或接下來的章節中進一步地解釋說明,但總體思路如下:
* 在完成包的 import 之后,開始對常量、變量和類型的定義或聲明。
* 如果存在 init 函數的話,則對該函數進行定義(這是一個特殊的函數,每個含有該函數的包都會首先執行這個函數)。
* 如果當前包是 main 包,則定義 main 函數。
* 然后定義其余的函數,首先是類型的方法,接著是按照 main 函數中先后調用的順序來定義相關函數,如果有很多函數,則可以按照字母順序來進行排序。
示例 4.4?[gotemplate.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/examples/chapter_4/gotemplate.go)
~~~
package main
import (
"fmt"
)
const c = "C"
var v int = 5
type T struct{}
func init() { // initialization of package
}
func main() {
var a int
Func1()
// ...
fmt.Println(a)
}
func (t T) Method1() {
//...
}
func Func1() { // exported function Func1
//...
}
~~~
Go 程序的執行(程序啟動)順序如下:
1. 按順序導入所有被 main 包引用的其它包,然后在每個包中執行如下流程:
2. 如果該包又導入了其它的包,則從第一步開始遞歸執行,但是每個包只會被導入一次。
3. 然后以相反的順序在每個包中初始化常量和變量,如果該包含有 init 函數的話,則調用該函數。
4. 在完成這一切之后,main 也執行同樣的過程,最后調用 main 函數開始執行程序。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#426-類型轉換)4.2.6 類型轉換
在必要以及可行的情況下,一個類型的值可以被轉換成另一種類型的值。由于 Go 語言不存在隱式類型轉換,因此所有的轉換都必須顯式說明,就像調用一個函數一樣(類型在這里的作用可以看作是一種函數):
~~~
valueOfTypeB = typeB(valueOfTypeA)
~~~
**類型 B 的值 = 類型 B(類型 A 的值)**
示例:
~~~
a := 5.0
b := int(a)
~~~
但這只能在定義正確的情況下轉換成功,例如從一個取值范圍較小的類型轉換到一個取值范圍較大的類型(例如將 int16 轉換為 int32)。當從一個取值范圍較大的轉換到取值范圍較小的類型時(例如將 int32 轉換為 int16 或將 float32 轉換為 int),會發生精度丟失(截斷)的情況。當編譯器捕捉到非法的類型轉換時會引發編譯時錯誤,否則將引發運行時錯誤。
具有相同底層類型的變量之間可以相互轉換:
~~~
var a IZ = 5
c := int(a)
d := IZ(c)
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md#427-go-命名規范)4.2.7 Go 命名規范
干凈、可讀的代碼和簡潔性是 Go 追求的主要目標。通過 gofmt 來強制實現統一的代碼風格。Go 語言中對象的命名也應該是簡潔且有意義的。像 Java 和 Python 中那樣使用混合著大小寫和下劃線的冗長的名稱會嚴重降低代碼的可讀性。名稱不需要指出自己所屬的包,因為在調用的時候會使用包名作為限定符。返回某個對象的函數或方法的名稱一般都是使用名詞,沒有?`Get...`?之類的字符,如果是用于修改某個對象,則使用?`SetName`。有必須要的話可以使用大小寫混合的方式,如 MixedCaps 或 mixedCaps,而不是使用下劃線來分割多個名稱。
- 前言
- 第一部分:學習 Go 語言
- 第1章:Go 語言的起源,發展與普及
- 1.1 起源與發展
- 1.2 語言的主要特性與發展的環境和影響因素
- 第2章:安裝與運行環境
- 2.1 平臺與架構
- 2.2 Go 環境變量
- 2.3 在 Linux 上安裝 Go
- 2.4 在 Mac OS X 上安裝 Go
- 2.5 在 Windows 上安裝 Go
- 2.6 安裝目錄清單
- 2.7 Go 運行時(runtime)
- 2.8 Go 解釋器
- 第3章:編輯器、集成開發環境與其它工具
- 3.1 Go 開發環境的基本要求
- 3.2 編輯器和集成開發環境
- 3.3 調試器
- 3.4 構建并運行 Go 程序
- 3.5 格式化代碼
- 3.6 生成代碼文檔
- 3.7 其它工具
- 3.8 Go 性能說明
- 3.9 與其它語言進行交互
- 第二部分:語言的核心結構與技術
- 第4章:基本結構和基本數據類型
- 4.1 文件名、關鍵字與標識符
- 4.2 Go 程序的基本結構和要素
- 4.3 常量
- 4.4 變量
- 4.5 基本類型和運算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 時間和日期
- 4.9 指針
- 第5章:控制結構
- 5.1 if-else 結構
- 5.2 測試多返回值函數的錯誤
- 5.3 switch 結構
- 5.4 for 結構
- 5.5 Break 與 continue
- 5.6 標簽與 goto
- 第6章:函數(function)
- 6.1 介紹
- 6.2 函數參數與返回值
- 6.3 傳遞變長參數
- 6.4 defer 和追蹤
- 6.5 內置函數
- 6.6 遞歸函數
- 6.7 將函數作為參數
- 6.8 閉包
- 6.9 應用閉包:將函數作為返回值
- 6.10 使用閉包調試
- 6.11 計算函數執行時間
- 6.12 通過內存緩存來提升性能
- 第7章:數組與切片
- 7.1 聲明和初始化
- 7.2 切片
- 7.3 For-range 結構
- 7.4 切片重組(reslice)
- 7.5 切片的復制與追加
- 7.6 字符串、數組和切片的應用
- 第8章:Map
- 8.1 聲明、初始化和 make
- 8.2 測試鍵值對是否存在及刪除元素
- 8.3 for-range 的配套用法
- 8.4 map 類型的切片
- 8.5 map 的排序
- 8.6 將 map 的鍵值對調
- 第9章:包(package)
- 9.1 標準庫概述
- 9.2 regexp 包
- 9.3 鎖和 sync 包
- 9.4 精密計算和 big 包
- 9.5 自定義包和可見性
- 9.6 為自定義包使用 godoc
- 9.7 使用 go install 安裝自定義包
- 9.8 自定義包的目錄結構、go install 和 go test
- 9.9 通過 Git 打包和安裝
- 9.10 Go 的外部包和項目
- 9.11 在 Go 程序中使用外部庫
- 第10章:結構(struct)與方法(method)
- 10.1 結構體定義
- 10.2 使用工廠方法創建結構體實例
- 10.3 使用自定義包中的結構體
- 10.4 帶標簽的結構體
- 10.5 匿名字段和內嵌結構體
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)與反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 類型斷言:如何檢測和轉換接口變量的類型
- 11.4 類型判斷:type-switch
- 11.5 測試一個值是否實現了某個接口
- 11.6 使用方法集與接口
- 11.7 第一個例子:使用 Sorter 接口排序
- 11.8 第二個例子:讀和寫
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高級編程
- 第12章 讀寫數據
- 12.1 讀取用戶的輸入
- 12.2 文件讀寫
- 12.3 文件拷貝
- 12.4 從命令行讀取參數
- 12.5 用buffer讀取文件
- 12.6 用切片讀寫文件
- 12.7 用 defer 關閉文件
- 12.8 使用接口的實際例子:fmt.Fprintf
- 12.9 Json 數據格式
- 12.10 XML 數據格式
- 12.11 用 Gob 傳輸數據
- 12.12 Go 中的密碼學
- 第13章 錯誤處理與測試
- 13.1 錯誤處理
- 13.2 運行時異常和 panic
- 13.3 從 panic 中恢復(Recover)
- 13.4 自定義包中的錯誤處理和 panicking
- 13.5 一種用閉包處理錯誤的模式
- 13.6 啟動外部命令和程序
- 13.7 Go 中的單元測試和基準測試
- 13.8 測試的具體例子
- 13.9 用(測試數據)表驅動測試
- 13.10 性能調試:分析并優化 Go 程序
- 第14章:協程(goroutine)與通道(channel)
- 14.1 并發、并行和協程
- 14.2 使用通道進行協程間通信
- 14.3 協程同步:關閉通道-對阻塞的通道進行測試
- 14.4 使用 select 切換協程
- 14.5 通道,超時和計時器(Ticker)
- 14.6 協程和恢復(recover)
- 第15章:網絡、模版與網頁應用
- 15.1 tcp服務器
- 15.2 一個簡單的web服務器
- 15.3 訪問并讀取頁面數據
- 15.4 寫一個簡單的網頁應用
- 第四部分:實際應用
- 第16章:常見的陷阱與錯誤
- 16.1 誤用短聲明導致變量覆蓋
- 16.2 誤用字符串
- 16.3 發生錯誤時使用defer關閉一個文件
- 16.5 不需要將一個指向切片的指針傳遞給函數
- 16.6 使用指針指向接口類型
- 16.7 使用值類型時誤用指針
- 16.8 誤用協程和通道
- 16.9 閉包和協程的使用
- 16.10 糟糕的錯誤處理
- 第17章:模式
- 17.1 關于逗號ok模式
- 第18章:出于性能考慮的實用代碼片段
- 18.1 字符串
- 18.2 數組和切片
- 18.3 映射
- 18.4 結構體
- 18.5 接口
- 18.6 函數
- 18.7 文件
- 18.8 協程(goroutine)與通道(channel)
- 18.9 網絡和網頁應用
- 18.10 其他
- 18.11 出于性能考慮的最佳實踐和建議
- 附錄