Go泛型系列:提前掌握Go泛型的基本使用
現(xiàn)在已經(jīng)確認(rèn),Go1.18 正式包含泛型(Go1.17 已經(jīng)可以試用,只是默認(rèn)不支持,見(jiàn)之前的文章:揚(yáng)眉吐氣:剛剛,Go 已經(jīng)默認(rèn)支持泛型了 )。
不過(guò),不少人對(duì)泛型還是迷迷糊糊的。本文就嘗試用簡(jiǎn)單的術(shù)語(yǔ)解釋泛型相關(guān)的內(nèi)容。
# 01 什么是泛型
Go 是一門(mén)強(qiáng)類型語(yǔ)言,意味著程序中的每個(gè)變量和值都有某種特定的類型,例如int、string 等。在函數(shù)簽名中,我們需要對(duì)參數(shù)和返回值指定類型,如下所示:
func?Add(a,?b?int)?int
參數(shù) a 和 b 的類型是 int,返回值類型也是 int,結(jié)果是 a 和 b 的和。
如果現(xiàn)在需要一個(gè)對(duì)兩個(gè) float64 求和的函數(shù),怎么辦?
大概率會(huì)出現(xiàn)類似這樣的函數(shù):
func?AddFloat(a,?b?float64)?float64
如果有更多其他的類型(比如字符串相加),可能需要寫(xiě)更多的對(duì)應(yīng)版本函數(shù),很不方便,也很繁瑣,一堆復(fù)制粘貼的代碼。
# 02 Go 中的泛型函數(shù)
如果有了泛型,上面的問(wèn)題怎么解決呢?只需要一個(gè)函數(shù)就搞定:
func?Add[T?any](a,?b?T)?T
是不是很簡(jiǎn)單?不過(guò)看著有點(diǎn)暈?稍微解釋下:
Add 后面的
[T any],T 表示類型的標(biāo)識(shí),any 表示 T 可以是任意類型a、b 和返回值的類型 T 和前面的 T 是同一個(gè)類型
為什么用
[],而不是其他語(yǔ)言中的<>,官方有過(guò)解釋,大概就是<>會(huì)有歧義。曾經(jīng)計(jì)劃使用(),因?yàn)樘菀谆煜?,最后使用?[]。
這樣就表示,a、b 和返回值可以是任意類型,但它們的類型是同一個(gè)。那具體是什么類型如何確定呢?根據(jù)調(diào)用時(shí)的實(shí)際參數(shù)決定。因此,我們現(xiàn)在可以這么使用:
Add(1,?2)
Add(2.1,?3.2)
不過(guò),這時(shí)候代碼會(huì)報(bào)錯(cuò)。你可以本地用 Go1.17 啟用泛型的方式試驗(yàn),也可以使用 gotip 版本,亦或直接訪問(wèn)這里試驗(yàn):https://go2goplay.golang.org/p/vTHnUA_8vOI
package?main
import?(
????"fmt"
)
func?Add[T?any](a,?b?T)?T?{
????return?a?+?b
}
func?main()?{
????fmt.Println(Add(1,?2))
????fmt.Println(Add(2.1,?3.2))
}
運(yùn)行會(huì)報(bào)錯(cuò):
type?checking?failed?for?main
prog.go2:8:9:?invalid?operation:?operator?+?not?defined?for?a?(variable?of?type?parameter?type?T)
為什么?請(qǐng)看下文。
# 03 約束
很顯然,并非所有類型都支持加法操作。因此我們需要給出約束,指定可以進(jìn)行加法操作的類型。
上面代碼中,我們對(duì)類型 T 使用的是 any,相當(dāng)于沒(méi)有進(jìn)行任何約束?,F(xiàn)在我們給一個(gè)約束:
type?Addable?interface?{
????type?int,?int8,?int16,?int32,?int64,?uint,?uint8,?uint16,?uint32,?uint64,?uintptr,?float32,?float64,?complex64,?complex128,?string
}
這是新語(yǔ)法,叫做類型列表(type list)。
首先,Addable 重用了接口語(yǔ)法,即 interface 關(guān)鍵字,表示約束,具體約束的類型通過(guò) type 指定,多個(gè)用逗號(hào)分隔。
現(xiàn)在 Add 函數(shù)中 T 的約束從 any 改為 Addable:
func?Add[T?Addable](a,?b?T)?T?{
????return?a?+?b
}
現(xiàn)在再次運(yùn)行:https://go2goplay.golang.org/p/4J52QmGrc-M ,發(fā)現(xiàn)正常了。而且還支持字符串、復(fù)數(shù)等:
Add("polaris",?"xu")
可見(jiàn),約束可以是任意接口類型。(any 相當(dāng)于空接口)
還有另外一種場(chǎng)景:可比較。比如 map 中的 key 要求是可比較的。比如下面的代碼:
func?findFunc[T?any](a?[]T,?v?T)?int?{????for?i,?e?:=?range?a?{???????if??e?==?v?{??????return?i????}?}???return?-1?}
T 的約束是任意類型,而實(shí)際上并非所有類型都是可比較的。怎么辦?我們當(dāng)然可以向上面 Addable 一樣定義一個(gè)約束,但為了方便,Go 內(nèi)置提供了一個(gè) comparable 約束,表示可比較的。參考下面代碼:
package?mainfunc?findFunc[T?comparable](a?[]T,?v?T)?int?{????for?i,?e?:=?range?a?{???????if?e?==?v?{?????????return?i????????}???}???return?-1}func?main()?{?print(findFunc([]int{1,?2,?3,?4,?5,?6},?5))}
# 04 constraints 包
寫(xiě)泛型代碼時(shí),約束挺常見(jiàn)。再看一個(gè)例子,從切片中找出最大值:
func?Max[T?any](input?[]T)?(max?T)?{
????for?_,?v?:=?range?input?{
????????if?v?>?max?{
????????????max?=?v
????????}
????}
????return
}
但運(yùn)行會(huì)報(bào)錯(cuò):
fmt.Println(Max([]int{1,?4,?2,?10}))
//?cannot?compare?v?>?max?(operator?>?not?defined?for?T)
這時(shí),我們自然想到使用上面 Add 函數(shù)類似的辦法,自定義一個(gè)約束:Ordered,把可能的類型都列上。
type?Ordered?interface?{
????type?int,?int8,?int16,?int32,?int64,?uint,?uint8,?uint16,?uint32,?uint64,?uintptr,?float32,?float64,?string
}
因?yàn)檫@樣的需求挺常見(jiàn)的,為了方面,官方提供了一個(gè)新包:constraints,預(yù)定義了一些約束,具體查看:https://github.com/golang/go/issues/45458 。
有了它,不需要自定義這個(gè) Ordered 約束,而是使用 constraints 包中的,即:
func?Max[T?constraints.Ordered](input?[]T)?(max?T)
# 05 泛型類型
上面,我們介紹了泛型函數(shù):即函數(shù)可以接受任意類型。注意和 interface{} 這樣的任意類型區(qū)分開(kāi),泛型中的類型,在函數(shù)內(nèi)部并不需要做任何類型斷言和反射的工作,在編譯期就可以確定具體的類型。
我們知道,Go 支持自定義類型,比如標(biāo)準(zhǔn)庫(kù) sort 包中的 IntSlice:
type?IntSlice?[]int
此外,還有 StringSlice、Float64Slice 等,一堆重復(fù)代碼。如果我們能夠定義泛型類型,就不需要定義這么多不同的類型了。比如:
type?Slice[T?any]?[]T
能看懂吧。
在使用時(shí),針對(duì) int 類型,就是這樣:
x?:=?Slice[int]{1,?2,?3}
如果作為函數(shù)參數(shù),這么使用:
func?PrintSlice[T?any](b?Slice[T])
如果為這個(gè)類型定義方法,則是這樣:
func?(b?Slice[T])?Print()
也就是說(shuō),Slice[T] 作為整體存在。
當(dāng)然,泛型類型也可以做類型約束,而不是 any 類型:
type?Slice[T?comparable]?[]T
# 06 總結(jié)
通過(guò)本文的講解,相信你對(duì) Go 泛型有了一個(gè)基本的掌握。
Go1.18 會(huì)包含不少泛型相關(guān)的標(biāo)準(zhǔn)庫(kù),包括對(duì)現(xiàn)有標(biāo)準(zhǔn)庫(kù)的泛型支持,這是目前 Go 官方的重要工作。
今天開(kāi)一個(gè)頭,后續(xù)會(huì)不斷分享 Go 泛型更多的內(nèi)容,大家一起提前掌握 Go 泛型。
? ?

???
