go version go1.11 windows/amd64golang
本文爲閱讀Go語言中文官網的規則說明書(https://golang.google.cn/ref/spec)而作的筆記,介紹Go語言的 內建函數(Built-in functions)。express
規格說明書中的目錄以下:c#
Built-in functions
-Close
-Length and capacity
-Allocation
-Making slices, maps and channels
-Appending to and copying slices
-Deletion of map elements
-Manipulating complex numbers
-Handling panics
-Bootstrappingwindows
在規格說明書中,介紹了15個內建函數,以下所示:數組
close, len, cap, new, make, append, copy, delete,
complex, real, imag, panic, recover,
print, println緩存
下面簡要介紹各個函數:app
close函數
關閉信道(channel)。學習
在沒有更多數據 要 發送 的時候關閉信道。測試
close(channel)
也就是說,channel必須是 只發 或 雙向類型的才能夠,不然錯誤。
發送 或 關閉 一個已經關閉的 信道 會出現 運行時錯誤(run-time panic)。
關閉 nil 信道 也會致使 運行時錯誤。
在調用close函數,而且以前發送的數據都已被接收到 以後,接收操做 會 返回 信道類型的0值,且不會阻塞,,多個值的接收操做 會返回 一個接收值 和 信道是否關閉的標誌。
// 單個值接收操做 v1 := <-ch v2 = <-ch // 多(兩)個值接收操做 x, ok = <-ch x, ok := <-ch var x, ok = <-ch var x, ok T = <-ch
len,cap
這兩個函數接收各類類型的參數,返回一個int型的結果。
len函數返回參數的 長度、元素個數,cap返回參數的 容量。
官文介紹:
Call Argument type Result len(s) string type string length in bytes [n]T, *[n]T array length (== n) []T slice length map[K]T map length (number of defined keys) chan T number of elements queued in channel buffer cap(s) [n]T, *[n]T array length (== n) []T slice capacity chan T channel buffer capacity
從官文介紹能夠看出,len函數 的參數能夠爲 字符串、數組類型、數組指針、分片、映射類型、信道——在信道緩存中排隊的元素數量,
cap函數 的參數能夠爲 數組、數組指針、分片、信道——緩存容量大小。
分片類型 的容量是指 分配給它的 底層數組 的元素的數量。在任什麼時候候,下面的公式是成立的:
0 <= len(s) <= cap(s)
值爲 nil 的 分片、映射、信道 的長度爲0,值爲 nil 的分片的容量爲 0。
若是 s 是 字符串常量,那麼,表達式 len(s) 也是常量;
若是 s 是 數組 或者 指向數組的指針,那麼,表達式 len(s) 和 cap(s) 也是常量,而且 s 的元素類型不是 接收信道 或 函數類型,在這種狀況下,s 是沒法被估值的(evaluated);
除了上面的狀況外,調用 len 和 cap 獲得的都不是常量,並且s是能夠估值的(翻譯的有些不許確,這個evaluated究竟是什麼意思?)。
官文示例:
const ( c1 = imag(2i) // imag(2i) = 2.0 is a constant c2 = len([10]float64{2}) // [10]float64{2} contains no function calls c3 = len([10]float64{c1}) // [10]float64{c1} contains no function calls c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call ) var z complex128
new
new函數 在學習接口類型時遇到了,使用new,能夠新建一個類型的實例。
new函數 屬於 Allocation(分配) 小節,分配 什麼呢?內存空間了。
官文翻譯:
在 運行時,給 變量 分配一個 存儲空間,這個空間大小由 變量類型決定,而且 返回 一個 變量類型的指針 指向分配的空間。
在分配完存儲空間後,再進行初始化——分配完畢後是0值。
疑問,不能在分配時進行初始化嗎?須要試驗!
type S1 struct {
int
float32
}
var sv2 = new(S1{23, 32.0}) // 錯誤:S1 literal is not a type
fmt.Println(sv2)
fmt.Println(sv2.int, sv2.float32)
看來不能在調用new函數時賦值!或者俺的方法不對!
更改代碼後測試:
var sv2 = new(S1) sv2.int = 23 sv2.float32 = 32.0 fmt.Println(sv2) fmt.Println(sv2.int, sv2.float32) // 測試結果 // 注意第一行的 & 符號! // 返回的 sv2 是一個 指針 &{23 32} 23 32
官文示例:
type S struct { a int; b float64 } new(S)
make
make函數 屬於 Making slices, maps and channels 一節的惟一函數,用於常見 分片、映射和信道。
make函數 的第一個參數爲 類型T,能夠選擇跟着一個表達式的指明類型的列表(原文:optionally followed by a type-specific list of expressions,翻譯存在問題)。
返回 類型T,而不是 *T,,空間初始化能夠參考initial values。
官文中介紹的用法:
Call Type T Result make(T, n) slice slice of type T with length n and capacity n // 第二個參數n表示長度,沒有第三個參數時,n也表示容量 make(T, n, m) slice slice of type T with length n and capacity m // 第二個參數n表示長度,第三個參數m表示容量 make(T) map map of type T // 映射,但沒有分配初始空間 make(T, n) map map of type T with initial space for approximately n elements // 分配了初始空間的映射,能夠容納n個元素,但能夠自動擴展(需確認) make(T) channel unbuffered channel of type T // 無緩衝信道 make(T, n) channel buffered channel of type T, buffer size n // 緩衝區大小爲 n(字節嗎?)的信道,關於信道的資料,本身還需dig
每個n、m都必須是 整型(integer type,各類整型嗎?),或者一個 無類型的常量。一個常量大小參數不能是非負數,而且能夠被類型int的值表示,,若是是無類型的常量,會被分配類型int。
n不能大於m。
若是n在運行時 爲 負數 或 大於m,會產生一個運行時錯誤。
官文示例——合法、非法使用的都有:
s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000 s := make([]int, 1<<63) // illegal: len(s) is not representable by a value of type int // 1<<63超過類型int的範圍了 s := make([]int, 10, 0) // illegal: len(s) > cap(s) c := make(chan int, 10) // channel with a buffer size of 10 m := make(map[string]int, 100) // map with initial space for approximately 100 elements
調用指定了類型和參數n的make函數建立映射時,會建立一個能夠包含n個元素的初始化空間,可是,空間大小是和編譯器的實現相關的(implementation-dependent)——這個大小是沒法統一。
append,copy
這兩個函數用來操做分片。從函數名可知,append用來給分片添加元素,copy用來從分片拷貝元素,拷貝到哪裏呢?拷貝到分片中。
對於這兩個函數,其返回結果是和 參數指向的內存是否覆蓋(whether the memory referenced by the arguments overlaps) 沒有關係的。
可變參函數append添加0到多個到分片,並返回 結果分片。
參數x中的值得類型T,必須是分片S的元素類型,但有一個特例,第一個參數爲 []byte類型,第二個參數爲string類型——但其結尾添加三個英文句號(...),這種狀況表示將添加字符串的字節數組到第一個參數。
append(s S, x ...T) S // T is the element type of S
若是分片s的容量不足於包含新添加的值,appen將分配新的、足夠大的 底層數組 來知足 存在的分片和新增值 的空間,,不然,appen函數將使用 底層數組。
疑問,前面講到,append會返回新的分片,那麼,這個分片和舊的分片是什麼關係呢?是在舊的分片上操做的嗎?須要dig!以前本身操做時,是須要把append的返回值 賦值給 舊分片的變量的。
官文示例:
s0 := []int{0, 0} s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
// 下面這個頗有意思!用到了 空接口,分片能夠包含 任何類型 的數據了 var t []interface{} t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' }
copy函數從 源分片拷貝若干元素到目的分片,並返回拷貝的元素的數量。源分片和目的分片的元素類型T必須相同,並能夠被賦值給[]T。
拷貝的元素的數量是 len(src)、len(dst) 中的最小值。
和append函數相似,copy函數也存在特例,目的分片爲 []byte類型,源分片爲字符串類型(string),這表示將字符串的字節數組拷貝到字符分片中。注意,特例不須要三個英文句號(...),這和append函數不一樣。
copy函數簽名:
copy(dst, src []T) int copy(dst []byte, src string) int
copy函數官文示例:
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7} var s = make([]int, 6) var b = make([]byte, 5) n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Hello, World!") // n3 == 5, b == []byte("Hello")
delete
刪除一個映射 m 中的鍵值爲 k 的元素。k 的類型 必須是 能夠賦值(assignable,這個不是很清楚什麼意思,類型相同 或 結果的類型相同) 給 映射 m 的鍵類型的。
delete(m, k) // remove element m[k] from map m
若是映射 m 的值爲 nil,或者,m[k] 不存在,那麼,調用函數 delete 將什麼也不作(no-op)。
complex,real,imag
這三個函數屬於 操做複數(Manipulating complex numbers) 這一節。
大概意思:
complex函數 根據參數的 浮點類型的 實部、虛部 創建複數,real函數 獲取一個複數的 實部,imag函數 獲取一個複數的 虛部。
官文的三個函數簽名:
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
complex的兩個參數必須是相同的浮點類型——float3二、float64,float32生成複數類型爲complex64,float64生產的複數類型爲complex128。
若是一個參數是 沒有類型的常量(untyped constant),它的類型就被轉換爲另外一個參數的類型;
若是兩個參數都是 沒有類型的常量,它們必須 不是複數、或者它們的虛部爲0,而且返回的是一個 沒有類型的複數(特別)。
對於real、imag函數,參數必須是複數類型,返回值的類型由參數的類型決定,對應關係:complex64-->float32,complex128-->float64。
若是參數時一個 沒有類型的常量,那麼,參數必須是數,而且返回值是 沒有類型的浮點常量(特別)。
real、imag函數 一塊兒 造成了 complex函數 的反函數,對於一個複數類型Z的值z,下面的公式是成立的:
z == Z(complex(real(z), imag(z)))
若是這些函數的操做數都是 常量,那麼,返回值也是常量。
官文示例:
var a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s uint = complex(1, 0) // untyped complex constant 1 + 0i can be converted to uint _ = complex(1, 2<<s) // illegal: 2 assumes floating-point type, cannot shift // 整數才能夠作 位運算 var rl = real(c64) // float32 var im = imag(a) // float64 const c = imag(b) // untyped constant -1.4 _ = imag(3 << s) // illegal: 3 assumes complex type, cannot shift // 整數才能夠作 位運算
panic,recover
這兩個函數分別用於 報告 和 處理 運行時錯誤 或 程序自定義的錯誤條件,簽名以下:
func panic(interface{}) func recover() interface{}
固然,panic函數 就是 報告一個錯誤,recover函數 就是負責處理的函數。
特別提醒,要讀懂這一節,須要理解 Go語言中 的 Defer statements(延遲語句?)。
下面是官文翻譯:
在執行 函數F 的時候,顯式調用panic函數或者發生運行時錯誤(run-time panic)都會 終斷函數F的執行,可是,任何被 函數F 延遲的函數 會當即被執行。
也就是說,函數F中發生錯誤了,若是沒有 延遲函數,那麼,函數F 被終斷,若是有延遲函數,則執行完延遲函數後再終斷。
前面講到 函數F 的延遲函數被調用,全部的延遲函數執行完畢後,,接下來,函數F 的調用者 的延遲函數 開始執行,,直到goroutine的頂級函數的延遲函數被執行。
看到了吧,發生了錯誤,就一直是 延遲函數 在執行,直到goroutine的頂級函數的延遲函數被執行。那麼,什麼事goroutine呢?goroutine的頂級函數的延遲函數被執行 以後怎麼樣呢?銷燬goroutine?
在這個時刻,程序被終止,錯誤條件被報告,同時被報告的還有函數panic的參數的值。這個終止序列被稱爲 panicking(翻譯爲何好呢?驚險追蹤?錯誤追蹤?)。
官文示例——調用panic函數:
panic(42) panic("unreachable") panic(Error("cannot parse"))
上面講了panic函數,用於 顯示製造錯誤,在錯誤發生後,延遲函數開始執行,直到goroutine的頂級函數的延遲函數被執行完畢。
注意,延遲函數 在沒有發生錯誤時也會執行,時機爲 函數返回前(有返回return語句的) 和 函數結束前。
從上面也看到了,若是不阻止panic的擴散,程序(goroutine?這個本身還不徹底清楚)會被終止!那麼,怎麼阻止呢?這就須要 recover函數 了!
recover函數 使用一段程序 去管理一個panicking goroutine的行爲。
設想一下,函數G 延遲了 函數D 的執行,函數D中調用了 recover函數。
在同一個goroutine中,函數G發生了panic(錯誤),此時,被函數G延遲的函數開始執行——其中包括函數D,當執行到函數D時,函數D 中調用recover函數 的返回值 是傳遞到 調用panic函數中的值。
若是 函數D 正常返回,也沒有產生新的panic,panicking這個過程就結束了!
在這種狀況下,調用函數G到調用函數panic(發生錯誤)期間的函數狀態就被忽略了,而且繼續程序的正常執行。
前面到了函數D被執行了, 返回了,panicking結束了,此時,在函數D以前被函數G 延遲的 函數開始執行——若是有,函數G的執行被終止,返回它的調用者。
以上,即是執行recover函數的「內幕」!調用recover且沒有產生新的panic就表示錯誤被處理了。程序仍是在函數G中執行,但執行的是函數G的延遲函數,執行完延遲函數了,就返回到它的調用者。
前面提到 recover函數 有返回值,值爲panic函數的參數。那麼,recover函數調用後何時其返回值爲 nil 呢?下面幾種狀況之一知足即返回 nil:
-panic函數的參數爲 nil (空嗎?)
-goroutine沒有發生panicking——就是沒錯的時候調用recover恢復;
-recover函數不是被延遲函數直接調用的——這是什麼狀況?還需dig!
官文示例:protect函數 調用了 參數中的 函數g,並延遲了一個函數 用於處理 調用函數g 引起的panic(run-time panics)。
func protect(g func()) {
defer func() { // 延遲函數定義,關鍵字 defer
log.Println("done") // Println executes normally even if there is a panic
if x := recover(); x != nil { // 調用recover:若是發生錯誤,返回值 x 就不是 nil,此時,處理錯誤,只是打印一條信息
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
幾乎理解了,還須要熟悉 goroutine,以及閱讀和練習更多Go代碼才行啊!
print,println
目前的Go語言實現提供了一些在引導時有用的內建函數,文檔爲了完整性會介紹這些函數,可是,不保證它們會存在於Go語言中,並且它們不會返回結果。
好比,本節的print、println兩個函數,都用來輸出 參數。
官文函數說明:
Function Behavior print prints all arguments; formatting of arguments is implementation-specific println like print but prints spaces between arguments and a newline at the end
實現(所謂的實現,是指,各個軟件開發組織 根據 這份規格說明書 來開發Go語言編譯器的實現吧)要求:
print、pringln不須要接受全部的參數類型,可是,打印 布爾、數值、字符串類型 是必須支持的。
後記
又是小半個下午加晚上的一兩個小時,這篇博文但是花了5個小時左右啊!
缺乏一些示例,本身的實踐,不夠完美,可是呢,大部分知識點是講清楚了,不清楚的也是有標記的。