千家信息网

Go语言的Slice怎么作为函数参数使用

发表于:2025-11-12 作者:千家信息网编辑
千家信息网最后更新 2025年11月12日,这篇文章主要介绍"Go语言的Slice怎么作为函数参数使用",在日常操作中,相信很多人在Go语言的Slice怎么作为函数参数使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家
千家信息网最后更新 2025年11月12日Go语言的Slice怎么作为函数参数使用

这篇文章主要介绍"Go语言的Slice怎么作为函数参数使用",在日常操作中,相信很多人在Go语言的Slice怎么作为函数参数使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Go语言的Slice怎么作为函数参数使用"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

前言

首先要明确Go语言中实质只有值传递,引用传递和指针传递是相对于参数类型来说。

个人认为上诉的结论不对,把引用类型看做对指针的封装,一般封装为结构体,结构体是值类型,所以感觉都是值传递。不然我感觉其它语言实质不也都是值传递?不过我刚学Go,可能还没完全弄懂,这个有问题可以互相讨论下。

Go语言中的值类型:int、float、bool、array、sturct等,声明一个值类型变量时,编译器会在栈中分配一个空间,空间里存储的就是该变量的值。

Go语言中的引用类型:slice,map,channel,interface,func,string等,声明一个引用类型的变量,编译器会把实例的内存分配在堆上。

string和其他语言一样,是引用类型,string的底层实现struct String { byte* str; intgo len; }; 但是因为string不允许修改,每次操作string只能生成新的对象,所以在看起来使用时像值类型。

其实引用类型可以看作对指针的封装。

Slice切片在Go语言中实质是一种结构体类型,源码中定义如下:

源码位置:src/runtime/slice.go

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

从定义中我们可以知道slice是一种值类型,array是底层数组指针,它指向底层分配的数组;len是底层数组的元素个数;cap是底层数组的容量,超过容量会扩容。

问题与解析

典型问题

有了上面知识的铺垫,下面我们来看下把slice作为函数参数传递的典型问题:

package mainimport "fmt"func main() { tmp := make([]int, 0)    fmt.Printf("%p\n", &tmp) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp) change(tmp) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}func change(tmp []int) {    fmt.Printf("%p\n", &tmp) tmp = append(tmp, 6)    fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}//运行结果//0xc000004078//[] 0 0 0x59cde0//0xc0000040c0//[6] 1 1 0xc000014098//[] 0 0 0x59cde0

这是一个典型问题,你所有疑问的基本这种类型的问题。

疑问点:slice不是引用类型吗?把它做参数传递时实参应该同步修改啊,为什么main函数中的tmp没变?

解析:

从之前讲的知识中我们已经知道slice实质是一个结构体,其作为参数传递时形参实质复制了实参整个结构体的内容,其实就是值传递。

形参分配有一份内存空间,存放和实参相同的内容,从运行结果可以看出形参的内存地址和实参是不同的。

因为形参中底层数组指针和实参相同,所以当做修改操作时会同步修改到实参中,但是当使用append函数添加元素时,append函数返回的slice会覆盖修改到形参的内存空间中,和实参无关,所以在main函数中实参不变。可以在上面代码中看到函数中形参已变但实参未变。

有同学看到上面解析之后可能还会有一些疑问,比如:

append函数有扩容机制,当函数内使用append未扩容时,是不是就可以同步增加元素到实参中?
为什么传指针就可以和实参完全同步,指针不也和引用类似吗?
函数中使用append时,如果扩容,其中形参内存空间中底层数组的地址会被覆盖修改为新的扩容后的底层数组地址,而实参无变化。上面的代码就是如此。

其它疑问1

package mainimport "fmt"func main() { tmp := make([]int, 0, 5) tmp = append(tmp, 1, 2, 3) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp) change(tmp) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}func change(tmp []int) { tmp = append(tmp, 4) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}//[1 2 3] 3 5 0xc00000c300//[1 2 3 4] 4 5 0xc00000c300//[1 2 3] 3 5 0xc00000c300

疑问点:从代码中可以看出函数中使用append时是没有扩容的,因为形参中底层数组地址和实参是一致的,那为什么实参中没有增加元素?

解析:

其实实参中tmp[3]已经变为4,但是实参和形参内存空间中len和cap是独立的,形参中len修改为了4但实参中len仍然为3,所以实参中未增加元素。

关于tmp[3]已经变为4可以从如下代码中反映出来:

package mainimport "fmt"func main() { tmp := make([]int, 0, 5) tmp = append(tmp, 1, 2, 3, 4, 5) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp) change(tmp[:3]) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}func change(tmp []int) { tmp = append(tmp, 6) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}//[1 2 3 4 5] 5 5 0xc00000c300//[1 2 3 6] 4 5 0xc00000c300//[1 2 3 6 5] 5 5 0xc00000c300

可以看出实参中4已经变为6

或者从如下代码中更为直接的看出:

package mainimport ( "fmt" "unsafe")func main() { tmp := make([]int, 0, 5) tmp = append(tmp, 1, 2, 3) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp) change(tmp) p := unsafe.Pointer(&tmp[2]) q := uintptr(p) + 8 t := (*int)(unsafe.Pointer(q)) fmt.Println(*t) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}func change(tmp []int) { tmp = append(tmp, 4) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}//[1 2 3] 3 5 0xc00000c300//[1 2 3 4] 4 5 0xc00000c300//4//[1 2 3] 3 5 0xc00000c300

用实参tmp[2]的地址往后移一个元素地址长度,得到tmp[3]的地址输出,可以看到变为了3。

其它疑问2

package mainimport "fmt"func main() { tmp := make([]int, 0, 5) tmp = append(tmp, 1, 2, 3) fmt.Printf("%p\n", &tmp) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp) change(&tmp) fmt.Printf("%v %d %d %p\n", tmp, len(tmp), cap(tmp), tmp)}func change(tmp *[]int) { *tmp = append(*tmp, 4) fmt.Printf("%p\n", tmp) fmt.Printf("%v %d %d %p\n", *tmp, len(*tmp), cap(*tmp), *tmp)}//0xc000004078//[] 0 0 0xffdde0//0xc000004078//[1] 1 1 0xc000014098//[1] 1 1 0xc000014098

疑问点:为什么指针可以同步修改到实参,*tmp = append(*tmp, 4)这不也是覆盖修改到形参吗?

解析:

首先明确传指针时传的是slice的地址,形参是地址而非一份和实参相同内容的内存空间,这点从代码中打印的0xc000004078地址可以看出。所以*tmp = append(*tmp, 4)这段代码覆盖修改的是0xc000004078这个地址指向的slice,即主函数中的tmp切片,这点从代码中主函数中切片tmp的底层数组地址从0xffdde0变为0xc000014098可以看出。

到此,关于"Go语言的Slice怎么作为函数参数使用"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

函数 类型 地址 形参 语言 底层 指针 数组 参数 代码 内存 疑问 空间 问题 和实 元素 实质 结构 同步 学习 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 软件开发企业是工业企业吗 不建议考网络安全 数据库外码怎样同步修改 科学技术发展数据库 在数据库中S和SC和C什么意思 梦幻西游新手服务器怎么换区 重庆办公系统软件开发哪家可靠 edc数据库 贵阳职业技术学院网络安全 诚邦网络技术有限公司干什么的 软件开发中心宣讲会 连云港火柴网络技术有限公司 国内高防服务器安全吗 贵州专业软件开发服务价格优惠 烟台亚图网络技术有限公司 西亚思维导图软件开发 宝塔创建的网站 数据库连接卡机 南京软件开发好找工作吗 手抄报网络安全还可以写什么 各种晶体衍射数据库可以通用吗 网络安全净化器 模拟山羊无法连接游戏服务器 地理信息数据库建库设置路径 网络安全周主题手抄报 在线设计下单系统软件开发 DDBJ 数据库 哪些服务器可以一起打史诗副本 微信小程序提交数据到云数据库 梦幻转服务器新规则 角色数据库原理与应用
0