函數(shù)是構(gòu)建 Go 程序的基礎(chǔ)部件;所遇有趣的事情都是在它其中發(fā)生的。函數(shù)的定義看起來像這樣:
http://wiki.jikexueyuan.com/project/learn-go-language/images/59.png" alt="pic" />
0 .關(guān)鍵字 func 用于定義一個(gè)函數(shù);
這里有兩個(gè)例子,左邊的函數(shù)沒有返回值,右邊的只是簡單的將輸入返回。
func subroutine(in i n t ) { return }
func identity(in i n t ) i n t { return in }
可以隨意安排函數(shù)定義的順序,編譯器會(huì)在執(zhí)行前掃描每個(gè)文件。所以函數(shù)原型在 Go 中都是過期的舊物。Go 不允許函數(shù)嵌套,然而你可以利用匿名函數(shù)實(shí)現(xiàn)它,參閱本章第31 頁的“函數(shù)作為值”。
遞歸函數(shù)跟其他語言是一樣的:
http://wiki.jikexueyuan.com/project/learn-go-language/images/60.png" alt="pic" />
在 Go 中,定義在函數(shù)外的變量是全局的,那些定義在函數(shù)內(nèi)部的變量,對(duì)于函數(shù)來說 是局部的。如果命名覆蓋——一個(gè)局部變量與一個(gè)全局變量有相同的名字——在函數(shù) 執(zhí)行的時(shí)候,局部變量將覆蓋全局量。
http://wiki.jikexueyuan.com/project/learn-go-language/images/61.png" alt="pic" />
在 2.3 中定義了函數(shù) q() 的局部變量 a。局部變量 a 僅在 q() 中可見。這也就是為什么代碼會(huì)打?。?56。在 2.4 中沒有定義局部變量,只有全局變量 a。這將使得對(duì) a 的賦值全局可見。這段代碼將會(huì)打?。?55。
在下面的例子中,我們?cè)?f() 中調(diào)用 g():
Listing 2.5. 當(dāng)函數(shù)調(diào)用函數(shù)時(shí)的作用域
http://wiki.jikexueyuan.com/project/learn-go-language/images/62.png" alt="pic" />
輸出內(nèi)容將是:565。局部變量僅僅在執(zhí)行定義它的函數(shù)時(shí)有效。
Go 一個(gè)非常特別的特性(對(duì)于編譯語言而言)是函數(shù)和方法可以返回多個(gè)值(Python和 Perl 同樣也可以)。這可以用于改進(jìn)一大堆在 C 程序中糟糕的慣例用法:修改參數(shù)的方式,返回一個(gè)錯(cuò)誤(例如遇到 EOF 則返回 -1)。在 Go 中,Write 返回一個(gè)計(jì)數(shù)值和一個(gè)錯(cuò)誤:“是的,你寫入了一些字節(jié),但是由于設(shè)備異常,并不是全部都寫入了?!薄s 包中的 *File.Write 是這樣聲明的:
func (file *File) Write(b []byte) (n int , err e r r o r )
如同文檔所述,它返回寫入的字節(jié)數(shù),并且當(dāng) n != len(b) 時(shí),返回非 nil 的 error。這是 Go 中常見的方式。
元組沒有作為原生類型出現(xiàn),所以多返回值可能是最佳的選擇。你可以精確的返回希望的值,而無須重載域空間到特定的錯(cuò)誤信號(hào)上。
Go 函數(shù)的返回值或者結(jié)果參數(shù)可以指定一個(gè)名字,并且像原始的變量那樣使用,就像輸入?yún)?shù)那樣。如果對(duì)其命名,在函數(shù)開始時(shí),它們會(huì)用其類型的零值初始化。如果函數(shù)在不加參數(shù)的情況下執(zhí)行了 return 語句,結(jié)果參數(shù)會(huì)返回。用這個(gè)特性,允許(再一次的)用較少的代碼做更多的事。
名字不是強(qiáng)制的,但是它們可以使得代碼更加健壯和清晰:這是文檔。例如命名 int 類型的 nextPos 返回值,就能說明哪個(gè)代表哪個(gè)。
func nextInt(b []byte, pos i n t ) (value, nextPos i n t ) { /* ... */ }
由于命名結(jié)果會(huì)被初始化并關(guān)聯(lián)于無修飾的 return,它們可以非常簡單并且清晰。這里有一段 io.ReadFull 的代碼,很好的運(yùn)用了它:
http://wiki.jikexueyuan.com/project/learn-go-language/images/63.png" alt="pic" />
假設(shè)有一個(gè)函數(shù),打開文件并且對(duì)其進(jìn)行若干讀寫。在這樣的函數(shù)中,經(jīng)常有提前返回的地方。如果你這樣做,就需要關(guān)閉正在工作的文件描述符。這經(jīng)常導(dǎo)致產(chǎn)生下面的代碼:
http://wiki.jikexueyuan.com/project/learn-go-language/images/64.png" alt="pic" />
在這里有許多重復(fù)的代碼。為了解決這些,Go 有了 defer 語句。在 defer 后指定的函數(shù)會(huì)在函數(shù)退出前調(diào)用。
上面的代碼可以被改寫為下面這樣。將 Close 對(duì)應(yīng)的放置于 Open 后,能夠使函數(shù)更加可讀、健壯。
http://wiki.jikexueyuan.com/project/learn-go-language/images/65.png" alt="pic" />
可以將多個(gè)函數(shù)放入“延遲列表”中,這個(gè)例子來自 [8]:
http://wiki.jikexueyuan.com/project/learn-go-language/images/66.png" alt="pic" />
延遲的函數(shù)是按照后進(jìn)先出(LIFO)的順序執(zhí)行,所以上面的代碼打?。? 3 2 1 0。利用 defer 甚至可以修改返回值,假設(shè)正在使用命名結(jié)果參數(shù)和函數(shù)符號(hào),例如:
http://wiki.jikexueyuan.com/project/learn-go-language/images/67.png" alt="pic" />
或者這個(gè)例子,更加容易了解為什么,以及在哪里需要括號(hào):
http://wiki.jikexueyuan.com/project/learn-go-language/images/68.png" alt="pic" />
在這個(gè)(匿名)函數(shù)中,可以訪問任何命名返回參數(shù):
http://wiki.jikexueyuan.com/project/learn-go-language/images/69.png" alt="pic" />
接受不定數(shù)量的參數(shù)的函數(shù)叫做變參函數(shù)。定義函數(shù)使其接受變參:
func myfunc(arg ... i n t ) { }
arg ...int 告訴 Go 這個(gè)函數(shù)接受不定數(shù)量的參數(shù)。注意,這些參數(shù)的類型全部是 int。在函數(shù)體中,變量 arg 是一個(gè) int 類型的 slice:
http://wiki.jikexueyuan.com/project/learn-go-language/images/70.png" alt="pic" />
如果不指定變參的類型,默認(rèn)是空的接口 interface{}(參閱第 5 章)。假設(shè)有另一個(gè)變參函數(shù)叫做 myfunc2,下面的例子演示了如何向其傳遞變參:
http://wiki.jikexueyuan.com/project/learn-go-language/images/71.png" alt="pic" />
就像其他在 Go 中的其他東西一樣,函數(shù)也是值而已。它們可以像下面這樣賦值給變量:
http://wiki.jikexueyuan.com/project/learn-go-language/images/72.png" alt="pic" />
如果使用 fmt.Printf("\%T\n", a) 打印 a 的類型,輸出結(jié)果是 func()。
函數(shù)作為值,也會(huì)被用在其他地方,例如 map。這里將整數(shù)轉(zhuǎn)換為函數(shù):
http://wiki.jikexueyuan.com/project/learn-go-language/images/73.png" alt="pic" />
也可以編寫一個(gè)接受函數(shù)作為參數(shù)的函數(shù),例如用于操作 int 類型的 slice 的 Map 函數(shù)。這是一個(gè)留給讀者的練習(xí),參考在第 34 頁的練習(xí) Q11。
由于函數(shù)也是值,所以可以很容易的傳遞到其他函數(shù)里,然后可以作為回調(diào)。首先定義一個(gè)函數(shù),對(duì)整數(shù)做一些“事情”:
http://wiki.jikexueyuan.com/project/learn-go-language/images/74.png" alt="pic" />
這個(gè)函數(shù)的標(biāo)識(shí)是 func printit(int),或者沒有函數(shù)名的:func(int)。創(chuàng)建新的函數(shù)使用這個(gè)作為回調(diào),需要用到這個(gè)標(biāo)識(shí):
http://wiki.jikexueyuan.com/project/learn-go-language/images/75.png" alt="pic" />
Go 沒有像 Java 那樣的異常機(jī)制,例如你無法像在 Java 中那樣拋出一個(gè)異常。作為替代,它使用了恐慌和恢復(fù)(panic-and-recover)機(jī)制。一定要記得,這應(yīng)當(dāng)作為最后的手段被使用,你的代碼中應(yīng)當(dāng)沒有,或者很少的令人恐慌的東西。這是個(gè)強(qiáng)大的工具,明智的使用它。那么,應(yīng)該如何使用它呢。下面的描述來自于[7]:
是一個(gè)內(nèi)建函數(shù),可以中斷原有的控制流程,進(jìn)入一個(gè)令人恐慌的流程中。當(dāng)函數(shù)F 調(diào)用 panic,函數(shù) F 的執(zhí)行被中斷,并且 F 中的延遲函數(shù)會(huì)正常執(zhí)行,然后F 返回到調(diào)用它的地方。在調(diào)用的地方,F(xiàn) 的行為就像調(diào)用 panic。這一過程繼續(xù)向上,直到程序崩潰時(shí)的所有 goroutine 返回。
恐慌可以直接調(diào)用 panic 產(chǎn)生。也可以由運(yùn)行時(shí)錯(cuò)誤產(chǎn)生,例如訪問越界的數(shù)組。
是一個(gè)內(nèi)建的函數(shù),可以讓進(jìn)入令人恐慌的流程中的 goroutine 恢復(fù)過來。recover 僅在延遲函數(shù)中有效。
在正常的執(zhí)行過程中,調(diào)用 recover 會(huì)返回 nil 并且沒有其他任何效果。如果當(dāng)前的 goroutine 陷入恐慌,調(diào)用 recover 可以捕獲到 panic 的輸入值,并且恢復(fù)正常的執(zhí)行。
這個(gè)函數(shù)檢查作為其參數(shù)的函數(shù)在執(zhí)行時(shí)是否會(huì)產(chǎn)生 panic:
http://wiki.jikexueyuan.com/project/learn-go-language/images/76.png" alt="pic" />
0 .定義一個(gè)新函數(shù) throwsPanic 接受一個(gè)函數(shù)作為參數(shù)(參看“函數(shù)作為值”)。函數(shù) f 產(chǎn)生 panic,就返回 true,否則返回 false;
(第 28 頁),無須指定 b。
Q5. (0) 平均值
Q6. (0) 整數(shù)順序
f(7,2) ! 2,7
f(2,7) ! 2,7
Q7. (1) 作用域
http://wiki.jikexueyuan.com/project/learn-go-language/images/77.png" alt="pic" />
Q8. (1) 棧
http://wiki.jikexueyuan.com/project/learn-go-language/images/78.png" alt="pic" />
2 .更進(jìn)一步。編寫一個(gè) String 方法將棧轉(zhuǎn)化為字符串形式的表達(dá)??梢赃@樣的方式打印整個(gè)棧:fmt.Printf("My stack %v\n", stack)
棧可以被輸出成這樣的形式:[0:m] [1:l] [2:k]
Q9. (1) 變參
Q10. (1) 斐波那契