go - Golang 切片拷贝
问题描述
我不明白最后一步的结果,我认为“复制”使用值。如果您能解释一下,我将不胜感激。
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
. 所以这有点长。
根据您要如何看待它(注意:这两种方式中只有一种是真正正确的),切片由两个(正确)或三个(错误,但在这里可能对您有所帮助)部分组成。让我们从错误的开始,注意到它是错误的,然后修复它:
- 有你的变量,比如
v.X
,它可以是nil; - 然后,如果您的变量不为零,则内存中的某处有一个切片标头,并且您的变量包含该标头;
- 并且,如果标头本身是好的,它会保存一个指向内存中某处存在的底层数组的指针。
这有什么问题很简单:你的变量总是包含一些标题。只是您的变量持有的标头可以比较等于 nil。如果是这样,该语言不会让您继续使用它的其余部分。所以正确的版本是:
您有某个类型的类型变量。这个变量要么是nil,要么不是nil。您可以使用例如.
[] T
T
v.X == nil
如果变量不为零,则变量本身包含切片头。保存该切片中值的数组位于其他位置。
现在让我们回到您的代码:
var v Vertex
这将创建struct
-valued 变量v
。它里面有两个切片变量,v.X
和v.Y
; 两者都是nil
。
x := []int{1, 2}
这是以下的简写:
var x []int
x = []int{1, 2}
第一行创建了保存标头的变量(最初为 nil,但编译器可能会跳过 set-to-nil-step,因为它不需要这样做)。第二行在某处创建一个双元素数组,用andint
填充该数组,然后在变量中设置切片头以保存切片值:1
2
x
- 指向包含 {1, 2} 的数组的指针;
- 长度,2;和
- 容量,2。
现在v.X
是一个有效的切片头,编译器已经构建了一个底层数组(某处)。
y := []int{3, 4}
我们现在对变量做同样的事情y
。它最终持有一个切片头,具有三个值:
- 指向包含 {3, 4} 的数组的指针;
- 长度 2;和
- 容量 2。
现在我们有:
v.X = x
v.Y = y
这两行将切片标头从 variable复制x
到v.X
,从 variable复制y
到v.Y
。两组切片标头现在匹配。还有两个底层数组:一个保存 {1, 2},另一个保存 {3, 4}。
fmt.Println(v)
该Println
函数内置了许多复杂的代码:它检查v
,发现它是一个struct
类型,检查元素v
以查找v.X
和v.Y
,检查它们以发现它们是[]int
类型,检查它们的标题是否为空,发现它们不是零,并打印出你所看到的。
x[0] = 5
这使用x
切片头中的值来查找包含 {1, 2} 的数组,然后将 1 替换为 5。所以现在,底层数组包含 {5, 2}。里面的切片头x
没有改变,当然里面的切片头v.X
也没有改变。这两个切片标头仍然将长度和容量列为 2,并且都仍然指向单个底层数组。
fmt.Println(v)
这重复了相当复杂的工作,这次打印了[5 2]
你看到的值。
copy(v.X, x)
该copy
函数需要两个切片头作为参数。1 第一个,destination dst
,必须是一个非零切片才能让任何有用的事情发生。2 第二个, source src
,可以是 nil 或非 nil。两者必须具有相同的基础类型。copy
然后是:
- 找到 和 中的较小
len(dst)
者len(src)
; - 将许多元素从底层数组复制
src
到底层数组dst
;和 - 返回那个号码。
所以我们找到len(v.X)
, 是 2,并且len(x)
, 这也是 2,因此会将 2从底层数组copy
复制到底层数组。int
x
v.X
这两个数组是同一个数组。因此,这会将元素复制回自身,而不会改变任何内容。该copy
函数现在返回 2,而不更改任一切片的长度或容量。
换句话说,v.X
or都没有发生x
任何事情,基础数组中也没有任何变化。
您现在对 重复这项工作v.Y, 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)
例如,这也使它成为单行v.Y
:
v.Y = copyIntSlice(y)
这make
是实际创建新数组的步骤。给定一些初始的底层数组,您可以随意摆弄切片头,但在某些时候,您可能需要创建一个新数组,否则您的所有切片都在使用旧的(共享)底层数组。
1第二个参数可以是字符串,而不是切片。字符串就像一个字节切片,但缺少单独的容量字段。幸运的是,copy
没有使用第二个参数的容量;这就是让这一切最终得以解决的原因。
2如果第一个参数copy
为 nil,则不copy
执行任何操作。调用它并没有错,它只是没有做任何事情。如果这是正确的做法——如果我们不应该为这个案子做任何事情——那很好。
推荐阅读
- reactjs - ReactJSA Typescript Route JSX 元素不接受
- java - Spring Boot - application.yml,从文件中读取值
- r - plot_ly 删除 r 中的某些行后重新投影 shapefile
- xpath - 如何在 XPath 中提取属性节点的某个值?
- snowflake-cloud-data-platform - 如何在雪花中连接数值和字符串值?
- keycloak - 将 google 身份验证与密钥 cloack 集成
- postgresql - 如何在没有 JSON 处理函数的情况下在 WHERE 中编写 jsonb
- python - Python shutil.copy 导致 PyQt GUI 冻结
- flutter - Flutter - 快速信息 UI
- html5-video - 为什么两个视频元素不能共享一个 blob url?