Dictionary 要在参数中使用的切片与贴图

Dictionary 要在参数中使用的切片与贴图,dictionary,go,reference,slice,Dictionary,Go,Reference,Slice,在golang中,slice和map都是引用类型。当您只需要修改切片/映射中的元素时,对切片/映射成员的修改将“广播”到所有切片。例如,给定的m1:=make(map[int]int);m2:=m1,m1[3]=5将导致m2[3]==5 然而,当您尝试向这两种类型中添加新元素时,情况开始有所不同。如下面的示例所示,添加到map参数中的新元素将自动显示在参数中;但是,添加到切片中的新元素在参数中被“丢弃” 问题是,为什么会有这种差异 func editMap(m map[int]int) {

在golang中,slice和map都是引用类型。当您只需要修改切片/映射中的元素时,对切片/映射成员的修改将“广播”到所有切片。例如,给定的
m1:=make(map[int]int);m2:=m1
m1[3]=5
将导致
m2[3]==5

然而,当您尝试向这两种类型中添加新元素时,情况开始有所不同。如下面的示例所示,添加到map参数中的新元素将自动显示在参数中;但是,添加到切片中的新元素在参数中被“丢弃”

问题是,为什么会有这种差异

func editMap(m map[int]int) {
    m[3] = 8
    m[4] = 9
}

func editSlice(s []int) {
    s = append(s, 5)
    s = append(s, 9)
}

func main() {
    m := make(map[int]int, 0)
    m[1] = 5
    m[2] = 3
    fmt.Printf("%v\n", m)  //map[1:5 2:3]
    editMap(m)
    fmt.Printf("%v\n", m)  //map[1:5 2:3 3:8 4:9]

    s := make([]int, 2)
    s[0] = 2
    s[1] = 5
    fmt.Printf("%v\n", s)  //[2 5]
    editSlice(s)
    fmt.Printf("%v\n", s)  //[2 5]
}
编辑: 我可能不清楚这个问题的意图,请让我重新措辞(对此表示抱歉,并感谢所有内部细节)

我真正想问的是,显然map是作为一个指针实现的,用于隐藏哈希映射的所有细节;为什么slice没有类似地实现


当前的slice实现确实非常轻量级,但是,从API的角度来看(我们这样的golang用户和Ross Cox这样的golang维护者之间的API),这两种“参考”类型的API并不统一,可能会给golang的新开发人员带来陷阱

行为上的差异在于这些类型的实现

映射是指向数据结构的指针,而切片是小结构,其中包含指向支持数组的指针、切片长度和容量。为切片标头建模:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
这是产生偏差的主要原因:指针与结构

向映射添加新元素时,指针保持不变。定点贴图结构可能会更改,但贴图指针不会更改

当您更改切片的一个元素时,实际上更改了备份数组的元素。切片头不会改变:它将继续保持相同的指针、长度和容量。切片值(切片头)的任何副本都指向相同的备份数组,因此,所有副本都将看到更改的元素

将新元素添加到切片时,描述新切片(包含附加元素)的切片标头必须更改:长度必须增加1,并且指针和容量也可以更改(如果必须分配新的支持数组以容纳新元素)

在Go中,所有内容都按值传递。这是造成偏差的第二个原因。当一个切片被传递时,一个副本将从头部生成,如果有东西被附加到副本,即使结果被正确地分配回它(返回到副本),原始切片头部也将对此一无所知

传递映射时,映射值(指针)也将被复制,但原始映射指针和复制映射指针都将指向相同的映射结构。添加一个值或通过这些指针中的任何一个来更改贴图,都将更改唯一的点贴图结构


要使它们具有相同的行为,您必须使它们具有相同的“类型”,即:指针。如上所述,映射已经是(隐藏的)指针。如果继续并开始向切片传递指针,并且对所指的值进行操作,则它们的行为将与贴图相同。在实践中,很少使用这种方法(与数组指针相比,用于帮助处理切片指针的语言支持更少),相反,替代方法在返回新切片的地方广泛使用。您可以在此处阅读更多信息:

要修改切片,只需像下面这样编辑代码():

以下是一些解释:

如果函数采用切片参数,则将其更改为 调用方将看到切片的元素

这就是为什么切片的修改参数对调用方可见,但新切片本身在返回它或分配给现有变量之前是不可见的。因为
append
返回一个新切片:

我们必须在之后返回切片,因为尽管Append可以 修改切片的元素,切片本身(运行时数据 保存指针的结构(长度和容量)通过 值


如果两个对象具有相同的内存地址,那么如果更改其中一个对象的值,则另一个对象将更改,因为它们实际上是一个对象

在代码中,
editMap
中的
m
main
中的
m
具有相同的值:相同字典对象的地址。但是,
editSlice
中的
s
main
中的
s
的值是两个不同的对象


我希望我的解释足够清楚。

最重要的是,Go中没有引用,所有内容都是按值传递的。让我们来看看切片是如何工作的,以及它们为什么会像你所观察到的那样表现。谢谢Adrian。我知道golang slice内部的细节,只是好奇slice为什么如此设计(如上编辑)。我相信这个问题值得更多的投票。虽然答案确实提供了一些关于实现的见解,但选择这种特定实现方式的理由并不完全清楚,至少对我来说是这样。为了使用一种语言并了解基本概念,没有必要了解每一个细节。我完全理解在切片之后隐藏的实现细节(如Go编程语言中所述,即在切片类型中,我们有Len、Cap等),并且完全理解按值复制机制——与C编程语言相同的机制,实际传递的参数实际上是推送到堆栈中的参数。原因是
func editSlice(s *[]int) {
    *s = append(*s, 5)
    *s = append(*s, 9)
}