Go中数组与切片的那点事

一、 数组与切片

在 Go 语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块(注:与python不同的是,定义一个数组后,此数组里元素只能是一种数据类型,且其占用的内存是连续分配的,既然数组的每个元素类型相同,又是连续分配,就可以以固定速度索引数组中的任意数据,速度非常快,会比python列表的索引取值快);切片时对底层数组进行了抽象,是围绕动态数组的概念构建的,可以按需自动增长和缩小,切片的动态增长是通过内置函数 append 来实现的。

  1. 定义及初始化
    数组:
    如下,对于数组有如下四种主要的定义及初始化方式,如前所述,数组是是有固定长度的,所以在定义或者初始化时便确定了数组的长度,数组的长度不会再发生改变,也就是任何试图超出数组长度的操作都是不合法的。
    var array1 [5]int
    fmt.Println("方式一:",array1)  //[0 0 0 0 0]
    array2 := [5]int{10, 20, 30, 40, 50}
    fmt.Println("方式二:",array2)   //[10 20 30 40 50]
    array3 := [...]int{10, 20, 30, 40, 50}  
    fmt.Println("方式三:",array3)  //[10 20 30 40 50](初始数据个数为数组的长度)
    array4 := [5]int{1: 10, 2: 20}
    fmt.Println("方式四:",array4)  //[0 10 20 0 0]
    
    切片:
    //指定长度但不指定容量时,容量和长度一致
    slice1 := make([]int, 5)
    fmt.Println("slice1:", slice1)  // [0 0 0 0 0]
    //指定长度为5,容量为8的切片
    slice2 := make([]int, 5, 8)  // [0 0 0 0 0]
    fmt.Println("slice2:", slice2)
    //通过字面量声明切片,长度与容量均为3
    slice3 := []string {"red", "yellow", "green"}
    fmt.Println("slice3:", slice3)  //[red yellow green]
    //使用索引声明切片
    slice4 := []string {9:"abc"}
    fmt.Println("slice4:", slice4)  //[         abc]
    fmt.Printf("slice4 length is %d, cap is %d", len(slice4), cap(slice4))  //slice4 length is 10, cap is 10
    
    注意,如上声名的切片并非空切片,通过打印结果即可看出来。如果通过append方法添加元素时候,会在上述默认切片后面追加元素。如下才是声名空切片的方式:
    var slice5 []int
    slice6 := make([]int, 0)
    slice7 := []int {}
    fmt.Println(slice5, slice6, slice7)  //[] [] []
    
  2. 函数调用方式
    数组:
    如下,每次函数 foo 被调用时,必须在栈上分配 8 MB 的内存。之后,整个数组的值(8 MB 的内存)被复制到刚分配的内存里。 虽然 Go 语言自己会处理这个复制操作,不过还有一种更好且更有效的方法来处理这个操作。可以只传入指向数组的指针,这样只需要复制 8 字节的数据而不是8 MB 的内存数据到栈上。
    // 声明一个需要 8 MB 的数组
    var array [1e6]int
    // 将数组传递给函数 foo
    foo(array)
    // 函数 foo 接受一个 100 万个整型值的数组
    func foo(array [1e6]int) {
    }
    ///
    // 分配一个需要 8 MB 的数组
    var array [1e6]int
    // 将数组的地址传递给函数 foo
    foo(&array)
    // 函数 foo 接受一个指向 100 万个整型值的数组的指针
    func foo(array *[1e6]int) {
    }
    
    函数 foo 接受一个指向 100 万个整型值的数组的指针。现在将数组的地址传入函数,只需要在栈上分配 8 字节的内存给指针就可以。这个操作会更有效地利用内存,性能也更好。不过要意识到,因为现在传递的是指针,所以如果改变指针指向的值,会改变共享的内存。

二、切片的使用

  1. 赋值和切片
    slice := []int{10, 20, 30, 40, 50}  //长度与容量均为5
    newSlice := slice[1:3]  //长度为2,容量为4
    
    通过如上方式我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分。对底层数组容量是 k 的切片 slice[i:j]来说,长度: j - i,容量: k - i。
    此图在这里插入图片描述
    如上图展示了实例两个切片共享同一个底层数组的情况,需要记住的是,现在两个切片共享同一个底层数组。如果一个切片修改了该底层数组的共享部分,另一个切片也能感知到。
    newSlice[1] = 35
    
    把 35 赋值给 newSlice 的第二个元素(索引为 1 的元素)的同时也是在修改原来的 slice的第 3 个元素(索引为 2 的元素)。
    newSlice[3] = 45  //报错
    
    切片只能访问到其长度内的元素。试图访问超出其长度的元素将会导致语言运行时异常,如上操作会报错。与切片的容量相关联的元素只能用于增长切片。在使用这部分元素前,必须将其合并到切片的长度里。(append可以做到)
  2. 切片增长
    Go 语言内置的 append函数会处理增加长度时的所有操作细节,函数 append 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。
    // 创建一个整型切片,其长度和容量都是 5 个元素
    slice := []int{10, 20, 30, 40, 50}
    // 创建一个新切片, 其长度为 2 个元素,容量为 4 个元素
    newSlice := slice[1:3]
    // 使用原有的容量来分配一个新元素,将新元素赋值为 60
    newSlice = append(newSlice, 60)
    
    如上操作后, 底层数组的布局图如下:
    在这里插入图片描述
    因为 newSlice 在底层数组里还有额外的容量可用,append 操作将可用的元素合并到切片的长度,并对其进行赋值。由于和原始的 slice 共享同一个底层数组,slice 中索引为 3 的元素的值也被改动了。如果切片的底层数组没有足够的可用容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
    slice := []int{10, 20, 30, 40}
    newSlice := append(slice, 50)
    
    如上操作后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍,布局图如下:
    在这里插入图片描述
    函数 append 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值