真正反转需双指针原地交换,而非sort.Slice排序;后者仅按规则重排,不改变物理位置,对含指针或map字段的结构体语义不同;泛型reverse函数零分配、O(1)空间,但会修改原slice。
用
配合自定义比较函数能倒序,但不是真反转
很多人看到“倒序排列”第一反应是
,但它只是按规则排序,不改变原 slice 的物理顺序逻辑——比如
排成
看似一样,但若元素是结构体且含指针或 map 字段,排序不会影响这些引用关系;而真正反转会严格交换位置。如果你要的是“把第 0 个和最后一个互换、第 1 个和倒数第二个互换……”,就得手写交换逻辑。
适合按字段(如
)降序,不适合纯位置翻转
对
或
这类值类型,结果看起来一样,但语义不同:一个是排序,一个是置换索引
如果后续还要基于原始索引做操作(比如记录某元素“原来在第 3 位”),用
会丢失这个信息
最直接的反转写法:双指针原地交换
Go 没有内置
函数,但几行代码就能搞定,且零分配、O(1) 空间。核心就是头尾两个索引往中间走,逐个交换。
必须传入 slice 的引用(即
类型本身),不能传指针——因为 slice header 包含底层数组指针,修改元素不需要额外解引用
泛型约束
覆盖所有可比较/不可比较类型,包括 struct、map、func(只要它们能存进 slice)
注意:如果 slice 是
或长度为 0/1,循环自动跳过,安全
别用
+
反向构造新 slice
常见错误是这么写:
看着没问题,但容易踩三个坑:
立即学习
“
go语言免费学习笔记(深入)
”;
如果
是
,
是 0,循环不执行,结果是空 slice——符合预期;但若你误以为
和
行为一致,后续用
判断就会出错
在底层数组满时会 realloc,最坏情况触发多次拷贝(虽然预分配了 cap,但实际仍可能因 GC 或其他写操作导致底层数组被复用)
多分配一次内存,对高频调用或大 slice(比如上百万元素)有明显 GC 压力,而原地交换无额外分配
反转字符串 rune 切片时,别直接操作
字符串含中文、emoji 时,
转
再反转,会把 UTF-8 多字节序列拆开,输出乱码。正确做法是先转
,反转后再转回
。
反转只适用于 ASCII 字符串,或你明确知道内容全是单字节
(UTF-8 字节数),但
,反转单位必须是 rune
不要用
遍历字符串,这是按 byte 下标,不是字符
真正反转 slice 就是头尾交换,没别的捷径。泛型函数写一次就能复用,但得记清楚:它改的是原 slice,调用后原变量已变,别指望“反转一个副本”还留着旧顺序——如果需要保留原数据,得先
一份。
sort.Slicesort.Slice[]int{1,2,3}[]int{3,2,1}sort.Sliceuser.Age[]string[]intsortreversefunc reverse[T any](s []T) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}[]TT anynilappendfor rangenewS := make([]int, 0, len(old))
for i := len(old)-1; i >= 0; i-- {
newS = append(newS, old[i])
}oldnillen(nil)nil[]int{}== nilappend[]bytestring[]byte[]runestrings := "hello世界?"
runes := []rune(s)
reverse(runes) // 复用上面的泛型函数
s = string(runes)[]bytelen("?") == 4len([]rune("?")) == 1for i := len(s)-1; i >= 0; i--copy