切片作为参数传参,使用 append 后在函数内切片被修改了,而在主函数里面没有被改变

yagamil · 2021-11-16 18:04:37 · 1342 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2021-11-16 18:04:37 的主题,其中的信息可能已经有所发展或是发生改变。

package main

import "fmt"

func main() {
        arr := make([]int, 3, 4)             //创建一个长度为 3 ,容量为 4 的切片
        fmt.Println(arr, len(arr), cap(arr)) //[0 0 0] 3 4
        // -----
        fmt.Printf("%p\n", arr)
        addNum(arr)
        // -----
        fmt.Println(arr, len(arr), cap(arr)) //[0 0 0] 3 4
        fmt.Printf("%p\n", arr)
}

func addNum(sli []int) {
        fmt.Printf("%p\n", sli)
        sli = append(sli, 4)
        fmt.Println(sli, len(sli), cap(sli)) //[0 0 0 4] 4 4
        fmt.Printf("%p\n", sli) 
}

看到网上的解释是, 在 addNum 里面,sli 的底层数组是的确被修改了,可是切片的 len 由于是值复制,所以切片的 len 没有被修改,导致外层 main 里面的切片没有被显示?

如果是这样,那么应该传参的时候传入的切片地址应该不一样才对,因为是传值,传入的是切片结构体的拷贝值,而不应该是切片的原地址。

type slice struct { array unsafe.Pointer //存储数组指针 len int cap int } 望大神指点。

有个大神的回复:

打印的是切片里引用的底层数据的地址,而不是切片本身的地址。实参和形参的切片是不同的切片,只不过它们引用的底层数据是一样的。

那么请问下,addNum(arr) 这个arr是值传递,传的是切片的地址值,还是切片里面的结构体的array地址 ?


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1342 次点击  
加入收藏 微博
6 回复  |  直到 2021-11-18 00:54:30
18328048335
18328048335 · #1 · 3年之前

我的推断:


golang中的参数传递都是拷贝传递,即使你传的参数是一个引用,它也会把这个引用给拷贝了,但是,它们两个都指向同一个地址,所以你做的修改并不是针对某个引用变量,而是某个引用变量所指向的对象。但是slice不是真正的引用类型,是大概长这样的一个结构体:

type slice struct {  
    array unsafe.Pointer
    len   int
    cap   int
}

传参拷贝后的确是复制了这样的一个结构体,但是内部的array指针也复制了,所以指向的是同一底层数组的首地址。 想必楼主已经知道了append函数的原理了,就不多言了。

这个修改。。。如果你addNum这么写:

sli[0] = 999

那么arr[0]当然也会变成999。 然后,append确实没有重新分配底层数组,完整的底层数组确实是[0 0 0 4]没错,但是你忘了,arr被声明的时候,还记得slice的数据结构吗?有个len属性,arr的len是3啊,它读不到第四个元素上。append的是sli这个arr的值拷贝对象,尽管它们都共用一个底层数组的起始地址,但是len可不一样, 所以arr只有底层数组的前面三个值[0 0 0],sli则是[0 0 0 4], %p直打slice打的确实是底层数组地址,如果要打切片本身的地址,要这样:("%p\n", &arr).

我推理的不知是否正确。

jan-bar
jan-bar · #2 · 3年之前

sli 在函数内部是个局部变量,只不过地址指向外面传参而已。局部变量发生扩容,那么这个局部变量指向扩容后的地址,这时候这个局部变量和外部参数没关系了。

haha_cat
haha_cat · #3 · 3年之前

要用指针啊

tablecell
tablecell · #4 · 3年之前

够烂苦海无边,转vlang回头是岸  https://github.com/vlang/v

18328048335
18328048335 · #5 · 3年之前
tablecelltablecell #4 回复

够烂苦海无边,转vlang回头是岸  https://github.com/vlang/v

认真的吗。。。

yagamil
yagamil · #6 · 3年之前
1832804833518328048335 #1 回复

我的推断: --- --- golang中的参数传递都是拷贝传递,即使你传的参数是一个引用,它也会把这个引用给拷贝了,但是,它们两个都指向同一个地址,所以你做的修改并不是针对某个引用变量,而是某个引用变量所指向的对象。但是slice不是真正的引用类型,是大概长这样的一个结构体: ``` type slice struct { array unsafe.Pointer len int cap int } ``` 传参拷贝后的确是复制了这样的一个结构体,但是内部的array指针也复制了,所以指向的是同一底层数组的首地址。 想必楼主已经知道了append函数的原理了,就不多言了。 这个修改。。。如果你addNum这么写: ``` sli[0] = 999 ``` 那么arr[0]当然也会变成999。 然后,append确实没有重新分配底层数组,完整的底层数组确实是[0 0 0 4]没错,但是你忘了,arr被声明的时候,还记得slice的数据结构吗?有个len属性,arr的len是3啊,它读不到第四个元素上。append的是sli这个arr的值拷贝对象,尽管它们都共用一个底层数组的起始地址,但是len可不一样, 所以arr只有底层数组的前面三个值[0 0 0],sli则是[0 0 0 4], %p直打slice打的确实是底层数组地址,如果要打切片本身的地址,要这样:`("%p\n", &arr)`. 我推理的不知是否正确。

%p直打slice打的确实是底层数组地址,如果要打切片本身的地址,要这样:("%p\n", &arr)

这个解释说的通。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传