Go 可變引數函式
12. 可變引數函式
什麼是可變引數函式
可變引數函式是一種引數個數可變的函式。
語法
如果函式最後一個引數被記作 ...T
,這時函式可以接受任意個 T
型別引數作為最後一個引數。
請注意只有函式的最後一個引數才允許是可變的。
通過一些例子理解可變引數函式如何工作
你是否曾經想過 append 函式是如何將任意個引數值加入到切片中的。這樣 append 函式可以接受不同數量的引數。
Copyfunc append(slice []Type, elems ...Type) []Type
上面是 append 函式的定義。在定義中 elems 是可變引數。這樣 append 函式可以接受可變化的引數。
讓我們建立一個我們自己的可變引數函式。我們將寫一段簡單的程式,在輸入的整數列表裡查詢某個整數是否存在。
Copypackage 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
不在切片中,則列印提示未找到該數字。
上面程式碼的輸出值如下,
Copytype 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
切片。
給可變引數函式傳入切片
下面例子中,我們給可變引數函式傳入一個切片,看看會發生什麼。
Copypackage 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
函式的說明如下,
func find(num int, nums ...int)
由可變引數函式的定義可知,nums ...int
意味它可以接受 int
型別的可變引數。
在上面程式的第 23 行,nums
作為可變引數傳入 find
函式。前面我們知道,這些可變引數引數會被轉換為 int
型別切片然後在傳入 find
函式中。但是在這裡 nums
已經是一個 int 型別切片,編譯器試圖在 nums
基礎上再建立一個切片,像下面這樣
find(89, []int{nums})
這裡之所以會失敗是因為 nums
是一個 []int
型別 而不是 int
型別。
那麼有沒有辦法給可變引數函式傳入切片引數呢?答案是肯定的。
有一個可以直接將切片傳入可變引數函式的語法糖,你可以在在切片後加上 ... 字尾。如果這樣做,切片將直接傳入函式,不再建立新的切片
在上面的程式中,如果你將第 23 行的 find(89, nums)
替換為 find(89, nums...)
,程式將成功編譯並有如下輸出
type of nums is []int
89 found at index 0 in [89 90 95]
下面是完整的程式供您參考。
Copypackage 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...)
}
不直觀的錯誤
當你修改可變引數函式中的切片時,請確保你知道你正在做什麼。
下面讓我們來看一個簡單的例子。
Copypackage 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
,這樣程式產生了下面的輸出值
[Go world]
這裡還有一個例子來理解可變引數函式。
Copypackage 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)
}