切片

数组 长度是固定的。相反, 切片 ( slice )是一个数组元素的弹性视图,长度可以是动态的。实际上,切片比 数组 更为常用。

类型 []T 就是一个由类型 T 元素组成的切片。

切片通过两个索引下标定义,一个表示 下边界 ( low bound ),一个表示 上边界 ( high bound ),以冒号( : )分隔:

  1. a[low : high]

这表示一个 半开半闭 区间,包括第一个元素,但不包括最后一个。

以下表达式创建一个包含元素 1 到元素 3 的切片(不包括元素 4):

  1. a[1:4]

完整例子如下:

/_src/tour/slices.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5.  
  6. func main() {
  7. primes := [6]int{2, 3, 5, 7, 11, 13}
  8.  
  9. var s []int = primes[1:4]
  10. fmt.Println(s)
  11. }

数组引用

切片实际上并 不存储任何数据 ,只是用来描述关联数组的一部分。因此,修改切片元素等价于修改对应的数组元素,其他共用该数组的切片对此也可见。换句话讲, 切片就像是数组的引用

/_src/tour/slice-pointers.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5.  
  6. func main() {
  7. names := [4]string{
  8. "John",
  9. "Paul",
  10. "George",
  11. "Ringo",
  12. }
  13. fmt.Println(names)
  14.  
  15. a := names[0:2]
  16. b := names[1:3]
  17. fmt.Println(a, b)
  18.  
  19. b[0] = "XXX"
  20. fmt.Println(a, b)
  21. fmt.Println(names)
  22. }

下标默认值

定义切片,可以忽略上边界或者下边界,而使用 默认值 。对于下边界,默认值为 0 ;下边界,默认值则是 切片长度

因此,对于数组:

  1. var a [10]int

以下切片表达式均是等价的:

  1. a[0:10]
  2. a[:10]
  3. a[0:]
  4. a[:]

/_src/tour/slice-bounds.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5.  
  6. func main() {
  7. s := []int{2, 3, 5, 7, 11, 13}
  8.  
  9. s = s[1:4]
  10. fmt.Println(s)
  11.  
  12. s = s[:2]
  13. fmt.Println(s)
  14.  
  15. s = s[1:]
  16. fmt.Println(s)
  17. }

长度和容量

切片有两个重要属性, 长度 ( length )和 容量 ( capacity )。

切片的长度等于切片包含的元素个数。

切片的容量等于底下数组的元素个数,从切片第一个元素算起。

对于切片 s ,长度和容量分别可以通过表达式 len(s) 以及 cap(s) 获得。

通过 重切 ( re-slicing ),你可以扩张一个切片的长度,只要容量足够。你可以试试修改下面这个例子,将切片扩张到超出容量,看看会发生什么事情:

/_src/tour/slice-len-cap.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5.  
  6. func main() {
  7. s := []int{2, 3, 5, 7, 11, 13}
  8. printSlice(s)
  9.  
  10. // Slice the slice to give it zero length.
  11. s = s[:0]
  12. printSlice(s)
  13.  
  14. // Extend its length.
  15. s = s[:4]
  16. printSlice(s)
  17.  
  18. // Drop its first two values.
  19. s = s[2:]
  20. printSlice(s)
  21. }
  22.  
  23.  
  24. func printSlice(s []int) {
  25. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  26. }

空切片

切片的 零值nil ,即 空切片 。空切片长度和容量均为 0 ,当然也不需要底层数组。

/_src/tour/nil-slices.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5.  
  6. func main() {
  7. var s []int
  8. fmt.Println(s, len(s), cap(s))
  9. if s == nil {
  10. fmt.Println("nil!")
  11. }
  12. }

make

切片可以由内置函数 make 来创建,相当于你可以创建动态长度的数组。

make 函数分配一个由 零值 填充的数组,并返回一个引用该数组的切片:

  1. a := make([]int, 5) // len(a)=5

要指定容量,可以通过 make 函数第三个参数指定:

  1. b := make([]int, 0, 5) // len(b)=0, cap(b)=5
  2.  
  3. b = b[:cap(b)] // len(b)=5, cap(b)=5
  4. b = b[1:] // len(b)=4, cap(b)=4

完整例子如下:

/_src/tour/making-slices.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func main() {
  6. a := make([]int, 5)
  7. printSlice("a", a)
  8.  
  9. b := make([]int, 0, 5)
  10. printSlice("b", b)
  11.  
  12. c := b[:2]
  13. printSlice("c", c)
  14.  
  15. d := c[2:5]
  16. printSlice("d", d)
  17. }
  18.  
  19. func printSlice(s string, x []int) {
  20. fmt.Printf("%s len=%d cap=%d %v\n",
  21. s, len(x), cap(x), x)
  22. }

切片的切片

切片可以包含任何类型,当然包括其他切片。

/_src/tour/slices-of-slices.go

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "strings"
  6. )
  7.  
  8. func main() {
  9. // Create a tic-tac-toe board.
  10. board := [][]string{
  11. []string{"-", "_", "_"},
  12. []string{"-", "_", "_"},
  13. []string{"-", "_", "_"},
  14. }
  15.  
  16. // The players take turns.
  17. board[0][0] = "X"
  18. board[2][2] = "O"
  19. board[1][2] = "X"
  20. board[1][0] = "O"
  21. board[0][2] = "X"
  22.  
  23. for i := 0; i < len(board); i++ {
  24. fmt.Printf("%s\n", strings.Join(board[i], " "))
  25. }
  26. }

元素追加

向切片追加元素是一个很常用的操作,为此 Go 提供了一个 内置函数内置包文档 详细描述了这个内置函数 append

  1. func append(s []T, vs ...T) []T

append 函数第一个参数 s 是一个类型为 T 的切片,其余参数均为追加至 sT 元素。

append 函数返回一个新切片,包含原切片以及所有追加元素。

如果底层数组太小, append 函数会分配一个更大的数组,新切片则指向新数组。

/_src/tour/append.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func main() {
  6. var s []int
  7. printSlice(s)
  8.  
  9. // append works on nil slices.
  10. s = append(s, 0)
  11. printSlice(s)
  12.  
  13. // The slice grows as needed.
  14. s = append(s, 1)
  15. printSlice(s)
  16.  
  17. // We can add more than one elements at a time.
  18. s = append(s, 2, 3, 4)
  19. printSlice(s)
  20. }
  21.  
  22. func printSlice(s []int) {
  23. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  24. }

遍历

配合 range 关键字, for 循环可对切片进行 遍历 。下节,我们将看到,这种做法也适用于 映射表 ( map )。

每次迭代都返回两个值,第一个是 下标 ( index ),第二个是与该下标对应的 元素拷贝

/_src/tour/range.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
  6.  
  7. func main() {
  8. for i, v := range pow {
  9. fmt.Printf("2**%d=%d\n", i, v)
  10. }
  11. }

如果无须下标,可以直接赋值给下划线 _ 。如果只要下标,则不写后半部分即可。

/_src/tour/range-continued.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func main() {
  6. pow := make([]int, 10)
  7.  
  8. for i := range pow {
  9. pow[i] = 1 << uint(i) // == 2**i
  10. }
  11.  
  12. for _, value := range pow {
  13. fmt.Printf("%d\n", value)
  14. }
  15. }

练习

1. 图片像素阵列

尝试实现一个函数 Pic :返回一个长度为 dy 的切片,切片元素为长度为 dx8 位无符号整数切片。这个切片,其实就是一个二维阵列,可以用来表示一张图片的像素。

当你运行这个程序时,它将展示一张图片,每个数值被解释为图片像素的 灰度值 ( grayscale )。

代码框架如下:

/_src/tour/exercise-slices-pic.go

  1. package main
  2.  
  3. import "golang.org/x/tour/pic"
  4.  
  5. func Pic(dx, dy int) [][]uint8 {
  6. }
  7.  
  8. func main() {
  9. pic.Show(Pic)
  10. }

你可以在 Go官网 进行练习。图片长啥样取决于你返回的切片。试试以下以下公式来生成切片,结果将非常有趣:

[\frac{x+y}{2}]

[x * y]

[x ^ y]

这三个公式生成的图片分别是:

../_images/1fb9b097a97fde32fcd3c8292605be52.png(x+y) / 2

../_images/7261fd8ba2ae27e30e9fcf93a1ed900e.pngx * y

../_images/0625993b39e53718ec2437d14d01f389.pngx ^ y

答案

相信你已经写出自己的程序,看到各种有趣的图片了。

如果练习过程中遇到什么问题,可以参考下面这个例子并尝试解决:

/_src/tour/solution-slices-pic.go

  1. package main
  2.  
  3. import "golang.org/x/tour/pic"
  4.  
  5. func Pic(dx, dy int) [][]uint8 {
  6. s := make([][]uint8, dy)
  7. for y := 0; y < dy; y++ {
  8. sx := make([]uint8, dx)
  9. for x := 0; x < dx; x++ {
  10. sx[x] = uint8((x+y)/2)
  11. //sx[x] = uint8(x*y)
  12. //sx[x] = uint8(x^y)
  13. }
  14. s[y] = sx
  15. }
  16. return s
  17. }
  18.  
  19. func main() {
  20. pic.Show(Pic)
  21. }

下一步

下一节 我们一起来看看 Go 语言 映射表

订阅更新,获取更多学习资料,请关注我们的 微信公众号

../_images/wechat-mp-qrcode.png小菜学编程

微信打赏