taowen
首发于taowen
Golang中slice与二级指针的陷阱

Golang中slice与二级指针的陷阱

本文参考:

golang函数返回slice和返回 slice的指针有什么区别

我们直接进入正题

Golang的指针其实与C和C++几乎一致,只是slice把数组指针封装了,刚开始容易搞混。接下来我们讨论golang中slice与二级指针的一些小坑。

slice一般不使用指针,因为slice本身就是引用类型,它本身就是一个指针,直接传递就可以修改其内部包含的数据。但其本质是一个结构体,其第一个字段是指向数据的指针,第二个字段是长度,第三个字段是容量。使用 slice 指针可以用来修改指针变量指向的slice。

比如:

package main

import (
	"fmt"
)

func FuncSlice(s []int, t int) {
	s[0], s[1], s[2], s[3] = t, t, t, t
}
func main() {
	a := []int{0, 1, 2, 3}
	FuncSlice(a, 4)
	fmt.Println(a)
}

Output:
[4 4 4 4]

所以

a := []int{0, 1, 2, 3} 

此时,a是一个slice,对于真实存储的数组相当于一个一级指针。

var a = &[]int{1, 2, 3} 

此时,a是一个slice的指针(*[]int),那么,相对于真实存储的数组,就相当于一个二级指针。

那么我们来看下一个例子:

package main

import "fmt"

func appendSliceInPlace(slice *[]int, data ...int) {
    // 这里相当于slice的二级指针,因为slice本身就是一个指针,
    // 所以这里我们让可以让slice指向一个新的数组[1 2 3 4 5 6]
    // 注意:append 其实会返回一个新的数组
    *slice = append(*slice, data...)
}

func main() {
    slice := &[]int{1, 2, 3}
    appendSliceInPlace(slice, 4, 5, 6)
    fmt.Println(*slice)
}

Output:
[1 2 3 4 5 6]

而在下面的情况就不同了

package main

import "fmt"

func appendSliceInPlace(slice []int, data ...int) {
    //在这里的slice其实本身就是一级指针,这里他只能修改数组里面的元素值,
    //其实这个函数收到的本质上数组元素地址的copy而已,只能修改指针元素,
    //并不能修改这个“指针”的“指向哪个数组”。
    slice = append(slice, data...)
    fmt.Println(slice)
}

func main() {
    var slice = []int{1, 2, 3}
    appendSliceInPlace(slice, 4, 5, 6)
    fmt.Println(slice)
}

Output: 
[1 2 3 4 5 6]
[1 2 3]

在了解前面两个问题之后,接下来我们来看终极版(本质上和前面的知识点相同):

package main

import "fmt"

func FuncSlice(s []int, t int) {
	s[0]++
	s = append(s, t)
	s[0]++
}
func main() {
	a := []int{0, 1, 2, 3}
	FuncSlice(a, 4)
	fmt.Println(a)
}

Output:
[1 1 2 3]

如果不清楚的话,我们来把流程走一遍:

我们创造了一个[0,1,2,3]数组,假设它的地址为0x11111111。

a是一个slice,a记录了0x11111111这个地址,相当于一级指针,我们可以用(*0x11111111)[0..n]来访问或者修改index为0到n的数组值。

此时我们调用函数FuncSlice(a, 4),把a传进去了,这个时候其实我们是传了0x11111111这个值的copy。在FuncSlice外部,a记录的还是0x11111111这个地址。

那么在FuncSlice函数内,我们知道了0x11111111这个值,也就相当于知道了原本的数组元素的地址,并且可以通过(*0x11111111)[0..n]来访问或者修改index为0到n的值。

所以,此时在函数内部的本地变量s记录的地址为0x11111111,所以s[0]++相当于

(*0x11111111)[0]++,这个时候数组本身的值被改变了。所以这个时候数组为[1,1,2,3]

下一步非常关键:

s = append(s, t) 

注意:append只有在容量不够的时候才会分配新的数组,例子中数组的cap明显不足,所以,这里是在底层产生了一个新的数组[1,1,2,3,4](感谢评论区的提醒,在此解释一下,以免误导大家)。

所以,他是一个新的数组,所以地址肯定不同,我们可以假设为0x22222222。所以此时s这个本地变量记录的地址为0x22222222。那么之后所有的操作,完全是对0x22222222这个地址的数组操作了。而在0x11111111处的数组依然为[1,1,2,3]。

而我们说过在FuncSlice外部,a记录的还是0x11111111这个地址。所以用a打印出来的数组当然还是[1,1,2,3]了。


希望能和对区块链感兴趣的朋友多多交流,我的blog地址:

DinghaoLIdinghaoli.github.io图标

编辑于 2018-12-02

文章被以下专栏收录