slice 理解

1、从数组到slice
2、slice 结构
3、切片作为参数


1、从数组到slice
  • 先用数组来模拟slice
// 数组的容量为20,类似于slice的cap
var vals [20]int

// 实际在数组中添加了5个元素,类似于slice的length,当前为5
for i := 0; i < 5; i++ {
  vals[i] = i * i
}
subsetLen := 5

fmt.Println("The subset of our array has a length of:", subsetLen)

// 数组模拟slice再添加一个元素,此时length为6
vals[subsetLen] = 123
subsetLen++
fmt.Println("The subset of our array has a length of:", subsetLen)
  • 上面代码转换为slice
// 类似于数组的 var vals [20]int
var := make([]int, 0, 20)

// 添加了5个元素,slice 的len 变为5
for i := 0; i < 5; i++ {
  vars = append(vals, i * i)
}
fmt.Println("The slice length of: ", len(vals)

// slice再添加一个元素,此时len为6
vals = append(vals, 123)
fmt.Println("The slice has a length of:", len(vals)
2、slice 结构
  • slice 结构

    • 一个数组指针:指向底层数组
    • 一个元素的个数 length:当前底层数组中加入了多少个元素
    • 一个容量cap:底层数组的容量或是分配的内存大小

    当length超过容量时,将导致切片底层数组重新被分配,分配后cap的大小将变为原来cap的两倍

// 定义一个切片,默认内置5个元素0,此时cap也是5
var sliceTwo = make([]int, 5)
fmt.Println("init slice info ", unsafe.Pointer(&sliceTwo[0]), 
          " len: ", len(sliceTwo), 
          " cap: ", cap(sliceTwo), 
          " content: ", sliceTwo)

// slice中cap已经被占满,再添加一个元素将引起slice
// 底层数组重新分配
sliceTwo = append(sliceTwo, 1)
fmt.Print("first append, slice info: ",
          unsafe.Pointer(&sliceTwo[0]), 
          " len: ", len(sliceTwo),
          " cap: ", cap(sliceTwo),
          " content: ", sliceTwo)

// 再添加一个,由于cap够,所以不应该重新分配底层数组
sliceTwo = append(sliceTwo, 1)
fmt.Print("second append, slice info: ",
          unsafe.Pointer(&sliceTwo[0]), 
          " len: ", len(sliceTwo),
          " cap: ", cap(sliceTwo),
          " content: ", sliceTwo)

输入如下

//初始地址:0xc42001a120, cap=5, len=5
init slice info:  0xc42001a120  len:  5  cap:  5  content [0 0 0 0 0]

//第一次添加元素:底层数组地址变化为:0xc4200180f0,len:6, cap翻倍为10
first append, slice info:  0xc4200180f0  len:  6  cap:  10  content:  [0 0 0 0 0 1]

//第二次添加,底层数组地址未变 0xc4200180f0, len=7, cap=10
second append, slice info:  0xc4200180f0  len:  7  cap:  10  content:  [0 0 0 0 0 1 1]

注意:make([]int, len, cap), 未指定cap时,len与cap相同,len的含义时在分配cap容量的数组之后,添加len个零值元素,所以后续再添加元素时要从len的位置开始添加,而不是从0位置开始

3、切片作为参数
  • slice作为参数,函数不进行append操作,此时可以改变现有的值

  • slice作为参数,函数进行添加append参数,但是并没有超过原来的cap,此时函数内部append后新的slice的长度length属性不影响外面的length,原来是多少就是多少,新的length只在函数内部生效

  • slice作为参数,进行append,同时cap发生变化时,函数内部实际操作的是另一个切片,完全和外面的切片无关

可以概括为,切片作为参数时,函数内部只有操作该切片已有length内的元素时会影响到原来的切片,其它不受影响


例1、操作切片的值

func reverseOne(s []int) {
    for i, j := 0, len(s) - 1; i < j; i++ {
        j = len(s) - (i + 1)
        s[i], s[j] = s[j], s[i]
    }
    fmt.Println("func reverse ",
                " addr=", unsafe.Pointer(&s[0]), 
                " len=", len(s), 
                " cap=", cap(s), 
                " content=", s)
}

func main() {
    var sliceOne []int
    for i := 1; i <= 3; i++ {
        sliceOne = append(sliceOne, i)
    }
    fmt.Println("init slice ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                "content=", sliceOne)
    fmt.Println()

    reverseOne(sliceOne)

    fmt.Println("after reverse ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                " content=", sliceOne)
    fmt.Println()
}

输出:

init slice  addr= 0xc420088000  len= 3  cap= 4  content= [1 2 3]

//传入函数之后,实际操作切片底层数组地址处的值
func reverse  addr= 0xc420088000  len= 3  cap= 4  content= [3 2 1]

// 实现数组翻转,因为函数操作了地址处的值
after reverse  addr= 0xc420088000  len= 3  cap= 4  content= [3 2 1]

例2、函数中不改变cap,但是改变length影响不了原来切片的length,但是可以操作已有值

调用append一个新的slice创建,新的slice有新的length属性,但它不是指针,但是底层还是只想同一块数组地址

func reverseOne(s []int) {
    s = append(s, 999)
    for i, j := 0, len(s) - 1; i < j; i++ {
        j = len(s) - (i + 1)
        s[i], s[j] = s[j], s[i]
    }
    fmt.Println("func reverse ",
                " addr=", unsafe.Pointer(&s[0]), 
                " len=", len(s), 
                " cap=", cap(s), 
                " content=", s)
}

func main() {
    var sliceOne []int
    for i := 1; i <= 3; i++ {
        sliceOne = append(sliceOne, i)
    }
    fmt.Println("init slice ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                "content=", sliceOne)
    fmt.Println()

    reverseOne(sliceOne)

    fmt.Println("after reverse ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                " content=", sliceOne)
    fmt.Println()
}

输出:

init slice  addr= 0xc420014100  len= 3  cap= 4  content= [1 2 3]

// 函数内部改变了原来切片0位置处的值
func reverse  addr= 0xc420014100  len= 4  cap= 4  content= [999 3 2 1]

// 原切片0位置处的值发生变化,但长度length没有变
after reverse  addr= 0xc420014100  len= 3  cap= 4  content= [999 3 2]

例3、函数中若重新分配了切片的cap,那么函数中append新创建的切片只在函数内部有效,不影响作为参数传进来时外部的切片

调用append一个新的slice创建,新的slice有新的length属性,但它不是指针,但是底层还是只想同一块数组地址

func reverseOne(s []int) {
    // 超过了cap,重新分配底层数组
    s = append(s, 999, 1000, 1001)
    for i, j := 0, len(s)-1; i < j; i++ {
        j = len(s) - (i + 1)
        s[i], s[j] = s[j], s[i]
    }
    fmt.Println("func reverse ",
                " addr=", unsafe.Pointer(&s[0]), 
                " len=", len(s), 
                " cap=", cap(s), 
                " content=", s)
}

func main() {
    var sliceOne []int
    for i := 1; i <= 3; i++ {
        sliceOne = append(sliceOne, i)
    }
    fmt.Println("init slice ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                "content=", sliceOne)
    fmt.Println()

    reverseOne(sliceOne)

    fmt.Println("after reverse ", 
                " addr=", unsafe.Pointer(&sliceOne[0]), 
                " len=", len(sliceOne), 
                " cap=", cap(sliceOne), 
                " content=", sliceOne)
    fmt.Println()
}

输出:

init slice  addr= 0xc420014100  len= 3  cap= 4  content= [1 2 3]

// 底层数组及cap重新分配了
func reverse  addr= 0xc420016080  len= 6  cap= 8  content= [1001 1000 999 3 2 1]

//没有影响外面的切片
after reverse  addr= 0xc420014100  len= 3  cap= 4  content= [1 2 3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值