《Go语言学习笔记》 - 第五章 - 切片

3. 切片

3.1 切片的概念

Go语言中切片是对数组的抽象,切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

3.2 切片的操作

3.2.1 切片的创建

切片的创建与数组有所不同,切片是引用类型,它的创建需要用到内置函数 make ,函数make 接受一个类型、一个长度和一个可选的容量参数。调用 make 时,内部会分配一个数组,然后返回数组对应的切片。当容量参数被忽略时,编译器默认容量等于长度。

func main() {
	var a []int
	a = make([]int,5,5)
	fmt.Println(a)  // [0 0 0 0 0]

	s := make([]int,5)
	fmt.Println(len(s))  // 5
	fmt.Println(cap(s))  // 5
}

也可基于现有数组或者数组指针创造切片,以开始和结束索引位置确定所引用的数组片段,不支持反向索引。

使用数组初始化创建切片后,切片会与切片共享底层数组,当修改切片或者数组的值时会相互影响,如果对切片添加数据超出cap限制,则会为新切片对象重新分配数组。

func main() {
	x := [...]int{0,1,2,3,4,5,6,7,8,9}
	fmt.Println(x[:],len(x[:]),cap(x[:]))  // [0 1 2 3 4 5 6 7 8 9] 10 10
	fmt.Println(x[2:5],len(x[2:5]),cap(x[2:5]))  // [2 3 4] 3 8
	fmt.Println(x[2:5:7],len(x[2:5:7]),cap(x[2:5:7]))  // [2 3 4] 3 5
	fmt.Println(x[4:],len(x[4:]),cap(x[4:]))  // [4 5 6 7 8 9] 6 6
	fmt.Println(x[:4],len(x[:4]),cap(x[:4]))  // [0 1 2 3] 4 10
	fmt.Println(x[:4:6],len(x[:4:6]),cap(x[:4:6]))  // [0 1 2 3] 4 6
}

从上面这段代码可以看出,如果没有指定切片的容量的话,从数组某个位置开始切片,那么从这个位置一直到最后一个位置的长度就是切片的容量。

3.2.2 空切片

切片的默认值是nil ,注意下面两种定义方式的区别,前者仅定义一个[ ]int 类型的变量,并未执行初始化操作,而后者则用初始化表达式完成了全部的创建过程。

func main() {
	var a []int
	b := []int{}
	fmt.Println(a == nil,b == nil)  // true false  
}

注意,a == nil 表示这是一个未初始化的切片对象,切片本身依然会分配所需内存。

3.2.3 操作符

切片不支持比较操作,就算元素类型支持也不行,仅能判断是否为nil

func main() {
	a := make([]int,1)
	b := make([]int,1)

	fmt.Println(a == b)  // slice can only be compared to nil
}
3.2.4 切片元素的访问

可以根据索引直接访问,也可以获取元素地址之后根据指针访问,但不能像数组那样直接用指针访问元素内容。

func main() {
	s := []int{1, 2, 3, 4, 5, 6}

	p := &s
	p0 := &s[0]
	p1 := &s[1]

	fmt.Println(p, p0, p1)  // 0xc00000c360 0xc00000c368
	// p[0] += 100  // type *[]int does not support indexing  不支持像数组那样的通过指针索引直接访问的方式

	(*p)[0] += 100
	*p1 += 100
	fmt.Println(s)  // [101 102 3 4 5 6]
}
3.2.5 reslice

可以将切片视为数据源,据此创建新的切片对象,新的切片对象不能超出cap,但不受len 的限制。新切片对象依旧指向底层数组,也就是说修改对所有的关联切片均可见。

func main() {
	s := []int{0,1,2,3,4,5,6,7,8,9}
	s1 := s[3:7]
	s2 := s1[1:3]

	for i:=range s2{
		s2[i]+=100
	}
	fmt.Println(s)
	fmt.Println(s1)
	fmt.Println(s2)
	/* 
	[0 1 2 3 104 105 6 7 8 9]
	[3 104 105 6]
	[104 105]
	*/
}

根据这个特性,我们可以实现一个栈式的数据结构。

func main() {
	stack := make([]int,0,5)
	push := func (x int) error{
		n := len(stack)
		if n==cap(stack){
			return errors.New("stack is full")
		}

		stack = stack[:n+1]
		stack[n] = x

		return nil
	}

	pop := func()(int,error){
		n := len(stack)
		if n==0{
			return 0, errors.New("stack is empty")
		}

		x := stack[n-1]
		stack = stack[:n-1]

		return x,nil
	}

	for i:=0;i<7;i++{
		fmt.Printf("push %d:%v,%v\n",i,push(i),stack)
	}

	for i:=0;i<7;i++{
		x,err := pop()
		fmt.Printf("pop: %d, %v, %v\n",x,err,stack)
	}
}
/* 
push 0:,[0]
push 1:,[0 1]
push 2:,[0 1 2]
push 3:,[0 1 2 3]
push 4:,[0 1 2 3 4]
push 5:stack is full,[0 1 2 3 4]
push 6:stack is full,[0 1 2 3 4]
pop: 4, , [0 1 2 3]
pop: 3, , [0 1 2]
pop: 2, , [0 1]
pop: 1, , [0]
pop: 0, , []
pop: 0, stack is empty, []
pop: 0, stack is empty, []
*/
3.2.6 append函数

切片可以通过使用append函数来向切片尾部添加数据,返回新的切片对象

func main(){
	a := []int{0,1,2,3,4}
	s1 := append(a,5)
	s2 := append(a,6,7,8)  // 追加多个数据

	fmt.Println(a,len(a),cap(a))  // [0 1 2 3 4] 5 5
	fmt.Println(s1,len(s1),cap(s1))  // [0 1 2 3 4 5] 6 10
	fmt.Println(s2,len(s2),cap(s2))  // [0 1 2 3 4 6 7 8] 8 10
}

数据被追加到原来的底层数组。如果超出cap 限制,则为新切片对象重新分配数组
注意:

  • 是超出切片cap限制,而非底层数组长度限制,因为cap可小于数组长度。
  • 新分配的数组的长度是原cap的2倍,而非原数组的2倍。
func main(){
	s := make([]int,0,100)
	s1 := s[:2:4]
	s2 := append(s1,1,2,3,4,5,6)  // 超出s1的cap的限制,会重新分配底层数组

	fmt.Printf("s1:%p : %v\n",&s1[0],s1)  // s1:0xc000110000 : [0 0]
	fmt.Printf("s2:%p : %v\n",&s2[0],s2)  // s2:0xc000010200 : [0 0 1 2 3 4 5 6]  数据地址不同,已重新分配地址

	fmt.Printf("s data:%v\n",s[:10])  // s data:[0 0 0 0 0 0 0 0 0 0]   append并未向原数组写入数据
	fmt.Printf("s1 cap: %d,s2 cap:%d\n",cap(s1),cap(s2))  // s1 cap: 4,s2 cap:8  新的数组长度是原数组长度的2倍
}

nil切片追加数据时,会为其分配底层数组。

func main(){
	var a []int
	a = append(a,1,2,3,4,5,6)
	fmt.Println(a)  // [1 2 3 4 5 6]
}

正是由于存在重新分配底层数组的缘故,建议在定义时预留足够多的空间,避免中途内存分配和数据复制开销。

3.2.7 copy函数

copy函数的使用格式如下:copy( destSlice, srcSlice []T) int,其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。

func main(){
	s := []int{0,1,2,3,4,5,6,7,8,9}

	s1 := s[5:8]
	n := copy(s[4:],s1)  // 在同一底层数组的不同区间复制  copy([4,5,6,7,8,9],[5,6,7]) ---> [5,6,7,7,8,9]
	fmt.Println(n,s)  // 3 [0 1 2 3 5 6 7 7 8 9]

	s2 := make([]int,6)
	n = copy(s2,s)
	fmt.Println(n,s2)  // 6 [0 1 2 3 5 6]
}

还可以从字符串中复制数据到[ ]byte

func main(){
	b := make([]byte,3)
	n := copy(b,"abcd")
	fmt.Println(n,b)  // 3 [97 98 99]
}

你可能感兴趣的:(Golang,-,《Go语言学习笔记》)