我是靠谱客的博主 时尚小天鹅,最近开发中收集的这篇文章主要介绍Go语言之切片切片的定义切片的拷贝与遍历切片追加元素使用copy函数赋值切片从切片中删除元素,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言:本文主要记录了切片的定义、切片的比较、切片的拷贝、遍历,以及如何给切片追加元素。

切片的定义

由于数组长度固定、类型局限性大、无法追加元素等缺陷存在,在Go中引入了切片(slice)。
切片的底层是数组,它是一个拥有相同类型元素的可变长度的序列,支持新元素的添加,可用于快速的操作一块数据的集合。
切片是引用类型,改变切片会改变底层数组,其包含了地址长度容量属性。切片的基本语法如下所示:

// var	关键字
//name	变量名
//T		数据类型
var name []T

//声明示例
func main() {
	//声明切片的类型
	var slice1 []int
	var slice2 = []string{}
	var slice3 = []bool{true, false}
	//var slice4 = []bool{true, false}

	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)
	fmt.Println(slice1 == nil)
	fmt.Println(slice2 == nil)
	fmt.Println(slice3 == nil)
	//fmt.Println(slice3 == slice4)	会直接报错,因为切片是引用类型,不支持直接比较,只能判断是否为nil
}

输出如下所示:

[]
[]
[true false]
true
false
false

切片的长度和容量

切片的长度和容量是不一样的属性值。切片的长度指的是切片所包含的元素的个数,切片的容量是指从第一个元素开始,到切片底层数组的末尾元素的个数。
切片的长度可以用函数len()来求,切片的容量用cap()来求。

在make造切片中会看到两者明显的差别。

切片的表达式

切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。它有两种表达方式:一种是表达式中的startend表示一个索引范围(左开右闭的区域)的简单形式,另一种是再第一种上指定容量的完整形式。

简单形式

因为切片的底层是一个数组,所以可以基于数组来得到一个切片,其长度等于end-start的值,容量等于数组首个元素开始得到切片的底层数组的容量,如下示例:

func main() {
	ar := [6]int{0, 1, 2, 3, 4, 5}
	slice := ar[1:3] //slice := ar[start,end]

	fmt.Printf("slice: %v, slice len is:%d ,slice cap is:%d ", slice, len(slice), cap(slice))
}

输出如下所示:

slice: [1 2], slice len is:2 ,slice cap is:5

这种简单形式可以省略掉startend,如下所示:

ar[1:]	//等同于 ar[1:len(ar)]
ar[:3]	//等同于	ar[0:3]
ar[:]	//等同于 ar[0:len(ar)]

索引越界:当0 <= start <= end <= len(ar)的时候索引合法,否则便会索引越界。
切片再切片:对切片再进行切片表达式时,end的上界限为切片的容量cap(ar),而不是切片的长度len(ar)
索引:索引常量必须是非负数,并且可以用int类型来表示,也必须在有效范围内,否则会在运行时panic
综上示例如下:

func main() {
	ar := [6]int{0, 1, 2, 3, 4, 5}
	slice := ar[1:3]
	fmt.Printf("slice: %v, slice len is:%d ,slice cap is:%dn", slice, len(slice), cap(slice))
	slice2 := slice[3:4] //索引的end是slice的cap而不是len
	fmt.Printf("slice: %v, slice len is:%d ,slice cap is:%d n", slice2, len(slice2), cap(slice2))
}

输出如下所示:

slice: [1 2], slice len is:2 ,slice cap is:5
slice2: [4], slice2 len is:1 ,slice2 cap is:2

切片的完整表达式

对于数组,指向数组的指针、切片ar(不能是字符串)支持完整的切片表达式方式,如下所示:

a[start:end:max]

注意:在上述表达式中,其会构造与简单切片a[start:end]相同类型、长度、元素的切片,其得到的切片的容量为end-start。在这个表达式中,能省略的只有start,省略后默认值为0
满足条件为0 <= start <= end <= max <=cap(ar)
如下示例:

func main() {
	ar := [6]int{0, 1, 2, 3, 4, 5}
	slice := ar[1:3:5]
	fmt.Printf("slice: %v, slice len is:%d ,slice cap is:%dn", slice, len(slice), cap(slice))
}

输出如下:

slice: [1 2], slice len is:2 ,slice cap is:4

使用make()函数构造切片

以上的例子都是基于数组来创造切片的,当我们需要动态的创建一个切片,这时候可以用内置函数make()来创建我们需要的切片,其格式如下:

//T:表示切片的元素类型
//size:表示切片的元素数量
//cap:表示切片的容量
make([]T,size,cap)

示例:

func main() {
	var slice = make([]int, 2, 5)
	fmt.Println(slice)
	fmt.Println(len(slice))
	fmt.Println(cap(slice))

}

输出如下:

[0 0]
2
5

示例中,切片的存储空间分配了十个,但是实际上只用了两个。所以其len(slice)才会为2,cap(slice)表示容量为10。

切片的本质

切片的本质就是对底层数组的封装,其包含了:底层数组的指针、切片的长度、切片的容量。

//源码定义为
type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

示例:
目前有一个数组a:=[8]int{0,1,2,3,4,5,6,7},切片为s:=a[:4],其指针指向、长度和容量示意图如下
在这里插入图片描述
切片s2:=a[3:7],相应示意图如下:
在这里插入图片描述

切片是否为空

检查切片是否为空,应该用len(slice)==0来判断,不可以用slice==nil来判断
原因:在go中由于切片是不能进行比较的,我们不能使用==来直接比较两个切片是否完全相同的元素。切片唯一能合法比较的为nil,一个nil值的切片其长度和容量都是0,因为其没有底层数组,但是对于长度和容量都为0的切片却不一定是nil切片。如下所示:

var slice1 []int				//len(slice1)=0,cap(slice1)=0,slice1=nil
var slice2 []int{}				//len(slice2)=0,cap(slice2)=0,slice2!=nil
var slice3 = make([]int,0)		//len(slice3)=0,cap(slice3)=0,slice3!=nil

slice2slice3其长度和容量都为0,但是其并不是nil切片。
所以说判断一个切片是否为空需要用len()函数来进行判断,而不是直接判断切片是否为空。

切片的拷贝与遍历

由于切片是引用类型,改变切片会改变底层数组。所以当多个切片共享一个底层数组的时候,改变其中一个切片中元素的值,会影响到另一个切片的内容。如下所示:

func main(){
	slice1:=make([]int,3)
	fmt.Println(slice1)
	slice2:=slice1
	slice2[2]=100
	fmt.Println(slice1)
	fmt.Println(slice2)
}

slice1将值直接赋值拷贝给slice2,所以这两个切片共享一个底层数组,改变slice2的值,之后打印两个切片,其输出如下:

[0 0 0]
[0 0 100]
[0 0 100]

切片的遍历和数组相同,都支持for循环的遍历,如下示例:

func main(){
	s:=[]int{1,3,5}
	for i:=0;i<1;i++{
		fmt.Printf("index: %d, s[i]:%dn", i, s[i])
	}
	//for range
	for index, v := range s{
		fmt.Printf("index: %d, s[i]:%dn", index, v)
	} 
}

切片追加元素

在Go中有个内建函数append(),可以动态的为切片追加元素。
append函数可以动态的为一个切片一个一个的添加元素,也可以一次添加多个元素,还可以将另外一个切片元素添加进去。示例如下:

func main(){
	var s:=[]int	//[]
	s = append(s, 1)
	fmt.Printf("add one :%vn",s)
	s = append(s, 2, 3, 4)
	fmt.Printf("add multiple :%vn",s)
	s2 := []int{10, 11, 12]
	s = append(s, s2...)
	fmt.Printf("add s2 :%vn",s)
}

上面代码首先定义勒一个空切片s,第一次添加一个元素,第二次添加多个元素,第三次将另一个切片s2的元素全部添加到s中去。在最后一次添加的时候,需要在s2后面添加...,其目的是为了将切片s2解包,类似第二次追加元素。
注意:在用append函数的时候必须要用原来的切片接受append的返回值

append函数的扩容

在对切片进行追加元素的时候,如果追加元素的后切片长度超过原来切片的容量,那么会按照一定的策略对底层数组进行"扩容"操作。此时,切片指向底层数组的指针会改变,会指向另外一个能够容纳足够数量的数组。“扩容"发生在使用append函数调用的过程中,所以我们需要用原来的切片来接收append返回的新"切片”。
示例:

func main(){
	var s []int
	for i:= 0; i < 10; i++{
		s = append(s,i)
		fmt.Printf("s:%v, len(s):%d, cap(s):%d, iddress:%pn",s, len(s), cap(s), s)
	} 
}

上面代码对切片s进行扩容,每次追加一个元素,之后分别打印出切片、切片的长度、切片的容量和切片的地址。
输出如下:

s:[0], len(s):1, cap(s):1, iddress:0xc0000a0068
s:[0 1], len(s):2, cap(s):2, iddress:0xc0000a00c0
s:[0 1 2], len(s):3, cap(s):4, iddress:0xc00009e160
s:[0 1 2 3], len(s):4, cap(s):4, iddress:0xc00009e160
s:[0 1 2 3 4], len(s):5, cap(s):8, iddress:0xc0000b60c0
s:[0 1 2 3 4 5], len(s):6, cap(s):8, iddress:0xc0000b60c0
s:[0 1 2 3 4 5 6], len(s):7, cap(s):8, iddress:0xc0000b60c0
s:[0 1 2 3 4 5 6 7], len(s):8, cap(s):8, iddress:0xc0000b60c0
s:[0 1 2 3 4 5 6 7 8], len(s):9, cap(s):16, iddress:0xc0000d0200
s:[0 1 2 3 4 5 6 7 8 9], len(s):10, cap(s):16, iddress:0xc0000d0200

从上可以看出:
append函数将元素追加到切片的最后并返回切片;
切片容量由1、2、4、8…这种策略进行扩容。实际上在1024之前长度为之前的两倍进行扩容,之后扩容量为原来切片容量的1/4,不再以伤两倍进行增加容量,以达到满足切片需求的同时又不会造成内存浪费。
但是需要注意的是:对不同的元素类型其扩容的策略是不相同的,例如stringint就不相同,有兴趣的可以自己查看append()源码。
源码路径:$GOROOT/src/runtime/slice.go
相关部分代码:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {	//若新申请的容量大于原容量两倍,则最终的容量就是新申请的容量
	newcap = cap
} else {
	if old.len < 1024 {			//当len不足1024之前按照两倍原容量扩容
		newcap = doublecap
	} else {
		// Check 0 < newcap to detect overflow
		// and prevent an infinite loop.
		for 0 < newcap && newcap < cap {	//在大于1024时新增原容量的1/4的容量
			newcap += newcap / 4
		}
		// Set newcap to the requested cap when
		// the newcap calculation overflowed.
		if newcap <= 0 {
			newcap = cap
		}
	}
}

使用copy函数赋值切片

在Go语言中,由于切片定义为引用类型,切片a给另一个切片b赋值的时候,会指向同一块内存地址,所以改变其中一个切片元素的值,另一个切片元素对应的值也会改变。
而Go的内置函数copy()可以迅速的将一个切片的数据复制到另一个空间中,其使用格式如下:

copy(destSlice, srcSlice []T)

destSlice:目标切片
srcSlice:数据来源切片

示例如下:

func main(){
	a := []int{0, 1, 2, 3, 4}
	s := make([]int, 5)
	copy(s, a)
	fmt.Println(a)
	fmt.Println(s)
	s[0] = 100
	fmt.Println("After changed: ")
	fmt.Println(a)
	fmt.Println(s)
}

输出为:

[0 1 2 3 4]
[0 1 2 3 4]
After changed:
[0 1 2 3 4]
[100 1 2 3 4]

从切片中删除元素

Go中并没有专门的函数来删除切片元素(当然你可以自定义QAQ),我们可以根据切片的特性来"删除"其元素,自定义函数如下:

func main() {
	a := []int{0, 1, 2, 3, 4, 5, 6}
	fmt.Println(a)
	//删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a)	
}

输出如下:

[0 1 2 3 4 5 6]
[0 1 3 4 5 6]

最后

以上就是时尚小天鹅为你收集整理的Go语言之切片切片的定义切片的拷贝与遍历切片追加元素使用copy函数赋值切片从切片中删除元素的全部内容,希望文章能够帮你解决Go语言之切片切片的定义切片的拷贝与遍历切片追加元素使用copy函数赋值切片从切片中删除元素所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部