建立函數

已完成

在 Go 中,函式可讓您將可從應用程式的其他部分呼叫的一組陳述式分組在一起。 您可以使用函式來組織程式碼並使其更容易讀取,而不是建立具有許多陳述式的程式。 容易讀取的程式碼也較容易維護。

到目前為止,我們已呼叫函式 fmt.Println(),並且在 main() 函式中撰寫程式碼。 在本節中,我們將探討如何建立自訂函式。 我們也將討論可在 Go 中用於函式的一些其他技巧。

Main 函式

您持續與之互動的函式是 main() 函式。 Go 中的所有可執行程式都有此函式,因為這是程式的起點。 您的程式中只能有一個 main() 函式。 如果您要建立 Go 套件,則無須撰寫 main() 函式。 我們將在後續的課程模組中探討如何建立套件。

在開始了解在 Go 中建立自訂函式的基本概念之前,我們先看看 main() 函式的一個重要層面。 您可能已注意到,main() 函式沒有任何參數,也不會傳回任何內容。 但這並不表示它無法讀取使用者的值,例如命令列引數。 如果您需要在 Go 中存取命令列引數,您可以使用 os 套件os.Args 變數來執行,以保存所有傳遞至程式的引數。

下列程式碼會從命令列讀取兩個數字,並將其加總:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    number1, _ := strconv.Atoi(os.Args[1])
    number2, _ := strconv.Atoi(os.Args[2])
    fmt.Println("Sum:", number1+number2)
}

os.Args 變數會保存傳遞至程式的每個命令列引數。 由於這些值屬於 string 類型,您必須將其轉換成 int 以便加總。

若要執行程式,請使用下列命令:

go run main.go 3 5

輸出如下:

Sum: 8

我們來看看如何重構上述程式碼,並建立我們的第一個自訂函式。

自訂函式

以下是建立函式的語法:

func name(parameters) (results) {
    body-content
}

請注意,您可以使用 func 關鍵字來定義函式,然後為其指派名稱。 在名稱之後,您可以指定函式的參數清單。 可能會有零或多個參數。 您也可以定義函式的傳回類型,而這些類型也可能有零或多個。 (我們將在下一節討論如何傳回多個值)。在這些值全部定義後,您可以撰寫函式的本文內容。

為了練習這項技術,我們將重構上一節的程式碼,以加總自訂函式中的數字。 我們將使用下列程式碼:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum := sum(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

func sum(number1 string, number2 string) int {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    return int1 + int2
}

此程式碼會建立一個採用兩個 string 引數的函式 sum,並將其轉換成 int,然後傳回加總的結果。 當您定義傳回類型時,您的函式必須傳回該類型的值。

在 Go 中,您也可以將名稱設定為函式的傳回值,如同是變數一樣。 例如,您可以重構 sum 函式,如下所示:

func sum(number1 string, number2 string) (result int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    result = int1 + int2
    return
}

請注意,現在您需要將函式的結果值括在括弧中。 您也可以在函式內使用變數,且可以直接在結尾處加上 return 行。 Go 會傳回這些傳回變數的現行值。 在函式結尾處撰寫 return 關鍵字是很容易的做法,在有多個傳回值時尤其吸引人。 我們不建議此方法。 函式傳回的內容可能不清楚。

傳回多個值

在 Go 中,函式可以傳回多個值。 您可以用定義函式參數的類似方式來定義這些值。 換句話說,您可以指定類型和名稱,但名稱是選用的。

例如,假設您想要建立會加總兩個數字、但同時加以相乘的函式。 函式程式碼將如下所示:

func calc(number1 string, number2 string) (sum int, mul int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    sum = int1 + int2
    mul = int1 * int2
    return
}

現在,您需要兩個變數來儲存函式的結果。 (無法以其他方式編譯)。其內容如下所示:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, mul := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
    fmt.Println("Mul:", mul)
}

Go 中另一項有趣的功能是,如果您不需要函式的其中一個傳回值,您可以將傳回的值指派給 _ 變數而加以捨棄。 _ 變數是 Go 忽略傳回值的慣用方式。 這可讓程式進行編譯。 因此,如果您只要加總值,可以使用下列程式碼:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, _ := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

在後續的課程模組中探索錯誤處理方式時,我們將進一步探討如何忽略函式的傳回值。

變更函式參數值 (指標)

當您將值傳至函式時,該函式中的每個變更都不會影響到呼叫者。 Go 是一種「以值傳遞」的程式設計語言。 每當您將值傳至函式時,Go 都會取用該值,並建立本機複本 (記憶體中的新變數)。 您在函式中對該變數所做的變更,不會影響到您傳送至函式的變數。

例如,假設您建立了會更新人員名稱的函式。 請留意您執行下列程式碼時發生的情況:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(firstName)
    fmt.Println(firstName)
}

func updateName(name string) {
    name = "David"
}

即使您已將函式中的名稱變更為 "David",輸出仍是 "John"。由於 updateName 函式中的變更僅修改了本機複本,因此輸出並未變更。 Go 傳遞了變數的值,而非變數本身。

如果您想要讓 updateName 函式中所做的變更影響 main 函式中的 firstName 變數,則必須使用指標。 指標是一個變數,其包含另一個變數的記憶體位址。 當您將指標傳送至函式時,您不會傳遞值,您會傳遞記憶體位址。 因此,您對該變數所做的每個變更都會影響到呼叫者。

在 Go 中,有兩個用來處理指標的運算子:

  • & 運算子會取用其後物件的位址。
  • * 運算子會取值指標。 其可讓您存取指標所含位址上的物件。

我們將修改先前的範例,以闡明指標的運作方式:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(&firstName)
    fmt.Println(firstName)
}

func updateName(name *string) {
    *name = "David"
}

執行上述程式碼。 請注意,輸出現在會顯示 David,而不是 John

您的首要工作是修改函式的簽章,以指出您想要接收指標。 為此,請將參數類型從 string 變更為 *string。 (後者仍是字串,但現在是字串 的 指標)。接著,當您將新值指派給該變數時,您必須在變數的左側加上星號 (*),以產生該變數的值。 您在呼叫 updateName 函式時並不會傳送值,而是會傳送變數的記憶體位址。 變數左邊的 & 符號表示變數的位址。