在 Go 中创建 2D 切片的简洁方法是什么?

新手上路,请多包涵

我正在通过 A Tour of Go 学习 Go 。其中一个练习要求我创建一个二维切片 dy 行和 dx 列包含 uint8 我目前有效的方法是:

 a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

我认为遍历每个切片来初始化它太冗长了。如果切片有更多维度,代码将变得笨拙。有没有一种简洁的方法可以在 Go 中初始化 2D(或 n 维)切片?

原文由 hazrmard 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 432
2 个回答

没有更简洁的方法,你所做的是“正确”的方法;因为切片总是一维的,但可以组合起来构造更高维的对象。有关详细信息,请参阅此问题: Go: How is two dimensional array’s memory representation

您可以简化的一件事是使用 for range 构造:

 a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

另请注意,如果您使用 复合文字 初始化切片,您将“免费”获得它,例如:

 a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

是的,这有其局限性,因为您似乎必须枚举所有元素;但是有一些技巧,即您不必枚举所有值,只枚举那些不是切片元素类型的 零值 的值。有关此的更多详细信息,请参阅 golang 数组初始化中的键控项

例如,如果您想要一个前 10 个元素为零的切片,然后是 12 ,它可以这样创建:

 b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

另请注意,如果您使用 数组 而不是 切片,则可以非常轻松地创建它:

 c := [5][5]uint8{}
fmt.Println(c)

输出是:

 [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

对于数组,您不必遍历“外部”数组并初始化“内部”数组,因为数组不是描述符而是值。有关更多详细信息,请参阅博客文章 数组、切片(和字符串):“追加”的机制

尝试 Go Playground 上的示例。

原文由 icza 发布,翻译遵循 CC BY-SA 3.0 许可协议

有两种方法可以使用切片创建矩阵。让我们来看看它们之间的区别。

第一种方法:

 matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

第二种方法:

 matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

关于第一种方法,进行连续的 make 调用并不能确保您最终会得到一个连续的矩阵,因此您可能会将矩阵划分在内存中。让我们考虑一个包含两个可能导致此问题的 Go 例程的示例:

  1. 例程 #0 运行 make([][]int, n)matrix 分配内存,获得从 0x000 到 0x07F 的一块内存。
  2. 然后,它开始循环并执行第一行 make([]int, m) ,从 0x080 到 0x0FF。
  3. 在第二次迭代中,它被调度程序抢占。
  4. 调度程序将处理器交给例程#1 并开始运行。这个也使用 make (出于自己的目的)并从 0x100 获取到 0x17F(紧挨着例程 #0 的第一行)。
  5. 一段时间后,它被抢占,例程 #0 再次开始运行。
  6. 它执行对应于第二个循环迭代的 make([]int, m) 并从 0x180 到第二行的 0x1FF。此时,我们已经得到了两个分开的行。

使用第二种方法,例程确实 make([]int, n*m) 将所有矩阵分配到一个切片中,确保连续性。之后,需要一个循环来更新矩阵指针到每一行对应的子切片。

您可以在 Go Playground 中使用上面显示的代码来查看使用这两种方法分配的内存的差异。请注意,我使用 runtime.Gosched() 只是为了让出处理器并强制调度程序切换到另一个例程。

使用哪一个?想象一下第一种方法的最坏情况,即每一行在内存中都不是另一行的下一个。然后,如果您的程序遍历矩阵元素(以读取或写入它们),则由于数据局部性较差,与第二种方法相比,可能会有更多的缓存未命中(因此延迟更高)。另一方面,使用第二种方法可能无法为矩阵分配一块内存,因为内存碎片(块分布在整个内存中),即使理论上可能有足够的空闲内存.

因此,除非有很多内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来利用数据局部性。

原文由 Marcos Canales Mayo 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题