[:]是什么
在一些文章或教程中,是这么描述的:
s := arr[:] // 创建一个与arr等长、等容量的切片,引用相同的底层数组 出处
5.通过 [:] 操作获取一个底层使用 vauto 的切片;第 5 步中的 [:] 就是使用下标创建切片的方法,从这一点我们也能看出 [:] 操作是创建切片最底层的一种方法。 出处
那么这就引出了一个问题:如果l是一个切片,s:= l[:],那么很明显,会产生一个引用相同底层数组但是拥有自己的切片结构的切片(包括切片长度,切片容量等数据)
也就是说,这似乎是一个对底层数组的浅拷贝,如果我们对数组s进行append且没有超过底层数组的容量(即不触发扩容),或者对s进行删除操作,那么会影响原数组的内容但是不会影响切片长度等内容。
实验
package main
import (
"fmt"
"unsafe"
)
func main() {
// 创建一个长度为3,容量为5的切片
originalSlice := make([]int, 3, 5)
originalSlice[0] = 1
originalSlice[1] = 2
originalSlice[2] = 3
// 通过切片操作创建一个新的切片
newSlice := originalSlice[:]
fmt.Printf("Before append:\n")
fmt.Printf("Original Slice: len=%d cap=%d %v, address=%p\n", len(originalSlice), cap(originalSlice), originalSlice, unsafe.Pointer(&originalSlice[0]))
fmt.Printf("New Slice: len=%d cap=%d %v, address=%p\n", len(newSlice), cap(newSlice), newSlice, unsafe.Pointer(&newSlice[0]))
// 对 newSlice 进行 append 操作,但不超过容量
newSlice = append(newSlice, 4)
fmt.Printf("\nAfter append:\n")
fmt.Printf("Original Slice: len=%d cap=%d %v, address=%p\n", len(originalSlice), cap(originalSlice), originalSlice, unsafe.Pointer(&originalSlice[0]))
fmt.Printf("New Slice: len=%d cap=%d %v, address=%p\n", len(newSlice), cap(newSlice), newSlice, unsafe.Pointer(&newSlice[0]))
// 再次对 newSlice 进行 append 操作,超过容量
newSlice = append(newSlice, 5, 6)
fmt.Printf("\nAfter second append:\n")
fmt.Printf("Original Slice: len=%d cap=%d %v, address=%p\n", len(originalSlice), cap(originalSlice), originalSlice, unsafe.Pointer(&originalSlice[0]))
fmt.Printf("New Slice: len=%d cap=%d %v, address=%p\n", len(newSlice), cap(newSlice), newSlice, unsafe.Pointer(&newSlice[0]))
}
结果:
Before append:
Original Slice: len=3 cap=5 [1 2 3], address=0xc000082030
New Slice: len=3 cap=5 [1 2 3], address=0xc000082030
After append:
Original Slice: len=3 cap=5 [1 2 3], address=0xc000082030
New Slice: len=4 cap=5 [1 2 3 4], address=0xc000082030
After second append:
Original Slice: len=3 cap=5 [1 2 3], address=0xc000082030
New Slice: len=6 cap=10 [1 2 3 4 5 6], address=0xc000096000
我们发现在append一个元素之后两个切片的底层数组的内存地址相同,但是输出的切片内容不同,那么我们发现了切片的一个特点:切片只会从底层数组中获得切片结构体中切片长度个元素(也就是说如果切片长度是3,那么它就只会去获取前三个元素,而不管底层数组中的其他元素)
看来新增元素没有效果,那么删除元素呢?
package main
import (
"fmt"
"unsafe"
)
func main() {
// 创建一个长度为3,容量为5的切片
originalSlice := make([]int, 3, 5)
originalSlice[0] = 1
originalSlice[1] = 2
originalSlice[2] = 5
// 通过切片操作创建一个新的切片
newSlice := originalSlice[:]
fmt.Printf("Before deletion:\n")
fmt.Printf("Original Slice: len=%d cap=%d %v, address=%p\n", len(originalSlice), cap(originalSlice), originalSlice, unsafe.Pointer(&originalSlice[0]))
fmt.Printf("New Slice: len=%d cap=%d %v, address=%p\n", len(newSlice), cap(newSlice), newSlice, unsafe.Pointer(&newSlice[0]))
// 删除 newSlice 中的第二个元素(索引为1)
indexToRemove := 0
newSlice = append(newSlice[:indexToRemove], newSlice[indexToRemove+1:]...)
fmt.Printf("\nAfter deletion:\n")
fmt.Printf("Original Slice: len=%d cap=%d %v, address=%p\n", len(originalSlice), cap(originalSlice), originalSlice, unsafe.Pointer(&originalSlice[0]))
fmt.Printf("New Slice: len=%d cap=%d %v, address=%p\n", len(newSlice), cap(newSlice), newSlice, unsafe.Pointer(&newSlice[0]))
结果:
Before deletion:
Original Slice: len=3 cap=5 [1 2 5], address=0xc000018060
New Slice: len=3 cap=5 [1 2 5], address=0xc000018060
After deletion:
Original Slice: len=3 cap=5 [2 5 5], address=0xc000018060
New Slice: len=2 cap=5 [2 5], address=0xc000018060
出现了异常情况:原有的切片的第二个元素变成了5,尝试把删除前后底层数组全部打印出来,结果如下:
Original Slice underlying array (all elements):
originalSlice[0] = 1
originalSlice[1] = 2
originalSlice[2] = 3
originalSlice[3] = 0
originalSlice[4] = 0
New Slice underlying array (all elements):
newSlice[0] = 1
newSlice[1] = 3
newSlice[2] = 3
newSlice[3] = 0
newSlice[4] = 0
那么我们发现,go中切片删除元素只会把目标位置之后所有元素向前移动一位(同时不会管最后一位内容)
结论
当我们使用[:]复制切片时,这是一个奇特的浅拷贝,它只浅拷贝了底层数组,而没有拷贝切片结构体的其他内容,那么当我们对新切片进行操作时旧切片并不会意识到,因此可能产生危险的出乎意料的结果,因此在实际生产中并不建议采用[:]的方式复制切片。
此外,s:=l[a:b]也有类似的效果,其长度会变为b-a,但是容量会变为l.len-a,即s的起始位置到l的底层数组末尾的元素数量,因此他们依然在共享一个底层数组的一部分。
当切片初始化时,会将底层数组中所有元素初始化为类型初始值(本文中为int的初始值0)但是切片只会从底层数组中取出len个元素(len为切片结构体中长度字段)。
切片在删除元素时,会把目标位置之后所有元素向前移动一位(同时不会管最后一位内容),因此删除元素的时间复杂度为O (n)。