Array和Slice
Array
Array可以看作一个有长度的变量,每个单元都可以存放一个元素,例如:
var buffer [100]int
在内存中buffer类似如下:
buffer: int int int ... 100 times ... int int int
Array中的长度也是类型的一部分,例如[200]int和[100]int是不同类型的:
testArray(arr [100]int) {
// ...
}
var a1 [100]int
var a2 [200]int
testArray(a1) // OK
testArray(a2) // Wrong
Array可以用来表示矩阵,但是通常是作为slice的存储基础
Slice Header
Slice可以看作是Array的部分片段,而不是Array(A Slice is not an an array,a slice is piece of an array)
slice := array[10:20]
slice也可以对slice进行“切片”:
s2 := slice[5:10] //新定义一个变量s2,是对slice片段的描述
slice := slice[5:10] //对自己切片并将变量付给自身
可以将Slice看成一个包含长度和起始地址的数据结构:
type sliceHeader struct {
Length int
ZeroElement *int //不一定是*int,元素是什么类型就是什么类型的指针
}
例如上面的slice变量可以看成如下:
slice := sliceHeader {
Length: 10,
ZeroElement: &array[10],
}
Slice作为函数参数传递
slice是一个包含长度和一个指针的结构体的值,而不是一个指向一个结构体的指针,函数参数中的slice在传进去的时候是一个struct值的复制值,修改slice中的元素值就是修改其所持有Array的元素值,例如:
func AddSlice(slice []int) {
for i := range slice {
slice[i]++
}
}
func main() {
slice := []int{1,2,3,4,5,6,7,8,9,10}
fmt.Println("before:",slice)
AddSlice(slice)
fmt.Println("after:",slice)
}
pirint:
before: [1,2,3,4,5,6,7,8,9,10]
after: [2,3,4,5,6,7,8,9,10,11]
函数传递进去时候slice只是一个复制值,可以通过如下例子看出:
func SubSlice(slice []int) []int {
slice := slice[0:len(slice)-1]
return slice
}
func main() {
slice := []int{1,2,3,4,5,6,7,8,9,10}
fmt.Println("before: len(slice) =",slice)
newSlice := SubSlice(slice)
fmt.Println("after: len(slice) =",slice)
fmt.Println("after: len(newSlice) =",newSlice)
}
print:
before: len(slice) = 10
after: len(slice) = 10
after: len(newSlice) = 9
指向Slice的指针
如果想修改slice header的内容,可以传递一个指向slice的指针,例如:
func PtrSubSlice(slicePtr *[]int) {
newSlice := *slicePtr
*slicePtr = newSlice[0:len(newSlice)-1]
}
func main() {
fmt.Println("before: len(slice) =", len(slice))
PtrSubSlice(&slice)
fmt.Println("after: len(slice) =", len(slice))
}
print:
before: len(slice) = 10
after: len(slice) = 9
作为方法的接收者,可以修改这个slice:
type path []byte
func (p *path) TruncateAtFinalSlash() {
i := bytes.LastIndex(*p,[]byte("/"))
if i >= 0 {
*p = (*p)[0:i]
}
}
//ASCII letters to Upper
func (p path) ToUpper() {
for i,b := rang p {
if 'a' <= b && b <= 'z' {
p[i] = b + 'A' - 'a'
}
}
}
func main() {
pathName := path("/usr/bin/tso")
pathName.TruncateAtFinalSlash()
fmt.Printf("%s\n",pathName)
pathName.ToUpper()
fmt.Printf("%s\n",pathName)
}
print:
/usr/bin
/USR/BIN
容量(Capacity)
如何扩展slice的长度呢?
func Extend(slice []int, element int) []int {
n := len(slice)
slice = slice[0:n+1]
slice[n] = element
return slice //why return the modified slice?
}
func main() {
var array [10]int
slice := array[0:0] // slice:[]
for i:=0; i<20; i++ {
slice = Extend(slice,i)
fmt.Println(slice)
}
}
这里运行的时候会报错,只会打印出前面10个数字,因为超过10的话已经超过了slice底层的array的长度,所以slice header还包含它的容量,表示其能表示的最大长度:
type sliceHeader struct {
Length int
Capacity int
ZeroElement *byte
}
当执行了slice := array[0:0]的时候,slice header如下:
slice := sliceHeader {
Length: 0,
Capacity: 10,
ZeroElement: &array[0],
}
我们可以通过内置的cap函数获得slice的容量:
if cap(slice) == len(slice) {
fmt.Println("slice is full")
}
Make
上面的例子中我们可以看到在要扩张slice的时候,这个时候我们就可以用内置的make函数来扩展我们的slice:
slice := make([]int, 10, 15)
fmt.Printf("len:%d, cap:%d\n",len(slice),cap(slice))
print:
len:10, cap:15
我们可以让我们的slice扩展两倍:
slice := make([]int, 10, 15)
fmt.Printf("len:%d, cap:%d\n",len(slice),cap(slice))
newSlice := make([]int, len(slice), 2*cap(slice))
for i := range slice {
newSlice[i] slice[i]
}
slice = newSlice
fmt.Printf("len:%d, cap:%d\n",len(slice),cap(slice))
pirnt:
len:10, cap:15
len:10, cap:30
make也可以只传递两个值,这样它的len和cap就是一样的了:
slice := make([]int, 10)
slice的len和cap都是10
make的时候是创建一个新的数组作为slice的存储,下面例子可以看出:
s1 := make([]int,5,10)
s2 := make([]int,len(s2),2*cap(s1))
fmt.Printf("s1.addr:%v,s2.addr:%v\n",&s2[1],&s8[0])
print:
s2.addr:0x11701540,s8.addr:0x1170155c
Copy
在扩展的时候,我们可能需要复制一个slice到另一个slice:
func Insert(slice []int, index,value int) []int {
slice := slice[0:len(slice)+1]
copy(slice[index+1:],slice[index:])
slice[index] = value
return slice
}
slice[i:]和slice[i:len(slice)]一样,slice[:]和slice一样
Append
自己创建一个Append来扩展slice:
func Append(slice []int, elements ...int) []int {
n := len(slice)
total := n + len(elements) //elements看以看作是一个slice,可以其计算长度
if total > cap(slice) {
newSlice := make([]int,total,total*3/2)
copy(newSlice,slice)
slice = newSlice
}
slcie = slice[:total]
copy(slice[n:],elements)
return slice
}
func main() {
s1 := []int{0,1,2,3,4}
s2 := []int{5,6,7}
fmt.Println(s1)
s1 = Append(s1,s2...) //'...'是必须的
fmt.Println(s1)
}
print:
[0,1,2,3,4]
[0,1,2,3,4,5,6,7]
内建的append
上面是自己写的一个Append的函数,其实go中实现了一个内建的append,而且这个append对于任何类型的slice都可以append:
// Create a couple of starter slices.
slice = []int{1, 2, 3}
slice2 := []int{55, 66, 77}
fmt.Println("Start slice: ", slice)
fmt.Println("Start slice2:", slice2)
fmt.Printf("slice[0]:%p,slice2[0]:%p\n",&slice[0],&slice2[0])
// Add an item to a slice.
slice = append(slice, 4)
fmt.Println("Add one item:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])
// Add one slice to another.
slice = append(slice, slice2...)
fmt.Println("Add one slice:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])
// Make a copy of a slice (of int).
slice3 := append([]int(nil), slice...)
fmt.Println("Copy a slice:", slice3)
fmt.Printf("slice3[0]:%p\n",&slice3[0])
// Copy a slice to the end of itself.
fmt.Println("Before append to self:", slice)
slice = append(slice, slice...)
fmt.Println("After append to self:", slice)
fmt.Printf("slice[0]:%p\n",&slice[0])
通过地址打印,我们会看到每次append之后返回的slice的地址都是不一样的,所以内建的append是会重新生成一个底层的array来持有数据的。
什么是nil
go中如果一个slice是nil的,那么他的长度和容量都是0,并且它的起始地址是nil的:
sliceHeader {
Length: 0,
Capacity: 0,
ZeroElement: nil,
}
或者
sliceHeader{}
需要注意的是它的起始地址为nil,如果一个slice是这样的:
slice = array[0:0]
那么这个slice不是nil的,因为它有起始地址,nil的slice是没有地方给它存储元素的并且是不可以增长,但是nil的slice是可以append的,因为append会重新分配空间
字符串(Strings)
字符串可以看作是一个只读的字节(bytes)slice:
slash := '/usr/bin'[0] //value is '/'
usr := '/usr/bin'[0:4] //value is '/usr'
我们还可以直接将byte slice强制转换成字符串
str := string(slice)
或者反过来:
slice := []byte(str)
当然,对于字符串还有很多,这只是一个简单的概述
最后,我们了解了slice和array的基本结构,尤其是slice的实现原理,对于我们使用slice是非常有益的,尤其是内建的copy和append函数