Go教程第九篇:可變參數(shù)函數(shù)

可變參數(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!

備注
本文系翻譯之作原文博客地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚕冬,一起剝皮案震驚了整個(gè)濱河市免猾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囤热,老刑警劉巖猎提,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旁蔼,居然都是意外死亡锨苏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門棺聊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞租,“玉大人,你說我怎么就攤上這事限佩】” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵犀暑,是天一觀的道長驯击。 經(jīng)常有香客問我,道長耐亏,這世上最難降的妖魔是什么徊都? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮广辰,結(jié)果婚禮上暇矫,老公的妹妹穿的比我還像新娘。我一直安慰自己择吊,他們只是感情好李根,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著几睛,像睡著了一般房轿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上所森,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天囱持,我揣著相機(jī)與錄音,去河邊找鬼焕济。 笑死纷妆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晴弃。 我是一名探鬼主播掩幢,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼逊拍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了际邻?” 一聲冷哼從身側(cè)響起芯丧,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎世曾,沒想到半個(gè)月后注整,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡度硝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寿冕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕊程。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驼唱,靈堂內(nèi)的尸體忽然破棺而出藻茂,到底是詐尸還是另有隱情,我是刑警寧澤玫恳,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布辨赐,位于F島的核電站,受9級(jí)特大地震影響京办,放射性物質(zhì)發(fā)生泄漏掀序。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一惭婿、第九天 我趴在偏房一處隱蔽的房頂上張望不恭。 院中可真熱鬧,春花似錦财饥、人聲如沸换吧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撕蔼,卻和暖如春鲸沮,著一層夾襖步出監(jiān)牢的瞬間讼溺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留视译,地道東北人酷含。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像呀舔,于是被迫代替她去往敵國和親别威。 傳聞我的和親對(duì)象是個(gè)殘疾皇子省古,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容