GoLang基礎數據類型-切片(slice)詳解


                   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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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:y1053419035@qq.com
 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]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM