切片

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

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

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

a[low : high]

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

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

a[1:4]

完整例子如下:

/_src/tour/slices.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "fmt"


func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}

    var s []int = primes[1:4]
    fmt.Println(s)
}

数组引用

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

/_src/tour/slice-pointers.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"


func main() {
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo",
    }
    fmt.Println(names)

    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b)

    b[0] = "XXX"
    fmt.Println(a, b)
    fmt.Println(names)
}

下标默认值

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

因此,对于数组:

var a [10]int

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

a[0:10]
a[:10]
a[0:]
a[:]
/_src/tour/slice-bounds.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"


func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4]
    fmt.Println(s)

    s = s[:2]
    fmt.Println(s)

    s = s[1:]
    fmt.Println(s)
}

长度和容量

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

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

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

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

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

/_src/tour/slice-len-cap.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"


func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // Slice the slice to give it zero length.
    s = s[:0]
    printSlice(s)

    // Extend its length.
    s = s[:4]
    printSlice(s)

    // Drop its first two values.
    s = s[2:]
    printSlice(s)
}


func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

空切片

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

/_src/tour/nil-slices.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"


func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}

make

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

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

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

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

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)]  // len(b)=5, cap(b)=5
b = b[1:]       // len(b)=4, cap(b)=4

完整例子如下:

/_src/tour/making-slices.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)

    b := make([]int, 0, 5)
    printSlice("b", b)

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

切片的切片

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

/_src/tour/slices-of-slices.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 package main

 import (
     "fmt"
     "strings"
 )

 func main() {
     // Create a tic-tac-toe board.
     board := [][]string{
         []string{"-", "_", "_"},
         []string{"-", "_", "_"},
         []string{"-", "_", "_"},
     }

     // The players take turns.
     board[0][0] = "X"
     board[2][2] = "O"
     board[1][2] = "X"
     board[1][0] = "O"
     board[0][2] = "X"

     for i := 0; i < len(board); i++ {
         fmt.Printf("%s\n", strings.Join(board[i], " "))
     }
 }

元素追加

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

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

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

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

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

/_src/tour/append.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    // We can add more than one elements at a time.
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

遍历

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

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

/_src/tour/range.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d=%d\n", i, v)
    }
}

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

/_src/tour/range-continued.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import "fmt"

func main() {
    pow := make([]int, 10)

    for i := range pow {
        pow[i] = 1 << uint(i)   // == 2**i
    }

    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

练习

1. 图片像素阵列

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

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

代码框架如下:

/_src/tour/exercise-slices-pic.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
    pic.Show(Pic)
}

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

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

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

../_images/1fb9b097a97fde32fcd3c8292605be52.png

(x+y) / 2

../_images/7261fd8ba2ae27e30e9fcf93a1ed900e.png

x * y

../_images/0625993b39e53718ec2437d14d01f389.png

x ^ y

答案

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

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

/_src/tour/solution-slices-pic.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
    s := make([][]uint8, dy)
    for y := 0; y < dy; y++ {
        sx := make([]uint8, dx)
        for x := 0; x < dx; x++ {
            sx[x] = uint8((x+y)/2)
            //sx[x] = uint8(x*y)
            //sx[x] = uint8(x^y)
        }
        s[y] = sx
    }
    return s
}

func main() {
    pic.Show(Pic)
}

下一步

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

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

../_images/wechat-mp-qrcode.png

小菜学编程

微信打赏