)
Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)你有没有在每个 Go 项目里都复制粘贴过一份sliceContains、sortByField、uniqueStrings的工具函数?Go 泛型落地前,这些代码只能靠手写,一个项目一份,还容易写错。从 Go 1.21 开始,标准库正式收纳了slices和maps两个泛型包,把这些高频操作一次性补齐。这篇就把最常用的函数过一遍,顺便讲清楚几个容易踩的坑。先看看以前有多啰嗦想判断一个切片里有没有某个元素,Go 1.21 之前得自己写循环:// 朴素写法:每个项目都要来一份funccontains(s[]string,targetstring)bool{for_,v:ranges{ifvtarget{returntrue}}returnfalse}排序稍微复杂一点就得实现sort.Interface,或者用sort.Slice传一个闭包,写法冗长且不类型安全。这些样板代码没有技术含量,却又不得不写。slices 包:查找、排序、增删一步到位引入slices后,上面那堆全都不用自己写了:packagemainimport(fmtslices)funcmain(){nums:[]int{3,1,4,1,5,9,2,6}// 排序:原地升序,不用再写 sort.Slice 闭包slices.Sort(nums)fmt.Println(nums)// [1 1 2 3 4 5 6 9]// 查找:是否包含fmt.Println(slices.Contains(nums,5))// true// 二分查找(切片必须已排序),返回下标和是否命中idx,found:slices.BinarySearch(nums,4)fmt.Println(idx,found)// 4 true// 找第一个满足条件的下标,没有返回 -1i:slices.IndexFunc(nums,func(nint)bool{returnn4})fmt.Println(i)// 6(值为 5 的位置)}自定义排序用slices.SortFunc,回调返回一个整数(负/零/正表示小于/等于/大于),语义和 C 的qsort一致:typeUserstruct{NamestringAgeint}users:[]User{{Bob,30},{Alice,25},{Carol,30}}// 先按年龄升序,年龄相同按名字字典序slices.SortFunc(users,func(a,b User)int{ifa.Age!b.Age{returna.Age-b.Age// 注意:字段是大整数时可能溢出,见下文}returnstrings.Compare(a.Name,b.Name)})用 slices 优雅去重去重是另一个高频需求。slices.Compact能删掉相邻的重复元素,配合先排序就是标准去重套路:funcunique[T cmp.Ordered](s[]T)[]T{slices.Sort(s)// 让重复元素相邻returnslices.Compact(s)// 删除相邻重复,返回新长度的切片}fmt.Println(unique([]int{3,1,1,4,3,3}))// [1 3 4]这里cmp.Ordered是cmp包提供的约束,涵盖所有可比较大小的基础类型(整数、浮点、字符串)。如果不想改变原顺序,就别用 Compact,改用 map 记录见过的元素。maps 包:提取 key、拷贝与比较maps包补齐了操作 map 的常用动作。要注意 Go 1.21 里maps.Keys返回的是迭代器(Go 1.23 起是iter.Seq),需要配合slices.Collect收集成切片:import(mapsslices)m:map[string]int{a:1,b:2,c:3}// 取出所有 key(顺序随机!),收集成切片再排序keys:slices.Collect(maps.Keys(m))slices.Sort(keys)fmt.Println(keys)// [a b c]// 浅拷贝一个 map,改副本不影响原 mapcp:maps.Clone(m)cp[a]99fmt.Println(m[a])// 1,原 map 没变// 比较两个 map 内容是否相等fmt.Println(maps.Equal(m,cp))// false如果你的 Go 版本是 1.21/1.22,maps.Keys的签名可能还是老的返回切片版本,升级到 1.23 后统一成迭代器。写代码前go version确认一下,避免编译报错找不到北。三个别踩的坑第一,slices.Clone是浅拷贝。它只复制切片本身的元素,如果元素是指针或含切片的结构体,拷贝后两者仍共享底层数据:typeBoxstruct{Items[]int}a:[]Box{{Items:[]int{1,2}}}b:slices.Clone(a)b[0].Items[0]99fmt.Println(a[0].Items[0])// 99!内层切片是共享的第二,SortFunc 的比较函数别用减法算大整数。a.Age - b.Age在数值接近 int 边界时会溢出,得到错误的正负号。安全写法是用cmp.Compare(a.Age, b.Age),它内部做了溢出保护。第三,slices.Compact只删相邻重复。没排序的切片直接 Compact 不会全局去重,[1,2,1]出来还是[1,2,1]。记住:先 Sort 再 Compact。小结Go 1.21 的slices/maps泛型包让 Contains、Sort、去重、Clone 这些操作不用再手写工具函数。自定义排序用slices.SortFunc,比较函数优先用cmp.Compare而不是减法,避免溢出。去重标准套路:Sort让重复相邻 Compact删除相邻重复。Clone是浅拷贝,元素含引用类型时要当心共享;maps.Keys在新版本返回迭代器,用slices.Collect收集。一句话记忆:能用标准库就别自己撸工具函数——slices和maps已经帮你写好、测好、优化好了。