Files
the-way-to-go_ZH_CN/eBook/07.2.md
2013-11-23 15:13:25 +08:00

11 KiB
Raw Blame History

#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] = 5。slice y[0:4]由元素3, 5 7和11组成。

示例 7.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不能被重新分片以获取数组的前一个元素。

注意绝对不要用指针指向slice。slice本身已经是一个引用类型所以它本身就是一个指针!!

问题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的内存结构

示例 7.8 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方法初始化下图给出了区别

在图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后一个是剩余的用一行代码实现。

##链接