五个例子搞懂Go Slice 参数传递

80 阅读3分钟

Go 参数传递

Go 的参数传递和 Java 一样都是值传递,下面我会用slice编写五个例子详细说明各种参数传递情况

直接传递slice

func foo(a []int) {
    a[0] = 200
}

func TestSlice(t *testing.T) {
    a := []int{1, 2}
    foo(a)
    fmt.Println(a)
}

上面的执行结果是:

[200,2]

很简单,也符合我们的直觉,因为slice是引用类型嘛。

slice赋值

func foo(a []int) {
    a = []int{100, 200}
}

func TestSlice(t *testing.T) {
    a := []int{1, 2}
    foo(a)
    fmt.Println(a)
}

因为a是值传递,使用a的值修改了,没有影响

slice扩容

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

func TestSlice(t *testing.T) {
    a := []int{1, 2}
    foo(a)
    fmt.Println(a)
}

上面的执行结果是:

[1,2]

这个的原因是 在 foo 函数中,切片 a 传入并发生了 append 操作。由于 a 的容量不够容纳新增的元素,append 会创建一个新的底层数组,并返回一个新的切片 a。因此,在 foo 函数中的 a 变量现在指向的是新的底层数组,而不再指向原来的底层数组。

修改Slice

func foo(a []int) {
    a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
    a[0] = 200
}
func TestSlice(t *testing.T) {
    a := make([]int, 2, 10)
    a[0] = 100
    a[1] = 99
    foo(a)
    fmt.Println(a)
}
//[200 99]

在这个测试中a的容量足够,所以在append的时候不需要创建新的底层数组,所以a[0]被成功修改。

但是a经过foo函数后len还是2,只能打印两个元素。这是因为参数传递是值传递

简单的说传递的是 slice的定义的值

slice的定义有三个值

  • len
  • cap
  • 数组指针

在foo中修改切片中的值,修改的是数组指针的值,所以两边都可见。但是修改不了len和cap,因为他们是基本类型值传递。下面我证明在foo中修改了数组指针中的值

证明修改了切片中的值

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

func TestSlice(t *testing.T) {
    a := make([]int, 2, 10)
    a[0] = 100
    a[1] = 99
    foo(a)
    //2,10,1,2,3,4,5,6,7,8
    fmt.Println(len(a), cap(a))
    index := 5
    fmt.Println("Value at index", index, "is", getSliceNum(a, index))
    fmt.Println(a)
}

func getSliceNum(a []int, index int) interface{} {
    if index >= cap(a) {
       fmt.Println("Index is out of range for the slice's cap.")
       return nil
    }

    // 获取切片的指针
    ptr := unsafe.Pointer(&a[0])

    // 计算目标索引的指针
    targetPtr := unsafe.Pointer(uintptr(ptr) + uintptr(index)*unsafe.Sizeof(a[0]))

    // 将指针转换回int类型
    value := *(*int)(targetPtr)
    return value
}

使用unsafe 直接访问地址,发现a[5]等于4,说明foo函数确实修改了其他值

修改len cap

上面我说因为值传递,无法修改cap和len,那么就没有办法了吗?方法是slice的引用(有点像指针的指针)

func foo(a *[]int) {
    *a = append(*a, 1, 2, 3, 4, 5, 6, 7, 8)
    (*a)[0] = 200
}
func TestSlice(t *testing.T) {
    a := make([]int, 2, 10)
    a[0] = 100
    a[1] = 99
    foo(&a)
    fmt.Println(a)
}

输出如下

[200 99 1 2 3 4 5 6 7 8]