请稍侯

go slice(切片)学习

25 December 2015

参考

##slice简介 slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块.

##slice 与 array的关系

  • Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,
  • 数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice,Slice也可以继续衍生Slice,而内存中,始终只有源数组,也有例外,最后会介绍。

##如何定义slice

  • 直接定义 直接上例子
var slice1 = []int{100, 200}
fmt.Println(slice1)
  • 通过数组生成切片slice, 例子如下
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
s := a[3:6]
fmt.Println(s)  

定义一个数组a,并截取下标3到6(包括3,不包括6)的元素构建slice s.

  • make定义切片slice, 例子如下:
s := make([]int, 10)
fmt.Println(s)
fmt.Printf("len s %d, cap s %d\n", len(s), cap(s))

make 函数第一个参数是构建slice的类型,第二个参数是slice的长度,第三个参数是slice的容量,默认是第二个参数的值

##slice 长度(len) 和 容量(cap)

**slice有两个比较混淆的概念,就是长度和容量. **

  • 何谓长度?这个长度跟数组的长度是一个概念,即在内存中进行了初始化实际存在的元素的个数。
  • 何谓容量?如果通过make函数创建Slice的时候指定了容量参数,那内存管理器会根据指定的容量的值先划分一块内存空间,然后才在其中存放有数组元素,多余部分处于空闲状态,在Slice上追加元素的时候,首先会放到这块空闲的内存中,如果添加的参数个数超过了容量值,内存管理器会重新划分一块容量值为原容量值*2大小的内存空间,依次类推。这个机制的好处在能够提升运算性能,因为内存的重新划分会降低性能。

######看如下例子

s := make([]int, 10)
fmt.Println(s)
fmt.Printf("len s %d, cap s %d\n", len(s), cap(s))
s = append(s, 100, 200, 300, 400)
fmt.Println(s)
fmt.Printf("len s %d, cap s %d\n", len(s), cap(s))

make构建一个slice s,长度和容量都是10, 执行append之后,长度变成13, 容量变成20。

##slice 引用数据类型

slice 是源数组的一个引用,改变slice的值,将会改变源数组的值。

var a = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(a)
s1 := a[3:6]
fmt.Println(s1)
fmt.Printf("len s1 %d, cap s1 %d\n", len(s1), cap(s1))
s1[2] = 200
fmt.Println(a)

程序输出结果如下:

[1 2 3 4 5 6 7 8 9 0]
[4 5 6]
len s1 3, cap s1 7
[1 2 3 4 5 200 7 8 9 0]

slice s1是通过数据a 构建的切片,当改变s1的下标为2的元素的值时,数组a下标为5的元素的值也被改变

##slice 切片非引用的情况 Slice是引用类型,指向的都是内存中的同一块内存,不过在实际应用中,有的时候却会发生“意外” 这种情况只有在像切片append元素的时候出现,Slice的处理机制是这样的,

  • 当Slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间;
  • 一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间,这样就不是引用了。
var a = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(a)
s1 := a[3:6]
fmt.Println(s1)
fmt.Printf("len s1 %d, cap s1 %d\n", len(s1), cap(s1))
fmt.Printf("init address: %p\n", s1)
s1 = append(s1, 1)
fmt.Printf("in cap len s1 %d, cap s1 %d\n", len(s1), cap(s1))
fmt.Printf("in cap, address: %p\n", s1)
s1 = append(s1, 1, 2, 3, 4, 4, 5)
fmt.Printf("out cap len s1 %d, cap s1 %d\n", len(s1), cap(s1))
fmt.Printf("out cap, address: %p\n", s1)

#####程序输出结果:

[1 2 3 4 5 6 7 8 9 0]
[4 5 6]
len s1 3, cap s1 7
init address: 0xc8200860b8
in cap len s1 4, cap s1 7
in cap, address: 0xc8200860b8
out cap len s1 10, cap s1 14
out cap, address: 0xc82006c070

可以看到切片s1第一次append之后,没有超过cap, 还是引用,s1 的地址没变; 第二次append之后,超过了原先预分配的cap, 变成了值复制,s1地址变了。