欢迎访问Ningto's博客

Menu
  • 首页
  • 归档
  • 关于
  • 书签
  • 必应壁纸
  • IT聚合
  • 工具
    • 我的工具列表
    • 我的网盘
    • 必应每日壁纸API
    • Html转Markdown
    • 仙尘光标
Menu

golang slice切片作为函数参数时的陷阱

最后更新 2019-07-02 10:30:38   阅读量 2137

直接用例子说话
例1:

func main() {
    s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
    fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

    modifySlice1(s)
    fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice1(s []int) {
    s = append(s, 2)
    s[0] = 1
    fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

运行结果:

before: slice addr 0xc000004480 point 0xc00000a480, val [0], len 1, cap 3
modify: slice addr 0xc000004500 point 0xc00000a480, val [1 2], len 2, cap 3
after: slice addr 0xc000004480 point 0xc00000a480, val [1], len 1, cap 3
  • 通过参数传过去的slice地址不一样,说明slice进行了拷贝(见:addr)
  • 函数里面修改了slice后原先的slice内容也改变了说明他们的底层指向的是同一份数据(见:point)

例2,与上面代码的区别是append了三个元素

func main() {
    s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
    fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

    modifySlice2(s)
    fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice2(s []int) {
    s = append(s, 2, 3, 4)
    s[0] = 1
    fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

运行结果:

before: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3
modify: slice addr 0xc0000544a0 point 0xc000088030, val [1 2 3 4], len 4, cap 6
after: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3
  • 函数里面slice底层point指向的地址变了,是由于append的元素个数超出了原先的cap,底层进行了内存重分配以及数据拷贝
  • 函数里面的修改并没有影响到外面的slice

通过上面两个例子就可以看出这样使用slice容易掉入陷阱,函数外面并不知道slice被修改了。

解决方法,遵守这两个规则:

  • 无返回参数的函数我们不应该去修改slice,只进行读取
  • 如果要修改slice那就应该通过返回值返回

所以正确的修改slice的方法是:

func main() {
    s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
    fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

    s = modifySlice3(s)
    fmt.Printf("after3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

    s = modifySlice4(s)
    fmt.Printf("after4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice3(s []int) []int {
    s = append(s, 2)
    s[0] = 1
    fmt.Printf("modify3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
    return s
}

func modifySlice4(s []int) []int {
    s = append(s, 3, 4, 5, 6)
    s[0] = 1
    fmt.Printf("modify4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
    return s
}

输出结果:

before: slice addr 0xc00004e420 point 0xc000054140, val [0], len 1, cap 3
modify3: slice addr 0xc00004e4a0 point 0xc000054140, val [1 2], len 2, cap 3
after3: slice addr 0xc00004e420 point 0xc000054140, val [1 2], len 2, cap 3
modify4: slice addr 0xc00004e540 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6
after4: slice addr 0xc00004e420 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6

这样不管底层数据有没有产生拷贝,表现在外的值都是一致的。

结论:

  • 函数返回值没有返回slice时,应该只读
  • 函数修改了slice时,应该通过返回值返回slice
  • 如果在函数里面确实要修改slice而又不想改变外面的值时,您应该深拷贝一份slice再进行修改。
(转载本站文章请注明作者和出处:泞途 - ningto.com)

下一篇 – linux expect scp自动输入密码
上一篇 – goquery基本用法

  1. Go

toningto@outlook.com

推荐文章

Win32 MSAA UIA技术介绍

C++20 模块入门指南

JavaScript优雅的编程方式

实现RPA软件的关键点

远程桌面切换到console session

获取windows桌面所有可见窗口信息

标签云

Life Windows Tips Product Mac Linux MongoDB ChatGPT Tools Shell Mobile Node.js Others Python Web MQ Android Design Java Javascript Bug Database IOS React Boost Go Qt C/C++

推广链接

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年99元

多谢支持,用了好几年,服务很稳定支持多设备!

其他

文章RSS

Copyright © 2016 Welcome To Ningto Blog | 鄂ICP备17003086号-2