我正在通过A Tour of Go学习Go.其中一个练习要求我创建一个包含dy
行和dx
列的2D切片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
}
Run Code Online (Sandbox Code Playgroud)
我认为迭代每个切片来初始化它太冗长了.如果切片具有更多尺寸,则代码将变得难以处理.是否有一种简洁的方法来初始化Go中的2D(或n维)切片?
icz*_*cza 98
没有更简洁的方式,你所做的就是"正确"的方式; 因为切片总是一维的,但可以组成以构造更高维的对象.有关更多详细信息,请参阅此问题:Go:二维数组的内存表示形式如何.
您可以简化的一件事是使用for range
构造:
a := make([][]uint8, dy)
for i := range a {
a[i] = make([]uint8, dx)
}
Run Code Online (Sandbox Code Playgroud)
另请注意,如果使用复合文字初始化切片,则可以"免费"获取此切片,例如:
a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]
Run Code Online (Sandbox Code Playgroud)
是的,这有其局限性,因为看似你必须列举所有元素; 但是有一些技巧,即您不必枚举所有值,只需要那些不是切片元素类型的零值的值.有关此内容的更多详细信息,请参阅golang数组初始化中的键控项.
例如,如果你想要一个切片,其中前10个元素是零,然后遵循1
和2
,它可以这样创建:
b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]
Run Code Online (Sandbox Code Playgroud)
另请注意,如果您使用数组而不是切片,则可以非常轻松地创建它:
c := [5][5]uint8{}
fmt.Println(c)
Run Code Online (Sandbox Code Playgroud)
输出是:
[[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]]
Run Code Online (Sandbox Code Playgroud)
对于数组,您不必遍历"外部"数组并初始化"内部"数组,因为数组不是描述符而是值.请参阅博客文章阵列,切片(和字符串):"附加"的机制以获取更多详细信息.
试试Go Playground上的例子.
有两种使用切片创建矩阵的方法。让我们看看它们之间的区别。
第一种方法:
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, m)
}
Run Code Online (Sandbox Code Playgroud)
第二种方法:
matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
matrix[i] = rows[i*m : (i+1)*m]
}
Run Code Online (Sandbox Code Playgroud)
关于第一种方法,进行连续make
调用并不能确保最终得到一个连续的矩阵,因此可能会将矩阵划分为内存。让我们考虑一个带有两个Go例程的示例,这可能会导致这种情况:
make([][]int, n)
以获取分配的内存matrix
,从0x000到0x07F获取一块内存。make([]int, m)
,从0x080到0x0FF。make
(出于自己的目的),并且从0x100到0x17F(在例程#0的第一行旁边)。make([]int, m)
对应于第二个循环迭代,并为第二行从0x180到0x1FF。至此,我们已经得到了两行。使用第二种方法,例程可以make([]int, n*m)
确保在单个切片中分配所有矩阵,从而确保连续性。之后,需要一个循环来更新指向与每一行相对应的子切片的矩阵指针。
您可以在Go Playground中使用上面显示的代码来查看使用这两种方法分配的内存的差异。请注意,我runtime.Gosched()
仅用于产生处理器并强制调度程序切换到另一个例程的目的。
使用哪一个?想象一下第一种方法的最坏情况,即每一行在内存中都不是另一行。然后,如果您的程序遍历矩阵元素(以读取或写入矩阵元素),则由于数据位置更差,与第二种方法相比,可能会有更多的缓存未命中(因此延迟时间更长)。另一方面,使用第二种方法,尽管理论上可能有足够的可用内存,但由于内存碎片(块散布在整个内存中),可能无法为矩阵分配单个内存。
因此,除非存在大量的内存碎片并且要分配的矩阵足够大,否则您总是想使用第二种方法来利用数据局部性。
使用 Go 1.18,你可以获得泛型。
这是一个使用泛型来为任何细胞类型创建 2D 切片的函数。
func Make2D[T any](n, m int) [][]T {
matrix := make([][]T, n)
rows := make([]T, n*m)
for i, startRow := 0, 0; i < n; i, startRow = i+1, startRow+m {
endRow := startRow + m
matrix[i] = rows[startRow:endRow:endRow]
}
return matrix
}
Run Code Online (Sandbox Code Playgroud)
使用工具箱中的该函数,您的代码将变为:
a := Make2D[uint8](dy, dx)
Run Code Online (Sandbox Code Playgroud)
您可以在 Go Playground 上使用代码。