可變參數(shù)函數(shù)
歡迎來到《Golang系列教程》的第九篇文章---可變參數(shù)函數(shù)。
什么是可變參數(shù)函數(shù) ?
通常情況下榄审,函數(shù)只接受固定長度的實(shí)參作為參數(shù)。而 可變參數(shù)函數(shù)(a variadic function) 可以接收不定數(shù)量的實(shí)參杆麸。如果最后一個(gè)參數(shù)的前綴是三個(gè)省略號(hào)"..."搁进,那么對(duì)于對(duì)那個(gè)參數(shù)而言,這個(gè)函數(shù)就可以接收任意數(shù)量的實(shí)參昔头。
只有函數(shù)的最后一個(gè)參數(shù)可以是可變的饼问。我們將在這篇文章的下一部分中學(xué)習(xí)到這究竟是為什么。
Syntax語法
func hello(a int,b ... int){
}
在上面的函數(shù)中揭斧,參數(shù)b是可變的莱革,因?yàn)樗星熬Y"..."。所以參數(shù)b可以接收任意數(shù)量的實(shí)參讹开。該函數(shù)可以用下面這樣的語法調(diào)用:
hello(1, 2) //passing one argument "2" to b
hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b
在上面這個(gè)代碼片中盅视,第一行我們調(diào)用了hello()函數(shù)并把2傳遞給參數(shù)b。在第二行代碼中旦万,我們把6,7,8,9這四個(gè)實(shí)參傳遞給參數(shù)b闹击。
對(duì)于一個(gè)可變參數(shù)函數(shù),我們也可以不傳參數(shù)成艘。
hello(1)
在上面的代碼中拇砰,我們調(diào)用了hello函數(shù),并且沒有給b傳遞任何實(shí)參狰腌。
我想現(xiàn)在你或許明白了,為什么只有函數(shù)的最后一個(gè)參數(shù)可以作為可變參數(shù)了牧氮。
如果我們讓hello()函數(shù)的第一個(gè)參數(shù)作為可變參數(shù)的話琼腔,那么語法結(jié)構(gòu)將會(huì)是下面的這樣:
func hello(b ...int, a int) {
}
對(duì)于上面的函數(shù),無法將一個(gè)實(shí)參傳遞給a,因?yàn)榇撕瘮?shù)的第一個(gè)參數(shù)b是可變參數(shù)踱葛,所以我們傳遞過去的值都會(huì)被b接收丹莲。故而光坝,可變參數(shù)只能是函數(shù)的最后一個(gè)參數(shù)。上面的函數(shù)編譯器也會(huì)報(bào)錯(cuò):syntax error: cannot use ... with non-final parameter b甥材。
案例以及理解可變參數(shù)函數(shù)的工作原理
我們創(chuàng)建一個(gè)自己的可變參數(shù)函數(shù)盯另。我們寫一個(gè)程序,判斷在傳遞的多個(gè)整數(shù)參數(shù)中洲赵,某個(gè)整型數(shù)是否存在鸳惯。
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形參可以接收多個(gè)實(shí)參叠萍。
在find函數(shù)的內(nèi)部芝发,nums的數(shù)據(jù)類型是[]int,比如:整型的slice苛谷。
程序的輸出結(jié)果如下:
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(87)辅鲸,find的函數(shù)調(diào)用只傳遞一個(gè)實(shí)參。我們并沒有傳遞任何實(shí)參給nums... int腹殿。正如之前討論過的独悴,這樣做完全是合法的。在本例中锣尉,nums將會(huì)是一個(gè)nil slice刻炒,其長度和容量都是0。
Slice實(shí)參 VS Variadic實(shí)參
你的心里肯定逗留著這樣一個(gè)疑問悟耘。在上面的章節(jié)中落蝙,我們已經(jīng)了解到一個(gè)函數(shù)的可變參數(shù)實(shí)際上是被轉(zhuǎn)換成了一個(gè)slice。那么暂幼,為什么我們還需要可變參數(shù)函數(shù)呢筏勒,我們直接使用slice實(shí)現(xiàn)相同的功能不就行了嗎?下面我會(huì)用slice重寫上述程序:
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, []int{89, 90, 95})
find(45, []int{56, 67, 45, 90, 109})
find(78, []int{38, 56, 98})
find(87, []int{})
}
從上面的程序旺嬉,我們或許可以看出相比于使用slice管行,可變參數(shù)函數(shù)能帶來哪些好處:
. 1
減少了每次函數(shù)調(diào)用都需要?jiǎng)?chuàng)建slice的麻煩,如果你查看上面的程序邪媳,你就可以看到 在第22行捐顷,23行,24行雨效,25行迅涮,這樣每次的函數(shù)調(diào)用,我們都需要?jiǎng)?chuàng)建一個(gè)新的slice徽龟。而使用可變參數(shù)函數(shù)就可以避免slice的創(chuàng)建叮姑,從而簡化代碼的開發(fā)。
. 2
在上述程序的第25行即:find(87, []int{}),我們創(chuàng)建了一個(gè)空數(shù)組传透,就是為了滿足find的函數(shù)定義耘沼。這在可變參數(shù)函數(shù)中完全是不必的。當(dāng)使用可變參數(shù)函數(shù)時(shí)朱盐,我們只需要寫個(gè)find(87)就行了群嗤。
. 3
在我個(gè)人看來,使用可變參數(shù)的程序相對(duì)于使用slice的程序來說兵琳,可讀性更強(qiáng)狂秘。
Append是一個(gè)可變參數(shù)函數(shù)
你曾經(jīng)想過沒有,當(dāng)使用append函數(shù)向一個(gè)slice中追加值時(shí)闰围,為什么append函數(shù)可以接收任意數(shù)目的實(shí)參赃绊。這是因?yàn)椋琣ppend函數(shù)其實(shí)是一個(gè)可變函數(shù):
func append(slice []Type, elems ...Type) []Type
上面就是append函數(shù)的定義羡榴。在此定義中碧查,elems是一個(gè)可變參數(shù)。因此append可以接收任意多個(gè)實(shí)參校仑。
傳遞slice給可變參數(shù)函數(shù)
把slice傳遞給一個(gè)可變參數(shù)函數(shù)忠售,弄明白下面的程序究竟發(fā)生了些什么事。
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行find(89, nums)迄沫,我們把一個(gè)slice傳遞給了一個(gè)可以接收任意多個(gè)實(shí)參的函數(shù)稻扬。
這是程序?qū)⑹遣缓戏ǖ模诰幾g時(shí)會(huì)報(bào)錯(cuò):./prog.go:23:10: cannot use nums (type []int) as type int in argument to find羊瘩。
為什么不能這樣做呢泰佳??尘吗? 好吧逝她,我們繼續(xù)往下深入。find函數(shù)的聲明是這樣的:
func find(num int, nums ...int)
根據(jù)可變函數(shù)的定義睬捶,nums ... int 意味著黔宛,它可以接收多個(gè)int類型的實(shí)參。
在程序的第23行擒贸,nums是作為[]int的slice被傳遞給find函數(shù)臀晃。而find函數(shù)是期望接收可變的int實(shí)參,我們之前討論過介劫,這些可變的實(shí)參將會(huì)被轉(zhuǎn)換成int類型的slice徽惋。在本例中nums已經(jīng)是一個(gè)[]int的slice了。當(dāng)編譯器試圖創(chuàng)建一個(gè)新的[]int時(shí)座韵,
例如編譯器試圖這樣做時(shí):
find(89, []int{nums})
即: 當(dāng)你試圖把nums放入find函數(shù)新建的[]int中寂曹,讓它作為其中的元素時(shí),將會(huì)失敗,因?yàn)閚ums是一個(gè)[]int而非int隆圆。那么,到底有沒有一種方式可以把一個(gè)slice傳遞給可變參數(shù)函數(shù)呢翔烁? 答案是:yes渺氧。
有一個(gè)語法糖,可以把一個(gè)slice傳遞給可變參數(shù)函數(shù)蹬屹。你必須給slice后綴省略號(hào)"..."侣背,這樣做的話,slice就會(huì)被直接傳遞個(gè)函數(shù)而函數(shù)也不必新建一個(gè)slice慨默。
如果你把上述程序中的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...)
}
Gotcha
當(dāng)你在一個(gè)可變參數(shù)函數(shù)的內(nèi)部修改slice時(shí)厦取,一定要清楚你到底做了什么潮太。我們來看一個(gè)例子:
package main
import (
"fmt"
)
func change(s ...string) {
s[0] = "Go"
}
func main() {
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
}
你認(rèn)為上面程序的輸出結(jié)果是什么? 如果你認(rèn)為是[Go world]的話虾攻,那么铡买,恭喜你,你已經(jīng)理解了可變參數(shù)函數(shù)和slice霎箍。如果你猜錯(cuò)了的話奇钞,也不是什么大問題,我們給你解釋一下漂坏,這是為什么景埃。
在程序的第13行 change(welcome...),我們使用了語法糖"..."顶别,把一個(gè)slice作為change函數(shù)的實(shí)參傳遞給了可變參數(shù)函數(shù)谷徙。
我們之前已經(jīng)討論過,如果使用了"..."的話筋夏,程序會(huì)把welcome這個(gè)slice傳遞給函數(shù)蒂胞,而函數(shù)此時(shí)也不會(huì)創(chuàng)建一個(gè)新的slice。
因此条篷,welcome是作為實(shí)參被傳遞給了change函數(shù)骗随。在這個(gè)change函數(shù)內(nèi)部,slice的第一個(gè)元素被改變?yōu)?Go"赴叹。因此鸿染,程序?qū)⑤敵鋈缦拢?/p>
[Go world]
這里還有一個(gè)程序幫助你理解可變參數(shù)函數(shù):
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)
}
我把這個(gè)程序當(dāng)做練習(xí)留個(gè)你,以幫助你理解上面程序的工作原理乞巧。
以上就是可變參數(shù)函數(shù)的全部內(nèi)容涨椒,感謝您的閱讀,請(qǐng)留下您珍貴的反饋和評(píng)論。Have a good Day!
備注
本文系翻譯之作原文博客地址