geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。

Home Page:https://geektutu.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Go 语言陷阱 - 数组和切片 | Go 语言高性能编程 | 极客兔兔

geektutu opened this issue · comments

https://geektutu.com/post/hpg-gotchas-array-slice.html

Go 语言/golang 高性能编程(high performance go),Go 语言进阶教程,Go 语言陷阱(gotchas)。这篇文章介绍了 Go 语言中数组(Array) 和切片(Slice)的常见陷阱和规避方式。例如数组作为参数,修改参数,原数组不会发生改变。

大佬真的牛批,起初知道对slice的修改会影响到原来的slice,就以为添加操作也有可能会被修改。无论slice的长度多少,只要对slice的添加操作都是会生成一个新的slice,原来的slice不受影响。感谢

看完收工

@chocolateszz 你的理解是对的,不同切片维护了 len 和 cap,添加操作,不改变 len 长度以前的底层数组的元素,所以不影响。

@bestgopher 哈哈,后面会增加新的文章,逐步增加难度~

commented

厉害 期待大佬的新文章 受益匪浅~

厉害 期待大佬的新文章 受益匪浅~

@zpng 感谢认可,近期会频繁更新哒~

想到 slice 只要不扩容,就不会重新分配空间,其实可以提前分配好空间,避免在调用的函数内部扩容。但是由于整个 slice 是作为传值传入函数内部的,所以 slice 结构体内部的 len 在函数结束后,依然是原来的长度。
我尝试了手动修改 slice 结构体内部的 len ,算是第三种 foo 函数影响原切片的方法(滑稽 🤣

func foo(a []int) {
        a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
        a[0] = 200
}

func main() {
        // 提前分配空间,防止扩容修改空间
        a := make([]int, 0, 20)
        a = append(a, 1, 2)

        foo(a)
        fmt.Println(a)

        // slice struct from https://golang.org/src/runtime/slice.go
        pointerOfa := unsafe.Pointer(&a)

        pointerSize := unsafe.Sizeof(pointerOfa)

        intSize := unsafe.Sizeof(int(0))

        internalLengthPointer := (*int64)(unsafe.Pointer(uintptr(pointerOfa) + uintptr(pointerSize)))
        internalCapPointer := (*int64)(unsafe.Pointer(uintptr(pointerOfa) + uintptr(pointerSize) + uintptr(intSize)))

        fmt.Printf("the internalCapLength: %d, cap(a): %d\n", *internalCapPointer, cap(a))
        fmt.Printf("the internalLengthPointer: %d, len(c): %d\n", *internalLengthPointer, len(a))

        *internalLengthPointer = 10

        fmt.Println("after modify")
        fmt.Println(a)

}

output:

[200 2]
the internalCapLength: 20, cap(a): 20
the internalLengthPointer: 2, len(c): 2
after modify
[200 2 1 2 3 4 5 6 7 8]

@kele1997 嗯,这一串操作好 6 ~

大佬这个系列写的挺好的,通过测试对性能有了直观的感受,多多交流

@wsqyouth Thanks♪(・ω・)ノ,多交流~

commented

大佬,文章什么时候再更新?

学到了,学到了

commented

全部看完了,学到了很多

传参时拷贝了新的切片,因此当新切片的长度发生改变时,原切片并不会发生改变。

这里应该是切片容量发生改变时,才会分配新的内存空间,导致函数体中的切片底层指针指向新的空间。如果容量不变,foo函数的作用效果是可以体现在原切片中的。

看完了,受益匪浅

谢谢大佬 看完了 收获颇多

func foo(a []int) {
a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
a[0] = 200
}

func main() {
a := []int{1, 2}
foo(a)
fmt.Println(a)
}

如果不append的话会影响a的,a是引用类型。归根到底是slice是引用类型,当 len > cap 后会分配新的内存。

博主不更新了吗

commented

看完,求更

大佬牛逼,看完收获颇多

切片是引用传递,在未扩容的情况下,修改原切片长度内的元素会修改原数组,但是一旦扩容后,就类似一个副本,怎么修改都不影响原切片

看完了,学到了好多,谢谢兔兔!