为什么简单append不安全?
平时开发经常遇到删除Slice中的元素,但像下面这样是不行的
package main
import "fmt"
func main() {
// 初始化包含重复数字的切片
a := []int{1, 2, 3, 4, 5, 5, 6, 5, 7, 8, 9, 10}
fmt.Println("初始切片:", a)
// 使用 range 遍历切片,尝试删除所有的5
for i, value := range a {
if value == 5 {
// 删除当前元素
a = append(a[:i], a[i+1:]...)
fmt.Printf("删除元素后的切片(当前删除的元素5的索引: %d): %v\n", i, a)
}
}
fmt.Println("最终切片:", a)
}
运行后输出:
初始切片: [1 2 3 4 5 5 6 5 7 8 9 10]
删除元素后的切片(当前删除的元素5的索引: 4): [1 2 3 4 5 6 5 7 8 9 10]
删除元素后的切片(当前删除的元素5的索引: 6): [1 2 3 4 5 6 7 8 9 10]
最终切片: [1 2 3 4 5 6 7 8 9 10]
漏了一个元素5没有删除
原因就在遍历中删除元素后,后续的元素都向前移动了一位,但 range 循环的索引 i 仍然递增,导致实际上跳过了一个本应该检查的元素(因为它现在位于前一个索引位置)。
解决方法
1. 使用索引操作
可以在遍历时使用额外的索引来控制修改操作,以确保不会因为切片变化而影响遍历结果。
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
n := len(a) // 获取初始长度
for i := 0; i < n; i++ {
if a[i] == 5 {
// 删除元素5,并调整切片长度
a = append(a[:i], a[i+1:]...)
// 减小n和i以适应新的切片长度
n--
i--
}
}
for _, item := range a {
fmt.Println(item)
}
}
2. 逆序遍历
另一种方法是从后向前遍历和修改数组,这样即使数组大小变化,也不会影响到未遍历到的元素。
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := len(a) - 1; i >= 0; i-- {
if a[i] == 5 {
a = append(a[:i], a[i+1:]...)
}
}
for _, item := range a {
fmt.Println(item)
}
}
3. 新建一个数组
这种方法缺点是要额外空间,但是更好理解和实现
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 创建一个新的切片用于存放删除特定元素后的结果
var b []int
for _, item := range a {
if item != 5 {
b = append(b, item)
}
}
// 输出新的切片,已经不包含元素5
for _, item := range b {
fmt.Println(item)
}
}
我比较喜欢第二种,简单的改成倒序就可以,也无需额外空间
关于golang slice的更多细节和扩容介绍可以看我的博客
https://www.cztcode.com/2023/5094/