切片是按值传递的吗?
Posted
技术标签:
【中文标题】切片是按值传递的吗?【英文标题】:Are slices passed by value? 【发布时间】:2017-02-20 23:10:17 【问题描述】:在 Go 中,我正在尝试为我的旅行推销员问题创建一个争夺切片函数。在执行此操作时,我注意到当我开始编辑切片时,每次传入时,我给的 scramble 函数都不同。
经过一些调试,我发现这是由于我在函数内部编辑了切片。但是既然 Go 应该是一种“按值传递”的语言,这怎么可能呢?
https://play.golang.org/p/mMivoH0TuV
我提供了一个游乐场链接来说明我的意思。 通过删除第 27 行,您将获得与保留它不同的输出,这应该不会产生影响,因为该函数应该在作为参数传入时制作自己的切片副本。 有人能解释一下这个现象吗?
【问题讨论】:
【参考方案1】:Go 中的所有内容都是按值传递的,切片也是如此。但是切片值是一个header,描述了一个支持数组的连续部分,切片值只包含一个指向实际存储元素的数组的指针。切片值不包括其元素(与数组不同)。
因此,当您将切片传递给函数时,将从该标头制作副本,包括指向同一个后备数组的指针。修改切片的元素意味着修改后备数组的元素,因此共享相同后备数组的所有切片都将“观察”更改。
要查看切片标头中的内容,请查看 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
【讨论】:
因此,解决方案是在函数内部制作切片的本地副本。而是编辑它? @user4901806 如果您不想修改传递的切片的元素(它指向的后备数组的元素),那么可以,复制一份。 @Sahas 切片头包含长度。如果附加一个元素,则必须增加长度,因此即使后备数组有空间容纳这个附加元素并且没有分配新的后备数组并且复制现有元素,原始切片头也不会“看到”它。这就是为什么内置的append()
函数必须返回一个新的切片值。更不用说是否必须分配一个新数组......
@vas type Container struct data []byte
没有嵌入,它只是一个常规字段。如果只有一个字段,那么答案是肯定的。如果您有多个字段,可能会应用隐式填充,因此整体结构大小可能会更大。
@vas 是的,无论您使用嵌入还是命名字段,它们都使用相同的内存量。【参考方案2】:
您可以在下面找到一个示例。简而言之,切片也是按值传递的,但原始切片和复制的切片链接到同一个底层数组。如果其中一个切片发生变化,则底层数组发生变化,然后其他切片发生变化。
package main
import "fmt"
func main()
x := []int1, 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
fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
【讨论】:
【参考方案3】:切片时,它会通过指向底层数组的指针传递,因此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
LotmustTime(time.Parse(custom, "1/7/2020")), "Jan", 50,
LotmustTime(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
【讨论】:
【参考方案4】:为了补充这篇文章,下面是一个通过引用传递您共享的 Golang PlayGround 的示例:
type point struct
x int
y int
func main()
data := []point1, 2, 3, 4, 5, 6, 7, 8
makeRandomDatas(&data)
func makeRandomDatas(dataPoints *[]point)
for i := 0; i < 10; i++
if len(*dataPoints) > 0
fmt.Println(makeRandomData(dataPoints))
else
fmt.Println("no more elements")
func makeRandomData(cities *[]point) []point
solution := []point(*cities)[0] //create a new slice with the first item from the old slice
*cities = append((*cities)[:0], (*cities)[1:]...) //remove the first item from the old slice
return solution
【讨论】:
以上是关于切片是按值传递的吗?的主要内容,如果未能解决你的问题,请参考以下文章