Go 泛型包 slices 來了
閱讀本文大概需要 10?分鐘。
大家好,我是 polarisxu。
前段時(shí)間,Russ Cox 明確了泛型相關(guān)的事情,原計(jì)劃在標(biāo)準(zhǔn)庫中加入泛型相關(guān)的包,改放到 golang.org/x/exp 下。
目前,Go 泛型的主要設(shè)計(jì)者 ianlancetaylor 完成了 slices 和 maps 包的開發(fā),代碼提交到了 golang.org/x/exp 中,如果經(jīng)過使用、討論等,社區(qū)認(rèn)可后,預(yù)計(jì)在 1.19 中會合入標(biāo)準(zhǔn)庫中。
今天,通過學(xué)習(xí) slices 包,掌握 Go 泛型的使用方法。
01 為什么增加 slices 包
標(biāo)準(zhǔn)庫有 bytes 和 strings 包,分別用來處理 []byte 和 string 類型,提供了眾多方便的函數(shù),但對普通的 slice,卻沒有相關(guān)的包可以使用。
比如 bytes 和 strings 都有 Index 函數(shù),用來在 []byte 或 string 查找某個(gè) byte 或字符串的索引。對于普通的 slice,沒法寫一大堆包來處理,只能用戶自己實(shí)現(xiàn),這也是沒有泛型的弊端。
提供 bytes 和 strings,主要是因?yàn)樗鼈兪褂妙l率高
現(xiàn)在有了泛型,可以實(shí)現(xiàn)一些便利的 slice 操作方法,必須要針對某一個(gè)具體類型的 slice 都實(shí)現(xiàn)一遍相同的功能。
02 constraints 包
繼續(xù)講解 slices 包之前,先看看 contraints 包。
該包定義了一組用于類型參數(shù)(泛型)的有用約束,這個(gè)包已經(jīng)確定在 Go 1.18 標(biāo)準(zhǔn)庫中包含,截止目前(2021.11.27),該包定義了 6 個(gè)約束類型:
//?Signed?is?a?constraint?that?permits?any?signed?integer?type.
//?If?future?releases?of?Go?add?new?predeclared?signed?integer?types,
//?this?constraint?will?be?modified?to?include?them.
type?Signed?interface?{
?~int?|?~int8?|?~int16?|?~int32?|?~int64
}
//?Unsigned?is?a?constraint?that?permits?any?unsigned?integer?type.
//?If?future?releases?of?Go?add?new?predeclared?unsigned?integer?types,
//?this?constraint?will?be?modified?to?include?them.
type?Unsigned?interface?{
?~uint?|?~uint8?|?~uint16?|?~uint32?|?~uint64?|?~uintptr
}
//?Integer?is?a?constraint?that?permits?any?integer?type.
//?If?future?releases?of?Go?add?new?predeclared?integer?types,
//?this?constraint?will?be?modified?to?include?them.
type?Integer?interface?{
?Signed?|?Unsigned
}
//?Float?is?a?constraint?that?permits?any?floating-point?type.
//?If?future?releases?of?Go?add?new?predeclared?floating-point?types,
//?this?constraint?will?be?modified?to?include?them.
type?Float?interface?{
?~float32?|?~float64
}
//?Complex?is?a?constraint?that?permits?any?complex?numeric?type.
//?If?future?releases?of?Go?add?new?predeclared?complex?numeric?types,
//?this?constraint?will?be?modified?to?include?them.
type?Complex?interface?{
?~complex64?|?~complex128
}
//?Ordered?is?a?constraint?that?permits?any?ordered?type:?any?type
//?that?supports?the?operators?<=?>=?>.
//?If?future?releases?of?Go?add?new?ordered?types,
//?this?constraint?will?be?modified?to?include?them.
type?Ordered?interface?{
?Integer?|?Float?|?~string
}
前面 3 個(gè)是整型相關(guān)類型約束,F(xiàn)loat 是浮點(diǎn)型約束,Complex 是負(fù)數(shù)類型約束,而 Ordered 表示支持排序的類型約束,表示支持大小比較的類型。
之前文章:《Go泛型系列:Go1.18 類型約束那些事》提到,約束語法變更了,一個(gè)是 | 符號,一個(gè)是 ~,上面定義中,很多地方都用到了 ~ 符號,它表示出了類型自身,底層類型是它的類型也適用該約束。
03 slices 包詳解
目前,slices 包有 14 個(gè)函數(shù),可以分成幾組:
slice 比較 元素查找 修改 slice 克隆 slice
其中,修改 slice 分為插入元素、刪除元素、連續(xù)元素去重、slice 擴(kuò)容和縮容。
slice 比較
比較兩個(gè) slice 中的元素,細(xì)分為是否相等和普通比較:
func?Equal[E?comparable](s1,?s2?[]E)?bool
func?EqualFunc[E1,?E2?any](s1?[]E1,?s2?[]E2,?eq?func(E1,?E2)?bool)?bool
func?Compare[E?constraints.Ordered](s1,?s2?[]E)?int
func?CompareFunc[E1,?E2?any](s1?[]E1,?s2?[]E2,?cmp?func(E1,?E2)?int)?int
其中 comparable 約束是語言實(shí)現(xiàn)的(因?yàn)楹艹S茫硎究杀容^約束(相等與否的比較)。主要,其中的 E、E1、E2 等,只是泛型類型表示,你定義時(shí),可以用你喜歡的,比如 T、T1、T2 等。
看一個(gè)具體的實(shí)現(xiàn):
func?Equal[E?comparable](s1,?s2?[]E)?bool?{
?if?len(s1)?!=?len(s2)?{
??return?false
?}
?for?i,?v1?:=?range?s1?{
??v2?:=?s2[i]
??if?v1?!=?v2?{
???return?false
??}
?}
?return?true
}
沒有什么特別的,只不過把 s1、s2 當(dāng)成同類型的 slice 進(jìn)行操作而已。
元素查找
在 slice 中查找某個(gè)元素,分為普通的所有查找和包含判斷:
func?Index[E?comparable](s?[]E,?v?E)?int
func?IndexFunc[E?any](s?[]E,?f?func(E)?bool)?int
func?Contains[E?comparable](s?[]E,?v?E)?bool
其中,IndexFunc 的類型參數(shù)沒有使用任何約束(即用的 any),說明查找是通過 f 參數(shù)進(jìn)行的,它的實(shí)現(xiàn)如下:
func?IndexFunc[E?any](s?[]E,?f?func(E)?bool)?int?{
?for?i,?v?:=?range?s?{
??if?f(v)?{
???return?i
??}
?}
?return?-1
}
參數(shù) f 是一個(gè)函數(shù),它接收一個(gè)參數(shù),類型是 E,是一個(gè)泛型,和 IndexFunc 的第一個(gè)參數(shù)類型 []E 的元素類型保持一致即可,因此可以直接將遍歷 s 的元素傳遞給 f。
修改 slice
一般不建議做相關(guān)操作,因?yàn)樾阅茌^差。如果有較多這樣的需求,可能需要考慮更換數(shù)據(jù)結(jié)構(gòu)。
//?往?slice?的位置?i?處插入元素(可以多個(gè))
func?Insert[S?~[]E,?E?any](s?S,?i?int,?v?...E)?S
//?刪除?slice?中?i?到?j?的元素,即刪除?s[i:j]?元素
func?Delete[S?~[]E,?E?any](s?S,?i,?j?int)?S
//?將連續(xù)相等的元素替換為一個(gè),類似于 Unix 的 uniq 命令。Compact 修改切片的內(nèi)容,它不會創(chuàng)建新切片
func?Compact[S?~[]E,?E?comparable](s?S)?
func?CompactFunc[S?~[]E,?E?any](s?S,?eq?func(E,?E)?bool)?S
//?增加?slice?的容量,至少增加?n?個(gè)
func?Grow[S?~[]E,?E?any](s?S,?n?int)?S
//?移除沒有使用的容量,相當(dāng)于縮容
func?Clip[S?~[]E,?E?any](s?S)?S
以上類型約束都包含了兩個(gè):
S ~[]E:表明這是一個(gè)泛型版 slice,這是對 slice 的約束。注意 [] 前面的 ~,表明支持自定義 slice 類型,如 type myslice []intE any 或 E comparable:對上面 slice 元素類型的約束。
克隆 slice
即獲得 slice 的副本,會進(jìn)行元素拷貝,注意,slice 中元素的拷貝是淺拷貝,非值類型不會深拷貝。
func?Clone[S?~[]E,?E?any](s?S)?S?{
?//?Preserve?nil?in?case?it?matters.
?if?s?==?nil?{
??return?nil
?}
?return?append(S([]E{}),?s...)
}
04 總結(jié)
因?yàn)榉盒偷拇嬖冢瑯拥墓δ埽瑢Σ煌愋偷?slice 再也不用寫多份代碼。因?yàn)橐恍┕δ芎艹R姡虼?Go 官方將其封裝,將來會在標(biāo)準(zhǔn)庫中提供。
出于謹(jǐn)慎考慮,slices 包不會在 1.18 中包含,如果你需要用到 slices 中的功能,可以采用從 slices 代碼中復(fù)制的方式,個(gè)人覺得依賴 golang.org/x/exp 還是不太好。
slices 源碼地址:https://github.com/golang/exp/blob/master/slices/slices.go。
推薦閱讀
