切片
类型 就是一个由类型 T
元素组成的切片。
切片通过两个索引下标定义,一个表示 下边界 ( low bound ),一个表示 上边界 ( high bound ),以冒号( )分隔:
这表示一个 半开半闭 区间,包括第一个元素,但不包括最后一个。
以下表达式创建一个包含元素 1 到元素 3 的切片(不包括元素 4):
- a[1:4]
完整例子如下:
- 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
- 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
长度和容量
切片有两个重要属性, 长度 ( length )和 容量 ( capacity )。
切片的长度等于切片包含的元素个数。
切片的容量等于底下数组的元素个数,从切片第一个元素算起。
对于切片 s ,长度和容量分别可以通过表达式 len(s)
以及 获得。
通过 重切 ( re-slicing ),你可以扩张一个切片的长度,只要容量足够。你可以试试修改下面这个例子,将切片扩张到超出容量,看看会发生什么事情:
/_src/tour/slice-len-cap.go
- package main
- import "fmt"
- func main() {
- s := []int{2, 3, 5, 7, 11, 13}
- // 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)
- }
- 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
完整例子如下:
- 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
向切片追加元素是一个很常用的操作,为此 Go 提供了一个 内置函数 。 详细描述了这个内置函数 append :
- func append(s []T, vs ...T) []T
append 函数第一个参数 s 是一个类型为 T 的切片,其余参数均为追加至 s 的 T 元素。
append 函数返回一个新切片,包含原切片以及所有追加元素。
如果底层数组太小, append 函数会分配一个更大的数组,新切片则指向新数组。
/_src/tour/append.go
- 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 ),第二个是与该下标对应的 元素拷贝 。
- 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
- 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)
- }
- }
练习
当你运行这个程序时,它将展示一张图片,每个数值被解释为图片像素的 灰度值 ( grayscale )。
代码框架如下:
/_src/tour/exercise-slices-pic.go
- 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]
这三个公式生成的图片分别是:
(x+y) / 2
x * y
x ^ y
答案
相信你已经写出自己的程序,看到各种有趣的图片了。
如果练习过程中遇到什么问题,可以参考下面这个例子并尝试解决:
/_src/tour/solution-slices-pic.go
我们一起来看看 Go 语言 映射表 。
订阅更新,获取更多学习资料,请关注我们的 :