Go泛型系列:Slices 包講解
大家好,我是 polarisxu。
前段時間,Russ Cox 明確了泛型相關的事情,原計劃在標準庫中加入泛型相關的包,改放到 golang.org/x/exp 下。
目前,Go 泛型的主要設計者 ianlancetaylor 完成了 slices 和 maps 包的開發(fā),代碼提交到了 golang.org/x/exp 中,如果經(jīng)過使用、討論等,社區(qū)認可后,預計在 1.19 中會合入標準庫中。
今天,通過學習 slices 包,掌握 Go 泛型的使用方法。
01 為什么增加 slices 包
標準庫有 bytes 和 strings 包,分別用來處理 []byte 和 string 類型,提供了眾多方便的函數(shù),但對普通的 slice,卻沒有相關的包可以使用。
比如 bytes 和 strings 都有 Index 函數(shù),用來在 []byte 或 string 查找某個 byte 或字符串的索引。對于普通的 slice,沒法寫一大堆包來處理,只能用戶自己實現(xiàn),這也是沒有泛型的弊端。
提供 bytes 和 strings,主要是因為它們使用頻率高
現(xiàn)在有了泛型,可以實現(xiàn)一些便利的 slice 操作方法,必須要針對某一個具體類型的 slice 都實現(xiàn)一遍相同的功能。
02 constraints 包
繼續(xù)講解 slices 包之前,先看看 contraints 包。
該包定義了一組用于類型參數(shù)(泛型)的有用約束,這個包已經(jīng)確定在 Go 1.18 標準庫中包含,截止目前(2021.11.27),該包定義了 6 個約束類型:
- // 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 個是整型相關類型約束,F(xiàn)loat 是浮點型約束,Complex 是負數(shù)類型約束,而 Ordered 表示支持排序的類型約束,表示支持大小比較的類型。
之前文章:《Go泛型系列:Go1.18 類型約束那些事》提到,約束語法變更了,一個是 | 符號,一個是 ~,上面定義中,很多地方都用到了 ~ 符號,它表示出了類型自身,底層類型是它的類型也適用該約束。
03 slices 包詳解
目前,slices 包有 14 個函數(shù),可以分成幾組:
- slice 比較
- 元素查找
- 修改 slice
- 克隆 slice
其中,修改 slice 分為插入元素、刪除元素、連續(xù)元素去重、slice 擴容和縮容。
slice 比較
比較兩個 slice 中的元素,細分為是否相等和普通比較:
- 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 約束是語言實現(xiàn)的(因為很常用),表示可比較約束(相等與否的比較)。主要,其中的 E、E1、E2 等,只是泛型類型表示,你定義時,可以用你喜歡的,比如 T、T1、T2 等。
看一個具體的實現(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 當成同類型的 slice 進行操作而已。
元素查找
在 slice 中查找某個元素,分為普通的所有查找和包含判斷:
- 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ù)進行的,它的實現(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 是一個函數(shù),它接收一個參數(shù),類型是 E,是一個泛型,和 IndexFunc 的第一個參數(shù)類型 []E 的元素類型保持一致即可,因此可以直接將遍歷 s 的元素傳遞給 f。
修改 slice
一般不建議做相關操作,因為性能較差。如果有較多這樣的需求,可能需要考慮更換數(shù)據(jù)結構。
- // 往 slice 的位置 i 處插入元素(可以多個)
- 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ù)相等的元素替換為一個,類似于 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 個
- func Grow[S ~[]E, E any](s S, n int) S
- // 移除沒有使用的容量,相當于縮容
- func Clip[S ~[]E, E any](s S) S
以上類型約束都包含了兩個:
- S ~[]E:表明這是一個泛型版 slice,這是對 slice 的約束。注意 [] 前面的 ~,表明支持自定義 slice 類型,如 type myslice []int
- E any 或 E comparable:對上面 slice 元素類型的約束。
克隆 slice
即獲得 slice 的副本,會進行元素拷貝,注意,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 總結
因為泛型的存在,同樣的功能,對不同類型的 slice 再也不用寫多份代碼。因為一些功能很常見,因此 Go 官方將其封裝,將來會在標準庫中提供。
出于謹慎考慮,slices 包不會在 1.18 中包含,如果你需要用到 slices 中的功能,可以采用從 slices 代碼中復制的方式,個人覺得依賴 golang.org/x/exp 還是不太好。
slices 源碼地址:https://github.com/golang/exp/blob/master/slices/slices.go。