一、函式基礎
- 函式由函式宣告關鍵字 func、函式名、引數串列、回傳串列、函式體組成
- 函式是一種型別,函式型別變數可以像其他型別變數一樣使用,可以作為其他函式的引數或回傳值,也可以直接呼叫執行
- 函式名首字母大小寫決定了其包可見性
- 引數和回傳值需用
()包裹,如果回傳值是一個非命名的引數,則可省略,函式體使用{}包裹,且{必須位于同行行尾
1. 基本使用
// 1. 可以沒有輸入引數,也可以沒有回傳值(默認回傳 0)
func A() {
...
}
// 2. 多個相鄰的相同型別引數可以使用簡寫模式
func B(a, b int) int {
return a + b
}
// 3. 支持有名的回傳值
func C(a, b int) (sum int) {
// sum 相當于函式體內的區域變數,初始化為零值
sum = a + b
return // 可以不帶 sum
}
// 4. 不支持默認值引數
// 5. 不支持函式多載
// 6. 不支持函式嵌套定義,但支持嵌套匿名函式
func D(a, b int) (sum int) {
E := func(x, y int) int {
return x + y
}
return E(a, b)
}
// 7. 支持多值回傳(一般將錯誤型別作為最后一個回傳值)
func F(a, b int) (int, int) {
return b, a
}
// 8. 函式實參到形參的傳遞永遠是**值拷貝**
func G(a *int) { // a 是實參指標變數的副本,和實參指向同一個地址
*a += 1
}
2. 不定引數
// 1. 不定引數型別相同
// 2. 不定引數必須是函式的最后一個引數
// 3. 不定引數在函式體內相當于切片
func sum(arr ...int) (sum int) {
for _, v := range arr { // arr 相當于切片,可使用 range訪問
sum += v
}
return
}
// 4. 可以將切片傳遞給不定引數
array := [...]int{1, 2, 3, 4} // 不能將陣列傳遞給不定引數
slice := []int{1, 2, 3, 4}
sum(slice...) // 切片名后要加 ...
// 5. 形參為不定引數的函式和形參為切片的函式型別不同
func suma(arr ...int) (sum int) {
for v := range arr {
sum += v
}
return
}
func sumb(arr []int) (sum int) {
for v := range arr {
sum += v
}
return
}
fmt.Printf("%T\n", suma) // func(...int) int
fmt.Printf("%T", sumb) // func([]int) int
3. 函式型別
函式型別又叫函式簽名:函式定義行去掉函式名、引數名和 {
func add(a, b int) int { return a + b }
func sub(x int, y int) (c int) { c = x - y; return }
fmt.Printf("%T", add) // func(int, int) int
fmt.Printf("%T", sub) // func(int, int) int
可以使用 type 定義函式型別,函式型別變數和函式名都可以看做指標變數,該指標指向函式代碼的開始位置
func add(a, b int) int { return a + b }
func sub(a, b int) int { return a - b }
type Op func(int, int) int // 定義一個函式型別:輸入兩個 int,回傳一個 int
func do(f Op, a, b int) int {
t := f
return t(a, b)
}
fmt.Println(do(add, 1, 2)) // 3
fmt.Println(do(sub, 1, 2)) // -1
4. 匿名函式
// 1. 直接賦值給函式變數
var sum = func(a, b int) int {
return a + b
}
func do(f func(int, int) int, a, b int) int {
return f(a, b)
}
// 2. 作為回傳值
func getAdd() func(int, int) int {
return func(a, b int) int {
return a + b
}
}
func main() {
// 3. 直接被呼叫
defer func() {
if err:= recover(); err != nil {
fmt.Println(err)
}
}()
sum(1, 2)
getAdd()(1 , 2)
// 4. 作為實參
do(func(x, y int) int { return x + y }, 1, 2)
}
二、函式高級
1. defer
可注冊多個延遲呼叫函式,以先進后出的順序執行,常用于保證資源最終得到回收釋放
func main() {
// defer 后跟函式或方法呼叫,不能是陳述句
defer func() {
println("first")
}()
defer func() {
println("second")
}()
println("main")
}
// main
// second
// first
defer 函式的實參在注冊時傳遞,后續變更無影響
func f() int {
a := 1
defer func(i int) {
println("defer i =", i)
}(a)
a++
return a
}
print(f())
// defer i = 1
// 2
defer 若位于 return 后,則不會執行
func main() {
println("main")
return
defer func() {
println("first")
}()
}
// main
若主動呼叫os.Exit(int)退出行程,則不會執行 defer
func main() {
defer func() {
println("first")
}()
println("main")
os.Exit(1)
}
// main
關閉資源例子
func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
// defer 一般放在錯誤檢查陳述句后面,若位置不當可能造成 panic
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close()
w, err = io.Copy(dstFile, srcFile)
return
}
defer 使用注意事項:
- defer 會延遲資源的釋放
- 盡量不要放在回圈陳述句中
- defer 相對于普通函式呼叫需要間接的資料結構支持,有一定性能損耗
- defer 中最好不要對有名回傳值進行操作
2. 閉包
- 閉包是由函式及其相關參考環境組合成的物體,一般通過在匿名函式中參考外部函式的區域變數或包全域變數構成
- 閉包對閉包外的環境引入是直接參考:編譯器檢測到閉包,會將閉包參考的外部變數分配到堆上
- 閉包是為了減少全域變數,在函式呼叫的程序中隱式地傳遞共享變數,但不夠清晰,一般不建議用
- 物件是附有行為的資料,而閉包是附有資料的行為,類在定義時已經顯式地集中定義了行為,但閉包中的資料沒有顯式地集中宣告的地方
// fa 回傳的是一個閉包:形參a + 匿名函式
func fa(a int) func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1) // f 使用的 a 是 0xc0000200f0
g := fa(1) // g 使用的 a 是 0xc0000200f8
// f、g 參考的閉包環境中的 a 是函式呼叫產生的副本:每次呼叫都會為區域變數分配記憶體
println(f(1))
println(f(1)) // 閉包共享外部參考,因此修改的是同一個副本
println(g(1))
println(g(1))
}
// 0xc0000200f0 1
// 2
// 0xc0000200f0 2
// 3
// 0xc0000200f8 1
// 2
// 0xc0000200f8 2
// 3
閉包參考全域變數(不推薦)
var a = 0
// fa 回傳的是一個閉包:全域變數a + 匿名函式
func fa() func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa()
g := fa()
// f、g 參考的閉包環境中的 a 是同一個
println(f(1))
println(g(1))
println(f(1))
println(g(1))
}
// 0x511020 0
// 1
// 0x511020 1
// 2
// 0x511020 2
// 3
// 0x511020 3
// 4
同一個函式回傳的多個閉包共享該函式的區域變數
func fa(a int) (func(int) int, func(int) int) {
println(&a, a)
add := func(i int) int {
a += i
println(&a, a)
return a
}
sub := func(i int) int {
a -= i
println(&a, a)
return a
}
return add, sub
}
func main() {
f, g := fa(0) // f、g 使用的 a 都是 0xc0000200f0
s, k := fa(0) // s、k 使用的 a 都是 0xc0000200f8
println(f(1), g(2))
println(s(1), k(2))
}
// 0xc0000200f0 0
// 0xc0000200f8 0
// 0xc0000200f0 1
// 0xc0000200f0 -1
// 1 -1
// 0xc0000200f8 1
// 0xc0000200f8 -1
// 1 -1
三、錯誤處理
1. 錯誤和例外
- 廣義的錯誤:發生非期望的行為
- 狹義的錯誤:發生非期望的己知行為
- 這里的己知是指錯誤型別是預料并定義好的
- 例外:發生非期待的未知行為,又被稱為未捕獲的錯誤
- 這里的未知是指錯誤的型別不在預先定義的范圍內
- 程式在執行時發生未預先定義的錯誤,程式編譯器和運行時都沒有及時將其捕獲處理,而是由作業系統進行例外處理,如 C 語言的 Segmentation Fault

Go 不會出現 untrapped error,只需處理 runtime errors 和程式邏輯錯誤
Go 提供兩種錯誤處理機制
- 通過 panic 列印程式呼叫堆疊,終止程式來處理錯誤
- 通過函式回傳錯誤型別的值來處理錯誤
Go 是靜態強型別語言,程式的大部分錯誤是可以在編譯器檢測到的,但有些錯誤行為需要在運行期才能檢測出來,此種錯誤行為將導致程式例外退出,建議:
- 若程式發生的錯誤導致程式不能繼續執行,此時程式應該主動呼叫 panic
- 若程式發生的錯誤能夠容錯繼續執行,此時應該使用 error 回傳值的方式處理,或在非關鍵分支上使用 recover 捕獲 panic

2. panic 和 recover
panic(i interface{}) // 主動拋出錯誤
recover() interface{} // 捕獲拋出的錯誤
- 引發panic情況:①主動呼叫panic;②程式運行時檢測拋出運行時錯誤
- panic 后,程式會從當前位置回傳,逐層向上執行 defer 陳述句,逐層列印函式呼叫堆疊,直到被 recover 捕獲或運行到最外層函式退出
- 引數為空介面型別,可以傳遞任意型別變數
- defer 中也可以 panic,能被后續 defer 捕獲
- recover 只有在 defer 函式體內被呼叫才能捕獲 panic,否則回傳 nil
// 以下場景捕獲失敗
defer recover()
defer fmt.Println(recover())
defer func() {
func() { // 兩層嵌套
println("defer inner")
recover()
}()
}()
// 以下場景捕獲成功
defer func() {
println("defer inner")
recover()
}()
func except() {
recover()
}
func test() {
defer except()
painc("test panic")
}
可以同時有多個 panic(只會出現在 defer 里),但只有最后一次 panic 能被捕獲
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println(recover())
}()
defer func() {
panic("first defer panic")
}()
defer func() {
panic("second defer panic")
}()
panic("main panic")
}
// first defer panic
// <nil>
包中 init 函式引發的 panic 只能在 init 函式中捕獲(init 先于 main 執行)
函式不能捕獲內部新啟動的 goroutine 拋出的 panic
func do() {
// 不能捕獲 da 中的 panic
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
go da()
time.Sleep(3 * time.Second)
}
func da() {
panic("panic da")
}
3. error
Go 內置錯誤介面型別 error,任何型別只要實作Error() string方法,都可以傳遞 error 介面型別變數???
type error interface {
Error() string
}
使用 error:
- 在多個回傳值的函式中,error 作為函式最后一個回傳值
- 若函式回傳 error 型別變數,先處理
error != nil的例外場景,再處理其他流程 - defer 放在 error 判斷的后面
四、底層實作
TODO
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/285530.html
標籤:Go
上一篇:行程內快取助你提高并發能力!
