golang切片是否超值?

时间:2016-10-12 08:15:07

标签: go slice pass-by-value

在Golang,我正在努力为我的旅行商问题制作一个争夺切片功能。在我这样做的时候,我注意到当我开始编辑切片时,每次传递它时,我都给出了加扰功能。

经过一些调试后我发现它是由于我编辑了函数内部的切片。但由于Golang应该是一种“通过价值传递”的语言,这怎么可能呢?

https://play.golang.org/p/mMivoH0TuV

我提供了一个游乐场链接来展示我的意思。 通过删除第27行,您获得的输出与输入不同,这应该没有区别,因为该函数在作为参数传入时应该创建自己的切片副本。
有人可以解释这种现象吗?

4 个答案:

答案 0 :(得分:70)

是的,Go中的所有内容都按值传递。切片也是。但切片值是标头,描述了支持数组的连续部分,切片值仅包含指向实际存储元素的数组的指针。切片值不包括其元素(与数组不同)。

因此,当您将切片传递给函数时,将从此标头创建一个副本,包括指针,该指针将指向相同的后备阵列。修改切片的元素意味着修改后备阵列的元素,因此共享相同后备阵列的所有切片将“观察”更改。

要查看切片标头中的内容,请查看reflect.SliceHeader类型:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

查看相关/可能重复的问题: Are Golang function parameter passed as copy-on-write?

阅读博文:Go Slices: usage and internals

答案 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)")
    }
}