
本文深入探讨了在go语言中如何以惯用的方式预分配和填充包含指针的切片。通过分析`make`函数对切片长度和容量的影响,以及`append`操作的行为,文章指出了常见的误区。我们提供了两种主要的解决方案:一是通过直接索引来填充已预设长度的切片,二是利用`make`函数预设容量并结合`append`操作来高效构建切片。
在Go语言中,切片(slice)是构建动态数组的强大工具。然而,当涉及到预分配并填充包含指针的切片时,如果不理解make函数和append操作的底层机制,可能会导致非预期的结果。本文将详细阐述Go语言中预分配和填充切片的惯用方法。
理解make与append的行为
在Go中,make函数用于创建切片、映射或通道。对于切片,它有以下两种常用形式:
make([]Type, length): 创建一个长度为length的切片,其所有元素都会被初始化为Type的零值。对于指针类型*Type,零值是nil。切片的容量(capacity)默认与长度相等。make([]Type, length, capacity): 创建一个长度为length,容量为capacity的切片。同样,所有元素会被初始化为Type的零值。
append函数用于向切片追加元素。它总是将新元素添加到切片的末尾,并返回一个新的切片(如果底层数组需要重新分配)。append操作会增加切片的长度,但不会改变已存在元素的索引或值。
常见误区示例
考虑以下代码,尝试预分配一个包含5个*UselessStruct指针的切片,然后使用append填充:
立即学习“go语言免费学习笔记(深入)”;
package mainimport "fmt"type UselessStruct struct { a int b int}func main() { mySlice := make([]*UselessStruct, 5) // 长度为5,元素为5个nil指针 for i := 0; i != 5; i++ { mySlice = append(mySlice, &UselessStruct{}) // 追加新元素 } fmt.Println(mySlice)}
这段代码的输出是:[ 0xc0… 0xc0… 0xc0… 0xc0… 0xc0…]。可以看到,前5个元素仍然是nil,append操作并没有替换这些nil值,而是将新的UselessStruct指针追加到了切片的末尾,导致切片的长度变成了10。
如果切片存储的是结构体值而非指针,情况类似:
package mainimport "fmt"type UselessStruct struct { a int b int}func main() { mySlice := make([]UselessStruct, 5) // 长度为5,元素为5个零值UselessStruct for i := 0; i != 5; i++ { mySlice = append(mySlice, UselessStruct{}) // 追加新元素 } fmt.Println(mySlice)}
输出将是:[{0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0}]。同样,前5个元素是零值结构体,后续追加的才是新的结构体。
这表明,当使用make([]Type, length)创建一个具有非零长度的切片时,切片已经包含了length个元素(零值)。append操作只会在此基础上增加新元素,而不会覆盖已存在的元素。
惯用解决方案
Go语言提供了两种惯用的方法来正确地预分配和填充切片,具体取决于你的需求:
方案一:直接索引填充(适用于已知固定长度)
当你事先知道切片的最终长度,并且希望填充所有元素时,最直接和惯用的方法是使用make([]Type, length)创建切片,然后通过索引直接为每个位置赋值。
package mainimport "fmt"type UselessStruct struct { a int b int}func main() { const size = 5 mySlice := make([]*UselessStruct, size) // 创建一个长度为5的切片,元素为nil指针 // 通过索引直接填充每个位置 for i := range mySlice { mySlice[i] = new(UselessStruct) // 为每个位置分配并赋值一个新的UselessStruct指针 // 或者使用 &UselessStruct{} 也可以 // mySlice[i] = &UselessStruct{} } fmt.Println(mySlice) // 预期输出:[0xc0... 0xc0... 0xc0... 0xc0... 0xc0...] (5个不同的指针)}
在这个例子中:
make([]*UselessStruct, size)创建了一个长度为size的切片,其中包含size个nil指针。for i := range mySlice遍历切片的索引。mySlice[i] = new(UselessStruct)在每个索引位置上创建了一个新的UselessStruct实例,并将其地址赋值给mySlice[i]。new(UselessStruct)和&UselessStruct{}都用于创建结构体实例的指针,它们在大多数情况下是等效的。
这种方法简洁明了,直接表达了“我需要一个固定大小的切片,并用特定值填充它”的意图。
方案二:预设容量与append结合(适用于动态增长但需优化性能)
当你需要动态地向切片添加元素,但又希望通过预分配内存来避免频繁的底层数组重新分配(这会带来性能开销)时,可以使用make([]Type, 0, capacity)来创建切片。这会创建一个长度为0但预留了capacity空间(底层数组)的切片。
package mainimport "fmt"type UselessStruct struct { a int b int}func main() { const capacity = 5 // 创建一个长度为0,但容量为5的切片 mySlice := make([]*UselessStruct, 0, capacity) // 使用append追加元素 for i := 0; i < capacity; i++ { mySlice = append(mySlice, &UselessStruct{}) // 追加新的UselessStruct指针 } fmt.Println(mySlice) // 预期输出:[0xc0... 0xc0... 0xc0... 0xc0... 0xc0...] (5个不同的指针)}
在这个例子中:
make([]*UselessStruct, 0, capacity)创建了一个空切片,但底层数组已分配了容纳capacity个元素的空间。append操作会将新元素添加到切片的末尾。由于切片有足够的容量,前capacity次append操作不会导致底层数组的重新分配,从而提高了效率。每次append都会增加切片的长度。
这种方法适用于你不知道最终长度,但可以预估一个大致容量的场景,以优化性能。
总结与注意事项
make([]Type, length):创建一个包含length个零值元素的切片。如果你想填充所有这些元素,应该使用索引mySlice[i] = value来赋值。make([]Type, 0, capacity):创建一个空切片,但预留了capacity的内存空间。如果你打算通过append动态添加元素,并希望优化性能,这是首选方式。append操作:append总是将元素添加到切片的末尾,并增加切片的长度。它不会覆盖或替换切片中已存在的元素。指针类型与值类型:无论是切片指针还是切片值,上述原则都适用。对于指针切片,你通常会分配新的对象并获取其地址(如new(Type)或&Type{});对于值切片,你直接提供值即可。
理解这些基本概念是编写高效、惯用Go代码的关键。选择哪种方法取决于你的具体需求:如果长度固定且已知,直接索引填充更简洁;如果长度动态且需要性能优化,预设容量并结合append是更好的选择。
以上就是Go语言中切片指针的预分配与填充:惯用方法解析的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1413359.html
微信扫一扫
支付宝扫一扫