## 10.6.1 方法是什么
在 Go 語言中,結構體就像是類的一種簡化形式,那么面向對象程序員可能會問:類的方法在哪里呢?在 Go 中有一個概念,它和方法有著同樣的名字,并且大體上意思相同:Go 方法是作用在接收者(receiver)上的一個函數,接收者是某種類型的變量。因此方法是一種特殊類型的函數。
接收者類型可以是(幾乎)任何類型,不僅僅是結構體類型:任何類型都可以有方法,甚至可以是函數類型,可以是 int、bool、string 或數組的別名類型。但是接收者不能是一個接口類型(參考 第 11 章),因為接口是一個抽象定義,但是方法卻是具體實現;如果這樣做會引發一個編譯錯誤:**invalid receiver type…**。
最后接收者不能是一個指針類型,但是它可以是任何其他允許類型的指針。
一個類型加上它的方法等價于面向對象中的一個類。一個重要的區別是:在 Go 中,類型的代碼和綁定在它上面的方法的代碼可以不放置在一起,它們可以存在在不同的源文件,唯一的要求是:它們必須是同一個包的。
類型 T(或 *T)上的所有方法的集合叫做類型 T(或 *T)的方法集。
因為方法是函數,所以同樣的,不允許方法重載,即對于一個類型只能有一個給定名稱的方法。但是如果基于接收者類型,是有重載的:具有同樣名字的方法可以在 2 個或多個不同的接收者類型上存在,比如在同一個包里這么做是允許的:
~~~
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
~~~
別名類型不能有它原始類型上已經定義過的方法。
定義方法的一般格式如下:
~~~
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
~~~
在方法名之前,`func`?關鍵字之后的括號中指定 receiver。
如果?`recv`?是 receiver 的實例,Method1 是它的方法名,那么方法調用遵循傳統的?`object.name`?選擇器符號:**recv.Method1()**。
如果?`recv`?一個指針,Go 會自動解引用。
如果方法不需要使用?`recv`?的值,可以用?**_**?替換它,比如:
~~~
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
~~~
`recv`?就像是面向對象語言中的?`this`?或?`self`,但是 Go 中并沒有這兩個關鍵字。隨個人喜好,你可以使用?`this`?或`self`?作為 receiver 的名字。下面是一個結構體上的簡單方法的例子:
示例 10.10 method .go:
~~~
package main
import "fmt"
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("The sum is: %d\n", two1.AddThem())
fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))
two2 := TwoInts{3, 4}
fmt.Printf("The sum is: %d\n", two2.AddThem())
}
func (tn *TwoInts) AddThem() int {
return tn.a + tn.b
}
func (tn *TwoInts) AddToParam(param int) int {
return tn.a + tn.b + param
}
~~~
輸出:
~~~
The sum is: 22
Add them to the param: 42
The sum is: 7
~~~
下面是非結構體類型上方法的例子:
示例 10.11 method2.go:
~~~
package main
import "fmt"
type IntVector []int
func (v IntVector) Sum() (s int) {
for _, x := range v {
s += x
}
return
}
func main() {
fmt.Println(IntVector{1, 2, 3}.Sum()) // 輸出是6
}
~~~
**練習 10.6**?employee_salary.go
定義結構體?`employee`,它有一個?`salary`?字段,給這個結構體定義一個方法?`giveRaise`?來按照指定的百分比增加薪水。
**練習 10.7**?iteration_list.go
下面這段代碼有什么錯?
~~~
package main
import "container/list"
func (p *list.List) Iter() {
// ...
}
func main() {
lst := new(list.List)
for _= range list.Iter() {
}
}
~~~
類型和作用在它上面定義的方法必須在同一個包里定義,這就是為什么不能在 int、float 或類似這些的類型上定義方法。試圖在 int 類型上定義方法會得到一個編譯錯誤:
~~~
cannot define new methods on non-local type int
~~~
比如想在?`time.Time`?上定義如下方法:
~~~
func (t time.Time) first3Chars() string {
return time.LocalTime().String()[0:3]
}
~~~
類型在在其他的,或是非本地的包里定義,在它上面定義方法都會得到和上面同樣的錯誤。
但是有一個繞點的方式:可以先定義該類型(比如:int 或 float)的別名類型,然后再為別名類型定義方法。或者像下面這樣將它作為匿名類型嵌入在一個新的結構體中。當然方法只在這個別名類型上有效。
示例 10.12 method_on_time.go:
~~~
package main
import (
"fmt"
"time"
)
type myTime struct {
time.Time //anonymous field
}
func (t myTime) first3Chars() string {
return t.Time.String()[0:3]
}
func main() {
m := myTime{time.Now()}
// 調用匿名Time上的String方法
fmt.Println("Full time now:", m.String())
// 調用myTime.first3Chars
fmt.Println("First 3 chars:", m.first3Chars())
}
/* Output:
Full time now: Mon Oct 24 15:34:54 Romance Daylight Time 2011
First 3 chars: Mon
*/
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1062-函數和方法的區別)10.6.2 函數和方法的區別
函數將變量作為參數:**Function1(recv)**
方法在變量上被調用:**recv.Method1()**
在接收者是指針時,方法可以改變接收者的值(或狀態),這點函數也可以做到(當參數作為指針傳遞,即通過引用調用時,函數也可以改變參數的狀態)。
**不要忘記 Method1 后邊的括號 (),否則會引發編譯器錯誤:`method recv.Method1 is not an expression, must be called`**
接收者必須有一個顯式的名字,這個名字必須在方法中被使用。
**receiver_type**?叫做?**(接收者)基本類型**,這個類型必須在和方法同樣的包中被聲明。
在 Go 中,(接收者)類型關聯的方法不寫在類型結構里面,就像類那樣;耦合更加寬松;類型和方法之間的關聯由接收者來建立。
**方法沒有和數據定義(結構體)混在一起:它們是正交的類型;表示(數據)和行為(方法)是獨立的。**
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1063-指針或值作為接收者)10.6.3 指針或值作為接收者
鑒于性能的原因,`recv`?最常見的是一個指向 receiver_type 的指針(因為我們不想要一個實例的拷貝,如果按值調用的話就會是這樣),特別是在 receiver 類型是結構體時,就更是如此了。
如果想要方法改變接收者的數據,就在接收者的指針類型上定義該方法。否則,就在普通的值類型上定義方法。
下面的例子?`pointer_value.go`?作了說明:`change()`接受一個指向 B 的指針,并改變它內部的成員;`write()`?接受通過拷貝接受 B 的值并只輸出B的內容。注意 Go 為我們做了探測工作,我們自己并沒有指出是是否在指針上調用方法,Go 替我們做了這些事情。b1 是值而 b2 是指針,方法都支持運行了。
示例 10.13 pointer_value.go:
~~~
package main
import (
"fmt"
)
type B struct {
thing int
}
func (b *B) change() { b.thing = 1 }
func (b B) write() string { return fmt.Sprint(b) }
func main() {
var b1 B // b1是值
b1.change()
fmt.Println(b1.write())
b2 := new(B) // b2是指針
b2.change()
fmt.Println(b2.write())
}
/* 輸出:
{1}
{1}
*/
~~~
試著在?`write()`?中改變接收者b的值:將會看到它可以正常編譯,但是開始的 b 沒有被改變。
我們知道方法不需要指針作為接收者,如下面的例子,我們只是需要?`Point3`?的值來做計算:
~~~
type Point3 struct { x, y, z float }
// A method on Point3
func (p Point3) Abs float {
return math.Sqrt(p.x*p.x + p.y*p.y + p.z*p.z)
}
~~~
這樣做稍微有點昂貴,因為?`Point3`?是作為值傳遞給方法的,因此傳遞的是它的拷貝,這在 Go 中合法的。也可以在指向這個類型的指針上調用此方法(會自動解引用)。
假設?`p3`?定義為一個指針:`p3 := &Point{ 3, 4, 5}`。
可以使用?`p3.Abs()`?來替代?`(*p3).Abs()`。
像例子 10.11(method1.go)中接收者類型是?`*TwoInts`?的方法?`AddThem()`,它能在類型?`TwoInts`?的值上被調用,這是自動間接發生的。
因此?`two2.AddThem`?可以替代?`(&two2).AddThem()`。
在值和指針上調用方法:
可以有連接到類型的方法,也可以有連接到類型指針的方法。
但是這沒關系:對于類型 T,如果在 *T 上存在方法?`Meth()`,并且?`t`?是這個類型的變量,那么?`t.Meth()`?會被自動轉換為?`(&t).Meth()`。
**指針方法和值方法都可以在指針或非指針上被調用**,如下面程序所示,類型?`List`?在值上有一個方法?`Len()`,在指針上有一個方法?`Append()`,但是可以看到兩個方法都可以在兩種類型的變量上被調用。
示例 10.14 methodset1.go:
~~~
package main
import (
"fmt"
)
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
func main() {
// 值
var lst List
lst.Append(1)
fmt.Printf("%v (len: %d)", lst, lst.Len()) // [1] (len: 1)
// 指針
plst := new(List)
plst.Append(2)
fmt.Printf("%v (len: %d)", plst, plst.Len()) // &[2] (len: 1)
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1064-方法和未導出字段)10.6.4 方法和未導出字段
考慮?`person2.go`?中的?`person`?包:類型?`Person`?被明確的導出了,但是它的字段沒有被導出。例如在?`use_person2.go`中?`p.firsetname`?就是錯誤的。該如何在另一個程序中修改或者只是讀取一個?`Person`?的名字呢?
這可以通過面向對象語言一個眾所周知的技術來完成:提供 getter 和 setter 方法。對于 setter 方法使用 Set 前綴,對于 getter 方法只適用成員名。
示例 10.15 person2.go:
~~~
package person
type Person struct {
firstName string
lastName string
}
func (p *Person) FirstName() string {
return p.firstName
}
func (p *Person) SetFirstName(newName string) {
p.firstName = newName
}
~~~
示例 10.16—use_person2.go:
~~~
package main
import (
"./person"
"fmt"
)
func main() {
p := new(person.Person)
// p.firstName undefined
// (cannot refer to unexported field or method firstName)
// p.firstName = "Eric"
p.SetFirstName("Eric")
fmt.Println(p.FirstName()) // Output: Eric
}
~~~
**并發訪問對象**
對象的字段(屬性)不應該由 2 個或 2 個以上的不同線程在同一時間去改變。如果在程序發生這種情況,為了安全并發訪問,可以使用包?`sync`(參考第 9.3 節)中的方法。在第 14.17 節中我們會通過 goroutines 和 channels 探索另一種方式。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1065-內嵌類型的方法和繼承)10.6.5 內嵌類型的方法和繼承
當一個匿名類型被內嵌在結構體中時,匿名類型的可見方法也同樣被內嵌,這在效果上等同于外層類型?**繼承**?了這些方法:**將父類型放在子類型中來實現亞型**。這個機制提供了一種簡單的方式來模擬經典面向對象語言中的子類和繼承相關的效果,也類似 Ruby 中的混入(mixin)。
下面是一個示例(可以在練習 10.8 中進一步學習):假定有一個?`Engine`?接口類型,一個?`Car`?結構體類型,它包含一個`Engine`?類型的匿名字段:
~~~
type Engine interface {
Start()
Stop()
}
type Car struct {
Engine
}
~~~
我們可以構建如下的代碼:
~~~
func (c *Car) GoToWorkIn() {
// get in car
c.Start()
// drive to work
c.Stop()
// get out of car
}
~~~
下面是?`method3.go`?的完整例子,它展示了內嵌結構體上的方法可以直接在外層類型的實例上調用:
~~~
package main
import (
"fmt"
"math"
)
type Point struct {
x, y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
name string
}
func main() {
n := &NamedPoint{Point{3, 4}, "Pythagoras"}
fmt.Println(n.Abs()) // 打印5
}
~~~
內嵌將一個已存在類型的字段和方法注入到了另一個類型里:匿名字段上的方法“晉升”成為了外層類型的方法。當然類型可以有只作用于本身實例而不作用于內嵌“父”類型上的方法,
可以覆寫方法(像字段一樣):和內嵌類型方法具有同樣名字的外層類型的方法會覆寫內嵌類型對應的方法。
在示例 10.18 method4.go 中添加:
~~~
func (n *NamedPoint) Abs() float64 {
return n.Point.Abs() * 100.
}
~~~
現在?`fmt.Println(n.Abs())`?會打印?`500`。
因為一個結構體可以嵌入多個匿名類型,所以實際上我們可以有一個簡單版本的多重繼承,就像:`type Child struct { Father; Mother}`。在第 10.6.7 節中會進一步討論這個問題。
結構體內嵌和自己在同一個包中的結構體時,可以彼此訪問對方所有的字段和方法。
**練習 10.8**?inheritance_car.go
創建一個上面?`Car`?和?`Engine`?可運行的例子,并且給?`Car`?類型一個?`wheelCount`?字段和一個?`numberOfWheels()`?方法。
創建一個?`Mercedes`?類型,它內嵌?`Car`,并新建?`Mercedes`?的一個實例,然后調用它的方法。
然后僅在?`Mercedes`?類型上創建方法?`sayHiToMerkel()`?并調用它。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1066-如何在類型中嵌入功能)10.6.6 如何在類型中嵌入功能
主要有兩種方法來實現在類型中嵌入功能:
A:聚合(或組合):包含一個所需功能類型的具名字段。 B:內嵌:內嵌(匿名地)所需功能類型,像前一節 10.6.5 所演示的那樣。
為了使這些概念具體化,假設有一個?`Customer`?類型,我們想讓它通過?`Log`?類型來包含日志功能,`Log`?類型只是簡單地包含一個累積的消息(當然它可以是復雜的)。如果想讓特定類型都具備日志功能,你可以實現一個這樣的?`Log`?類型,然后將它作為特定類型的一個字段,并提供?`Log()`,它返回這個日志的引用。
方式 A 可以通過如下方法實現(使用了第 10.7 節中的?`String()`?功能):
示例 10.19 embed_func1.go:
~~~
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
log *Log
}
func main() {
c := new(Customer)
c.Name = "Barak Obama"
c.log = new(Log)
c.log.msg = "1 - Yes we can!"
// shorter
c = &Customer{"Barak Obama", &Log{"1 - Yes we can!"}}
// fmt.Println(c) &{Barak Obama 1 - Yes we can!}
c.Log().Add("2 - After me the world will be a better place!")
//fmt.Println(c.log)
fmt.Println(c.Log())
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) Log() *Log {
return c.log
}
~~~
輸出:
~~~
1 - Yes we can!
2 - After me the world will be a better place!
~~~
相對的方式 B 可能會像這樣:
~~~
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
Log
}
func main() {
c := &Customer{"Barak Obama", Log{"1 - Yes we can!"}}
c.Add("2 - After me the world will be a better place!")
fmt.Println(c)
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) String() string {
return c.Name + "\nLog:" + fmt.Sprintln(c.Log)
}
~~~
輸出:
~~~
Barak Obama
Log:{1 - Yes we can!
2 - After me the world will be a better place!}
~~~
內嵌的類型不需要指針,`Customer`?也不需要?`Add`?方法,它使用?`Log`?的?`Add`?方法,`Customer`?有自己的?`String`?方法,并且在它里面調用了?`Log`?的?`String`?方法。
如果內嵌類型嵌入了其他類型,也是可以的,那些類型的方法可以直接在外層類型中使用。
因此一個好的策略是創建一些小的、可復用的類型作為一個工具箱,用于組成域類型。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1067-多重繼承)10.6.7 多重繼承
多重繼承指的是類型獲得多個父類型行為的能力,它在傳統的面向對象語言中通常是不被實現的(C++ 和 Python 例外)。因為在類繼承層次中,多重繼承會給編譯器引入額外的復雜度。但是在 Go 語言中,通過在類型中嵌入所有必要的父類型,可以很簡單的實現多重繼承。
作為一個例子,假設有一個類型?`CameraPhone`,通過它可以?`Call()`,也可以?`TakeAPicture()`,但是第一個方法屬于類型?`Phone`,第二個方法屬于類型?`Camera`。
只要嵌入這兩個類型就可以解個問題,如下所示:
~~~
package main
import (
"fmt"
)
type Camera struct{}
func (c *Camera) TakeAPicture() string {
return "Click"
}
type Phone struct{}
func (p *Phone) Call() string {
return "Ring Ring"
}
type CameraPhone struct {
Camera
Phone
}
func main() {
cp := new(CameraPhone)
fmt.Println("Our new CameraPhone exhibits multiple behaviors...")
fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
fmt.Println("It works like a Phone too: ", cp.Call())
}
~~~
輸出:
~~~
Our new CameraPhone exhibits multiple behaviors...
It exhibits behavior of a Camera: Click
It works like a Phone too: Ring Ring
~~~
**練習 10.9**?point_methods.go:
從?`point.go`?開始(第 10.1 節的聯系):使用方法來實現?`Abs()`?和?`Scale()`函數,`Point`?作為方法的接收者類型。也為?`Point3`?和?`Polar`?實現?`Abs()`?方法。完成了?`point.go`?中同樣的事情,只是這次通過方法。
**練習 10.10**?inherit_methods.go:
定義一個結構體類型?`Base`,它包含一個字段?`id`,方法?`Id()`?返回?`id`,方法?`SetId()`?修改?`id`。結構體類型?`Person`包含?`Base`,及?`FirstName`?和?`LastName`?字段。結構體類型?`Employee`?包含一個?`Person`?和?`salary`?字段。
創建一個?`employee`?實例,然后顯示它的?`id`。
**練習 10.11**?magic.go:
首先預測一下下面程序的結果,然后動手實驗下:
~~~
package main
import (
"fmt"
)
type Base struct{}
func (Base) Magic() {
fmt.Println("base magic")
}
func (self Base) MoreMagic() {
self.Magic()
self.Magic()
}
type Voodoo struct {
Base
}
func (Voodoo) Magic() {
fmt.Println("voodoo magic")
}
func main() {
v := new(Voodoo)
v.Magic()
v.MoreMagic()
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1068-通用方法和方法命名)10.6.8 通用方法和方法命名
在編程中一些基本操作會一遍又一遍的出現,比如打開(Open)、關閉(Close)、讀(Read)、寫(Write)、排序(Sort)等等,并且它們都有一個大致的意思:打開(Open)可以作用于一個文件、一個網絡連接、一個數據庫連接等等。具體的實現可能千差萬別,但是基本的概念是一致的。在 Go 語言中,通過使用接口(參考 第 11 章),標準庫廣泛的應用了這些規則,在標準庫中這些通用方法都有一致的名字,比如?`Open()`、`Read()`、`Write()`等。想寫規范的 Go 程序,就應該遵守這些約定,給方法合適的名字和簽名,就像那些通用方法那樣。這樣做會使 Go 開發的軟件更加具有一致性和可讀性。比如:如果需要一個 convert-to-string 方法,應該命名為?`String()`,而不是?`ToString()`(參考第 10.7 節)。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.6.md#1069-和其他面向對象語言比較-go-的類型和方法)10.6.9 和其他面向對象語言比較 Go 的類型和方法
在如 C++、Java、C# 和 Ruby 這樣的面向對象語言中,方法在類的上下文中被定義和繼承:在一個對象上調用方法時,運行時會檢測類以及它的超類中是否有此方法的定義,如果沒有會導致異常發生。
在 Go 語言中,這樣的繼承層次是完全沒必要的:如果方法在此類型定義了,就可以調用它,和其他類型上是否存在這個方法沒有關系。在這個意義上,Go 具有更大的靈活性。
下面的模式就很好的說明了這個問題:
[](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/images/10.6.9_fig10.4.jpg?raw=true)
Go 不需要一個顯式的類定義,如同 Java、C++、C# 等那樣,相反地,“類”是通過提供一組作用于一個共同類型的方法集來隱式定義的。類型可以是結構體或者任何用戶自定義類型。
比如:我們想定義自己的?`Integer`?類型,并添加一些類似轉換成字符串的方法,在 Go 中可以如下定義:
~~~
type Integer int
func (i *Integer) String() string {
return strconv.Itoa(i)
}
~~~
在 Java 或 C# 中,這個方法需要和類?`Integer`?的定義放在一起,在 Ruby 中可以直接在基本類型 int 上定義這個方法。
**總結**
在 Go 中,類型就是類(數據和關聯的方法)。Go 不知道類似面向對象語言的類繼承的概念。繼承有兩個好處:代碼復用和多態。
在 Go 中,代碼復用通過組合和委托實現,多態通過接口的使用來實現:有時這也叫?**組件編程**。
許多開發者說相比于類繼承,Go 的接口提供了更強大、卻更簡單的多態行為。
**備注**
如果真的需要更多面向對象的能力,看一下?[`goop`](https://github.com/losalamos/goop)?包(Go Object-Oriented Programming),它由 Scott Pakin 編寫: 它給 Go 提供了 JavaScript 風格的對象(基于原型的對象),并且支持多重繼承和類型獨立分派,通過它可以實現你喜歡的其他編程語言里的一些結構。
**問題 10.1**
我們在某個類型的變量上使用點號調用一個方法:`variable.method()`,在使用 Go 以前,在哪兒碰到過面向對象的點號?
**問題 10.2**
a)假設定義:?`type Integer int`,完成?`get()`?方法的方法體:?`func (p Integer) get() int { ... }`。
b)定義:?`func f(i int) {}; var v Integer`?,如何就 v 作為參數調用f?
c)假設?`Integer`?定義為?`type Integer struct {n int}`,完成?`get()`?方法的方法體:`func (p Integer) get() int { ... }`。
d)對于新定義的?`Integer`,和 b)中同樣的問題。
- 前言
- 第一部分:學習 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 出于性能考慮的最佳實踐和建議
- 附錄