Golang递归中传递结构体引用遇到的问题
Golang递归中传递结构体引用遇到的问题 大家好,我来自阿拉伯联合酋长国(阿联酋)迪拜,遇到了一个递归函数的问题,希望能得到一些关于可能出错原因的建议或意见。
以下是情况的简要概述:
我有一个递归函数 play(b),其中 b 是一个包含两个切片(s 和 w)的结构体。这些切片包含具有几个字段的结构体:string1、string2 和 bool。
在 play(b) 函数内部,有一段代码如下:
bNew := mm(bIn)
bOrigIn := bIn
play(bNew)
play() 会递归调用自身,最终递归会返回。我看到的问题是,在从某些递归层级返回后,bIn 和 bOrigIn 都与递归调用前的状态不同,即使它们本应保持不变。
问题:
- 函数达到某个递归深度(例如 23 层),然后继续深入另外 10 层。
- 当返回到第 23 层时,
bIn已从其原始值改变。奇怪的是,bOrigIn也以与bIn完全相同的方式发生了变化(这表明它们以某种方式关联在一起)。 - 然而,
bNew如预期保持不变。
补充细节:
bIn包含切片s和w,每个切片有 9 个元素。- 当返回到第 23 层时,切片
w的第 9 个元素已被修改。具体来说,它现在包含了切片s第 9 个元素的值,但布尔值被翻转了,并且s的第 9 个元素被移除了。 - 这个修改是由递归过程中的某个时刻调用的函数
mm(bIn)完成的。
核心问题:
-
为什么
bIn和bOrigIn会改变,它们本应是局部变量?- 既然
bIn和bOrigIn都是函数级别的变量(非包级别),为什么它们似乎会受到递归中发生的变化的影响?
- 既然
-
这是切片问题吗?
- 我怀疑由于 Go 的切片是引用类型(即它们指向同一个底层数组),递归中所做的更改可能会同时影响
bIn和bOrigIn,因为它们共享对相同内存位置的引用。如果是这种情况,这是否解释了为什么两者都被修改,而本应只有其中一个被修改?
- 我怀疑由于 Go 的切片是引用类型(即它们指向同一个底层数组),递归中所做的更改可能会同时影响
-
我应该注意什么来防止这种情况?
- 在 Go 中处理切片和递归时,是否有任何最佳实践可以避免此类意外的副作用?具体来说,在将切片(或结构体)传递给像
mm()这样的函数之前,我是否应该复制它们,以确保更改不会意外传播?
- 在 Go 中处理切片和递归时,是否有任何最佳实践可以避免此类意外的副作用?具体来说,在将切片(或结构体)传递给像
我尝试过的:
- 我尝试过使用
:=和=来创建bOrigIn := bIn,但问题仍然存在。 - 我知道 Go 中的切片是引用类型,因此除非显式复制,否则在函数中对一个切片的任何修改都可能影响原始切片。
寻求建议:
如果有人对如何处理这种情况或如何避免这类切片引用问题有建议,我将非常感谢您的想法!
感谢阅读! Emmanuel Katto
更多关于Golang递归中传递结构体引用遇到的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
在未看到实际代码的情况下,很难准确指出你的具体问题。
但一般来说,你是对的。像切片(slice)和映射(map)这样的几种类型,实际上是经过修饰的指针。因此,当你使用赋值语句 a := b 时,你是在复制指针,它仍然指向同一个值。如果你想复制切片的内容,你必须使用:
b := make([]whatever, len(a))
copy(b,a)
但是请注意——这种复制只有一层的深度。例如,如果切片是一个指向指针、映射或可能包含指针的结构体的切片,那么这些引用将不会被复制。如果你也想复制这些值,就必须使用递归复制。
更多关于Golang递归中传递结构体引用遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我怀疑由于 Go 的切片是引用类型(即它们指向同一个底层数组)
切片是值类型;但它们持有一个指向数组的指针。根据这个回答,切片头看起来是这样的:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
来自 Go 语言之旅:
切片就像数组的引用
切片不存储任何数据,它只是描述底层数组的一个部分。
更改切片的元素会修改其底层数组的相应元素。
共享同一底层数组的其他切片将会看到这些更改。
这里有一篇 Dave Cheney 写的很好的博客文章:
https://dave.cheney.net/2018/07/12/slices-from-the-ground-up
这是一个典型的切片引用问题。在Go中,切片是引用类型,当你在递归中传递包含切片的结构体时,实际上传递的是对底层数组的引用。
问题分析:
bOrigIn := bIn创建的是结构体的浅拷贝,切片字段仍然指向相同的底层数组mm(bIn)函数可能修改了切片的底层数据- 递归调用中的修改会影响到所有共享相同底层数组的变量
示例代码说明问题:
package main
import "fmt"
type Data struct {
S []int
W []int
}
func mm(d Data) Data {
// 修改切片元素 - 这会影响到原始数据
if len(d.S) > 0 {
d.S[0] = 999
}
return d
}
func play(d Data, depth int) {
if depth >= 3 {
return
}
fmt.Printf("Depth %d - Before: d.S[0] = %d\n", depth, d.S[0])
bNew := mm(d)
bOrigIn := d // 浅拷贝,切片仍然共享底层数组
fmt.Printf("Depth %d - After mm: d.S[0] = %d, bOrigIn.S[0] = %d\n",
depth, d.S[0], bOrigIn.S[0])
play(bNew, depth+1)
fmt.Printf("Depth %d - After recursion: d.S[0] = %d, bOrigIn.S[0] = %d\n",
depth, d.S[0], bOrigIn.S[0])
}
func main() {
d := Data{
S: []int{1, 2, 3},
W: []int{4, 5, 6},
}
play(d, 0)
}
输出会显示所有变量都被修改了,因为它们共享相同的底层数组。
解决方案:深度拷贝结构体
import "golang.org/x/exp/slices"
func deepCopyData(d Data) Data {
return Data{
S: slices.Clone(d.S), // Go 1.21+ 或使用 exp/slices
W: slices.Clone(d.W),
}
}
// 或者手动实现
func deepCopyDataManual(d Data) Data {
sCopy := make([]int, len(d.S))
wCopy := make([]int, len(d.W))
copy(sCopy, d.S)
copy(wCopy, d.W)
return Data{
S: sCopy,
W: wCopy,
}
}
// 在递归中使用
func play(d Data, depth int) {
if depth >= 3 {
return
}
// 创建深度拷贝
bNew := deepCopyData(d)
bNew = mm(bNew) // 现在修改不会影响原始数据
bOrigIn := deepCopyData(d) // 深度拷贝保持原始状态
play(bNew, depth+1)
// 现在 d 和 bOrigIn 保持不变
}
对于包含结构体切片的深度拷贝:
type Item struct {
String1 string
String2 string
Bool bool
}
type Container struct {
S []Item
W []Item
}
func deepCopyContainer(c Container) Container {
sCopy := make([]Item, len(c.S))
wCopy := make([]Item, len(c.W))
for i := range c.S {
sCopy[i] = c.S[i] // Item 是值类型,会被拷贝
}
for i := range c.W {
wCopy[i] = c.W[i]
}
return Container{
S: sCopy,
W: wCopy,
}
}
关键点:
- Go中的切片赋值是浅拷贝,只拷贝切片头(指针、长度、容量)
- 结构体赋值会拷贝所有字段,但对于切片字段只拷贝切片头
- 递归中需要显式进行深度拷贝来隔离数据修改
- 使用
copy()函数或slices.Clone()来复制切片内容

