75142913在线留言
GO语言学习笔记5:数组切片Slice详解_Go语言_网络人

GO语言学习笔记5:数组切片Slice详解

Kwok 发表于:2020-10-01 19:10:22 点击:5 评论: 0

前面(http://www.neter8.com/go/73.html)介绍了数组在定义的时候就需要定义长度,一但设置好了长度,不能增长,也不能减少,GO语言为了解决这个问题,新增了一个切片Slice的概念。切片是一个数组的引用,所以切片是一个引用类型,在传递的时候就是引用传递,切片在使用上基本和数组一样,比如遍历,统计等,但切片的长度是可以变化的,数组却是固定的,这是它们最大的区别。

可以把切片理解为可动态变化的数组。切片定义和数组的区别是不指定长度(类似于PHP数组定义)

一、切片的定义与数组的区别

var Name []string //声明一个string切片(未初始)只要在声明时不做任何初始化,就会创建一个 nil 切片
var Number = []int{} //声明一个int切片,并初始化为空切片,不能访问Number[0]

//下面对比一下数组的区别,数据的[]是有值的。而切片的没有。

var Name2 [2]string //定义一个长度为2的string 数组
var Name2 =[...]string {"数组值1","数组值2"}//定义一个自动推导的string数组

nil 切片是很常见的创建切片的方法。nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil 切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。nil切片在内存里分配一个nil指针,容量与长度都是0,空切片与nil切片差不多。在调用内置函数append,len,cap效果都会一样。

二、基于数组定义的切片

var Name = [10]string{"陈大", "黄二", "张三", "李四", "王五", "赵六", "田七", "亢八", "周九", "吴十"}//定义一个10个长度的数组
NameSlice := Name[1:4] ////基于数组Name创建一个切片,包括元素Name[1]=黄二,Name[2]=张三,Name[3]=李四

还支持下面的几种方式:

a := Name[1:] //从第1条开始到最后一条
b := Name[:4] //从第0条开始,到第4条
c := Name[:]  //从第0条开始到最后一条
//通过make创建切片
d := make([]int, 5) //创建一个整型切片其长度和容量都是 5 个元素
d2 := make([]int, 3, 5) //长度为 3 个元素,容量为 5 个元素

d3 := make([]int, 5, 3) //长度大于容量会报错len larger than cap in make([]int) 

切片引用的是一个数据,通过修改切片索引的目的来管理数组,当2个或者多个切片同时引用一个数组的时候,修改切片1也会同时改变切片2的值。关于为什么这样可以参考本文的内存图。

var Name = [10]string{"陈大", "黄二", "张三", "李四", "王五"} //定义数组	
Slice1 := Name[:]//基于数组Name创建第一个切片
Slice2 := Name[:]//基于数组Name创建第二个切片
Slice2[0]="大陈陈"//通过Slice2[0]修改Name的值
fmt.Println(Slice1[0])//被Slice2[0]修改后的Slice1[0]结果为: 大陈陈
fmt.Println(Name)//[大陈陈 黄二 张三 李四 王五]

可以看到Slice2[0]不光改了自己的值,还同时修改了Slice1[0]和Name[0]的值,所以在1个数组被多个切片链接的时候一定要注意哦~

切片是引用类型,通过函数修改切片slice的值会改变数组的值。

func changeSlice(slice []string) {
	slice[0] = "陈大大"
}
func main() {
	var Name = [10]string{"陈大", "黄二", "张三", "李四", "王五"} //定义数组
	Slice := Name[:] //基于数组Name创建一个切片
	changeSlice(Slice)//把Slice[0]改为陈大大
	fmt.Println(Slice[0]) //被changeSlice修改后的Slice1[0]结果为: 陈大大
	fmt.Println(Name)     //[陈大大 黄二 张三 李四 王五]
}

三、切片还可以再切片

//使用上面的Name里的值,张三、李四等
Slice1 := Name[1:3]//里面存的第1条和第2条即:黄二、张三
Slice2 := Slice1[0:9]//把Slice1再切一次,可以取到原始数组从黄二到吴十的所有数据哦~

注意: 对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。

 GO语言学习笔记5数组切片Slice详解

切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append() 来实现的,这个函数可以快速且高效地增长切片,也可以通过对切片再次切割,缩小一个切片的大小。因为切片的底层也是在连续的内存块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。

四、使用len与cap的区别

len用于统计切片的元素个数(长度),cap统计切片的容量,容量可动态变化的,但是make创建时长度不能超过容量。

var Name = [10]string{"陈大", "黄二", "张三", "李四", "王五", "赵六", "田七", "亢八", "周九", "吴十"}
NameSlice := Name[1:5]	//将数组Name的第1到5条(不包括第5条即:1,2,3,4条)数据放入切片
fmt.Println("容量(cap):",cap(NameSlice))//打印:容量(cap): 9
fmt.Println("元素个数(长度len)",len(NameSlice))//打印:元素个数(长度len) 4

GO语言学习笔记5数组切片Slice详解

newSlice是slice的再切片,可以看到参数不一样,他们指向的内存地址是不一样的。注意看newSlice的元素len为2但cap容量为4哦~

// runtime/slice.go
type slice struct {
    array unsafe.Pointer // 元素指针
    len   int // 长度 
    cap   int // 容量
}

附上slice的源代码。可以看到slice是一个结构体。

 五、切片的使用方式

1、定义一个切片,让切片去引用一个创建好的数组。前面的示例说的都是这样方式,这里不再赘述。

2、使用make来创建切片,在第二项里有过演示,这里做一个详细说明:

基本语法为:var 切片名称 []数据类型 =make([],长度,[容量])即:var sliceName []type=make([],len,[cap]),其中容量是可选值。因为切片的容量可动态变化。

var sliceName []string//定义一个空切片
sliceName = make([],5)//使用make初始化分配空间

//下面是缩写,也是常规写法即定义和初始化一起完成
var sliceName []string = make([]string,5)//创建一个元素长度为5,容量自动的string类型切片

如果分配了cap值,那么cap>=len要不然就会报编译错误,就像500ml的瓶子就只能最多装500ml的水,超过了就会溢出。

切片的元素值修改方式和数组是一模一样的。按下列代码操作即可:

sliceName[0]="陈大"
sliceName[1]="黄二"
sliceName[2]="张三"

为什么会有这样的操作,可以看看下面的内存图就一目了然:

GO语言学习笔记5数组切片Slice详解

3、定义切片的时候就直接指定具体数组(make的简体版)下面的几种写法效果是一样的,按自己喜欢的来。内存分部情况同上(make)

var sliceName []int = []int{10,20,30,40,50,60}//第一种写法
var sliceName = []int{10,20,30,40,50,60}//第二种写法
sliceName := []int{10,20,30,40,50,60}//第三种写法

这几种定义切片的方式在项目开发中根据程序员的使用习惯都可能会遇到,只要能看和并理解了切片的内存分部情况就能很好的理解切片。

六、切片的遍历

切片遍历和数组一样,可以使用for和for range详情可看http://www.neter8.com/go/73.html 第二条 访问数组元素

for i := 0; i < len(slice); i++ {
	fmt.Println(slice[i])
}
for _,v := range slice{
	fmt.Println(v)
}

七、切片动态增长

切片定义后不能越界,范围在0-len(array)之间,如果需要对切片动态增长需要使用append追加数据。语法如下:

sliceName := []int{10, 20, 30, 40, 50, 60}
fmt.Println("sliceName追加前:", sliceName) //sliceName追加前: [10 20 30 40 50 60]
sliceName = append(sliceName, 70, 80, 90, 100)
fmt.Println("sliceName追加后:", sliceName) //sliceName追加后: [10 20 30 40 50 60 70 80 90 100]

以上是直接追加数据,当然也可以把其它切片或者自己再追加给自己。

sliceName:= []int{10, 20, 30, 40, 50, 60}
sliceAppend := []int{70, 80, 90, 100}
sliceName= append(sliceName, sliceAppend ...)
fmt.Println("sliceName追加后:", sliceName) //sliceName追加后: [10 20 30 40 50 60 70 80 90 100]

注意: sliceAppend ... 后面的三个点是固定写法哦~切片动态增长的原理:

切片指向一个匿名数组A,当使用append函数对切片进行追加的时候将新建一个根据要追加的数据量计算后的匿名数组B,然后将匿名数组A的值拷贝给匿名数组B并追加数据到最后,删除匿名数组A后将指针指向匿名数组B就完成了切片数据的追加。

八、切片的拷贝操作copy

切片可以使用copy内置函数完成拷贝操作copy(源切片,覆盖切片):

sliceA := []int{10, 20, 30, 40, 50, 60}
sliceCopy := make([]int, 10)                  //make一个长度为10个元素的切片
sliceSmall := make([]int, 3)                  //make一个长度为3个元素的切片
copy(sliceCopy, sliceA)                       //将sliceA拷贝给sliceCopy
fmt.Println("拷贝后sliceCopy的结果为:", sliceCopy)   //拷贝后sliceCopy的结果为: [10 20 30 40 50 60 0 0 0 0]
copy(sliceSmall, sliceA)                      //将sliceA拷贝给sliceSmall
fmt.Println("拷贝后sliceSmall的结果为:", sliceSmall) //拷贝后sliceSmall的结果为: [10 20 30]

可以看到当sliceSmall 长度不够时,并不会增长,而以根据sliceSmall的长度覆盖。只有切片才能进行拷贝操作,数组是不可以的。

这里再提一嘴,string底层是一个byte数组,因此string也可以进行切片处理。 

str := "abcdefghijk"
slice := str[5:7]  //取出第5和6个元素
fmt.Println(slice) //fg
str = "网络人www.neter8.com"
slice2 := str[0:9]  //取出第0-8个元素,1个中文占3个byte,取3个汉字需要3*3=9
fmt.Println(slice2) //网络人

string是不可变的,所以不能通过slice[0]="s"的方式进行修改,如果要修改需要将str转为byte数组改变值以后再改成string

str := "abcdefghijk"	
slice := []byte(str)  //将str转为byte切片
slice[0]='z'//将第1个元素a改为z
str =string(slice)//转回string
fmt.Println(str) //zbcdefghijk

这种方式只能处理英文,不能处理中文哦,上面注释里提到了1个汉字是3个byte,如果要处理中文需要转为[]rune切片。

str := "中文处理哦~"	
slice := []rune(str)  //将str转为rune切片
slice[0]='汉'//将第1个元素“中”改为“汉”
slice[1]='字'//将第2个元素“文”改为“字”
str =string(slice)//转回string
fmt.Println(str) //汉字处理哦~

 

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:http://www.neter8.com/go/74.html
标签:GOSlice切片
0
感谢打赏!

《GO语言学习笔记5:数组切片Slice详解》的网友评论(0)

本站推荐阅读

热门点击文章