This commit is contained in:
Unknown
2014-11-04 23:48:07 -05:00
parent 37397a7d02
commit ff809d956a
4 changed files with 78 additions and 66 deletions

View File

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