5.6 函式

基礎語法

  • 呼叫同一套件的函式,只要寫函式名稱。
  • 呼叫其他套件提供的(exported)函式,則須使用 <package>.<function> 語法。

範例:

// 定義一個函式,名稱為 hello,沒有傳入參數,也沒有回傳值。
func hello() {
    fmt.Println("Hello, world!")
}

func main() {
    hello() // 呼叫 hello 函式。
}

函式與回傳值

本節內容:

  • 撰寫具名函式。
  • 撰寫匿名函式。

具名函式

以下兩種寫法都可以:

func parse1(filename string) ([]string, []byte, error )
func parse2(filename string) ([]string headers, []byte body, err error)

兩者差異在於回傳值的寫法:parse1 沒有預先宣告回傳的參數名稱,parse2 則有。

《100 Go Mistakes and How to Avoid Theme》書中的建議(#43: Never using named result parameters)如下:

  • 如果不至於令程式碼不好理解,應避免使用具名的回傳參數。
  • 有些情況,例如介面方法,或者函式的回傳參數有兩個以上的參數型別相同,使得閱讀程式碼的時候不容易辨識,則建議使用具名的回傳參數。

簡單來說,要不要使用具名的回傳參數,應該以程式碼的可讀性為主要考量。

值得留意的是,採用具名回傳參數的寫法時,那些回傳參數即等同於一進入函式就初始為 0 或 nil 的區域變數。所以函式返回時的 return 敘述可以不用寫回傳參數,像這樣:

func foo(a int) (b int)
{
    b = a
    return // naked return
}

這種寫法叫做 naked return

Naked return 的寫法應該只用於簡短的函式,因為 return 時沒有寫明回傳的參數,程式碼比較不好理解。至於多簡短的函式才比較能讓人接受 naked return 的寫法,則沒有標準,大原則是以程式碼是否容易閱讀和維護來決定。

如果一定要有一個具體準則,這裡給出一個參考依據:當一個函式的程式碼超出編輯器的顯示區域,而必須上下捲動才能看到完整的程式碼,則此函式可能不易「一看就懂」,故應避免使用具名的回傳參數和 naked return 語法。久而久之,可能自然而然就不會使用 naked return 了。

具名回傳參數的副作用

使用具名回傳參數時,可能一不留神就寫出類似底下的程式碼:

func parse(ctx AppContext) (title string, err error)
{
    if ctx.CheckError() != nil {
        return "", err
    }

    // ...
}

上面的程式碼可以通過編譯,但是有一個 bug:永遠不會回傳錯誤,因為 err 在一進入函式的時候就已經初始為 nil 了。底下是正確的寫法:

func parse(ctx AppContext) (title string, err error)
{
    if err := ctx.CheckError(); err != nil {
        return "", err
    }

    // ...
}

匿名函式

func main() {
    // 撰寫匿名函式,並指派給變數 f
    f := func() {
        fmt.Println("Inside a function")
    }

    f()  // 呼叫 f 指向的函式
}

init 函式

Go 有一個特殊用途的函式,名稱固定叫做 init()。此函式會在一個 package 載入時自動執行,故通常會把一些初始化的操作寫在此函式中。

特性與用法:

  • init 函式不能有參數和回傳值。
  • 每個 package 可以有多個 init 函式,而各 packages 的 init 函式的執行順序便是按照 packages 之間的依賴關係來決定。換言之,先載入哪個 package,就會先執行那個 package 的 init 函式。
  • 每個 .go 檔案也可以有多個 init 函式,這些函式會按照它們在檔案中出現的順序執行。但一般來說,通常不會在一個 .go 檔案裡面寫多個 init 函式,以免讓程式碼更難理解和維護。
  • 應避免在 init 函式中執行耗時工作。

範例:

var greeting string

func init() {
    fmt.Println("Hello")
}

func main() {
    fmt.Println("world")
}

執行結果:

Hello
world

Using init() as side effects

有時候,我們的程式不會呼叫某個套件裡面的任何函式或變數,但是卻需要應用程式載入時執行那個套件的 init() 函式,以便完成某些預設的配置或初始化操作。碰到這種情形,就必須在 import 該套件時使用 blank identifier (_),像這樣:

import (
    _ "github.com/lib/pq"
    _ "image/png"
    ...
)

這種在引用套件的名稱前面加一個底線字元的寫法稱為 importing side effects。換言之,那些套件的 init() 函式便是我們的應用程式所需要的「副作用」。

Note

如果應用程式沒有直接使用某個套件裡面的任何東西,該套件名稱就不能出現在 import 區塊裡,除非前面加上底線字元 _ 字元來告訴 Go 編譯器:我需要這個套件載入時自動執行的 init()


先這樣,也許有空時會再更新。   我的其他站點:      

Last modified: 2024-10-15