切片是按值传递的吗?
Are slices passed by value?
在 Go 中,我正在尝试为我的旅行商问题制作一个争夺切片功能。在这样做时,我注意到当我开始编辑切片时,我每次传入的打乱功能都不一样。
经过一些调试我发现这是因为我在函数内部编辑了切片。但是既然 Go 应该是一种“按值传递”的语言,这怎么可能呢?
https://play.golang.org/p/mMivoH0TuV
我提供了一个游乐场 link 来展示我的意思。
通过删除第 27 行,您会得到与保留它不同的输出,这应该没有什么区别,因为函数应该在作为参数传入时制作自己的切片副本。
谁能解释一下这个现象?
Go 中的一切都是按值传递的,切片也是。但是切片值是 header,描述了后备数组的连续部分,切片值仅包含指向实际存储元素的数组的指针。切片值不包括其元素(与数组不同)。
因此,当您将一个切片传递给一个函数时,将从此 header 生成一个副本,包括指针,该指针将指向同一个后备数组。修改切片的元素意味着修改支持数组的元素,因此共享相同支持数组的所有切片都将 "observe" 更改。
要查看切片 header 中的内容,请查看 reflect.SliceHeader
类型:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
查看相关/可能重复的问题:
Are Golang function parameter passed as copy-on-write?
阅读博客 post:Go Slices: usage and internals
Slices 当它传递时,它与指向底层数组的指针一起传递,所以 slice 是一个指向底层数组的小结构。复制了 small 结构,但它仍然指向相同的底层数组。包含切片元素的内存块由 "reference" 传递。包含容量、元素个数和指向元素的指针的切片信息三元组按值传递。
处理传递给函数的切片的最佳方法(如果切片的元素被操作到函数中,并且我们不希望这反映在元素内存块中,则使用 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
}
您可以在下面找到示例。简而言之,切片也是按值传递的,但原始切片和复制的切片链接到相同的底层数组。如果其中一个切片发生变化,则基础数组发生变化,然后其他切片发生变化。
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
}
fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}
为了补充此 post,这里有一个通过引用传递给您分享的 Golang PlayGround 的示例:
type point struct {
x int
y int
}
func main() {
data := []point{{1, 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
}
在 Go 中,我正在尝试为我的旅行商问题制作一个争夺切片功能。在这样做时,我注意到当我开始编辑切片时,我每次传入的打乱功能都不一样。
经过一些调试我发现这是因为我在函数内部编辑了切片。但是既然 Go 应该是一种“按值传递”的语言,这怎么可能呢?
https://play.golang.org/p/mMivoH0TuV
我提供了一个游乐场 link 来展示我的意思。
通过删除第 27 行,您会得到与保留它不同的输出,这应该没有什么区别,因为函数应该在作为参数传入时制作自己的切片副本。
谁能解释一下这个现象?
Go 中的一切都是按值传递的,切片也是。但是切片值是 header,描述了后备数组的连续部分,切片值仅包含指向实际存储元素的数组的指针。切片值不包括其元素(与数组不同)。
因此,当您将一个切片传递给一个函数时,将从此 header 生成一个副本,包括指针,该指针将指向同一个后备数组。修改切片的元素意味着修改支持数组的元素,因此共享相同支持数组的所有切片都将 "observe" 更改。
要查看切片 header 中的内容,请查看 reflect.SliceHeader
类型:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
查看相关/可能重复的问题: Are Golang function parameter passed as copy-on-write?
阅读博客 post:Go Slices: usage and internals
Slices 当它传递时,它与指向底层数组的指针一起传递,所以 slice 是一个指向底层数组的小结构。复制了 small 结构,但它仍然指向相同的底层数组。包含切片元素的内存块由 "reference" 传递。包含容量、元素个数和指向元素的指针的切片信息三元组按值传递。
处理传递给函数的切片的最佳方法(如果切片的元素被操作到函数中,并且我们不希望这反映在元素内存块中,则使用 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
}
您可以在下面找到示例。简而言之,切片也是按值传递的,但原始切片和复制的切片链接到相同的底层数组。如果其中一个切片发生变化,则基础数组发生变化,然后其他切片发生变化。
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
}
fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}
为了补充此 post,这里有一个通过引用传递给您分享的 Golang PlayGround 的示例:
type point struct {
x int
y int
}
func main() {
data := []point{{1, 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
}