• 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
    ",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
    ","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
    ",primes)
    17     var  sum []int = primes[1:4]   //定义一个切片
    18     fmt.Printf("`sum`切片的值:%d
    ",sum)
    19     fmt.Printf("`sum[0]`所对应的内存地址是:%x
    ",&sum[0])
    20     fmt.Printf("`primes[1]`所对应的内存地址是:%x
    ",&primes[1])
    21     var  s1 []int
    22     s1 = sum
    23     fmt.Printf("`s1`切片对应的值为:%d
    ",s1)
    24     fmt.Printf("s1[0] == sum[0]为:%v
    ",&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 ]:	长度:%v	内存地址:%p	是否为空(空为真):%v	包含元素:%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 ]:	长度:%v	内存地址:%p	是否为空(空为真):%v	包含元素:%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 ]:	长度:%v	内存地址:%p	是否为空(空为真):%v	包含元素:%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
    ",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 ]:	长度:%v	内存地址:%p	是否为空(空为真):%v	包含元素:%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]
    ",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 		长度:%v		容量:%v		包含元素:%v
    ",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 		长度:%v		容量:%v		包含元素:%v
    ",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		指针内存地址:%p		长度:%v		容量:%v		包含元素:%v
    ",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]
  • 相关阅读:
    一种稀少的漏洞-内网穿透代理漏洞【原创】
    实战修改密码处的一处逻辑问题【原创】
    Redis Cluster集群搭建与配置
    tomcat热部署,更改java类不用重新加载context
    Redis集群方案应该怎么做
    Flume + HDFS + Hive日志收集系统
    hadoop 1.2 集群搭建与环境配置
    epub、ocf等常用电子书格式浅析----附JAVA示例程序
    zookeeper-3.4.8 集群搭建
    centos6.6 虚拟机集群搭建
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/7646422.html
Copyright © 2020-2023  润新知