移除切片中的元素

时间:2018-08-30 09:51:17

标签: arrays go slice

Go没有提供任何高级功能来从切片中删除元素。我编写了一个函数,该函数以通常在此处建议的方式从切片中删除给定值,但产生了非常意外的结果。

package main

import "fmt"

type Area struct {
    Cells [2][]uint8
}
func main() {
    var area1 Area
    area1.Cells[1] = []uint8 {5, 6, 7}

    area2 := area1

    area1.Cells[1] = removeValueFromCell(area1.Cells[1], 6)

    fmt.Println(area1.Cells[1])
    fmt.Println(area2.Cells[1])
}


func removeValueFromCell(cell []uint8, value uint8) []uint8{
    var res = cell
    for i := 0; i < len(cell); i++ {
        if cell[i] == value {
            res = append(cell[:i], cell[i+1:]...)
        }
    }
    return res
}

该程序输出:

[5 7] <- as expected

[5 7 7] <- why not [5 6 7] or [5 7] ?

1 个答案:

答案 0 :(得分:6)

切片值只是标题,指向后备数组。切片标头仅包含指针。因此,当您复制切片值时,副本还将指向相同的后备数组。因此,如果您通过原始切片标头更改了支持数组,则副本也会观察到更改。

这就是您的情况。您将area1分配给area2。单元格是切片的数组。因此,将复制包含切片头的数组,因此将复制切片头。 Sice标头包含指向后备数组的指针,该后备数组将不会重复。

因此,只有一个支持[5, 6, 7]元素的支持数组。然后调用removeValueFromCell(),它将修改此后备数组:

Before:
[5, 6, 7]
After:
[5, 7, 7]

因为元素6被删除,并且切片的其余部分(元素[7])被复制,而不是删除的元素。

然后您将这个新的slice头(正确地只包含2个元素)分配给area1.Cells[1]

但是切片值area2.Cells[1]指向相同的后备数组,并且由于您没有触摸此切片值,因此其底线的长度为3,因此它将看到所有后备数组更改的元素:[5, 7, 7]

还要注意,您对removeValueFromCell()的实现是错误的,因为如果可移动元素将在切片中多次列出,则其行为将不正确。这样做的原因是因为当您删除一个元素时,后续元素的索引会移动(减少1),但是循环变量不能解决这个问题。最简单的解决方法是使用向下循环。有关详细信息,请参见How to remove element of struct array in loop in golang