切片的坑特别多

mlzhou · 2020-06-04 15:50:14 · 3238 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2020-06-04 15:50:14 的主题,其中的信息可能已经有所发展或是发生改变。

testList := []uint16{1, 2, 3, 4}
myTest := testList            //1.mytest 和 testList指向同一段
myTest = append(myTest, 9)   //分离指向,所以在设计的时候最好给一个cap,不需要频繁的开辟内存
fmt.Println(testList)

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

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

3238 次点击  
加入收藏 微博
13 回复  |  直到 2020-06-16 10:17:34
focusonline
focusonline · #1 · 5年之前

这算什么坑, go本来就是值复制, 不是引用复制. 这是基础问题,不是所谓的坑.

mlzhou
mlzhou · #2 · 5年之前

@focusonline 那请问range多重map[]map[]val呢,不注意么?

mlzhou
mlzhou · #3 · 5年之前
focusonlinefocusonline #1 回复

这算什么坑, go本来就是值复制, 不是引用复制. 这是基础问题,不是所谓的坑.

你的值复制在这里没意义好吧,myTest := testList 本义就是将testList的指向地址(内容,所谓的值复制)赋值给了mytest. 我这里的问题是这个地址的cap发生了变化,产生了重新赋值, 即append后myTest的指向发生了变化,区分了原先的testList.

focusonline
focusonline · #4 · 5年之前
    testList := []uint16{1, 2, 3, 4}
    fmt.Printf("%p\n", &testList)
myTest := testList            //1.mytest 和 testList指向同一段
    testList[0] = 10
    fmt.Printf("%p\n", &myTest)
myTest = append(myTest, 9)   //分离指向,所以在设计的时候最好给一个cap,不需要频繁的开辟内存
    fmt.Printf("%p\n", &myTest)
    myTest[0] = 9
fmt.Println(testList)
    fmt.Println(myTest)
0xc21000a020
0xc21000a060
0xc21000a060
[10 2 3 4]
[9 2 3 4 9]

如果你还是看不懂, 放弃吧, 你不适合golang, 还是去学java更合适.

mlzhou
mlzhou · #5 · 5年之前
focusonlinefocusonline #4 回复

```go testList := []uint16{1, 2, 3, 4} fmt.Printf("%p\n", &testList) myTest := testList //1.mytest 和 testList指向同一段 testList[0] = 10 fmt.Printf("%p\n", &myTest) myTest = append(myTest, 9) //分离指向,所以在设计的时候最好给一个cap,不需要频繁的开辟内存 fmt.Printf("%p\n", &myTest) myTest[0] = 9 fmt.Println(testList) fmt.Println(myTest) 0xc21000a020 0xc21000a060 0xc21000a060 [10 2 3 4] [9 2 3 4 9] ``` 如果你还是看不懂, 放弃吧, 你不适合golang, 还是去学java更合适.

不是看不懂,而是实际应用的时候开辟了一块复用内存,在这里append被改了大小,我也不是请教为什么会这样,只是记录一下,因为实际中后面的操作是通过mytest初始化testList,所以你根本没看懂我写的啥。

focusonline
focusonline · #6 · 5年之前
    testList := []uint16{1, 2, 3, 4}
    fmt.Printf("%p\n", &testList)
    myTest := &testList            //1.mytest 和 testList指向同一段
    fmt.Printf("%p\n", myTest)
    *myTest = append(*myTest, 9)   //分离指向,所以在设计的时候最好给一个cap,不需要频繁的开辟内存
    fmt.Printf("%p\n", &myTest)
    (*myTest)[0] = 9
    fmt.Println(testList)
    fmt.Println(*myTest)
0xc21000a020
0xc21000a020
0xc210000020
[9 2 3 4 9]
[9 2 3 4 9]

你是想要这个么?你如果想真正复用内存,就应该使用指针, 否则是做不到真正的复用的,在append之后不是改了原来复用内存的大小而是复制了一个全新的内存区域,而且频繁的开辟内存也不是什么坏事, 因为小对象在栈上的申请内存不需要GC回收.这样没啥不对,或许是为了内存安全.也许我确实不懂你在说什么,也难怪你不知道我在说什么...

jarlyyn
jarlyyn · #7 · 5年之前

题主是没注意append到底是干什么的吗?

The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself:

https://golang.org/pkg/builtin/#append

Schr0dingerCat
Schr0dingerCat · #8 · 5年之前

@mlzhou 你使用的 testList := []uint16{1, 2, 3, 4},初始化的slice 的 len, cap 都是一样的 ,当你在使用append的时候,如果这时len=cap,append会重新分配slice,所有返回的地址变了;如果你使用append时,testList 的cap - len 剩下的足够放append函数第二个参数的,那他就不会重新分配空间,返回的地址也不会变。 这个真心不是go的坑,只能说你基础知识看的不仔细

saberlong
saberlong · #9 · 5年之前

题主想要的是链表。 slice背后是数组不是链表,仔细想想2者的区别就可以明白了。这里使用数组作为slice是从性能考虑。但是使用数组就会有扩容问题,地址就会改变。如果再问为什么不改成内部自动指向新地址,那么你是要求go更改传值这个原则。那么你得想想为什么不传引用。说实在,本质上没有所谓的传引用,只是使用感觉上的问题。提示,和内存分配以及gc有关

hailong52369
hailong52369 · #10 · 5年之前

哈哈,讨论起来就好

ichsonx
ichsonx · #11 · 5年之前

要说坑,严格来说不算。但从开发角度来说,确实会很容易给开发人员造成运行时问题。对开发来说跟坑差不多了

所以用slice的时候最好避免多处赋值,或者粗鲁点直接用指针。

这其实都源于slice、appen等的一些特性造成的。

linweijie2012
linweijie2012 · #12 · 5年之前

这个不是切片的坑,而是append的特性

kzh125
kzh125 · #13 · 5年之前

切片背后是数组,数组长度是不变的,切片长度是可变的。

切片背后可以看作是结构体,包含三部分:(数组第一个元素地址,len,cap)

myTest := testList这句话可以看作结构体的值拷贝,或者是结构体的指针拷贝(看背后具体实现)

append函数会生成带有三部分信息的结构体并返回,如果原切片内部的数组长度足够,那append返回的切片(结构体)内部的数组指向的还是原数组。如果append发现数组长度不够了,由于数组长度是不变的,肯定要重新分配一个更长的数组,把原数组东西拷贝过来,再append再返回,这时返回的切片(结构体)内部就是新数组了。

myTest = append(myTest, 9)这句话append生成了一个切片并返回,append是有返回值的,如果右边myTest内部数组长度足够,那右边结构体和左边内部是同一块数组,这里右边myTest内部数组长度不够,所以两边内部是不同的数组。

至于频繁的开辟内存问题,你可以看作cap不够时,一直把cap乘以2,这样指数级别的增长,其实不会很频繁。

假如调用两个函数,changeMap(map1) changeSlice(slice1),changeMap肯定是没有疑问,传进去引用类型map1,函数里面对map1修改,函数的调用者可以看到。而changeSlice虽然也是传入引用类型,但是由于append的特性,若函数里面调用了append,调用者可能会不知道发生了改变。此时可以使用slice1 = changeSlice(slice1),模仿内置的append函数(推荐),或者传入slice指针。

建议把The Go Programming Language 看一遍,绝对理解深刻。

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