Golang中切片创建时使用MAKE的最佳实践
Golang中切片创建时使用MAKE的最佳实践 我在数组和切片方面有些困惑,特别是关于如何定义数组大小和/或切片底层数组的问题;不过我还在继续阅读《Go语言之旅》网站上的内容。
但是在定义/创建切片时使用’make’有什么好处呢?
源代码:
https://tour.golang.org/moretypes/13
我原本以为切片本身就是对数组的动态不定长引用,而且数组的大小是固定的。那么如果我使用’append(array, “add-value”)’,数组会变大。这是否意味着数组被销毁并重新创建,只是变得更大?
我来自Perl和(一些)Python脚本背景,所以我认为由于使用了这些让数组/列表赋值变得简单的语言,我可能没有掌握一些数组内存管理/分配技能。
谢谢
更多关于Golang中切片创建时使用MAKE的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
约翰,
你提到的"指针"是否类似于C语言中指向内存地址的指针?非常感谢你的帮助…不知为何我一直对这个概念感到困惑。
[!quote] romulanghoul […] 你介意我贴一些代码来讨论吗?
当然,没问题。请注意这里讨论的代码发布方法: 如何在本论坛发布代码
嗨 Lucas:
非常感谢你在脚本中添加了内存位置信息。这真是太棒了,因为这正是我研究需要处理内存管理的编程语言的原因。
我还记得C语言课程中的内存位置概念,那些内容让我非常着迷,这真是太棒了!谢谢你。
你好Jay, 感谢你提供的详细信息。出于某些奇怪的原因,我一直在努力理解这个概念;不过我真的很喜欢GO语言,并且非常兴奋能将我的代码移植到这门语言上。
请给我一些时间来阅读你提供的链接,我相信我会有更多问题。我感觉自己快要理解了,但还不太确定(哈哈哈)。
“将切片设置为 nil 的语句会销毁底层数组,这可以通过该数组不再具有内存地址来证明。”
它既不会销毁内存也不会销毁数组。它只是将切片与该数组分离。
此外,您已经声明了 slice2 变量,该变量仍然引用 slice 变量的数组。因此,第一个切片的数组永远不会被销毁。
嗨 Jay, 我购买了《Go实战》这本书,并开始学习"数组和切片"这一章;不过仍然对一些概念感到困惑,但我只需要坚持下去。
吸引我学习Go的另一个原因是它并非面向对象编程。我不太理解面向对象的概念,更倾向于使用结构体并按照这种方式来组织代码。
再次感谢您的帮助和建议。您介意我发布一些代码供我们讨论吗?
切片"仅仅"是一个指向数组的指针,它包含长度(切片的容量)和已使用项的数量(切片的长度)。当向切片追加元素时,其长度会增加;如果超过了底层数组的长度,就会创建一个新的数组(通常是原大小的两倍),并将所有元素复制到新数组中。这个新数组成为新的底层数组,其长度就是新的容量。
使用 make 有两个主要目的:
- 创建指定大小的空切片
- 当你知道或预估切片的大致大小时,可以从一开始就创建足够大的底层数组,从而避免不必要的内存分配。
希望这回答了你的问题。今天开了一整天车,有点累了。
func main() {
fmt.Println("hello world")
}
Jay, 非常感谢你详细的帖子。正如你指出的——我的背景仅限于:Perl、TCL 和一些 Python。不过我一直觉得这些语言对我的工作——网络工程师来说很合适,但我一直想学习一门真正的编译型编程语言。我始终知道背后有各种"事情"在发生,但当时我并不真正关心……我只需要我的脚本能运行,这样我就能完成工作。
但最近,我对编程产生了更大的兴趣,也确实研究过 C 语言。然而,当我发现 GO 语言后,我就有点放弃 C 语言了。此外,我看到创建 C 语言的那个人也创造了 GO 语言。而且,我现在需要能为目标机器编译我的代码。Perl Packer 和 PyInstaller 是很棒的工具;然而,我仍然觉得我在程序员技能方面缺少些什么。
我现在有时间学习和研究……所以我认为是时候使用一门真正的编程语言来学习,并将我的应用程序移植到一个能为目标系统提供原生编译代码的生态系统中了。
再次感谢你提供的详细信息……我今晚晚些时候会阅读,明天会有更多问题。
RR
这只是一段我编写的简单代码,用于熟悉切片操作。
package main
import(
"fmt"
)
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main(){
var slice []int
//slice := []int{}
slice = append(slice,10)
slice = append(slice,20)
printSlice(slice)
// remove items in array/slice
slice = slice[:0]
printSlice(slice)
// null out the cap of the slice
slice = nil
printSlice(slice)
slice = append(slice,45)
slice = append(slice,77)
printSlice(slice)
}
这是我目前用来测试和熟悉切片的代码。问题是,我注意到使用 slice[:0] 时,它移除了数组中的元素,但并没有缩小数组的大小。我深入研究后发现 slice = nil 可以让数组缩小。
我这样做对吗?这是最佳实践吗?
你好 Roman,
这篇文章有点长,但我希望对你有所帮助:
https://blog.golang.org/slices
基本上,Go语言中的切片可能看起来与Perl或Python中的切片类似,但在Go中,它们是一种基本数据类型,而不仅仅是从数组中取出一部分的方式。刚接触Go的程序员可能不会立即理解这一点,并试图主要使用数组进行编程,就像他们在其他一些语言中所做的那样。
Go语言中存在数组,并且是存储切片元素的基础方式,但在Go中通常使用的是切片,而不是数组。切片是一种引用类型,这使得它们在使用时更加高效。切片允许你使用类似这样的数据结构来处理数组,而不是传递或复制多元素数组:
// 伪代码!
type slice struct {
array *slice_type
int len
int cap
}
第一部分是指向元素数组的指针。这是底层数组。此外,还有切片的长度(切片中有多少个元素)和容量(在需要重新分配之前可以使用多少个元素)。
所以,学习切片的工作原理,训练自己用切片而不是数组来思考,只在适当的时候使用数组。
数组在处理固定大小的数据集时仍然很有用。例如,一个包含月份名称的数组。它的大小将被限制为12,并且数组永远不需要增长。
希望这能帮到你!
func main() {
fmt.Println("hello world")
}
你好 Roman,
我对 Go 的底层机制还不是很熟悉,但让我尝试解释一下发生了什么。
使用 slice[:0] 语句时,你并没有修改或破坏切片的基础数组,你只是将新切片的值赋给了该数组。在这种情况下,由于新切片是一个空切片,所以切片的长度会降为零,因为你分配的是一个空切片。
与 slice = nil 语句的区别在于,你给切片赋了所谓的零值(关于零值的更多信息可以在这里找到:https://golang.org/ref/spec#Program_initialization_and_execution)。由于零值不会初始化基础数组,因此这条语句会破坏之前创建的数组。
我冒昧地修改了你的代码,并在每一行添加了相应数组的内存地址。
https://play.golang.org/p/f9rkHDmKAw-
如你所见,slice[:0] 语句并没有破坏基础数组,因为内存地址没有被改变。唯一的变化是数组中包含的值。
相反,slice = nil 语句破坏了基础数组,这一点可以从现在没有该数组的内存地址看出。
我添加了最后一行,创建了一个数组但没有初始化它,以展示在这种情况下内存地址与你之前的例子相同。
回答你的问题,手动操作数组在 Go 中并不是最佳实践,除非你非常清楚自己想要做什么。更安全的选择是在创建切片时不指定容量和长度,让程序在运行时决定。
此致。
func main() {
fmt.Println("hello world")
}
嗨 Roman,
选择学习Go语言是个非常棒的决定。实际上,Go语言部分是由Rob Pike和Ken Thompson设计的。Thompson创建了Unix的第一个版本和B语言,这促使Dennis Ritchie(已故)创造了C语言。Rob Pike后来加入了贝尔实验室,但与Ken共同开发了Plan 9操作系统并参与了UTF-8 Unicode编码的设计。
在某些时候,你可能会对观看Rob Pike关于Go语言的演讲感兴趣,这些演讲可以在YouTube上找到。他详细解释了我下面要写的内容,但讲解得更加深入。
Go语言不仅为现在而设计,更是为未来而设计。效率较低的解释型语言面临的一个问题是,计算机CPU核心的速度不再大幅提升。摩尔定律基本上已经终结,我们无法再每隔几年就获得速度的翻倍。晶体管的尺寸和导线只能做到这么小,当它们窄到没有足够原子来传输电流时,就达到了物理极限——这正是我们现在所处的境况。(大约7纳米。顺便提一下,在1980年代初期我设计VLSI电路时,我们的特征尺寸大约是4微米,这在当时看起来已经非常小了!)
对于早期Perl和Python刚出现的那代人来说,处理器速度似乎不是真正的限制因素,因为如果你的程序运行缓慢,只需等待几年升级电脑即可。但现在,为了让程序运行得更快,我们需要高效的代码和更多的CPU核心。
Go语言主要针对实际硬件而设计,而不是某种编程范式如OOP或函数式编程,并且内置了对并发编程的出色支持。基于这个原因及其他因素,它现在正成为网络编程、Web服务和云计算应用的热门语言。所以,如果你想提升网络编程技能并为未来做好准备,学习Go语言是个非常明智的决定。
在Go语言中,切片确实是对底层数组的动态引用,但理解其内存分配机制至关重要。使用make创建切片允许你预先指定容量,这在性能优化方面有显著优势。
使用make的好处:
- 避免频繁内存重新分配:当你知道切片的大致大小时,可以预先分配足够的容量,减少
append操作时的内存重新分配次数。 - 性能优化:减少底层数组的复制和重新分配,特别是在处理大量数据时。
关于append的行为:
当你使用append时,如果切片的容量不足,Go会创建一个新的、更大的底层数组,并将原有数据复制到新数组中。原数组不会被"销毁",但如果没有其他引用,会被垃圾回收器回收。
示例代码:
package main
import "fmt"
func main() {
// 使用make创建切片,指定长度和容量
// 语法:make([]T, length, capacity)
slice := make([]int, 0, 10) // 长度为0,容量为10
fmt.Printf("长度: %d, 容量: %d\n", len(slice), cap(slice))
// 添加元素,不会触发重新分配(直到超过容量10)
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("添加 %d: 长度=%d, 容量=%d\n", i, len(slice), cap(slice))
}
// 超过初始容量,会触发重新分配
slice = append(slice, 10)
fmt.Printf("超过容量: 长度=%d, 容量=%d\n", len(slice), cap(slice))
}
对比示例:
// 方式1:不使用make(零值切片)
var slice1 []int
// 初始容量为0,第一次append就会触发分配
// 方式2:使用make预分配
slice2 := make([]int, 0, 100)
// 可以容纳100个元素而无需重新分配
实际应用场景:
// 处理已知大小的数据时
func processItems(items []string) {
// 预分配结果切片
results := make([]string, 0, len(items))
for _, item := range items {
if isValid(item) {
results = append(results, process(item))
}
}
return results
}
在性能敏感的场景中,合理使用make预分配容量可以显著减少内存分配次数,提高程序效率。

