切片原理常问
2022年11月23日
切片常问
go 的 slice 在函数传参的时候是值传递还是引用传递
严格意义上是值传递,效果又呈现出引用传递的效果。
func printSlice(data []string) {
data[0] = "java"
}
func main() {
course := []string{"go", "slice", "gin"}
printSlice(courses)
fmt.Println(courses)
}
上述代码,将切片使用打印函数更改第一个元素的时候,再次打印会显示更改了元素;
func printSlice(data []string) {
data[0] = "java"
for i := 0; i < 10; i++ {
data = append(data, strconv.Itoa(i))
}
}
func main() {
course := []string{"go", "slice", "gin"}
printSlice(courses)
fmt.Println(courses)
}
但是这里,你往里面添加数据,下面打印的时候又不会新增元素;这里又表现出不像个引用传递。
提示
其实,它是值传递;go
的切片本质上是一个结构体
type slice struct {
array unsafe.Pointer // 切片中实际存储数据的数组指针,指向一块连续的内存
len int // 切片中的元素的数量
cap int // 切片中的容量
}
go
语言中结构体是值传递,在编译的course := []string{"go", "slice", "gin"}
时候会变成,如果使用make
来定义,则都可以指定切片的结构体的 3 个属性。
myslice := slice {
len: 3,
cap: 3,
array // 真正指向数据的数组指针
}
切片在进行append
的时候极有可能会触发扩容机制,因为是值传递,复制了一份,如果遇到扩容情况,底层指向的数字也会进行复制,两者修改的内容就会不一样,就产生不了交集,所以一般使用append
都需要有一个参数来接收。
func main() {
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := data[1:6]
s2 := data[2:7]
s2[0] = 22
fmt.Println(s2) // [22 4 5 6 7]
fmt.Println(s1) // [2 22 4 5 6]
}
func main() {
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := data[1:6]
s2 := data[2:7]
s2 = append(s2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 加到它扩容为止
s2[0] = 22 // 底层指向的数组的是一个新的地址
fmt.Println(s2) // [22 4 5 6 7 1 2 3 4 5 6 7 8 9 10]
fmt.Println(s1) // [2 3 4 5 6]
}
这样一看,对应的s1
就没有被修改掉值。
可以通过以下代码来观察容量的变化
func main() {
var data []int
for i := 0; i < 2000; i++ {
data = append(data, i)
fmt.Printf("len: %d, cap: %d\r\n", len(data), cap(data))
}
}
- 大部分都是以成倍的增长
- 增长到了 1024 的时候,就只增长前面的 1/4
源码位置:runtime/slice.go
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
如图所示:1280 = 1024 + 1024 / 4
正好对上newcap += newcap / 4
这一段代码。
总结
如果当前所需容量 (
cap
) 大于原先容量的两倍 (doublecap
),则最终申请容量(newcap
)为当前所需容量(cap
);如果条件 1 不满足,表示当前所需容量(
cap
)不大于原容量的两倍(doublecap
),则进行如下判断;如果原切片长度(
old.len
)小于 1024,则最终申请容量(newcap)等于原容量的两倍(doublecap
);否则,最终申请容量(
newcap
,初始值等于old.cap
)每次增加newcap/4
,直到大于所需容量(cap
)为止,然后,判断最终申请容量(newcap
)是否溢出,如果溢出,最终申请容量(newcap
)等于所需容量(cap
)
切片自定义实现新增元素和删除元素
func Add(s []int, index int, value int) []int {
// 先把长度+1
s = append(s, 0)
copy(s[index+1:], s[index:])
s[index] = value
return s
}
func Delete(s []int, index int) []int {
return append(s[:index], s[index+1:]...)
}
func TestAppendSlice(t *testing.T) {
a := make([]int, 6, 10)
a[1] = 12
a[2] = 13
fmt.Println(a)
Add(a, 1, 10)
Add(a, 2, 11)
fmt.Println(a)
fmt.Println(len(a))
}
func TestDeleteSlice(t *testing.T) {
a := []int{1, 2, 3, 4}
fmt.Println(a)
fmt.Println(a[2:])
Delete(a, 1)
fmt.Println(a)
}
func TestFibonacci(t *testing.T) {
t.Log(Fibonacci(4))
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
c := make(chan int, 10)
fib(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
Loading...