GoLang基础数据类型-切片(slice)详解
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫slice,slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度,它是可变长的,可以随时往slice里面加数据。
一.什么是切片(slice)
简单的说,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量:
1>.一个指向原生数组的指针(point):指向数组中slice指定的开始位置;
2>.数组切片中的元素个数(len):即slice的长度;
3>.数组切片已分配的存储空间(cap):也就是slice开始位置到数组的最后位置的长度。
从底层实现的角度来看,数组切片实际上仍然使用数组来管理元素,基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制。
二.定义切片
其实定义一个切片和定义一个数组的方式很相似,不过很有意思的时候切片的定义方式到是蛮有意思的,它比数组要灵活的多,因为我们知道数组的长度和容量一旦在定义之后就无法被修改,但是切片可以,因此相比数组,切片更受程序员欢迎吧,但是我们不能否定数组的重要性,因为从底层实现的角度来看,Golang切片实际上仍然使用数组来管理元素。
1.用make方法初始化切片;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func my_slice(s string ,x []int) { 13 fmt.Printf("`%s`切片的长度为:[%d] 切片容量为:[%d] 切片中的元素是:%v\n",s,len(x),cap(x),x) 14 } 15 16 func main() { 17 var yinzhengjie []int //声明一个名称为“yinzhengjie”的切片,其默认长度均为零,但是可以并不意味着它不能存取更多的元素哟! 18 Golang_array := [5]int{1,3,5,7} 19 Golang_slice := make([]int,2,5) //表示定义一个长度为“2”,容量为“5”的切片 20 fmt.Printf("`%s`数组的长度为:[%d];数组的容量为:[%d];数组的元素是:%v\n","Golang_array",len(Golang_array),cap(Golang_array),Golang_array) 21 my_slice("Golang_slice",Golang_slice) 22 my_slice("yinzhengjie",yinzhengjie) 23 yinzhengjie = append(yinzhengjie, 100,200,300) //尽管之前的“yinzhengjie”这个切片长度为0,但是仍然可以往里面追加更多的元素。 24 my_slice("yinzhengjie",yinzhengjie) 25 } 26 27 28 29 #以上代码执行结果如下: 30 `Golang_array`数组的长度为:[5];数组的容量为:[5];数组的元素是:[1 3 5 7 0] 31 `Golang_slice`切片的长度为:[2] 切片容量为:[5] 切片中的元素是:[0 0] 32 `yinzhengjie`切片的长度为:[0] 切片容量为:[0] 切片中的元素是:[] 33 `yinzhengjie`切片的长度为:[3] 切片容量为:[4] 切片中的元素是:[100 200 300]
2.用已有数组生成新切片
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 ) 13 14 func main() { 15 primes := [8]int{2,3,5,7,9,11,13,15,} //定义一个数组 16 fmt.Printf("`primes`数组的值:%d\n",primes) 17 var sum []int = primes[1:4] //定义一个切片 18 fmt.Printf("`sum`切片的值:%d\n",sum) 19 fmt.Printf("`sum[0]`所对应的内存地址是:%x\n",&sum[0]) 20 fmt.Printf("`primes[1]`所对应的内存地址是:%x\n",&primes[1]) 21 var s1 []int 22 s1 = sum 23 fmt.Printf("`s1`切片对应的值为:%d\n",s1) 24 fmt.Printf("s1[0] == sum[0]为:%v\n",&s1[0] == &sum[0]) 25 } 26 27 28 29 #以上代码输出结果如下: 30 `primes`数组的值:[2 3 5 7 9 11 13 15] 31 `sum`切片的值:[3 5 7] 32 `sum[0]`所对应的内存地址是:c042046088 33 `primes[1]`所对应的内存地址是:c042046088 34 `s1`切片对应的值为:[3 5 7] 35 s1[0] == sum[0]为:true
3.切片的字面量 (初始化)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 num := []int{100,200,300,400,500} //切片的初始化方法,专业术语叫做切片字面量。 14 fmt.Println(num) 15 16 r := []bool{true,false,true,true} 17 fmt.Println(r) 18 } 19 20 21 22 #以上代码输出结果如下: 23 [100 200 300 400 500] 24 [true false true true]
三.切片的追加
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg string,slice []string){ 13 fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice) 14 fmt.Println() 15 } 16 17 func main() { 18 var yinzhengjie []string 19 MyPrint("原切片",yinzhengjie) 20 for i:=0;i<5;i++{ 21 yinzhengjie=append(yinzhengjie,fmt.Sprintf("yzj%d",i)); 22 } 23 MyPrint("追加后",yinzhengjie) 24 } 25 26 27 28 #以上代码执行结果如下: 29 [ 原切片 ]: 长度:0 内存地址:0x0 是否为空(空为真):true 包含元素:[] 30 [ 追加后 ]: 长度:5 内存地址:0xc042056200 是否为空(空为真):false 包含元素:[yzj0 yzj1 yzj2 yzj3 yzj4]
四.切片的修改
Golang的切片长得和数组很像,我们可以对一个数组做切片。要注意的是:当我们对一个数组做切片的时候,如果我们修改了切片下标所对应的值,那么被切片的数组的值也会跟着改变,因为他们都指向了同一块内存地址。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 names := [4]string{ //定义了一个字符串数组 14 "尹正杰", 15 "百度", 16 "谷歌", 17 "FQ", 18 } 19 fmt.Println(names) 20 21 a := names[0:2] 22 b := names[1:3] 23 fmt.Println(a,b) 24 25 b[0] = "xxx" //修改b的元素,会将names的对应的地址做相应的修改。 26 fmt.Println(a,b) 27 fmt.Println(names) 28 } 29 30 31 #以上代码输出结果如下: 32 [尹正杰 百度 谷歌 FQ] 33 [尹正杰 百度] [百度 谷歌] 34 [尹正杰 xxx] [xxx 谷歌] 35 [尹正杰 xxx 谷歌 FQ]
五.切片的删除
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg string,slice []string){ 13 fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice) 14 fmt.Println() 15 } 16 17 func main() { 18 var yinzhengjie []string 19 for i:=97;i<105;i++{ 20 yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i))); 21 } 22 MyPrint("删除前",yinzhengjie) 23 index:= 5 24 fmt.Println("删除的元素是:",yinzhengjie[index]) 25 yinzhengjie=append(yinzhengjie[:index],yinzhengjie[index+1:]...) //你会发现删除的本质就是在之前的切片上做切割。 26 MyPrint("删除后",yinzhengjie) 27 } 28 29 30 31 #以上代码执行结果如下: 32 [ 删除前 ]: 长度:8 内存地址:0xc042056180 是否为空(空为真):false 包含元素:[a b c d e f g h] 33 删除的元素是: f 34 [ 删除后 ]: 长度:7 内存地址:0xc042056180 是否为空(空为真):false 包含元素:[a b c d e g h]
六.切片的访问方式
1.通过range访问
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg string,slice []string){ 13 fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice) 14 fmt.Println() 15 } 16 17 func main() { 18 var yinzhengjie []string 19 for i:=97;i<105;i++{ 20 yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i))); 21 } 22 23 for k,v := range yinzhengjie{ 24 fmt.Printf("yinzhengjie[%d]=%v\n",k,v) //range具有两个返回值,第一个返回值i是元素的数组下标,第二个返回值v是元素的值。 25 } 26 } 27 28 29 30 31 #以上代码执行结果如下: 32 yinzhengjie[0]=a 33 yinzhengjie[1]=b 34 yinzhengjie[2]=c 35 yinzhengjie[3]=d 36 yinzhengjie[4]=e 37 yinzhengjie[5]=f 38 yinzhengjie[6]=g 39 yinzhengjie[7]=h
2.通过for循环来访问
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg string,slice []string){ 13 fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice) 14 fmt.Println() 15 } 16 17 func main() { 18 var yinzhengjie []string 19 for i:=97;i<105;i++{ 20 yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i))); 21 } 22 23 for i := 0; i < len(yinzhengjie); i++ { 24 fmt.Printf("yinzhengjie[%d]=[%s]\n",i,yinzhengjie[i]) 25 } 26 } 27 28 29 30 #以上代码执行结果如下: 31 yinzhengjie[0]=[a] 32 yinzhengjie[1]=[b] 33 yinzhengjie[2]=[c] 34 yinzhengjie[3]=[d] 35 yinzhengjie[4]=[e] 36 yinzhengjie[5]=[f] 37 yinzhengjie[6]=[g] 38 yinzhengjie[7]=[h]
七.切片进阶知识(切片的指针)
当我们用append追加元素到切片时,如果容量不够,go就会创建一个新的切片变量,这意味着内存地址也会跟着发生变化。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg []string) { 13 fmt.Printf("内存地址:%p \t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",msg,len(msg),cap(msg),msg) 14 } 15 16 func main() { 17 var yinzhengjie []string 18 MyPrint(yinzhengjie) 19 for i:=0;i<10;i++{ 20 yinzhengjie=append(yinzhengjie,fmt.Sprintf("%d",i)) //我们会发现随着切片的长度增大,容量也在增大,内存地址也发生变化啦! 21 MyPrint(yinzhengjie) 22 } 23 MyPrint(yinzhengjie) 24 } 25 26 27 28 #以上代码执行结果如下: 29 内存地址:0x0 长度:0 容量:0 包含元素:[] 30 内存地址:0xc042008270 长度:1 容量:1 包含元素:[0] 31 内存地址:0xc042002740 长度:2 容量:2 包含元素:[0 1] 32 内存地址:0xc04200c2c0 长度:3 容量:4 包含元素:[0 1 2] 33 内存地址:0xc04200c2c0 长度:4 容量:4 包含元素:[0 1 2 3] 34 内存地址:0xc04204e200 长度:5 容量:8 包含元素:[0 1 2 3 4] 35 内存地址:0xc04204e200 长度:6 容量:8 包含元素:[0 1 2 3 4 5] 36 内存地址:0xc04204e200 长度:7 容量:8 包含元素:[0 1 2 3 4 5 6] 37 内存地址:0xc04204e200 长度:8 容量:8 包含元素:[0 1 2 3 4 5 6 7] 38 内存地址:0xc042000400 长度:9 容量:16 包含元素:[0 1 2 3 4 5 6 7 8] 39 内存地址:0xc042000400 长度:10 容量:16 包含元素:[0 1 2 3 4 5 6 7 8 9] 40 内存地址:0xc042000400 长度:10 容量:16 包含元素:[0 1 2 3 4 5 6 7 8 9]
如果在make初始化切片的时候给出了足够的容量,append操作不会创建新的切片。当容量不够时,会自动将现在的容量翻倍。简直就是一言不合就翻倍啊土豪级别啊!
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import "fmt" 11 12 func MyPrint(msg []string) { 13 fmt.Printf("内存地址:%p \t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",msg,len(msg),cap(msg),msg) 14 } 15 16 func main() { 17 var yinzhengjie = make([]string,0,11) 18 MyPrint(yinzhengjie) 19 for i:=0;i<12;i++{ 20 yinzhengjie=append(yinzhengjie,fmt.Sprintf("%d",i)) //注意,当容量不够使时,会自动将现在的容量翻倍。 21 MyPrint(yinzhengjie) 22 } 23 MyPrint(yinzhengjie) 24 } 25 26 27 28 #以上地面执行结果如下: 29 内存地址:0xc04205c000 长度:0 容量:11 包含元素:[] 30 内存地址:0xc04205c000 长度:1 容量:11 包含元素:[0] 31 内存地址:0xc04205c000 长度:2 容量:11 包含元素:[0 1] 32 内存地址:0xc04205c000 长度:3 容量:11 包含元素:[0 1 2] 33 内存地址:0xc04205c000 长度:4 容量:11 包含元素:[0 1 2 3] 34 内存地址:0xc04205c000 长度:5 容量:11 包含元素:[0 1 2 3 4] 35 内存地址:0xc04205c000 长度:6 容量:11 包含元素:[0 1 2 3 4 5] 36 内存地址:0xc04205c000 长度:7 容量:11 包含元素:[0 1 2 3 4 5 6] 37 内存地址:0xc04205c000 长度:8 容量:11 包含元素:[0 1 2 3 4 5 6 7] 38 内存地址:0xc04205c000 长度:9 容量:11 包含元素:[0 1 2 3 4 5 6 7 8] 39 内存地址:0xc04205c000 长度:10 容量:11 包含元素:[0 1 2 3 4 5 6 7 8 9] 40 内存地址:0xc04205c000 长度:11 容量:11 包含元素:[0 1 2 3 4 5 6 7 8 9 10] 41 内存地址:0xc042064000 长度:12 容量:22 包含元素:[0 1 2 3 4 5 6 7 8 9 10 11] 42 内存地址:0xc042064000 长度:12 容量:22 包含元素:[0 1 2 3 4 5 6 7 8 9 10 11]
如果不能准确预估切片的大小,又不想改变变量(如:为了共享数据的改变),这时候就要请出指针来帮忙了。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:[email protected] 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 ) 13 14 func MyPrint(Slice []string,Pointer *[]string) { 15 fmt.Printf("内存地址:%p\t\t指针内存地址:%p\t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",Slice,Pointer,len(Slice),cap(Slice),Pointer) 16 } 17 18 func main() { 19 var yinzhengjie []string 20 yzj := &yinzhengjie //注意:“yzj”就是“yinzhengjie”的指针。将地址都保存到了“yzj”中,因此我们通过该指针始终可以访问到真正的数据。 21 MyPrint(yinzhengjie,yzj) 22 for i:=0;i<10;i++{ 23 *yzj=append(*yzj,fmt.Sprintf("%d",i)) //随着容量的增大,内存地址也发生变化啦,但是指针的内存地址始终没有变化。 24 MyPrint(yinzhengjie,yzj) 25 } 26 MyPrint(yinzhengjie,yzj) 27 } 28 29 30 31 #以上代码执行结果如下: 32 内存地址:0x0 指针内存地址:0xc042002680 长度:0 容量:0 包含元素:&[] 33 内存地址:0xc042008270 指针内存地址:0xc042002680 长度:1 容量:1 包含元素:&[0] 34 内存地址:0xc042002740 指针内存地址:0xc042002680 长度:2 容量:2 包含元素:&[0 1] 35 内存地址:0xc04200c280 指针内存地址:0xc042002680 长度:3 容量:4 包含元素:&[0 1 2] 36 内存地址:0xc04200c280 指针内存地址:0xc042002680 长度:4 容量:4 包含元素:&[0 1 2 3] 37 内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:5 容量:8 包含元素:&[0 1 2 3 4] 38 内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:6 容量:8 包含元素:&[0 1 2 3 4 5] 39 内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:7 容量:8 包含元素:&[0 1 2 3 4 5 6] 40 内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:8 容量:8 包含元素:&[0 1 2 3 4 5 6 7] 41 内存地址:0xc042000400 指针内存地址:0xc042002680 长度:9 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8] 42 内存地址:0xc042000400 指针内存地址:0xc042002680 长度:10 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8 9] 43 内存地址:0xc042000400 指针内存地址:0xc042002680 长度:10 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8 9]