Golang中奇怪的指针行为解析
Golang中奇怪的指针行为解析 你好,我在使用指针时遇到了奇怪的行为。有人能解释一下这是如何运作的吗?
package main
import "fmt"
func addAndCheck(candidates []int, candidateIndex int, target int, currentTest []int, l int, finalSolution *[][]int) {
currentTest = append(currentTest, candidates[candidateIndex])
if target == 0 {
fmt.Println("currentTest --> ", currentTest)
*finalSolution = append(*finalSolution, currentTest)
fmt.Println("current finalResult --> ", *finalSolution)
return
}
if target < 0 {
return
}
for i := candidateIndex; i < l; i++ {
//fmt.Println(target, candidates[i], i, target-candidates[i], currentTest )
addAndCheck(candidates, i, target-candidates[i], currentTest, l, finalSolution)
}
}
func combinationSum(candidates []int, target int) [][]int {
solution := make([][]int, 0)
for i, k := range candidates {
currentTest := make([]int, 0)
addAndCheck(candidates, i, target-k, currentTest, len(candidates), &solution)
}
return solution
}
func main() {
a := combinationSum([]int{2, 3, 5}, 8)
fmt.Println("finalResult --> ", a)
}
如你从代码中所见,我将对我有效的结果追加到 finalResult 数组中。奇怪的事情发生在数组的第一个被推入的元素上。以下是我在程序执行期间打印 currentTest 和 finalResult 时收到的输出。
currentTest → [2 2 2 2] current finalResult → [[2 2 2 2]] currentTest → [2 3 3] current finalResult → [[2 2 2 5] [2 3 3]] currentTest → [3 5] current finalResult → [[2 2 2 5] [2 3 3] [3 5]] finalResult → [[2 2 2 5] [2 3 3] [3 5]]
第一个被推入的切片 [2 2 2 2] 怎么会变成 [2 2 2 5] 呢?
提前感谢你的帮助。
更多关于Golang中奇怪的指针行为解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
当你对 currentTest 进行追加操作时,并不能保证每次都会获得新的内存。在达到很高的容量之前,append 的当前实现会加倍容量,所以你从容量 0 开始,第一次追加得到 1,然后是 2,接着是 4。
你的算法深入 addAndCheck 函数 4 层,但在第 3 层之后,currentTest 的容量是 4,而不是 3。在第 4 层调用 addAndCheck 的第一次迭代中,产生了 []int{2, 2, 2, 2},将该切片追加到 finalResult,然后返回。现在第 3 层的 addAndCheck 继续其循环,并通过调用另一层 addAndCheck 来测试 5,该调用将 5 追加到 currentTest[:3](这与追加到 finalResult 的实际切片是同一个,其中包含 []int{2, 2, 2, 2}),因此你得到了 []int{2, 2, 2, 5}。
这里有一个使用 printTest 函数的相同示例,它展示了切片内存和递归的情况:https://play.golang.org/p/_dZAN1u7-4Z
一个修复方法是使用三索引切片来对 currentTest 进行切片,这样在第 8 行追加时总是会创建一个新的切片:
currentTest = append(currentTest[:len(currentTest):len(currentTest)], candidates[candidateIndex])
这个问题涉及Go语言中切片底层数组的共享机制。当您使用append修改切片时,如果底层数组容量不足,会创建新的数组,否则会复用原有数组。在您的代码中,currentTest切片在递归过程中被多个结果共享并修改。
关键问题出现在这一行:
*finalSolution = append(*finalSolution, currentTest)
这里存储的是currentTest切片的引用(底层数组指针),而不是数据的副本。当后续递归调用修改currentTest时,之前存储的结果也会被修改。
以下是具体解释和修复方案:
问题分析:
- 第一次找到
[2 2 2 2]时,将其追加到finalSolution - 后续递归继续使用同一个
currentTest切片 - 当找到
[2 3 3]时,append操作可能修改了底层数组 - 由于
[2 2 2 2]和后续结果共享底层数组,导致第一个结果被意外修改
解决方案: 创建切片的副本后再存储:
func addAndCheck(candidates []int, candidateIndex int, target int, currentTest []int, l int, finalSolution *[][]int) {
currentTest = append(currentTest, candidates[candidateIndex])
if target == 0 {
// 创建切片的副本
result := make([]int, len(currentTest))
copy(result, currentTest)
*finalSolution = append(*finalSolution, result)
return
}
if target < 0 {
return
}
for i := candidateIndex; i < l; i++ {
addAndCheck(candidates, i, target-candidates[i], currentTest, l, finalSolution)
}
}
或者使用更简洁的append方式创建副本:
func addAndCheck(candidates []int, candidateIndex int, target int, currentTest []int, l int, finalSolution *[][]int) {
currentTest = append(currentTest, candidates[candidateIndex])
if target == 0 {
// 使用append创建新切片
*finalSolution = append(*finalSolution, append([]int{}, currentTest...))
return
}
if target < 0 {
return
}
for i := candidateIndex; i < l; i++ {
addAndCheck(candidates, i, target-candidates[i], currentTest, l, finalSolution)
}
}
修改后的输出将是:
finalResult → [[2 2 2 2] [2 3 3] [3 5]]
这个问题的核心是理解Go语言切片的三要素:指针、长度和容量。当多个切片共享同一个底层数组时,对其中一个切片的修改可能会影响其他切片,特别是在使用append且容量足够时不会重新分配数组的情况下。

