前言
Go是一門使用比較簡(jiǎn)單的編程語(yǔ)言巍实,以其天生支持高并發(fā)的特點(diǎn)深受程序員們的歡迎画切。語(yǔ)法簡(jiǎn)單的同時(shí)姜贡,也時(shí)常需要自己封裝一些函數(shù)或者方法,本身較少地提供了高級(jí)API磷支。
比如定義一個(gè)切片:
var s = []string{"abc", "123", "abc", ..., "abc", "123"}
現(xiàn)在需要?jiǎng)h除這個(gè)切片所有的字符串"abc"谒撼,沒有捷徑,只能循環(huán)查找abc的索引(下標(biāo))雾狈,然后通過子切片的方式逐一刪除廓潜。偽代碼如下:
for {
idx := findIndex(s, "abc")
if idx == -1 {
break
} else {
removeByIndex(s, idx)
}
}
問題很嚴(yán)峻
1)沒有封裝的話,每個(gè)切片都得重復(fù)寫這段代碼善榛,簡(jiǎn)直是不可接受的辩蛋。另外findIndex和removeByIndex兩個(gè)函數(shù),也需要實(shí)現(xiàn)一下移盆。
2)這只是以字符串切片為例悼院,如果是int/float/自定義的struct切片,怎么辦咒循?復(fù)制一份代碼据途,修改參數(shù)類型,這也是不可接受的叙甸。
怎么辦颖医?
問題1:我們可以封裝函數(shù),如
func Remove(s []string, tar string){
...
}
這樣封裝裆蒸,只能用在字符串切片上熔萧,顯然不夠完美,go 1.18引入了泛型僚祷,那讓這個(gè)函數(shù)支持泛型哪痰,是可以的:
func Remove[T any](s []T, tar T){
...
}
理論上可行了,別忘了我們還需要實(shí)現(xiàn)FindIndex這個(gè)函數(shù)久妆,由于T的范圍是any跺株,所以沒有辦法用 == 去判斷相等
func FindIndex[T any](s []T, tar T) int {
for i, e := range s {
if e == tar { // 編譯報(bào)錯(cuò)
return i
}
return -1
}
所以這里我們需要傳入一個(gè)匿名函數(shù),讓上層確定FindIndex的規(guī)則慕匠。所以FindIndex的方法就變成這樣:
func FindIndex[T any](s []T, compare func(t T) bool) int {
for i, e := range s {
if compare(e) {
return i
}
return -1
}
// 相應(yīng)的remove的函數(shù)簽名得是這樣:
func Remove[T any](s []T, compare func(t T) bool){
...
}
到這里應(yīng)該是可以用了术唬。
升華
上述內(nèi)容還是典型的面向過程思維抑诸,我們要讓程序看著更“面向?qū)ο蟆币恍K晕覀兎庋b一個(gè)“泛型類”爹殊,go里面沒有類的叫法蜕乡,所以叫泛型機(jī)構(gòu)體,定義很簡(jiǎn)單梗夸,
type Slice[T any] []T
//同時(shí)給它配個(gè)“構(gòu)造函數(shù)”
func NewSlice[T any]() *Slice[T] {
s := make(Slice[T], 0)
return &s
}
//再配個(gè)Append方法层玲,讓它可以被追加元素
func (s *Slice[T]) Append(t ...T) {
*s = append(*s, t...)
}
實(shí)現(xiàn)FindIndex方法,
func (s *Slice[T]) FindIndex(compare func(t T) bool) int {
for i, e := range *s {
if compare(e) {
// 由于T的范圍是any, 無(wú)法簡(jiǎn)單地判斷t==e, 故需傳入比較函數(shù)
return i
}
}
return -1
}
實(shí)現(xiàn)Remove方法反症,
func (s *Slice[T]) Remove(compare func(t T) bool) {
for {
idx := s.FindIndex(compare)
if idx == -1 {
break
} else {
*s = append((*s)[:idx], (*s)[idx+1:]...)
}
}
}
測(cè)試一下:
// 實(shí)例化
s := NewSlice[string]()
// 往切片里追加元素
s.Append("abc", "123", "xyz", "abc", "ABC")
// 定義一個(gè)匿名函數(shù)辛块,指定要?jiǎng)h除的元素
toDel := func(s string) bool { return strings.ToUpper(s)=="ABC"}
// 調(diào)用刪除方法
s.Remove(toDel)
總結(jié)
這里以刪除指定元素為例說明泛型切片封裝的一般思路,其他功能也可以采用相同的思路铅碍,舉一隅以三隅反吧润绵。