Go语言切片(Slice)完全指南:从入门到精通

Go语言切片(Slice)完全指南:从入门到精通

在Go语言中,切片(Slice)是一个既强大又灵活的数据结构,它不仅是数组的动态视图,更是高效处理数据集合的利器。无论是初学者还是资深开发者,掌握切片的使用技巧都能让你的代码更加简洁高效。然而,切片的许多特性(如默认索引、长度与容量、append函数等)往往让人感到困惑。本文将带你从基础概念到高级应用,全面解析Go语言切片的奥秘,助你轻松玩转动态数组,写出更优雅、更高效的代码!

本文是Go语言切片(Slice)的完全指南,内容涵盖从基础到高级的方方面面:

  1. 切片的基础概念:什么是切片?它与数组的区别是什么?
  2. 切片的操作:如何通过默认索引、复合字面值等方式创建和操作切片?
  3. 动态管理切片:如何使用append函数和make函数实现切片的动态扩展与预分配?
  4. 切片的高级特性:长度与容量的关系、三索引切分操作的妙用。
  5. 实战应用:如何在实际开发中高效使用切片?如何通过切片优化代码性能?

通过丰富的代码示例和实用技巧,本文不仅适合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)
}

小测试

  1. 切分数组会产生什么?
  2. 使用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语言的魅力!

相关推荐
Asthenia04128 分钟前
ES-Java:一网打尽SearchRequest/SearchSourceBuilder/BoolQueryBuilder/QueryBuilders
后端
Aska_Lv30 分钟前
业务架构设计---硬件设备监控指标数据上报业务Java企业级架构
后端·架构
m0_7482552641 分钟前
Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)
spring boot·后端·ui
小华同学ai1 小时前
吊打中文合成!这款开源语音神器效果炸裂,逼真到离谱!
前端·后端·github
语落心生1 小时前
算法计算与训练如何支持低开销流式计算? deepseek背后的smallpond需要些新改造
后端
sensen_kiss1 小时前
Git和GitHub基础教学
git·github
uhakadotcom1 小时前
Python高并发实战:阿里云函数计算 + 异步编程高效处理万人请求
后端·面试·github
uhakadotcom1 小时前
Apache Flink:实时数据处理的强大工具
后端·面试·github
AI云极1 小时前
Coze、Dify、FastGPT、MaxKB全面对比,选对平台让开发效率翻倍!
github
INSO1 小时前
Docker Compose
后端