Golang中数组和切片在使用len()与cap()时的行为差异解析

Golang中数组和切片在使用len()与cap()时的行为差异解析 大家好,优秀的Gopher们… 我学习Go语言才5天(之前有3年Python经验)。

我遇到了数组和切片的一些(在我看来)奇怪行为,这些行为与 len()cap()(尤其是 cap())有关,以及 [:][::] 的不同用法和行为。

我只是想问一下…,这些特性是我以后会逐渐欣赏的优点,还是…会让我讨厌的东西?

谢谢。 很高兴来到这里。

8 回复

我仍然不确定切片中有什么让你感到奇怪,但尽管它们与例如 Python 中的 list 或 C# 中的 List<T> 等不同,但对我来说它们似乎很直观,不过我已经使用它们很多年了 😄。

更多关于Golang中数组和切片在使用len()与cap()时的行为差异解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的回复。我已经完全理解了其中的思路。只是感觉有点奇怪,我想问的是社区是否真的接受这种做法,或者这是否是每个人都希望改变的事情?

我觉得切片上 [ : : ] 的行为应该成为默认设置。只是说说我的想法,我对目前的情况感到不太安全。希望我能习惯它而不留下心理阴影 😂。

你好,Oluwatobi,欢迎来到论坛!

Oluwatobi_Giwa: 我遇到了数组和切片(我认为是)的这种奇怪行为,与 len()cap()(尤其是 cap())有关,以及 [ : ][ : : ] 的不同用法和行为。

如果你能描述一下这个奇怪的行为是什么,将会很有帮助!你是否有任何示例代码可以展示你期望的结果与实际发生的情况?

十年了…… 😲😲😲😲 😊😊😊

我五天前才开始学习Go语言……后端Python的就业市场变得太紧张了。我看到Go语言将在服务器端领域占据未来。所以,我来到了这里。

我已经学完了数据结构、接口、Goroutines、通道……目前正在学习测试……使用的是O’Reilly出版的《Learning Go, an idiomatic approach》这本书。

请问,对于在Go语言后端岗位以及其他Go语言机会方面,如何迈出这新的一步,有什么建议吗?

谢谢

Oluwatobi_Giwa:

每个人都真正想要改变的事情

这极不可能,因为(三索引切片)这个特性在 Go 语言中存在已近 10 年。如果每个人都想改变它,那么要么这门语言已经改变了(需要发布 v2 版本),要么它早就消亡了。你仍然没有澄清你不喜欢什么。如果修改容量成为默认行为,那么将会产生更多的内存分配波动、复制和垃圾回收。值得一提的是,在我主要使用 Go 编码的八年里,我几乎从未使用过三索引切片。我几乎所有会改变容量的操作都使用 append。通常我是在 make 中指定必要的容量。

好的,事情是这样的。

var arrOne [4]int = make([4]int, 4) // 即 len() 为 4,cap() 为 4 int{0,0,0,0}

arrTwo := arrOne[:2]. // int{0,0}

arrThree := arrTwo[:3] // int{0,0,0}

arrThree 的值看起来有点奇怪……不过我现在完全明白了,这涉及到指针和 cap() 这些东西。

我想,我只需要习惯它。

Oluwatobi_Giwa:

var arrOne [4]int = make([4]int, 4)

make 的使用无效。

./prog.go:8:22: invalid argument: cannot make [4]int; type must be slice, map, or channel

相反,var arrOne [4]int 会得到一个长度为 4 的数组。

Oluwatobi_Giwa:

arrTwo := arrOne[:2]. // int{0,0}

arrThree := arrTwo[:3] // int{0,0,0}

这些不是数组,因此你不应该这样命名它们。Go 语言的数组在编译时大小是固定的。传递数组会复制整个数组。Go 语言的切片是数组的视图,所以这样理解有助于完全掌握它们。传递切片的开销也很小,因为复制切片并不会复制其底层的数据元素。你得到的是对同一个底层数组的一个新视图。

在Go语言中,数组和切片在len()cap()行为上的差异是核心特性,理解这些差异对高效使用Go至关重要。

数组(Array)

数组是固定长度的值类型,len()始终返回声明时的长度,cap()始终等于len()

arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(len(arr)) // 5
fmt.Println(cap(arr)) // 5

切片(Slice)

切片是动态长度的引用类型,包含三个组件:指针、长度和容量:

// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4]  // 从索引1到3(不包括4)

fmt.Println(len(slice1)) // 3,包含[2,3,4]
fmt.Println(cap(slice1)) // 4,从索引1到底层数组末尾

// 直接创建切片
slice2 := []int{1, 2, 3, 4, 5}
fmt.Println(len(slice2)) // 5
fmt.Println(cap(slice2)) // 5

切片操作符 [:][::]

[start:end] 创建新切片,共享底层数组:

arr := [10]int{0,1,2,3,4,5,6,7,8,9}
s1 := arr[2:6]    // len=4, cap=8(从索引2到底层数组末尾)
s2 := s1[1:3]     // len=2, cap=7(共享同一底层数组)

fmt.Println(len(s2), cap(s2)) // 2, 7

[start:end:max] 限制容量,防止意外修改:

arr := [10]int{0,1,2,3,4,5,6,7,8,9}
s1 := arr[2:6:8]  // len=4, cap=6(max-start)

fmt.Println(len(s1), cap(s1)) // 4, 6
// s1[0:5] 会panic,因为超出容量

容量变化规则

s := make([]int, 3, 5)  // len=3, cap=5
s = append(s, 1)        // len=4, cap=5
s = append(s, 2, 3)     // len=6, cap=10(容量翻倍)

fmt.Println(len(s), cap(s)) // 6, 10

这些特性是Go切片设计的精髓。容量机制允许高效的内存重用,三索引切片操作提供了安全的边界控制。从Python转来的开发者初期可能觉得繁琐,但掌握后会发现这些特性在性能和安全方面提供了显著优势。

回到顶部