go语言中,切片(slice)作为函数参数时,其行为是按值传递切片描述符,而非底层数组。这意味着函数内部对切片描述符(如长度、容量或指向底层数组的指针)的修改不会影响到调用者持有的原始切片。本文将深入探讨这一机制,并通过示例代码演示如何正确地在函数中修改切片并使其变更反映到调用者。
在Go语言中,切片并不是一个简单的指针,而是一个包含三个字段的结构体:
当一个切片作为函数参数传递时,Go语言会复制这个切片结构体。这意味着函数内部会得到一个与原始切片拥有相同ptr、len和cap的副本。
关键行为:
考虑以下原始代码片段,旨在对一个PairSlice进行去重并统计频率:
package main
import (
"fmt"
)
type Pair struct {
a int
b int
}
type PairAndFreq struct {
Pair
Freq int
}
type PairSlice []PairAndFreq
type PairSliceSlice []PairSlice
func (pss PairSliceSlice) Weed() {
fmt.Println(pss[0]) // 第一次打印:原始切片状态
weed(pss[0])
fmt.Println(pss[0]) // 第三次打印:期望修改后,但实际未变
}
func weed(ps PairSlice) {
m := make(map[Pair]int)
for _, v := range ps {
m[v.Pair]++
}
// 问题所在:这里修改的是局部变量 ps 的切片描述符
ps = ps[:0] // 将局部切片 ps 重新切片为空,但其容量不变
for k, v := range m {
// 这里的 append 操作会修改局部切片 ps,
// 如果容量不足可能导致底层数组重新分配,
// 无论如何,它将新的切片描述符赋值给局部变量 ps
ps = append(ps, PairAndFreq{k, v})
}
fmt.Println(ps) // 第二次打印:局部切片 ps 已经修改
}
func main() {
pss := make(PairSliceSlice, 12)
pss[0] = PairSlice{PairAndFreq{Pair{1, 1}, 1}, PairAndFreq{Pair{1, 1}, 1}}
pss.Weed()
}执行结果与预期不符的原因:

pss.Weed() 中的 fmt.Println(pss[0]) (第一次打印): 输出 [{{1 1} 1} {{1 1} 1}],这表示 pss[0] 的初始状态。
weed(ps PairSlice) 函数内部:
pss.Weed() 中的 fmt.Println(pss[0]) (第三次打印): 输出 [{{1 1} 2} {{1 1} 1}]。 为什么会这样?因为 weed 函数内部对 ps 的修改(ps = ps[:0] 和 ps = append(...))只影响了函数内部的 ps 变量副本。当 weed 函数返回时,pss[0] 仍然保持着它原始的切片描述符,指向原始的底层数组。然而,由于 weed 函数内部的 append 操作可能在原底层数组上进行了修改(如果容量足够),或者在新的底层数组上进行了修改。
在原代码中,pss[0] 最初的容量是2。weed 函数内部 ps = ps[:0] 后,ps 长度为0,容量为2。append(ps, PairAndFreq{k,v}) 会将 {1 1} 写入底层数组的第一个位置,并将其频率设为2。此时,pss[0] 仍然指向这个底层数组,但它的长度和容量描述符未变。因此,当 pss[0] 再次被打印时,它会显示底层数组的第一个元素被修改为 {{1 1} 2},而第二个元素 {{1 1} 1} 保持不变(因为 pss[0] 的长度仍然是2)。
要使函数内部对切片的修改反映到调用者,有两种主要方法:
这是Go语言中最常用且推荐的方法,尤其当函数可能改变切片的长度、容量或底层数组时。函数将新生成的或修改后的切片作为返回值,调用者负责接收并更新其自身的切片变量。
package main
import (
"fmt"
)
type Pair struct {
a int
b int
}
type PairAndFreq struct {
Pair
Freq int
}
type PairSlice []PairAndFreq
type PairSliceSlice []PairSlice
func (pss PairSliceSlice) Weed() {
fmt.Println("Before weed:", pss[0])
// 关键:将 weed 函数的返回值赋给 pss[0]
pss[0] = weed(pss[0])
fmt.Println("After weed:", pss[0])
}
// weed 函数现在返回一个 PairSlice
func weed(ps PairSlice) PairSlice {
m := make(map[Pair]int)
for _, v := range ps {
m[v.Pair]++
}
// 创建一个新的切片来存储结果,或者重新使用传入的切片
// 为了清晰起见,这里创建一个新的切片
result := make(PairSlice, 0, len(m))
for k, v := range m {
result = append(result, PairAndFreq{k, v})
}
fmt.Println("Inside weed (modified slice):", result)
return result // 返回修改后的切片
}
func main() {
pss := make(PairSliceSlice, 12)
pss[0] = PairSlice{PairAndFreq{Pair{1, 1}, 1}, PairAndFreq{Pair{1, 1}, 1}}
pss.Weed()
}输出:
Before weed: [{{1 1} 1} {{1 1} 1}]
Inside weed (modified slice): [{{1 1} 2}]
After weed: [{{1 1} 2}]这符合预期行为。
如果函数需要直接修改调用者持有的切片变量本身(例如,将其设置为nil,或者彻底改变其底层数组和描述符),可以传递切片的指针。
package main
import (
"fmt"
)
type Pair struct {
a int
b int
}
type PairAndFreq struct {
Pair
Freq int
}
type PairSlice []PairAndFreq
type PairSliceSlice []PairSlice
func (pss PairSliceSlice) Weed() {
fmt.Println("Before weed:", pss[0])
// 关键:传递 pss[0] 的地址
weedPtr(&pss[0])
fmt.Println("After weed:", pss[0])
}
// weedPtr 函数接收一个 *PairSlice 类型的指针
func weedPtr(psPtr *PairSlice) {
// 通过指针解引用获取切片
ps := *psPtr
m := make(map[Pair]int)
for _, v := range ps {
m[v.Pair]++
}
// 创建一个新的切片来存储结果
result := make(PairSlice, 0, len(m))
for k, v := range m {
result = append(result, PairAndFreq{k, v})
}
fmt.Println("Inside weedPtr (modified slice):", result)
// 关键:将新的切片赋值给指针指向的原始切片变量
*psPtr = result
}
func main() {
pss := make(PairSliceSlice, 12)
pss[0] = PairSlice{PairAndFreq{Pair{1, 1}, 1}, PairAndFreq{Pair{1, 1}, 1}}
pss.Weed()
}输出:
Before weed: [{{1 1} 1} {{1 1} 1}]
Inside weedPtr (modified slice): [{{1 1} 2}]
After weed: [{{1 1} 2}]这种方法同样达到了预期效果。然而,对于大多数需要变换切片内容的情况,返回新切片的方法通常更简洁、更符合Go的习惯。传递切片指针在需要函数内部直接控制切片变量的生命周期或彻底替换它时更为适用。
理解Go语言中切片的这种行为对于编写健壮和高效的代码至关重要,尤其是在处理数据集合的函数中。
# go
# go语言
# app
# ai
# 作用域
# 为什么
# for
# 局部变量
# 结构体
# int
# 指针
相关文章:
如何高效配置IIS服务器搭建网站?
python的本地网站制作,如何创建本地站点?
如何选择高效可靠的多用户建站源码资源?
建站之星后台管理:高效配置与模板优化提升用户体验
实例解析angularjs的filter过滤器
专业公司网站制作公司,用什么语言做企业网站比较好?
北京网页设计制作网站有哪些,继续教育自动播放怎么设置?
Swift中swift中的switch 语句
如何用腾讯建站主机快速创建免费网站?
如何生成腾讯云建站专用兑换码?
如何选择美橙互联多站合一建站方案?
黑客入侵网站服务器的常见手法有哪些?
淘宝制作网站有哪些,淘宝网官网主页?
哈尔滨网站建设策划,哈尔滨电工证查询网站?
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
文字头像制作网站推荐软件,醒图能自动配文字吗?
如何在云虚拟主机上快速搭建个人网站?
如何通过网站建站时间优化SEO与用户体验?
如何选择高效稳定的ISP建站解决方案?
建站上市公司网站建设方案与SEO优化服务定制指南
Swift中循环语句中的转移语句 break 和 continue
宠物网站制作html代码,有没有专门介绍宠物如何养的网站啊?
成都网站制作公司哪家好,四川省职工服务网是做什么用?
如何在IIS7中新建站点?详细步骤解析
教学论文网站制作软件有哪些,写论文用什么软件
?
建站之星如何一键生成手机站?
建站之星如何实现网站加密操作?
Python多线程使用规范_线程安全解析【教程】
c++如何打印函数堆栈信息_c++ backtrace函数与符号名解析【方法】
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
智能起名网站制作软件有哪些,制作logo的软件?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
大学网站设计制作软件有哪些,如何将网站制作成自己app?
如何快速辨别茅台真假?关键步骤解析
c# 在高并发下使用反射发射(Reflection.Emit)的性能
建站为何优先选择香港服务器?
公司网站制作费用多少,为公司建立一个网站需要哪些费用?
广州营销型建站服务商推荐:技术优势与SEO优化解析
C++如何编写函数模板?(泛型编程入门)
长沙企业网站制作哪家好,长沙水业集团官方网站?
英语简历制作免费网站推荐,如何将简历翻译成英文?
用v-html解决Vue.js渲染中html标签不被解析的问题
如何在阿里云通过域名搭建网站?
为什么Go需要go mod文件_Go go mod文件作用说明
如何确认建站备案号应放置的具体位置?
,在苏州找工作,上哪个网站比较好?
建站之星好吗?新手能否轻松上手建站?
建站主机服务器选型指南与性能优化方案解析
新网站制作渠道有哪些,跪求一个无线渠道比较强的小说网站,我要发表小说?
如何通过山东自助建站平台快速注册域名?
*请认真填写需求信息,我们会在24小时内与您取得联系。