geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
167 stars 21 forks source link

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

Open geektutu opened 3 years ago

geektutu commented 3 years ago

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

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

Fxskyzz commented 3 years ago

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

bestgopher commented 3 years ago

看完收工

geektutu commented 3 years ago

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

geektutu commented 3 years ago

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

zpng commented 3 years ago

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

geektutu commented 3 years ago

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

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

kele1997 commented 3 years ago

想到 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]
geektutu commented 3 years ago

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

wsqyouth commented 3 years ago

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

geektutu commented 3 years ago

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

zhangxuanru commented 3 years ago

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

rankingcong commented 3 years ago

学到了,学到了

UOvOU commented 2 years ago

全部看完了,学到了很多

SericaLaw commented 2 years ago

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

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

dablelv commented 2 years ago

看完了,受益匪浅

WAYLON commented 2 years ago

谢谢大佬 看完了 收获颇多

tiankongnanhai commented 2 years ago

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 后会分配新的内存。

JYGQAQ commented 2 years ago

博主不更新了吗

js1219 commented 2 years ago

看完,求更

JJliangxunfu commented 2 years ago

大佬牛逼,看完收获颇多

Leviathangk commented 1 year ago

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

LydiaCai1203 commented 1 year ago

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

PiggyCh commented 2 months ago

不错不错