📢 本文由 gemini-3-flash-preview 翻譯
Golang 系列
Hello GoLang:
https://blog.yexca.net/archives/154
GoLang (var and const) 變數與常數:
https://blog.yexca.net/archives/155
GoLang (func) 函式: 本文
GoLang (slice and map) 切片:
https://blog.yexca.net/archives/160
GoLang (OOP) 物件導向:
https://blog.yexca.net/archives/162
GoLang (reflect) 反射:
https://blog.yexca.net/archives/204
GoLang (struct tag) 結構體標籤:
https://blog.yexca.net/archives/205
GoLang (goroutine) Goroutine:
https://blog.yexca.net/archives/206
GoLang (channel) 通道:
https://blog.yexca.net/archives/207
多個回傳值
Go 函式可以回傳多個值
匿名回傳
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package main
import "fmt"
func swap(a, b int) (int, int) {
return b, a
}
func main() {
var x, y = swap(10, 20)
fmt.Println(x, y)
}
/* 輸出
* 20 10
*/
|
具名回傳
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package main
import "fmt"
func swap(a, b int) (x int, y int) {
x = b
y = a
return
}
func main() {
var x, y = swap(10, 20)
fmt.Println(x, y)
}
|
上述回傳值型別相同,可以合併
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package main
import "fmt"
func swap(a, b int) (x, y int) {
x = b
y = a
// 如果不給 x,y 賦值,預設為 0
return
}
func main() {
var x, y = swap(10, 20)
fmt.Println(x, y)
}
|
init 與 main
init 可以在任何套件中,也可以在同一個套件中出現多次,但建議只寫一個。
main 只能在 package main 中,且該套件必須有該函式。
這兩個函式為保留函式,定義時不能有參數和回傳值。
Go 程式會自動呼叫 init() 和 main()。
程式執行
程式的初始化和執行都起始於 main 套件,同一個套件就算被多個套件 import 匯入也只會匯入一次,下圖為執行順序:

例子
假設結構如下:
1
2
3
4
5
6
| hello
-- InitLib1
-- lib1.go
-- InitLib2
-- lib2.go
main.go
|
內容如下:
lib1.go
1
2
3
4
5
6
7
| package InitLib1
import "fmt"
func init() {
fmt.Println("lib1 init")
}
|
lib2.go
1
2
3
4
5
6
7
| package InitLib2
import "fmt"
func init() {
fmt.Println("lib2 init")
}
|
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package main
import (
"fmt"
// 此處底線為套件起別名,不給別名且匯入後不呼叫,編譯不會通過
_ "hello/InitLib1"
_ "hello/InitLib2"
)
func init() {
fmt.Println("main init")
}
func main() {
fmt.Println("main")
}
|
執行結果:
1
2
3
4
| lib1 init
lib2 init
main init
main
|
現在將 Lib1 套件匯入 Lib2,main 程式碼不變。
lib1.go
1
2
3
4
5
6
7
8
9
10
| package InitLib1
import (
"fmt"
_ "hello/InitLib2"
)
func init() {
fmt.Println("lib1 init")
}
|
執行 main 結果:
1
2
3
4
| lib2 init
lib1 init
main init
main
|
lib2 只出現一次。
呼叫其他套件函式
上例使用 _ 作為別名是匿名的,無法呼叫相應套件的方法。
在 lib1 中增加函式:
lib1.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package InitLib1
import (
"fmt"
//_ "hello/InitLib2"
)
// 首字母大寫才可在其他套件呼叫
func Lib1Test() {
fmt.Println("lib1 test")
}
func init() {
fmt.Println("lib1 init")
}
|
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import (
"fmt"
// 給套件起別名
mylib1 "hello/InitLib1"
_ "hello/InitLib2"
)
func main() {
// 透過 別名.方法 呼叫
mylib1.Lib1Test()
fmt.Println("main")
}
/*
* 輸出
* lib1 init
* lib2 init
* lib1 test
* main
*/
|
或者可以直接使用 .,檔案 main.go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package main
import (
"fmt"
// 別名改為 .
. "hello/InitLib1"
_ "hello/InitLib2"
)
func main() {
// 直接使用
Lib1Test()
fmt.Println("main")
}
|
不推薦使用,假設兩個套件有同名函式,將會產生歧義。
指標
與 C 指標類似。
呼叫函式可以使用兩種方式傳遞參數:值傳遞與指標(引用傳遞/傳址)。預設情況使用值傳遞,如本文第一段程式碼即為值傳遞。
使用 & 可以獲取變數對應的記憶體位址。
1
2
3
4
5
6
7
8
9
10
| package main
import (
"fmt"
)
func main() {
var a int
fmt.Printf("%x", &a)
}
|
傳址將記憶體位址傳遞給函式,函式修改將影響實際參數。同樣是交換函式,這次使用指標:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package main
import "fmt"
func swap(a, b *int) {
var tmp = *a
*a = *b
*b = tmp
}
func main() {
x, y := 10, 20
swap(&x, &y)
fmt.Println("x =", x, "y =", y)
}
|
defer
defer 語句用於預定對一個函式的呼叫,可以稱為延遲函式,作用:
類似於 try…catch…finally 的 finally。
常用於處理成對的操作,如打開/關閉檔案、獲取/釋放鎖、連接/斷開連接等,確保資源被適當釋放,即使在發生錯誤或提前返回的情況下也能保證執行。
如果一個函式中有多個 defer 語句,類似於堆疊(Stack),以 LIFO(後進先出)順序執行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import "fmt"
func deferDemo() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}
func main() {
deferDemo()
}
/*
* 輸出
* 4
* 3
* 2
* 1
*/
|
recover
執行時 panic 異常一旦被引發就會導致程式崩潰,recover 為用於「攔截」執行時 panic 的內建函式,類似 Java 的 try…catch 的抓取異常。
recover 只有在 defer 呼叫的函式中有效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| package main
import "fmt"
func deferDemo(i int) {
var arr [10]int
// 錯誤攔截在錯誤發生前設置
defer func() {
// 設置 recover 攔截錯誤資訊
err := recover()
if err != nil {
fmt.Println(err)
}
}()
arr[i] = 10
}
func main() {
deferDemo(10)
fmt.Println("main code")
}
/*
* 輸出
* runtime error: index out of range [10] with length 10
* main code
*/
|