繁体   English   中英

Golang 切片复制

[英]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,则内存中某处有一个切片标头,并且您的变量保存该标头;
  • 并且,如果头本身是好的,它会保存一个指向内存中某处存在的底层数组的指针。

这有什么问题很简单:你的变量总是包含一些标题。 只是您的变量保存标头可以比较等于 nil 如果是,则该语言不允许您继续使用它的其余部分。 所以正确的版本是:

  • 对于某些类型T您有一个[] T类型的变量。 该变量或者零,或者是不为零。 您可以使用例如vX == nil来测试。

  • 如果变量不是nil,则变量本身保存切片头。 保存该切片中数组位于其他地方。

现在让我们回到你的代码:

var v Vertex

这将创建struct值变量v 它里面有两个切片变量, vXvY 两者都是nil

x := []int{1, 2}

这是以下的简写:

var x []int
x = []int{1, 2}

第一行创建保存标头的变量(它最初是 nil,除了编译器可能跳过 set-to-nil-step 因为它不需要这样做)。 第二行在某处创建一个int的双元素数组,用12填充该数组,然后在变量x设置切片头以保存切片值:

  • 指向包含 {1, 2} 的数组的指针;
  • 长度,2;
  • 容量,2。

所以vX现在是一个有效的切片头,编译器已经构建了一个底层数组(某处)。

y := []int{3, 4}

我们现在对变量y做同样的事情。 它最终持有一个切片标头,其中包含三个值:

  • 指向包含 {3, 4} 的数组的指针;
  • 长度2;
  • 容量 2.

现在我们有:

v.X = x
v.Y = y

这两vX切片头从变量x复制到vX ,从变量y复制到vY 两组切片标头现在匹配。 还有两个底层数组:一个保存{1, 2},另一个保存{3, 4}。

fmt.Println(v)

Println函数内置了很多复杂的代码:它检查v ,发现它是struct类型,检查v的元素以找到vXvY ,检查它们以发现它们是[]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 个intx下的数组复制到vX下的数组。

这两个数组是同一个数组 所以这将元素复制回自身,没有任何改变。 copy函数现在返回 2,而不改变任一切片的长度或容量。

换句话说, vXx没有发生任何变化,底层数组中也没有任何变化。

您现在对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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM