1. 程式人生 > 其它 >Go 可變引數函式

Go 可變引數函式

12. 可變引數函式

什麼是可變引數函式

可變引數函式是一種引數個數可變的函式。

語法

如果函式最後一個引數被記作 ...T ,這時函式可以接受任意個 T 型別引數作為最後一個引數。

請注意只有函式的最後一個引數才允許是可變的。

通過一些例子理解可變引數函式如何工作

你是否曾經想過 append 函式是如何將任意個引數值加入到切片中的。這樣 append 函式可以接受不同數量的引數。

Copy
func append(slice []Type, elems ...Type) []Type

上面是 append 函式的定義。在定義中 elems 是可變引數。這樣 append 函式可以接受可變化的引數。

讓我們建立一個我們自己的可變引數函式。我們將寫一段簡單的程式,在輸入的整數列表裡查詢某個整數是否存在。

Copy
package main

import (
    "fmt"
)

func find(num int, nums ...int) {
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
} } if !found { fmt.Println(num, "not found in ", nums) } fmt.Printf("\n") } func main() { find(89, 89, 90, 95) find(45, 56, 67, 45, 90, 109) find(78, 38, 56, 98) find(87) }

在上面程式中 func find(num int, nums ...int) 中的 nums 可接受任意數量的引數。在 find 函式中,引數 nums 相當於一個整型切片。

可變引數函式的工作原理是把可變引數轉換為一個新的切片。以上面程式中的第 22 行為例,find 函式中的可變引數是 89,90,95 。 find 函式接受一個 int 型別的可變引數。因此這三個引數被編譯器轉換為一個 int 型別切片 int []int{89, 90, 95} 然後被傳入 find函式。

在第 10 行, for 迴圈遍歷 nums 切片,如果 num 在切片中,則列印 num 的位置。如果 num 不在切片中,則列印提示未找到該數字。

上面程式碼的輸出值如下,

Copy
type of nums is []int
89 found at index 0 in [89 90 95]

type of nums is []int
45 found at index 2 in [56 67 45 90 109]

type of nums is []int
78 not found in  [38 56 98]

type of nums is []int
87 not found in  []

在上面程式的第 25 行,find 函式僅有一個引數。我們沒有給可變引數 nums ...int 傳入任何引數。這也是合法的,在這種情況下 nums 是一個長度和容量為 0 的 nil 切片。

給可變引數函式傳入切片

下面例子中,我們給可變引數函式傳入一個切片,看看會發生什麼。

Copy
package main

import (
    "fmt"
)

func find(num int, nums ...int) {
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {
    nums := []int{89, 90, 95}
    find(89, nums)
}

在第 23 行中,我們將一個切片傳給一個可變引數函式。

這種情況下無法通過編譯,編譯器報出錯誤 main.go:23: cannot use nums (type []int) as type int in argument to find

為什麼無法工作呢?原因很直接,find 函式的說明如下,

Copy
func find(num int, nums ...int)

由可變引數函式的定義可知,nums ...int 意味它可以接受 int 型別的可變引數。

在上面程式的第 23 行,nums 作為可變引數傳入 find 函式。前面我們知道,這些可變引數引數會被轉換為 int 型別切片然後在傳入 find 函式中。但是在這裡 nums 已經是一個 int 型別切片,編譯器試圖在 nums 基礎上再建立一個切片,像下面這樣

Copy
find(89, []int{nums})

這裡之所以會失敗是因為 nums 是一個 []int型別 而不是 int型別。

那麼有沒有辦法給可變引數函式傳入切片引數呢?答案是肯定的。

有一個可以直接將切片傳入可變引數函式的語法糖,你可以在在切片後加上 ... 字尾。如果這樣做,切片將直接傳入函式,不再建立新的切片

在上面的程式中,如果你將第 23 行的 find(89, nums) 替換為 find(89, nums...) ,程式將成功編譯並有如下輸出

Copy
type of nums is []int
89 found at index 0 in [89 90 95]

下面是完整的程式供您參考。

Copy
package main

import (
    "fmt"
)

func find(num int, nums ...int) {
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {
    nums := []int{89, 90, 95}
    find(89, nums...)
}

不直觀的錯誤

當你修改可變引數函式中的切片時,請確保你知道你正在做什麼。

下面讓我們來看一個簡單的例子。

Copy
package main

import (
    "fmt"
)

func change(s ...string) {  
    s[0] = "Go"
}

func main() {
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}

你認為這段程式碼將輸出什麼呢?如果你認為它輸出 [Go world] 。恭喜你!你已經理解了可變引數函式和切片。如果你猜錯了,那也不要緊,讓我來解釋下為什麼會有這樣的輸出。

在第 13 行,我們使用了語法糖 ... 並且將切片作為可變引數傳入 change 函式。

正如前面我們所討論的,如果使用了 ...welcome 切片本身會作為引數直接傳入,不需要再建立一個新的切片。這樣引數 welcome 將作為引數傳入 change 函式

change 函式中,切片的第一個元素被替換成 Go,這樣程式產生了下面的輸出值

Copy
[Go world]

這裡還有一個例子來理解可變引數函式。

Copy
package main

import (
    "fmt"
)

func change(s ...string) {
    s[0] = "Go"
    s = append(s, "playground")
    fmt.Println(s)
}

func main() {
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}