[譯] part 12: goalng 變參函式

咔嘰咔嘰發表於2019-04-18

什麼是變參函式

變參函式是一個可以接受引數數量可變的函式。

語法

如果函式的最後一個引數用...T表示,那麼該函式最後一個引數可以接受任意數量的T型別的引數。

注意,只允許函式的最後一個引數為可變引數。

示例

append函式可以增加任意數量的引數給切片。變參函式就是用到了切片的這個原理。

func append(slice []Type, elems ...Type) []Type  
複製程式碼

以上是append函式的定義。在這個定義中,elems是一個可變引數。通過這個語法,append可以接受可變數量的引數。

來建立一個變參函式。我們將編寫一個查詢輸入列表中是否存在指定整數的簡單程式。

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)
}
複製程式碼

Run in playground

在上面的程式中,func find(num int, nums ...int)接收可變數量的引數nums。在函式find中,nums的型別等價於[]int,即整數切片。

變參函式的原理是將傳遞的可變引數轉換為一個新切片。例如,在上面程式的第 22 中,find函式的可變引數是 89, 90, 95。find函式需要一個可變的int引數。因此,這三個引數將由編譯器轉換為 int 型的[] int {89,90,95}切片,然後它將被傳遞給find函式。

上述程式將列印,

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是一個長度和容量都為 0 的切片。

使用切片作為變參函式的引數

我們將一個切片傳遞給一個變參函式,並看看下面的例子會發生什麼。

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)
}
複製程式碼

Run in playground

在第 23 行,我們將一個切片傳遞給一個變參函式。

上面的程式將會出現編譯錯誤main.go:23: cannot use nums (type []int) as type int in argument to find

為什麼這個例子不能執行呢?因為find函式的簽名如下所示,

func find(num int, nums ...int) 
複製程式碼

根據變參函式的定義,nums ...int意味著它將接受 int 型別的可變數量的引數。

在第 23 行中,nums作為可變引數傳遞給find函式。正如我們已經討論過的,這些可變引數將被轉換為 int 型別的切片,因為 find 需要可變的 int 引數。在這個例子中,nums已經是一個 int 切片了,並且還嘗試使用 nums 去建立切片,即編譯器嘗試執行

find(89, []int{nums}) 
複製程式碼

因為 nums 是[] int而不是 int,所以會失敗。

那麼有沒有辦法將切片傳遞給可變引數函式?當然可以。

有一個語法糖可用於將切片傳遞給變參函式。就是在切片後使用...。如果這樣做,切片將直接傳遞給函式,而不會建立新切片。

上述程式碼中,我們用find(89, nums...)替換find(89, nums),程式將執行並列印,

type of nums is []int  
89 found at index 0 in [89 90 95]  
複製程式碼

這是完整的程式供參考。

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...)
}
複製程式碼

Run inplayground

注意事項

當你在變參函式中修改切片時,請確保知道自己在做什麼。

來看個例子。


package main

import (  
    "fmt"
)

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

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

Run in playground

你覺得上面的程式碼會輸出什麼?如果你覺得是 [Go world],那麼恭喜你!你已經理解了變參函式和切片。如果你弄錯了,沒什麼大不了的,讓我解釋一下我們為什麼是這個輸出。

上面程式的第 13 行,我們使用語法糖...並將切片作為可變引數傳遞給change函式。

正如我們已經討論的那樣,如果使用了...welcome切片本身將作為引數傳遞,而不會建立新的切片。因此welcome將作為引數傳遞給change函式。

change函式內部,切片的第一個元素更改為 Go。因此該程式輸出

[Go world]
複製程式碼

這是另一個瞭解變參函式的程式。

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)
}
複製程式碼

Run in playground

這個作為練習讓你弄清楚上面的程式是如何工作的:)。

相關文章