slice vs map to be used in parameter

与世无争的帅哥 提交于 2019-12-17 20:29:35

问题


In golang, slice and map are both reference types. When you simply need to modify elements in slice/map, the modification to slice/map members will be "broadcasted" to all slices. E.g, given m1 := make(map[int]int); m2 := m1, m1[3] = 5 will lead to m2[3] == 5.

However, when you try to add new elements into these two types, things start to differ. As is shown in the example below, the new elements added into a map param will be shown automatically in the argument; however, the new elements added into a slice, are 'discarded' in the argument.

Question is, why is this difference?

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]
}

Edit: I may not be clear on the intention of this question and please let me rephrase it(sorry for this and thanks for all the internal details).

What I really want to ask is, apparently map is implemented as a pointer to hide all details for the hash map; why wasn't slice implemented similarly?

The current slice implementation is indeed quite lightweight, however, from an API point of view (API between golang users like us and golang maintainers like Ross Cox), the APIs of these two 'reference' types are not that uniform and may lead to pitfalls for developers new to golang.


回答1:


The difference in behavior lies in the implementation of those types.

Maps are pointers to a data structure, while slices are small structs which contain the pointer to the backing array, the slice length and capacity. reflect.SliceHeader models a slice header:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

This is the main reason for the deviation: pointer vs struct.

When you add a new element to a map, the pointer remains the same. The pointed map structure may change, but the map pointer will not.

When you change an element of a slice, you effectively change the element of the backing array. The slice header will not change: it will continue to hold the same pointer, length and capacity. Any copiy of the slice value (the slice header) points to the same backing array, and as such, all will see the changed element.

When you add a new element to a slice, the slice header that describes the new slice (which contains the additional element) must change: the length must be increased by 1, and optionally the pointer and capacity may change too (if a new backing array had to be allocated to accommodate the new element).

Everything is passed by value in Go. This is the 2nd reason for the deviation. When a slice is passed, a copy is made from the header, and if something is appended to the copy, even if the result is properly assigned back to it (back to the copy), the original slice header will know nothing about that.

When a map is passed, the map value (which is a pointer) will also be copied, but both the original map pointer and the copy map pointer will point to the same map structure. Adding a value or changing the map via any of those pointers will change the one and only pointed map structure.

To make them behave the same, you would have to make them the same "type" of type, that is: pointers. As noted above, maps are already (hidden) pointers. If you go ahead and start passing pointers to slices, and you operate on the pointed value, they will behave the same as maps. In practice this is rarely used (there is even less language support to aid working with slice pointers than array pointers), and rather the alternative approach is wide spread where the new slice is returned. You can read more about it here: Slicing a slice pointer passed as argument




回答2:


To modify the slice you just need to edit the code like this (https://play.golang.org/p/2SeP93itIL):

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

There is some explanation in Effective Go:

If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller

That's why modified arguments of the slice will be visible to the caller, but the new slice itself is not visible until you return it or assign to the existing variable. Because append returns a new slice:

We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.




回答3:


If two objects have the same memory address, then if you change the value of one of them, the other will change, because they are actually one object.

In your code, m in editMap and m in main have the same value: the address of a same dictionary object. However, the values of s in editSlice and s in main are two different objects.

I hope my explanation is clear enough.



来源:https://stackoverflow.com/questions/47590444/slice-vs-map-to-be-used-in-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!