【go笔记】切片(slice)结构的简介

我们java里有个ArrayList, 那go有切片(slice)。
看起来有点像,但是了解切片(slice)结构后,会发现这其实是个两个不同的结构。
下面简单的整理了切片(slice)的结构。

1.切片(slice)

切片(slice)是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
看看下图就应该能明白,为什么说是引用类型,可又说自身又是结构体。

图片备用地址

slice_struct

Slice 的数据结构可以如下定义:

type slice struct {
    array unsafe.Pointer  //指向第一个元素的地址
    len   int
    cap   int
}

2.切片(slice)的内存布局

我们看看下图切片(slice)的内存布局是如何实现的。

图片备用地址

slice_struct

如图所示,切片指向数组的第2个元素,长度是2,留了4个容量。
切片指向了23元素,长度是2 所以切片指的是[23,37],再往后留了2个预留空间。
可以看下图:

图片备用地址

slice_struct

如果上述结构了解了,下面的这个现象就好理解了。

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

    s := data[2:4]
    s[0] += 100
    s[1] += 200

    fmt.Println(s)    //[102 203]
    fmt.Println(data) //[0 1 102 203 4 5]

    s = append(s, 300) // 这个操作会影响 s1,data1 值, 因为都看同一个值

    fmt.Println(s)    //[102 203 300]
    fmt.Println(data) //[0 1 102 203 300 5]

3.切片(slice)的append

给切片(slice)添加元素需要用append。
当切片(slice)的cap有剩余空间时是直接添加到下一个位置,
如果没有剩余空间,会扩容2倍的空间(高于1024 扩容 1.25倍)。
如图下图:

图片备用地址

slice_struct

4.切片(slice)代码实验

切片(slice)的几个特性直接写了代码了。
可以尝试着看下面的代码,了解切片(slice)的一些特性和用法。

//slice声明方式
func sliceTest() {
    //1.声明切片
    var s1 []int
    if s1 == nil {
        fmt.Println("是空")
    } else {
        fmt.Println("不是空")
    }
    // 2.:=
    s2 := []int{}
    // 3.make()
    var s3 []int = make([]int, 0)
    fmt.Println(s1, s2, s3)
    // 4.初始化赋值
    // make([]type, len, cap)
    var s4 []int = make([]int, 0, 0)
    fmt.Println(s4)
    s5 := []int{1, 2, 3}
    fmt.Println(s5)
    // 5.从数组切片
    arr := [5]int{1, 2, 3, 4, 5}
    var s6 []int
    // 前包后不包
    s6 = arr[1:4]
    fmt.Println(s6)
}

//slice截取
func sliceTest1() {
    var arr = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    var slice0 []int = arr[2:8]
    var slice1 []int = arr[0:6]        //可以简写为 var slice []int = arr[:end]
    var slice2 []int = arr[5:10]       //可以简写为 var slice[]int = arr[start:]
    var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
    var slice4 = arr[:len(arr)-1]

    fmt.Printf("arr: %v\n", arr)
    fmt.Printf("slice0: %v\n", slice0)
    fmt.Printf("slice1: %v\n", slice1)
    fmt.Printf("slice2: %v\n", slice2)
    fmt.Printf("slice3: %v\n", slice3)
    fmt.Printf("slice4: %v\n", slice4)
}

//slice截取的值,会指向原有的值。
func sliceTest2() {
    data := []int{0, 1, 2, 3, 4, 5}

    s := data[2:4]
    s[0] += 100
    s[1] += 200

    fmt.Println(s)    //[102 203]
    fmt.Println(data) //[0 1 102 203 4 5]

    //需要注意的现象
    data1 := make([]int, 6, 30)
    for i := 0; i < 6; i++ {
        data1[i] = i
    }
    s1 := data1[0:2]
    s1 = append(s1, 10) // 这个操作会影响 s1,data1 值, 因为都看同一个值

    fmt.Printf("%v\n", s1)    //[0 1 10]
    fmt.Printf("%v\n", data1) //[0 1 10 3 4 5]

    data2 := make([]int, 6, 30)
    for i := 0; i < 6; i++ {
        data2[i] = i
    }
    s2 := data2[3:] //s2的值是 指向s2[0] 所以s2和data2的指针地址不一样,但是s2[1],和data2[4] 指向的都是同一个内存空间。
    s2[0] = 10
    // s2 = append(s2, 10)

    fmt.Printf("%v, %v, %v, %p\n", s2, len(s2), cap(s2), &s2[1])             //[10 4 5], 3, 27, 0xc000108110
    fmt.Printf("%v, %v, %v, %p\n", data2, len(data2), cap(data2), &data2[4]) //[0 1 2 10 4 5], 6, 30, 0xc000108110

}

//声明slice的时候 len 和 cap 的关系
func sliceTest3() {
    s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
    fmt.Println(s1, len(s1), cap(s1))

    s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。
    fmt.Println(s2, len(s2), cap(s2))

    s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
    fmt.Println(s3, len(s3), cap(s3))
}

func sliceTest4() {
    s := []int{0, 1, 2, 3}
    p := &s[2] // *int, 获取底层数组元素指针。
    *p += 100

    fmt.Println(s) //[0 1 102 3]
}

func sliceTest5() {
    data := [][]int{
        []int{1, 2, 3},
        []int{100, 200},
        []int{11, 22, 33, 44},
    }
    fmt.Println(data)
}

func sliceTest6() {
    d := [5]struct {
        x int
    }{}

    s := d[:] //指向同一个内存空间

    d[1].x = 10
    s[2].x = 20

    fmt.Printf("%p, %p, %p, %p, %p\n", &d, &d[0], s, &s, &s[0]) //0xc0000c0060, 0xc0000c0060, 0xc0000c0060, 0xc000096060, 0xc0000c0060
    fmt.Printf("d: %v,  s: %v\n", d, s)                         //d: [{0} {10} {20} {0} {0}],  s: [{0} {10} {20} {0} {0}]

    s1 := d //新建一个内存空间
    s1[3].x = 30

    fmt.Printf("%p, %p, %p, %p\n", &d, &d[0], &s1, &s1[0]) //0xc0000c0060, 0xc0000c0060, 0xc0000c00c0, 0xc0000c00c0
    fmt.Printf("d: %v,  s: %v\n", d, s1)                   //d: [{0} {10} {20} {0} {0}],  s: [{0} {10} {20} {30} {0}]

}

//slice追加添加元素,append
func sliceTest7() {
    var a = []int{1, 2, 3}
    fmt.Printf("slice a : %v\n", a)
    var b = []int{4, 5, 6}
    fmt.Printf("slice b : %v\n", b)
    c := append(a, b...)
    fmt.Printf("slice c : %v\n", c)
    d := append(c, 7)
    fmt.Printf("slice d : %v\n", d)
    e := append(d, 8, 9, 10)
    fmt.Printf("slice e : %v\n", e)
}

//虽然有多余的容量cap但是超出长度,append后
func sliceTest8() {
    s1 := make([]int, 0, 5)
    fmt.Printf("%p\n", &s1) //0xc000004078

    s1 = append(s1, 1)
    fmt.Printf("%p\n", &s1) //0xc000004078

    s2 := append(s1, 3)
    fmt.Printf("%p, %p\n", &s1, &s2) //0xc000004078, 0xc000004090
    fmt.Println(s1, s2)              //[1] [1 3]

    s2 = append(s2, 4)
    fmt.Printf("%p, %p\n", &s1, &s2) //0xc000004078, 0xc000004090

    fmt.Println(s1, s2) //[1] [1 3 4]
}

func sliceTest9() {
    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]

    s = append(s, 100) // 第一次 append 不超出 s.cap 限制。

    fmt.Println(s, data)         // 不会从新分配底层数组。         [0 1 100] [0 1 100 3 4 0 0 0 0 0 0]
    fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。 0xc000086060 0xc000086060

    s = append(s, 200) // 一次 append 两个值,超出 s.cap 限制。

    fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。    [0 1 100 200] [0 1 100 3 4 0 0 0 0 0 0]
    fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。             0xc0000c0060 0xc000086060
}

// cap的变化
func sliceTest10() {
    s := make([]int, 0, 1)
    c := cap(s)

    for i := 0; i < 50; i++ {
        s = append(s, i)
        if n := cap(s); n > c {
            fmt.Printf("cap: %d -> %d\n", c, n)
            c = n
        }
    }
    /*
        cap: 1 -> 2
        cap: 2 -> 4
        cap: 4 -> 8
        cap: 8 -> 16
        cap: 16 -> 32
        cap: 32 -> 64
    */
}

//copy的使用
func sliceTest11() {
    s1 := []int{1, 2, 3, 4, 5}
    fmt.Printf("slice s1 : %v\n", s1)
    s2 := make([]int, 10)
    fmt.Printf("slice s2 : %v\n", s2)
    copy(s2, s1)
    fmt.Printf("copied slice s1 : %v\n", s1)
    fmt.Printf("copied slice s2 : %v\n", s2)
    s3 := []int{1, 2, 3}
    fmt.Printf("slice s3 : %v\n", s3)
    s3 = append(s3, s2...)
    fmt.Printf("appended slice s3 : %v\n", s3)
    s3 = append(s3, 4, 5, 6)
    fmt.Printf("last slice s3 : %v\n", s3)

    /*
        slice s1 : [1 2 3 4 5]
        slice s2 : [0 0 0 0 0 0 0 0 0 0]
        copied slice s1 : [1 2 3 4 5]
        copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
        slice s3 : [1 2 3]
        appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
        last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
    */

}

//copy的使用
func sliceTest12() {
    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("array data : ", data)
    s1 := data[8:]
    s2 := data[:5]
    fmt.Printf("slice s1 : %v\n", s1)
    fmt.Printf("slice s2 : %v\n", s2)
    copy(s2, s1)
    fmt.Printf("copied slice s1 : %v\n", s1)
    fmt.Printf("copied slice s2 : %v\n", s2)
    fmt.Println("last array data : ", data)

    /*
        array data :  [0 1 2 3 4 5 6 7 8 9]
        slice s1 : [8 9]
        slice s2 : [0 1 2 3 4]
        copied slice s1 : [8 9]
        copied slice s2 : [8 9 2 3 4]
        last array data :  [8 9 2 3 4 5 6 7 8 9]
    */
}

//使用 for range 遍历
func sliceTest13() {
    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    slice := data[:]
    for index, value := range slice {
        fmt.Printf("index : %v , value : %v\n", index, value)
    }
    /*
        index : 0 , value : 0
        index : 1 , value : 1
        index : 2 , value : 2
        index : 3 , value : 3
        index : 4 , value : 4
        index : 5 , value : 5
        index : 6 , value : 6
        index : 7 , value : 7
        index : 8 , value : 8
        index : 9 , value : 9
    */
}

//slice 截取赋值的时候注意事项 重点看 c
func sliceTest14() {
    var a = []int{1, 3, 4, 5}
    fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a)) //slice a : [1 3 4 5] , len(a) : 4

    b := a[1:2]
    fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b)) //slice b : [3] , len(b) : 1

    c := b[0:3]
    fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c)) //slice c : [3 4 5] , len(c) : 3
}

//slice string的操作
func sliceTest15() {
    str := "hello world"
    s1 := str[0:5]
    fmt.Println(s1) //hello

    s2 := str[6:]
    fmt.Println(s2) //world
}

//slice string的操作
func sliceTest16() {
    str := "Hello world"
    s := []byte(str) //中文字符需要用[]rune(str)
    s[6] = 'G'
    s = s[:8]
    s = append(s, '!')
    str = string(s)
    fmt.Println(str) //Hello Go!
}

//slice string的操作
func sliceTest17() {
    str := "你好,世界!hello world!"
    s := []rune(str)
    s[3] = '够'
    s[4] = '浪'
    s[12] = 'g'
    s = s[:14]
    str = string(s)
    fmt.Println(str) //你好,够浪!hello go
}

//[:] 截取的时候cap是 原有数据的截取后的容量
func sliceTest18() {
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    d1 := slice[5:7]
    fmt.Println(d1, len(d1), cap(d1)) //[5 6] 2 5
    d2 := slice[:6:8]
    fmt.Println(d2, len(d2), cap(d2)) //[0 1 2 3 4 5] 6 8
}

func sliceTest19() {
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    str := strings.Replace(strings.Trim(fmt.Sprint(slice), "[]"), " ", ",", -1)
    fmt.Println(str) //0,1,2,3,4,5,6,7,8,9
}

欢迎大家的意见和交流

email: li_mingxie@163.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容