Files
the-way-to-go_ZH_CN/eBook/07.2.md
2014-12-29 20:56:01 +08:00

264 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 7.2 切片
## 7.2.1 概念
slice 是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以 slice 是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在 slice 内。 Slice 提供了一个相关数组的动态窗口。
Slice 是可索引的,并且可以由 len() 方法获取长度。
给定项的 slice 索引可能比相关数组的相同元素的索引小。和数组不同的是slice 的长度可以在运行时修改,最小为 0 最大为相关数组的长度slice 是一个 **长度可变的数组**
slice 提供了计算容量的方法 cap() 可以测量 slice 最长可以达到多少:它等于 slice 的长度 + 数组除 slice 之外的长度。如果 s 是一个 slicecap 就是从 s[0] 到数组末尾的数组长度。slice 的长度永远不会超过它的容量,所以对于 slice s 来说该不等式永远成立: 0 <= len(s) <= cap(s)
多个 slice 如果表示同一个数组的片段,它们可以共享数据;因此一个 slice 和相关数组的其他 slice 是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是 slice 的构建块。
**优点** 因为 slice 是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 slice 比数组更常用。
声明 slice 的格式是: `var identifier []type` 不需要说明长度
一个 slice 在未初始化之前默认为 nil长度为 0。
slice 的初始化格式是:`var slice1 []type = arr1[start:end]`
这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集切分数组start:end 被称为 slice 表达式)。所以 slice1[0] 就等于 arr1[start]。这可以在 arr1 被填充前就定义好。
如果某个人写:`var slice1 []type = arr1[:]` 那么 slice1 就等于完整的 arr1 数组(所以这种表示方式是 `arr1[0:len(arr1)]` 的一种缩写)。另外一种表述方式是:`slice1 = &arr1`
arr1[2:] 和 arr1[2:len(arr1)] 相同,都包含了数组从第二个到最后的所有元素。
arr1[:3] 和 arr1[0:3] 相同,包含了从第一个到第三个元素(不包括第三个)。
如果你想去掉 slice1 的最后一个元素,只要 `slice1 = slice1[:len(slice1)-1]`
一个由数字 1、2、3 组成的切片可以这么生成:`s := [3]int{1,2,3}` 甚至更简单的 `s := []int{1,2,3}`
`s2 := s[:]` 是用 slice 组成的 slice拥有相同的元素但是仍然指向相同的相关数组。
一个 slice s 可以这样扩展到它的大小上限:`s = s[:cap(s)]`,如果再扩大的话就会导致运行时错误(参见第 7.7 节)。
对于每一个 slice包括 string以下状态总是成立的
s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s)
len(s) < cap(s)
Slice 也可以用类似数组的方式初始化`var x = []int{2, 3, 5, 7, 11}`这样就创建了一个长度为5的数组并且创建了一个相关 slice
slice 在内存中的组织方式实际上是一个有 3 个域的结构体指向相关数组的指针slice 长度以及 slice 容量下图给出了一个长度为 2容量为 4 slice
`y[0] = 3` `y[1] = 5`slice y[0:4] 元素 3, 5 7 11 组成
![](images/7.2_fig7.2.png?raw=true)
示例 7.7 [array_slices.go](examples/chapter_7/array_slices.go)
package main
import "fmt"
func main() {
var arr1 [6]int
var slice1 []int = arr1[2:5] // item at index 5 not included!
// load the array with integers: 0,1,2,3,4,5
for i := 0; i < len(arr1); i++ {
arr1[i] = i
}
// print the slice
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of arr1 is %d\n", len(arr1))
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice
slice1 = slice1[0:4]
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice beyond capacity
//slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range
}
输出
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
The length of arr1 is 6
The length of slice1 is 3
The capacity of slice1 is 4
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
Slice at 3 is 5
The length of slice1 is 4
The capacity of slice1 is 4
如果 s2 是一个 slice你可以将 s2 向后移动一位 `s2 = s2[1:]`但是末尾没有移动slice 只能向后移动`s2 = s2[-1:]` 会导致编译错误slice 不能被重新分片以获取数组的前一个元素
**注意** 绝对不要用指针指向 sliceslice 本身已经是一个引用类型所以它本身就是一个指针!!
问题7.2 给定 `slice b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}`那么 b[1:4]b[:2]b[2:] b[:] 分别是什么
## 7.2.2 将 slice 传递给函数
如果你有一个函数需要对数组做操作你可能总是需要把参数声明为 slice当你调用该函数时把数组分片创建为一个 slice 引用并传递给该函数这里有一个计算数组元素和的方法:
func sum(a []int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
func main {
var arr = [5]int{0, 1, 2, 3, 4}
sum(arr[:])
}
## 7.2.3 用 make() 创建一个 slice
当相关数组还没有定义时我们可以使用 make() 方法来创建一个 slice 同时创建好相关数组`var slice1 []type = make([]type, len)`
也可以简写为 `slice1 := make([]type, len)`这里 `len` 是数组的长度并且也是 `slice` 的初始长度
所以定义 `s2 := make([]int, 10)`那么 `cap(s2) == len(s2) == 10`
make 接受 2 个参数元素的类型以及 slice 的元素个数
如果你想创建一个 slice1它不占用整个数组而只是占用以 len 为个数个项那么只要`slice1 := make([]type, len, cap)`
make 的使用方式是`func make([]T, len, cap)` 其中 cap 是可选参数
所以下面两种方法可以生成相同的 slice:
make([]int, 50, 100)
new([100]int)[0:50]
下图描述了使用 make 方法生成的 slice 的内存结构![](images/7.2_fig7.2.1.png?raw=true)
示例 7.8 [make_slice.go](examples/chapter_7/make_slice.go)
package main
import "fmt"
func main() {
var slice1 []int = make([]int, 10)
// load the array/slice:
for i := 0; i < len(slice1); i++ {
slice1[i] = 5 * i
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
}
输出结构
Slice at 0 is 0
Slice at 1 is 5
Slice at 2 is 10
Slice at 3 is 15
Slice at 4 is 20
Slice at 5 is 25
Slice at 6 is 30
Slice at 7 is 35
Slice at 8 is 40
Slice at 9 is 45
The length of slice1 is 10
The capacity of slice1 is 10
因为字符串是纯粹不可变的字节数组它们也可以被切分成 slice
练习 7.4 fobinacci_funcarray.go: 为练习 7.3 写一个新的版本主函数调用一个使用序列个数作为参数的函数该函数返回一个大小为序列个数的 Fibonacci slice
## 7.2.4 new() 和 make() 的区别
看起来二者没有什么区别都在堆上分配内存但是它们的行为不同适用于不同的类型
new(T) 为每个新的类型T分配一片内存初始化为 0 并且返回内存地址类型 *T这种方法 **返回一个指向类型为 T值为 0 的地址的指针**它适用于值类型如数组和结构体参见第 10 它相当于 `&T{}`
make(T) **返回一个类型为 T 的初始值**它只适用于3种内建的引用类型slice, map channel参见第 8 13
换言之new 方法分配内存make 方法初始化下图给出了区别
![](images/7.3_fig7.3.png?raw=true)
在图 7.3 的第一幅图中
var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)
在第二幅图中 `p := make([]int, 0)` slice 已经被初始化但是指向一个空的数组
这两种方式实用性都不高下面的方法
var v []int = make([]int, 10, 50)
或者
v := make([]int, 10, 50)
这样分配一个有 50 int 值的数组并且创建了一个长度为 10容量为 50 slice v slice 指向数组的前 10 个元素
问题 7.3给定 `s := make([]byte, 5)`len(s) cap(s) 分别是多少`s = s[2:4]`len(s) cap(s) 又分别是多少
问题 7.4假设 `s1 := []byte{'p', 'o', 'e', 'm'}` `s2 := d[2:]`s2 的值是多少如果我们执行 `s2[1] == 't'`s1 s2 现在的值又分配是多少
## 7.2.5 多维 slice
和数组一样slice 通常也是一维的但是也可以由一维组合成高维通过分片的分片或者 slice 的数组长度可以任意动态变化所以 Go 语言的多维 slice 可以任意切分而且内层的 slice 必须单独分配通过 make 方法
## 7.2.6 bytes 包
bytes slice 十分常见Go 语言有一个 bytes 包专门用来解决这种类型的操作方法
bytes 包和字符串包十分类似参见第 4.7 )。而且它还包含一个十分有用的类型 Buffer:
import "bytes"
type Buffer struct {
...
}
这是一个 bytes 的定长 buffer提供 Read Write 方法因为读写不知道长度的 bytes 最好使用 buffer
Buffer 可以这样定义`var buffer bytes.Buffer`
或者 new 出一个指针`var r *bytes.Buffer = new(bytes.Buffer)`
或者通过函数`func NewBuffer(buf []byte) *Buffer`这就用创建了一个 Buffer 对象并且用 buf 初始化好了NewBuffer 最好用在从 buf 读取的时候使用
通过 buffer 串联字符串类似于 Java StringBuilder
创建一个 Buffer通过 buffer.WriteString(s) 方法将每个 string s 追加到后面最后再通过 buffer.String() 方法转换为 string下面是代码段
var buffer bytes.Buffer
for {
if s, ok := getNextString(); ok { //method getNextString() not shown here
buffer.WriteString(s)
} else {
break
}
}
fmt.Print(buffer.String(), "\n")
这种实现方式比使用 `+=` 要更节省内存和 CPU尤其是要串联的字符串数目特别多的时候
练习
练习 7.5 给定 slice sl a []byte 数组追加到 sl 后面写一个函数 `Append(slice, data []byte) []byte`该函数在 sl 不能存储更多数据的时候自动扩容
练习 7.6 把一个缓存 buf 分片成两个 slice第一个是前 n bytes后一个是剩余的用一行代码实现
## 链接
- [目录](directory.md)
- 上一节[声明和初始化](07.1.md)
- 下一节[For-range 结构](07.3.md)