在Golang,我正在努力为我的旅行商问题制作一个争夺切片功能。在我这样做的时候,我注意到当我开始编辑切片时,每次传递它时,我都给出了加扰功能。
经过一些调试后我发现它是由于我编辑了函数内部的切片。但由于Golang应该是一种“通过价值传递”的语言,这怎么可能呢?
https://play.golang.org/p/mMivoH0TuV
我提供了一个游乐场链接来展示我的意思。
通过删除第27行,您获得的输出与输入不同,这应该没有区别,因为该函数在作为参数传入时应该创建自己的切片副本。
有人可以解释这种现象吗?
答案 0 :(得分:70)
是的,Go中的所有内容都按值传递。切片也是。但切片值是标头,描述了支持数组的连续部分,切片值仅包含指向实际存储元素的数组的指针。切片值不包括其元素(与数组不同)。
因此,当您将切片传递给函数时,将从此标头创建一个副本,包括指针,该指针将指向相同的后备阵列。修改切片的元素意味着修改后备阵列的元素,因此共享相同后备阵列的所有切片将“观察”更改。
要查看切片标头中的内容,请查看reflect.SliceHeader
类型:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
查看相关/可能重复的问题: Are Golang function parameter passed as copy-on-write?
答案 1 :(得分:1)
切片通过时会与指向基础数组的指针一起传递,因此slice是指向基础数组的小型结构。小结构被复制,但是它仍然指向相同的基础数组。包含切片元素的存储块通过“引用”传递。包含容量,元素数量和元素指针的切片信息三元组按值传递。
处理传递给函数的切片的最佳方法(如果将切片的元素操纵到函数中,并且我们不希望将其反映在元素存储块上,则使用copy(s, *c)
将它们复制为:
package main
import "fmt"
type Team []Person
type Person struct {
Name string
Age int
}
func main() {
team := Team{
Person{"Hasan", 34}, Person{"Karam", 32},
}
fmt.Printf("original before clonning: %v\n", team)
team_cloned := team.Clone()
fmt.Printf("original after clonning: %v\n", team)
fmt.Printf("clones slice: %v\n", team_cloned)
}
func (c *Team) Clone() Team {
var s = make(Team, len(*c))
copy(s, *c)
for index, _ := range s {
s[index].Name = "change name"
}
return s
}
但是要小心,如果此切片包含sub slice
,则需要进一步复制,因为我们仍然需要共享指向相同内存块元素的子切片元素,例如:
type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
Warehouse string
Item string
Batches Lots
}
type Lots []Lot
type Lot struct {
Date time.Time
Key string
Value float64
}
func main() {
ins := Inventory{
Warehouse: "DMM",
Item: "Gloves",
Batches: Lots{
Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
},
}
inv2 := CloneFrom(c Inventories)
}
func (i *Inventories) CloneFrom(c Inventories) {
inv := new(Inventories)
for _, v := range c {
batches := Lots{}
for _, b := range v.Batches {
batches = append(batches, Lot{
Date: b.Date,
Key: b.Key,
Value: b.Value,
})
}
*inv = append(*inv, Inventory{
Warehouse: v.Warehouse,
Item: v.Item,
Batches: batches,
})
}
(*i).ReplaceBy(inv)
}
func (i *Inventories) ReplaceBy(x *Inventories) {
*i = *x
}
答案 2 :(得分:0)
您可以在下面找到一个示例。简短地说,片段也按值传递,但是原始片段和复制的片段链接到相同的基础数组。如果其中一个切片发生变化,则基础数组发生变化,其他切片也发生变化。
package main
import "fmt"
func main() {
x := []int{1, 10, 100, 1000}
double(x)
fmt.Println(x) ----> 3 will print [2, 20, 200, 2000] (original slice changed)
}
func double(y []int) {
fmt.Println(y) ----> 1 will print [1, 10, 100, 1000]
for i := 0; i < len(y); i++ {
y[i] *= 2 ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}
fmt.Println(y)
}
答案 3 :(得分:0)
为了补充这篇文章,以下是您分享的 Golang PlayGround 的引用传递示例:
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
@ObservedObject var item: ViewModel<Model> = ViewModel<Model>()
var body: some View {
VStack {
Button("Add", action: { item.add(model:Model()) })
Button("Modify", action: { item.selected.toggle() })
}
}
}
protocol ModelType {
var objectDidChange: ObservableObjectPublisher { get }
var selected: Bool { get set }
}
class Model: ModelType {
let objectDidChange = ObservableObjectPublisher()
var selected = false {
didSet {
objectDidChange.send()
}
}
}
class ViewModel<Model:ModelType>: ObservableObject {
var selected = false {
didSet {
model.selected = selected
}
}
func add(model: Model) {
self.model = model
cancellable = model.objectDidChange.sink(receiveValue: { _ in
self.update()
})
}
private var model: Model! = nil
private var cancellable: AnyCancellable? = nil
func update() {
// Crash log: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
print("update \(model.selected)")
}
}