[英]Golang Slice Copy
我不明白最后一步的结果,我认为“复制”使用值。 如果你解释一下,我将不胜感激。
type Vertex struct {
X []int
Y []int
}
func main() {
var v Vertex
x := []int{1, 2}
y := []int{3, 4}
v.X = x
v.Y = y
fmt.Println(v)
x[0] = 5
fmt.Println(v)
copy(v.X, x)
copy(v.Y, y)
fmt.Println(v)
x[0] = 6
fmt.Println(v)
}
结果:
{[1 2] [3 4]}
{[5 2] [3 4]}
{[5 2] [3 4]}
{[6 2] [3 4]}
我并不完全清楚您的误解在哪里:是切片本身还是copy
。 所以这有点长。
根据您想如何看待它(注意:只有这两种方式中的一种才是真正正确的),切片由两个(正确)或三个(错误,但在这里可能对您有所帮助)部分组成。 让我们从错误的开始,注意它是错误的,然后修复它:
vX
,它可以为零;这有什么问题很简单:你的变量总是包含一些标题。 只是您的变量保存的标头可以比较等于 nil 。 如果是,则该语言不允许您继续使用它的其余部分。 所以正确的版本是:
对于某些类型T
您有一个[] T
类型的变量。 该变量或者是零,或者是不为零。 您可以使用例如vX == nil
来测试。
如果变量不是nil,则变量本身保存切片头。 保存该切片中值的数组位于其他地方。
现在让我们回到你的代码:
var v Vertex
这将创建struct
值变量v
。 它里面有两个切片变量, vX
和vY
; 两者都是nil
。
x := []int{1, 2}
这是以下的简写:
var x []int
x = []int{1, 2}
第一行创建保存标头的变量(它最初是 nil,除了编译器可能跳过 set-to-nil-step 因为它不需要这样做)。 第二行在某处创建一个int
的双元素数组,用1
和2
填充该数组,然后在变量x
设置切片头以保存切片值:
所以vX
现在是一个有效的切片头,编译器已经构建了一个底层数组(某处)。
y := []int{3, 4}
我们现在对变量y
做同样的事情。 它最终持有一个切片标头,其中包含三个值:
现在我们有:
v.X = x
v.Y = y
这两vX
切片头从变量x
复制到vX
,从变量y
复制到vY
。 两组切片标头现在匹配。 还有两个底层数组:一个保存{1, 2},另一个保存{3, 4}。
fmt.Println(v)
Println
函数内置了很多复杂的代码:它检查v
,发现它是struct
类型,检查v
的元素以找到vX
和vY
,检查它们以发现它们是[]int
类型,检查它们的nil-ness 的标头,发现它们不是 nil,并打印出您看到的内容。
x[0] = 5
这使用x
中的值(切片头)来查找包含 {1, 2} 的数组,然后将 1 替换为 5。所以现在,底层数组包含 {5, 2}。 x
的切片头没有改变,当然vX
的切片头也没有改变。 这两个切片头仍然将长度和容量列为 2,并且仍然指向该单个底层数组。
fmt.Println(v)
这重复了相当复杂的工作,这次打印了你看到的[5 2]
值。
copy(v.X, x)
copy
函数需要两个切片头作为参数。 1第一个目标dst
需要是一个非 nil 切片才能发生任何有用的事情。 2第二个,源src
,可以是 nil 或非 nil。 两者必须具有相同的基础类型。 那么copy
作用是:
len(dst)
和len(src)
的较小者;src
的底层数组复制到dst
的底层数组; 和 所以我们发现len(vX)
是 2 ,而len(x)
也是 2 ,因此copy
会将 2 个int
从x
下的数组复制到vX
下的数组。
这两个数组是同一个数组。 所以这将元素复制回自身,没有任何改变。 copy
函数现在返回 2,而不改变任一切片的长度或容量。
换句话说, vX
或x
没有发生任何变化,底层数组中也没有任何变化。
您现在对vY, y
重复这项工作,它再次没有任何改变。 两个切片头共享相同的底层数组,因此此副本确实复制了两个元素,但没有任何更改。
其余代码以明显的方式进行。
如果您想制作某个切片的新副本,您应该:
在这种情况下,例如:
tmp := make([]int, len(x))
copy(tmp, x)
v.X = tmp
tmp
变量不是必需的:您可以改为编写:
v.X = make([]int, len(x))
copy(v.X, x)
但是你可以定义一个小的分配和复制函数并使用它:
func copyIntSlice([]int a) []int {
tmp := make([]int, len(a))
copy(tmp, a)
return tmp
}
然后使用:
v.X = copyIntSlice(x)
例如,这使得vY
也可以在一行中执行此操作:
v.Y = copyIntSlice(y)
这是实际创建新数组的make
步骤。 给定一些初始底层数组,您可以随意修改切片标头,但在某些时候,您可能需要创建一个新数组,否则您的所有切片都将使用旧的(共享)底层数组。
1第二个参数可以是字符串,而不是切片。 字符串就像一个字节片,但缺少单独的容量字段。 幸运的是, copy
不使用第二个参数的容量; 这就是使这一切最终得以解决的原因。
2如果copy
的第一个参数是 nil,则copy
什么都不做。 调用它并没有错,它只是没有做任何事情。 如果那是正确的做法——如果我们不应该为这个案子做任何事——那就没问题了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.