Go语言切片(Slice)完全指南:从入门到精通
在Go语言中,切片(Slice)是一个既强大又灵活的数据结构,它不仅是数组的动态视图,更是高效处理数据集合的利器。无论是初学者还是资深开发者,掌握切片的使用技巧都能让你的代码更加简洁高效。然而,切片的许多特性(如默认索引、长度与容量、append函数等)往往让人感到困惑。本文将带你从基础概念到高级应用,全面解析Go语言切片的奥秘,助你轻松玩转动态数组,写出更优雅、更高效的代码!
本文是Go语言切片(Slice)的完全指南,内容涵盖从基础到高级的方方面面:
- 切片的基础概念:什么是切片?它与数组的区别是什么?
- 切片的操作:如何通过默认索引、复合字面值等方式创建和操作切片?
- 动态管理切片:如何使用append函数和make函数实现切片的动态扩展与预分配?
- 切片的高级特性:长度与容量的关系、三索引切分操作的妙用。
- 实战应用:如何在实际开发中高效使用切片?如何通过切片优化代码性能?
通过丰富的代码示例和实用技巧,本文不仅适合Go语言初学者快速上手,也能帮助有经验的开发者深入理解切片的核心原理,提升编程效率。
Slice(切片)
Slice指向数组的窗口
- 假设planets是一个数组,那么planets[0:4] 就是一个切片,它切分出了数组里前4个元素。
- 切分数组不会导致数组被修改,它只是创建了指向数组的一个窗口或视图,这种视图就是slice类型。
切分数组
-
Slice使用的是半开区间
- 例如planets[0:4],包含索引0、1、2、3对应的元素,不包括索引4对应的元素。
go
package main
import "fmt"
func main() {
planets := [...]string{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn",
"Uranus",
"Neptune",
}
terrestrial := planets[0:4]
gasGiants := planets[4:6]
iceGiants := planets[6:8]
fmt.Println(terrestrizl, gasGiants, iceGiants)
}
小测试
- 切分数组会产生什么?
- 使用planets[4:6]切分数组时,结果包含多少个元素?
Slice的默认索引
- 忽略掉slice的起始索引,表示从数组的起始位置进行切分;
- 忽略掉slice的结束索引,相当于使用数组的长度作为结束索引。
- 注意:slice的索引不能是负数。
go
package main
import "fmt"
func main() {
planets := [...]string{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn",
"Uranus",
"Neptune",
}
terrestrial := planets[:4]
gasGiants := planets[4:6]
iceGiants := planets[6:]
allPlenets := planets[:]
fmt.Println(terrestrizl, gasGiants, iceGiants, allPlanets)
}
- 如果同时省略掉起始和结束索引,那就是包含数组所有元素的一个slice。
- 切分数组的语法也可以用于切分字符串
go
package main
import "fmt"
func main() {
neptune := "Neptune"
tune := neptune[3:]
fmt.Println(tune)
neptune = "Poseidon"
fmt.Println(tune) // 将原来切片的值赋给一个新的值,并不会改变原来切片的值
}
- 切分字符串时,索引代表的是字节数而非 rune 的数。
go
package main
import "fmt"
func main() {
question := "¡Qué pasa!"
fmt.Println(question[:6])
}
Slice的复合字面值
-
Go里面很多函数都倾向于使用slice而不是数组作为参数。
-
想要获得与底层数组相同元素的slice,那么可以使用 [:] 进行切分
-
切分数组并不是创建slice唯一的方法,可以直接声明slice:
- 例如[]string
go
package main
import "fmt"
func main() {
dwarfArray := [...]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dwarfSlice := dwarfArray[:]
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
fmt.Println(dwarfSlice, dwarfs)
}
小测试
- 使用格式化动词%T比较dwarfArray和dwarfs的类型。
切片的威力
go
package main
import "fmt"
// hyperspace removes the space surrounding worlds
func hyperspace(worlds []string) {
for i := range worlds {
worlds[i] = strings.TrimSpace(worlds[i])
}
}
func main() {
planets := []string{" Venus ", "Earth ", " Mars"}
hyperspace(planets)
fmt.Println(strings.Join(planets, ""))
}
小测试
- 访问Go标准库网站,查阅TrimSpace和Join函数的相关文档,说出他们的作用
带有方法的切片
- 在Go里,可以将slice或数组作为底层类型,然后绑定其它方法。
go
package main
import (
"fmt"
"sort"
)
type StringSlice []string
func (p StringSlice) Sort() {
}
func main() {
planets := []string{
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune",
}
sort.StringSlice(planets).Sort()
fmt.Println(planets)
}
小测试
- 执行代码sort.StringSlice(planets)会发生什么?
作业题
-
编写一个程序:
- 它通过给字符串slice中所有的行星加上"New "前缀来完成行星的地球化处理,然后使用该程序对Mars、Uranus、Neptune实行地球化
- 必须使用planets类型,并为之实现相应的terraform方法。
更大的slice
append函数
- append函数也是内置函数,它可以将元素添加到slice里面。
go
package main
import "fmt"
func main() {
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dwarfs = append(dwarfs, "Orcus")
dwarfs = append(dwarfs, "Salacia", "Quaoar", "Sedna")
fmt.Println(dwarfs)
}
小测试
- 执行上例的代码后,dwarfs切片总共包含多少个矮行星?用什么函数能够获知这一点?
长度和容量(length & capacity)
- Slice中元素的个数决定了slice的长度。
- 如果slice的底层数组比slice还大,那么就说该slice还有容量可供增长。
go
package main
import "fmt"
// dump slice length, capacity, and contents
func dump(label string, slice []string) {
fmt.Printf("%v: length %v, capacity %v %v\n", label, len(slice), cap(slice), slice)
}
func main() {
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dump("dwarfs", dwarfs)
dump("dwarfs[1:2]", dwarfs[1:2])
}
小测试
- 为什么dwarfs[1:2]这个slice的容量是4?
详解append
go
package main
import "fmt"
// dump slice length, capacity, and contents
func dump(label string, slice []string) {
fmt.Printf("%v: length %v, capacity %v %v\n", label, len(slice), cap(slice), slice)
}
func main() {
planets := []string{
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune",
}
terrestrial := planets[0:4:4]
worlds := append(terrestrial, "Ceres")
dump("planets", planets) // 8 8
dump("terrestrial", terrestrial) // 4 4
dump("worlds", worlds) // 5 8
}
小测试
- 对于上面例子中的dwarfs3这个slice,如果我们修改它的一个元素,那么dwarfs2和dwarfs1这两个切片会发生变化吗?dwarfs3[1] = "Pluto!"
三个索引的切分操作
- Go 1.2中引入了能够限制新建切片容量的三索引切分操作。
go
package main
import "fmt"
// dump slice length, capacity, and contents
func dump(label string, slice []string) {
fmt.Printf("%v: length %v, capacity %v %v\n", label, len(slice), cap(slice), slice)
}
func main() {
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dump("dwarfs", dwarfs)
dump("dwarfs[1:2]", dwarfs[1:2])
}
小测试
- 什么时候应该使用三索引的切片操作?
使用make函数对slice进行预分配
-
当slice的容量不足以执行append操作时,Go必须创建新数组并复制旧数组中的内容。
-
但通过内置的make函数,可以对slice进行预分配策略。
- 尽量避免额外的内存分配和数组复制操作。
go
package main
import "fmt"
// dump slice length, capacity, and contents
func dump(label string, slice []string) {
fmt.Printf("%v: length %v, capacity %v %v\n", label, len(slice), cap(slice), slice)
}
func main() {
dwarfs := make([]string, 0, 10)
dump("dwarfs", dwarfs)
dwarfs = append(dwarfs, "Ceres", "Pluto", "Haumea", "Makemake", "Eris")
dump("dwarfs", dwarfs)
}
小测试
- 使用make函数创建slice有什么好处?
声明可变参数的函数
- 声明Printf、append这样的可变参数函数,需要在函数的最后一个参数前面加上 ... 符号。
go
package main
import "fmt"
func terraform(prefix string, worlds ...string) []string {
newWorlds := make([]string, len(worlds))
for i := range worlds {
newWorlds[i] = prefix + " " + worlds[i]
}
return newWorlds
}
func main() {
twoWorlds := terraform("New", "Venus", "Mars")
fmt.Println(twoWorlds)
planets := []string{"Venus", "Mars", "Jupiter"}
newPlanets := terraform("New", planets...)
fmt.Println(newPlanets)
}
小测试
- 关于 ... 符号,到目前为止,我们见过的3种用法分别是什么?
作业
-
编写一个程序:
- 通过循环,持续的将元素追加到slice里
- 在slice容量发生变化的时候打印出它的容量
- 请判断append 函数在底层数组的空间被填满之后,是否会将数组的容量增加一倍?
总结
切片(Slice)是Go语言中不可或缺的数据结构,它提供了对数组的动态操作能力,极大地提升了代码的灵活性和效率。通过本文的学习,你已经掌握了切片的基础操作、高级特性以及实际应用场景。无论是切片的创建、动态扩展,还是长度与容量的管理,这些知识都能帮助你在Go语言开发中更加得心应手。
希望这篇指南能成为你学习Go语言切片的得力助手,助你写出更高效、更优雅的代码!如果你觉得本文对你有帮助,欢迎分享给更多开发者,一起探索Go语言的魅力!