GoLang 函式

📢 本文由 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 匯入也只會匯入一次,下圖為執行順序:

image

例子

假設結構如下:

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
 */