這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
目錄 [−]
- 內建函數
- 包
- import
- 包的初始化
- error、panic和recover
本章介紹Go語言的其它部分,算是Go語言規範的深入學習的收尾工作。
內建函數
前面幾章我們已經認識了幾個內建的函數,這裡我們將所有的內建函數羅列一下。
如果你查看它們的定義的話,你可以訪問builtin/builtin.go,但這些內建的函數只有聲明,沒有方法體,它們不是標準的Go類型,你不能把它們作為函數值進行參數傳遞,只能出現在調用運算式中。事實上這些內建類型並不真的在builtin包中,只是為了產生文檔的需要。
你可以在任意的包中調用這些內建函數,不必引入特定的包如"builtin"。
1、close: 關閉channel
2、len(s):得到字串、數組、數值指標、slice、map、chan的長度
3、cap(s):得到數組、數組指標的長度,得到slice、channel的容量
4、new(T): 組建類型T的零值指標,注意它返回的是指標 *T
5、make: 產生slice、map、channel對象
調用 類型 T 結果make(T, n) slice slice of type T with length n and capacity nmake(T, n, m) slice slice of type T with length n and capacity mmake(T) map map of type Tmake(T, n) map map of type T,初始可以容納 n 元素的空間make(T) channel 不帶緩衝的channel of type T,比如我們在聲明訊號channel的時候make(T, n) channel 帶緩衝的 channel of type T, 緩衝大小為 n
6、append(s S, x ...T) S: 增加0到n個元素到slice中,返回新的slice
7、copy(dst, src []T) int: 複製源src slice的元素到目標dst slice中,返回複製的元素的數量n, n是src和dst長度的最小值。字串也可以作為src,但是T的類型必須是byte
8、delete(m,k): 刪除map中的一個映射, m為nil或者m[k]不存在也不會panic,而是一個空操作
9、complex、real、imag: 複數操作
10、panic、recover: 報告panic和處理panic,後面講
11、print、println: 盡量不用這兩個函數,因為保證將來它們還會留在Go語言中,使用fmt.Print、fmt.Println
包
Go的代碼檔案中都會有包(package)的定義,在import聲明的前面。
同一個檔案夾下的所有的檔案都要使用同一個包名(當然,你如果單獨編譯每一個檔案的話,可以不遵守,但是要編譯整個項目,必須遵守)。
但是測試檔案可以叫另外的包名,比如正常代碼的包名為"package abc",測試代碼和範例程式碼的包名為"package abc_test". Go標準庫中混用了這兩種風格。
main包是一個特殊的包,必須聲明一個main函數,main函數無參數,無傳回值,它用來建立可執行程式。
包名不一定和檔案夾的名字保持一致,經常我們的項目的名稱很長,不太適合做包名,所以包名可以用一個簡短的名稱,但是如果可能,盡量用一樣的名字,這樣在下載庫的時候就能直到包的名字了。
import
import用來引入所需要的類型,允許你訪問另外的包中的匯出類型。
以下四種形式都是可以接受的mport:
Import declaration Local name of Sinimport "lib/math" math.Sinimport m "lib/math" m.Sinimport . "lib/math" Sinimport _ "lib/math"
為了避免同名的包名衝突,你可以為匯入的包名起一個名字,比如上例中的"m"。
你也可以使用.,這樣無需包名標識符,可以直接使用這個包下的匯出類型。
最後一個情況是使用空標識符,主要是想利用包的初始化,而不是使用它的匯出類型。
import不能匯入包自己,不管是直接的還是間接的(循環參考)。你也不能直接匯入一個包而不使用它的匯出類型,所幸一些工具可以自動幫我們修正import的錯誤,或者自動幫我們匯入,比如goimports。
import可以匯入相對路徑,如"import \"../foo/bar\"",但是強烈你不要這麼做,這不是常用的匯入風格。
import可以用小括弧括起來匯入多個包。
包的初始化
包變數的初始化順序和它們聲明的順序一致,但是也得考慮它們的依賴。
循環相依性初始化也不可以:
12345 |
package abcvar i int = jvar j int = kvar k int = i |
包變數的初始化之後可以調用一個init函數實現其它的初始化過程,你可以顯示地定義這個函數,它可以出現在同一個包下的多個檔案中,執行的順序由編譯器決定。
如果包匯入了其它包,則匯入的包會先初始化。如果多個包都匯入同一個包,則匯入的包只被初始化一次。
error、panic和recover
Go預定義了error類型,雖然它的首字母不是大寫的,但是確可以在任何包下使用:
123 |
type error interface {Error() string} |
而errors包定義了產生簡單Error的方法errors.New(text String) error。如果你想自訂Error類型,你可以實現error介面。
運行時Error,比如數組索引越界會觸發一個運行時的panic,它等價調用panic函數,panic的值是一個runtime.Error:
123456 |
package runtimetype Error interface {error// and perhaps other methods} |
而panic的處理是在一個defer函數中執行的:
12345678910111213141516 |
func main() {defer func() {fmt.Println("foo")}()defer func() {if x := recover(); x != nil {fmt.Println(x)}}()defer func() {fmt.Println("bar")}()panic("trigger panic") fmt.Println("end")} |
注意defer的執行順序,前面已經講過,和它們聲明的順序相反,所以輸出結果為:
最後一句沒有機會繼續執行,因為recover執行完後函數就終止了。
這帶來一個問題,如果函數有傳回值,recover後函數的傳回值是什嗎?
12345678910111213141516171819202122 |
func main() {i := z()fmt.Println(i)}func z() int {defer func() {fmt.Println("foo")}()defer func() {if x := recover(); x != nil {fmt.Println(x)}}()defer func() {fmt.Println("bar")}()panic("trigger panic")fmt.Println("end")return 100} |
輸出結果是:
1234 |
bartrigger panicfoo0 |
可以函數的返回是傳回型別的零值。
當然說零值也不完全正確,如果函數有命名的返回參數,並且命名的返回參數在panic之前賦值了的話,返回的結果還是最後的賦值結果,下面的代碼中函數的返回結果為50:
1234567891011121314151617181920212223 |
func main() {i := z()fmt.Println(i)}func z() (r int) {defer func() {fmt.Println("foo")}()defer func() {if x := recover(); x != nil {fmt.Println(x)}}()defer func() {fmt.Println("bar")}()r = 50panic("trigger panic")fmt.Println("end")return 100} |
panic如果沒有處理,會傳遞給它的調用者,這就是panic的bubble。當然如果可以預見panic,最好的處理方式就是在本函數內進行處理,因為你不能控制外部調用者的行為,而且外部調用者不一定知道會有panic發生:
123456789101112131415161718192021222324252627 |
func main() {defer func() {if x := recover(); x != nil {fmt.Println("recover from main:", x)}}()f1()}func f1() {fmt.Println("start f1")f2()fmt.Println("end f1")}func f2() {fmt.Println("start f2")f3()fmt.Println("end f2")}func f3() {fmt.Println("start f3")panic("triggered from f3")fmt.Println("end f3")} |
上面的代碼輸出:
1234 |
start f1start f2start f3recover from main: triggered from f3 |
如果連main都沒有 recover,則程式異常退出。
還有一個不太引人注意的地方就是如果在recover中產生panic會怎麼樣?還是看例子:
123456789101112131415161718192021222324 |
func main() {defer func() {if x := recover(); x != nil {fmt.Println("recover from main:", x)}}()f1()}func f1() {defer func() {if x := recover(); x != nil {fmt.Println("recover from f1:", x)panic("triggered from f1")}}()f2()}func f2() {panic("triggered from f2")} |
函數f2產生的panic會被函數f1處理, f1在recover的過程中產生一個新的panic,這個panic會把它的調用者main捕獲。
所以recover產生的panic會往上傳遞。