Golang切片Slice功能操作详情_第1页
Golang切片Slice功能操作详情_第2页
Golang切片Slice功能操作详情_第3页
Golang切片Slice功能操作详情_第4页
Golang切片Slice功能操作详情_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第Golang切片Slice功能操作详情目录一、概述二、切片2.1切片的定义2.2切片的长度和容量2.3切片表达式简单切片表达式完整切片表达式2.4使用make()函数构造切片2.5forrange循环迭代切片2.6切片的本质2.7判断切片是否为空三、切片功能操作3.1切片不能直接比较3.2切片的赋值拷贝3.3使用copy()函数复制切片3.4append()方法为切片添加元素3.5从切片中删除元素从开头位置删除从中间位置删除从尾部删除3.6切片的扩容策略

一、概述

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

切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。

二、切片

2.1切片的定义

声明切片类型的基本语法如下:

varname[]T

说明

name:表示变量名T:表示切片中的元素类型

示例:

funcmain(){

//声明切片类型

vara[]string//声明一个字符串切片

varb=[]int{}//声明一个整型切片并初始化

varc=[]bool{false,true}//声明一个布尔切片并初始化

vard=[]bool{false,true}//声明一个布尔切片并初始化

fmt.Println(a)//[]

fmt.Println(b)//[]

fmt.Println(c)//[falsetrue]

fmt.Println(a==nil)//true

fmt.Println(b==nil)//false

fmt.Println(c==nil)//false

//fmt.Println(c==d)//切片是引用类型,不支持直接比较,只能和nil比较

}

2.2切片的长度和容量

一个slice由三个部分构成:指针、长度和容量。

指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。

长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。

简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。

内置的len和cap函数分别返回slice的长度和容量。

s:=make([]string,3,5)

fmt.Println(len(s))//3

fmt.Println(cap(s))//5

如果切片操作超出上限将导致一个panic异常。

s:=make([]int,3,5)

fmt.Println(s[10])//panic:runtimeerror:indexoutofrange[10]withlength3

2.3切片表达式

切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。

它有两种变体:

一种指定low和high两个索引界限值的简单的形式另一种是除了low和high索引界限值外还指定容量的完整的形式。

简单切片表达式

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。

切片表达式中的low和high表示一个索引范围(左包含,右不包含),也就是下面代码中从数组a中选出1=索引值4的元素组成切片s,得到的切片长度=high-low,容量等于得到的切片的底层数组的容量。

funcmain(){

a:=[5]int{1,2,3,4,5}

s:=a[1:3]//s:=a[low:high]

fmt.Printf("s:%vlen(s):%vcap(s):%v\n",s,len(s),cap(s))

}

运行结果:

s:[23]len(s):2cap(s):4

为了方便起见,可以省略切片表达式中的任何索引。

省略了low则默认为0;省略了high则默认为切片操作数的长度:

a[2:]//等同于a[2:len(a)]

a[:3]//等同于a[0:3]

a[:]//等同于a[0:len(a)]

注意:

对于数组或字符串,如果0=low=high=len(a),则索引合法,否则就会索引越界(outofrange)。

对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度。

常量索引必须是非负的,并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。

如果low和high两个指标都是常数,它们必须满足low=high。如果索引在运行时超出范围,就会发生运行时panic。

funcmain(){

a:=[5]int{1,2,3,4,5}

s:=a[1:3]//s:=a[low:high]

fmt.Printf("s:%vlen(s):%vcap(s):%v\n",s,len(s),cap(s))

s2:=s[3:4]//索引的上限是cap(s)而不是len(s)

fmt.Printf("s2:%vlen(s2):%vcap(s2):%v\n",s2,len(s2),cap(s2))

}

输出:

s:[23]len(s):2cap(s):4

s2:[5]len(s2):1cap(s2):1

完整切片表达式

对于数组,指向数组的指针,或切片a(注意不能是字符串)支持完整切片表达式:

a[low:high:max]

上面的代码会构造与简单切片表达式a[low:high]相同类型、相同长度和元素的切片。另外,它会将得到的结果切片的容量设置为max-low。在完整切片表达式中只有第一个索引值(low)可以省略;它默认为0。

funcmain(){

a:=[5]int{1,2,3,4,5}

t:=a[1:3:5]

fmt.Printf("t:%vlen(t):%vcap(t):%v\n",t,len(t),cap(t))

}

运行结果:

t:[23]len(t):2cap(t):4

完整切片表达式需要满足的条件是0=low=high=max=cap(a),其他条件和简单切片表达式相同。

2.4使用make()函数构造切片

上面都是基于数组来创建的切片,如果需要动态的创建一个切片,就需要使用内置的make()函数,

格式如下:

make([]T,size,cap)

说明:

T:切片的元素类型size:切片中元素的数量cap:切片的容量

示例:

funcmain(){

a:=make([]int,2,10)

fmt.Println(a)//[00]

fmt.Println(len(a))//2

fmt.Println(cap(a))//10

}

上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

提示:

使用make()函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

2.5forrange循环迭代切片

forrange可以用来迭代切片里的每一个元素,如下所示:

funcmain(){

//创建一个整型切片,并赋值

slice:=[]int{10,20,30,40}

//迭代每一个元素,并显示其值

forindex,value:=rangeslice{

fmt.Printf("Index:%dValue:%d\n",index,value)

}

运行结果:

Index:0Value:10

Index:1Value:20

Index:2Value:30

Index:3Value:40

index:表示每一个元素的索引value:表示每一个元素的值

当迭代切片时,forrange会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本,如下图所示。

注意forrange返回的是每个元素的副本,而不是直接返回对该元素的引用。

示例:

funcmain(){

//创建一个整型切片,并赋值

slice:=[]int{10,20,30,40}

//迭代每个元素,并显示值和地址

forindex,value:=rangeslice{

fmt.Printf("Value:%dValue-Addr:%XElemAddr:%X\n",value,value,slice[index])

}

运行结果:

Value:10Value-Addr:C00009E058ElemAddr:C00009C120

Value:20Value-Addr:C00009E058ElemAddr:C00009C128

Value:30Value-Addr:C00009E058ElemAddr:C00009C130

Value:40Value-Addr:C00009E058ElemAddr:C00009C138

因为迭代返回的变量是一个在迭代过程中根据切片依次赋值的新变量,所以value的地址总是相同的,要想获取每个元素的地址,需要使用切片变量和索引值(例如上面代码中的slice[index])。

如果不需要索引值,也可以使用下划线_来忽略这个值,

代码如下所示:

funcmain(){

//创建一个整型切片,并赋值

slice:=[]int{10,20,30,40}

//迭代每个元素,并显示其值

for_,value:=rangeslice{

fmt.Printf("Value:%d\n",value)

}

运行结果;

Value:10

Value:20

Value:30

Value:40

forrange总是会从切片头部开始迭代。如果想对迭代做更多的控制,则可以使用传统的for循环,

代码如下所示:

funcmain(){

//创建一个整型切片,并赋值

slice:=[]int{10,20,30,40}

//从第三个元素开始迭代每个元素

forindex:=2;indexlen(slice);index++{

fmt.Printf("Index:%dValue:%d\n",index,slice[index])

}

运行结果:

Index:2Value:30

Index:3Value:40

forrange不仅仅可以用来遍历切片,它还可以用来遍历数组、字符串、map或者通道等。

2.6切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a:=[8]int{0,1,2,3,4,5,6,7},切片s1:=a[:5],

相应示意图如下:

切片s2:=a[3:6],相应示意图如下:

2.7判断切片是否为空

要检查切片是否为空,请始终使用len(s)==0来判断,而不应该使用s==nil来判断。

三、切片功能操作

3.1切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。切片唯一合法的比较操作是和nil比较。一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。

但是我们不能说一个长度和容量都是0的切片一定是nil,

例如下面的示例:

vars1[]int//len(s1)=0;cap(s1)=0;s1==nil

s2:=[]int{}//len(s2)=0;cap(s2)=0;s2!=nil

s3:=make([]int,0)//len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用len(s)==0来判断,不应该使用s==nil来判断。

3.2切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

funcmain(){

s1:=make([]int,3)//[000]

s2:=s1//将s1直接赋值给s2,s1和s2共用一个底层数组

s2[0]=100

fmt.Println(s1)//[10000]

fmt.Println(s2)//[10000]

}

由于切片是引用类型,所以s1和s2其实都指向了同一块内存地址。修改s2的同时s1的值也会发生变化。

3.3使用copy()函数复制切片

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice,srcSlice[]T)int

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

copy()函数就是将srcSlice复制到destSlice,目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy()函数的返回值表示实际发生复制的元素个数。

示例

funcmain(){

slice1:=[]int{1,2,3,4,5}

slice2:=[]int{5,4,3}

//copy(slice2,slice1)//只会复制slice1的前3个元素到slice2中

copy(slice1,slice2)//只会复制slice2的3个元素到slice1的前3个位置

for_,value:=rangeslice1{

fmt.Printf("%d\t",value)

//for_,value:=rangeslice2{

//fmt.Printf("%d\t",value)

}

虽然通过循环复制切片元素更直接,不过内置的copy()函数使用起来更加方便,copy()函数的第一个参数是要复制的目标slice,第二个参数是源slice,两个slice可以共享同一个底层数组,甚至有重叠也没有问题。

示例:

funcmain(){

//设置元素数量为1000

constelementCount=1000

//预分配足够多的元素切片

srcData:=make([]int,elementCount)

//将切片赋值

fori:=0;ielementCount;i++{

srcData[i]=i

//引用切片数据

refData:=srcData

//预分配足够多的元素切片

copyData:=make([]int,elementCount)

//将数据复制到新的切片空间中

copy(copyData,srcData)

//修改原始数据的第一个元素

srcData[0]=999

//打印引用切片的第一个元素

fmt.Println(refData[0])

//打印复制切片的第一个和最后一个元素

fmt.Println(copyData[0],copyData[elementCount-1])

//复制原始数据从4到6(不包含)

copy(copyData,srcData[4:6])

fori:=0;ii++{

fmt.Printf("%d",copyData[i])

}

运行结果:

999

0999

45234

3.4append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加)。

funcmain(){

vars[]int

s=append(s,1)//[1]

s=append(s,2,3,4)//[1234]

s2:=[]int{5,6,7}

s=append(s,s2...)//[1234567]

}

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

vars[]int

s=append(s,1,2,3)

没有必要像下面的代码一样初始化一个切片再传入append()函数使用。

s:=[]int{}//没有必要初始化

s=append(s,1,2,3)

vars=make([]int)//没有必要初始化

s=append(s,1,2,3)

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时该切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

示例:

funcmain(){

//append()添加元素和切片扩容

varnumSlice[]int

fori:=0;ii++{

numSlice=append(numSlice,i)

fmt.Printf("%vlen:%dcap:%dptr:%p\n",numSlice,len(numSlice),cap(numSlice),numSlice)

}

运行结果:

[0]len:1cap:1ptr:0xc00009e058

[01]len:2cap:2ptr:0xc00009e0a0

[012]len:3cap:4ptr:0xc00009c140

[0123]len:4cap:4ptr:0xc00009c140

[01234]len:5cap:8ptr:0xc0000b2100

[012345]len:6cap:8ptr:0xc0000b2100

[0123456]len:7cap:8ptr:0xc0000b2100

[01234567]len:8cap:8ptr:0xc0000b2100

[012345678]len:9cap:16ptr:0xc0000d0080

[0123456789]len:10cap:16ptr:0xc0000d0080

从上面的结果可以看出:

append()函数将元素追加到切片的最后并返回该切片。切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

append()函数还支持一次性追加多个元素。

示例

varcitySlice[]string

//追加一个元素

citySlice=append(citySlice,"北京")

//追加多个元素

citySlice=append(citySlice,"上海","广州","深圳")

//追加切片

a:=[]string{"成都","重庆"}

citySlice=append(citySlice,a...)

fmt.Println(citySlice)//[北京上海广州深圳成都重庆]

3.5从切片中删除元素

Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。

从开头位置删除

删除开头的元素可以直接移动数据指针:

a=[]int{1,2,3}

a=a[1:]//删除开头1个元素

a=a[N:]//删除开头N个元素

也可以不移动数据指针,但是将后面的数据向开头移动,可以用append原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):

a=[]int{1,2,3}

a=append(a[:0],a[1:]...)//删除开头1个元素

a=append(a[:0],a[N:]...)//删除开头N个元素

还可以用copy()函数来删除开头的元素:

a=[]int{1,2,3}

a=a[:copy(a,a[1:])]//删除开头1个元素

a=a[:copy(a,a[N:])]//删除开头N个元素

从中间位置删除

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append或copy原地完成:

a=[]int{1,2,3,...}

a=append(a[:i],a[i+1:]...)//删除中间1个元素

a=append(a[:i],a[i+N:]...)//删除中间N个元素

a=a[:i+copy(a[i:],a[i+1:])]//删除中间1个元素

a=a[:i+copy(a[i:],a[i+N:])]//删除中间N个元素

从尾部删除

a=[]int{1,2,3}

a=a[:len(a)-1]//删除尾部1个元素

a=a[:len(a)-N]//删除尾部N个元素

删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况,下面来看一个示例。

示例:

删除切片指定位置的元素

funcmain(){

seq:=[]string{"a","b","c","d","e"}

//指定删除位置

index:=2

//查看删除位置之前的元素和

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论