add 07.2.md, 07.3.md, 07.4.md, 07.5.md

This commit is contained in:
chidouhu
2013-11-23 15:13:25 +08:00
parent b8d6e4d469
commit 22b640d2bf
8 changed files with 489 additions and 0 deletions

251
eBook/07.2.md Normal file
View File

@@ -0,0 +1,251 @@
#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]
一个由数组第12,3个元素组成的分片可以这么生成 s := [3]int{1,2,3} 或者 s := [...]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] = 5slice 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)
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](exmaples/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的地址的指针`它适用于值类型如数组和结构体参见第十章它相当于&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)

94
eBook/07.3.md Normal file
View File

@@ -0,0 +1,94 @@
#7.3 For range构建方法
这种构建方法可以应用与数组和slice:
for ix, value := range slice1 {
...
}
第一个返回值dx是数组或者slice的索引第二个是在该索引位置的值他们都是仅在for循环内部可见的局部变量所以该值只是该索引项slice值的一个拷贝并且不能被修改。
示例 7.9 [slices_forrange.go](exmaples/chapter_7/slices_forrange.go)
package main
import "fmt"
func main() {
var arr1 [5]int
for i:=0; i < len(arr1); i++ {
arr1[i] = i * 2
}
for i:=0; i < len(arr1); i++ {
fmt.Printf("Array at index %d is %d\n", i, arr1[i])
}
}
示例 7.10 [slices_forrange2.go](exmaples/chapter_7/slices_forrange2.go)
package main
import "fmt"
func main() {
seasons := []string{"Spring", "Summer", "Autumn", "Winter"}
for ix, season := range seasons {
fmt.Printf("Season %d is: %s\n", ix, season)
}
var season string
for _, season = range seasons {
fmt.Printf("%s\n", season)
}
}
slices_forrange2.go给出了一个关于字符串的例子 `_`可以用于忽略索引
如果你只需要索引你可以忽略第二个变量例如
for ix := range seasons {
fmt.Printf("%d", ix)
}
// Output: 0 1 2 3
如果你需要修改seasons[ix]的值可以使用这个版本
多维slice下的for range方法
通过计算行数和矩阵值可以很方便的写出如参考7.1.3的for-loops方法来例如参考7.5的例子multidim_array.go
for row := range screen {
for column := range screen[0] {
screen[row][column] = 1
}
}
问题 7.5 假设我们有如下slice items := [...]int{10, 20, 30, 40, 50}
a) 如果我们写了如下的for循环那么执行完for循环后的item的值是多少如果你不确定的话可以测试一下:)
for _, item := range items {
item *= 2
}
b) 如果a)无法正常工作写一个for循环让值可以double
问题 7.6 通过使用省略号操作符`...`来实现累加方法
练习
练习 7.7 sum_array.go
a) 写一个Sum函数传入参数为一个4位float数组成的数组arrF返回该数组的所有数字和
如果把数组修改为slice的话代码要做怎样的修改如果用slice形式方法实现不同长度数组的的和呢
b) 写一个SumAndAverage方法返回两个int和float32类型的未命名变量的和与平均值
练习 7.8 min_max.go
写一个minSlice方法传入一个int的slice并且返回最小值再写一个maxSlice方法返回最大值
##链接
- [目录](directory.md)
- 上一节[分片](07.2.md)
- 下一节[分片重组](07.4.md)

75
eBook/07.4.md Normal file
View File

@@ -0,0 +1,75 @@
#7.4 分片重组
我们已经知道slice创建的时候通常比相关数组小例如
slice1 := make([]type, start_length, capacity)
其中start_length作为slice初始长度而capacity作为相关数组的长度。
这么做的好处是我们的slice在达到容量上限后可以扩容。改变slice长度的过程称之为分片重组`reslicing`做法如下slice1 = slice1[0:end]其中end是新的末尾索引即长度
将slice扩展1位可以这么做 sl = sl[0:len(sl)+1]
slice可以反复扩展直到占据整个相关数组。
示例 7.11 [reslicing.go](exmaples/chapter_7/reslicing.go)
package main
import "fmt"
func main() {
slice1 := make([]int, 0, 10)
// load the slice, cap(slice1) is 10:
for i := 0; i < cap(slice1); i++ {
slice1 = slice1[0:i+1]
slice1[i] = i
fmt.Printf("The length of slice is %d\n", len(slice1))
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
}
输出结果
The length of slice is 1
The length of slice is 2
The length of slice is 3
The length of slice is 4
The length of slice is 5
The length of slice is 6
The length of slice is 7
The length of slice is 8
The length of slice is 9
The length of slice is 10
Slice at 0 is 0
Slice at 1 is 1
Slice at 2 is 2
Slice at 3 is 3
Slice at 4 is 4
Slice at 5 is 5
Slice at 6 is 6
Slice at 7 is 7
Slice at 8 is 8
Slice at 9 is 9
另一个例子
var ar = [10]int{0,1,2,3,4,5,6,,7,8,9}
var a = ar[5:7] // reference to subarray {5,6} - len(a) is 2 and cap(a) is 5
将a重新分片
a = a[0:4] // ref of subarray {5,6,7,8} - len(a) is now 4 but cap(a) is still 5
问题 7.7
1) 如果a是一个slice那么s[n:n]的长度是多少
2) s[n:n+1]的长度又是多少
##链接
- [目录](directory.md)
- 上一节[For range构建方法](07.3.md)
- 下一节[拷贝与追加slice](07.5.md)

55
eBook/07.5.md Normal file
View File

@@ -0,0 +1,55 @@
#7.5 拷贝与追加slice
如果想增加slice的容量我们必须创建一个新的更大的slice并把原分片的内容都拷贝过来。下面的代码描述了从拷贝slice的copy方法和向slice追加新元素的append方法。
示例 7.12 [copy_append_slice.go](exmaples/chapter_7/copy_append_slice.go)
package main
import "fmt"
func main() {
sl_from := []int{1, 2, 3}
sl_to := make([]int, 10)
n := copy(sl_to, sl_from)
fmt.Println(sl_to)
fmt.Printf("Copied %d elements\n", n) // n == 3
sl3 := []int{1, 2, 3}
sl3 = append(sl3, 4, 5, 6)
fmt.Println(sl3)
}
func append(s[]T, x ...T) []T 其中append方法将0个或多个具有相同类型s的元素追加到slice后面并且返回新的slice追加的元素必须和原slice的元素同类型。如果s的容量不足以存储新增元素append会分配新的slice来保证已有slice元素和新增元素的存储。因此返回的slice可能已经指向一个不同的相关数组了。append方法总是返回成功除非系统内存耗尽了。
如果你想将slice y追加到slice x后面只要将第二个参数扩展成一个列表即可 x = append(x, y...)
**注意** append在大多数情况下很好用但是如果你想完全掌控整个追加过程你可以实现一个这样的AppendByte方法
func AppendByte(slice []byte, data ...byte[]) []byte {
m := len(slice)
n := m + len(data)
if n > cap(slice) { // if necessary, reallocate
// allocate doublke what's needed, for future growth.
newSlice := make([]byte, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
func copy(dst, src []T) int copy方法将类型为T的slice从源地址src拷贝到目标地址dst覆盖dst的相关元素并且返回拷贝的元素个数。源地址和目标地址可能会有重叠。拷贝个数是src和dst的长度最小值。如果src是字符串那么元素类型就是byte。如果你还想继续使用src在拷贝技术后执行src =dst。
练习 7.9 magnify_slice.go: 给定slice s[]int和一个int类型的因子扩展s使其长度为len(s) * factor。
练习 7.10filter_slice.go 用顺序函数过滤容器s是前10个整形的slice。构造一个函数Filter第一个参数是s第二个参数是一个fn func(int) bool返回满足函数fn的元素slice。通过fn测试方法测试当整型值是偶数时的情况。
练习 7.11insert_slice.go写一个函数InsertStringSlice将slice插入到另一个slice的指定位置。
练习 7.12remove_slice.go写一个函数RemoveStringSlice将从start到end索引的元素从slice中移除。
##链接
- [目录](directory.md)
- 上一节:[分片重组](07.4.md)
- 下一节:[字符串数组和slice应用](07.6.md)

View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func main() {
seasons := []string{"Spring", "Summer", "Autumn", "Winter"}
for ix, season := range seasons {
fmt.Printf("Season %d is: %s\n", ix, season)
}
var season string
for _, season = range seasons {
fmt.Printf("%s\n", season)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
eBook/images/7.2_fig7.2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
eBook/images/7.3_fig7.3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB