怎么在切片的指定索引处插入一个元素?

怎么在切片的指定索引处插入一个元素?

假设有如下切片:

s := []int{2, 8 , 6, 9}

怎么在索引2处插入新元素100?变成:

{2, 8, 100, 6, 9}

朴素实现

// 1.s扩容,长度加一
s = append(s, 0) // 追加的元素值是什么不重要,只是为了扩容
// 2. 从待插入索引2开始,元素一一后移
for i := len(s)-1; i > 2; i-- { // 为了防止覆盖,从后向前遍历,每一位的值等于前一位的值
	s[i] = s[i-1]
}
// 3.将要插入的100赋给s[2]
s[2] = 100

一般化,不妨把这个逻辑提取成一个函数

// 在切片s的索引i处插入新元素v
func insert(s *[]int, i, v int) {
	*s = append(*s, 0)
	for j := len(*s) - 1; j > i; j-- {
		(*s)[j] = (*s)[j-1]
	}
	(*s)[i] = v
}

注意到函数接收的是切片的指针,有兴趣的话考虑下去掉*会怎么样?为什么会这样?
当前要调用的话是这样:

insert(&s, 2, 100)

*、&看起来有点吓人,可以这样修改下:让insert函数返回修改后的切片:

func insert(s []int, i, v int) []int {
	s = append(s, 0)
	for j := len(s) - 1; j > i; j-- {
		s[j] = s[j-1]
	}
	s[i] = v
	return s
}

当然调用时变成这样了:

s = insert(s, 2, 100)

至此我们完成了朴素实现的insert()

用上内置函数copy(),减少代码量

将从i开始的元素一一后移,这里可以用内置函数copy,减少那个循环:

func insert(s []int, i, v int) []int {
	s = append(s, 0)
	_ = copy(s[i+1:], s[i:len(s)-1])
	s[i] = v
	return s
}

不用循环,不用copy,纯用append的实现

有一次看到别人实现这个功能的代码,惊艳了,先上代码:

func insert(s []int, i, v int) []int {
	r := s[:i:i]
	r = append(r, v)
	r = append(r, s[i:]...)
	return r
}

也很容易理解,先获取i前边的部分,再追加v,再追加包括i在内的后边的部分
有个细节需要注意,第一步为什么不写成s[:i],明确要求r的容量为i是为什么?思考题~ 基于上边的实现,甚至可以用一行搞定:

func insert(s []int, i, v int) []int {
	return append(append(s[:i:i], v), s[i:]...)
}

简洁!同时也很明朗:前半部分+v+后半部分
值得一提的是最后这个实现甚至允许i为s的长度,不像前边的实现,i为s长度时会索引越界。