Golang学习笔记--Array和Slice

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012299594/article/details/82793125

目录

 

Reference

Array

Slice

slice的创建

Slice常用操作

reslice

append函数

copy函数

range遍历

扫描二维码关注公众号,回复: 4527758 查看本文章

Reference

https://blog.golang.org/go-slices-usage-and-internals

Array

  1. 数组是值类型,赋值和传参会复制整个数组,而不是指针。
  2. 数组⻓长度必须是常量,且是类型的组成部分。[2]int 和 [3]int 是不同类型。
  3. ⽀支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
  4. 指针数组 [n]*T,数组指针 *[n]T。

Slice

因为array是值类型,赋值和传参行为使用array都会发生数据拷贝,如果操作对象是比较大的array,那么时间和空间上的开销都会成为问题。

  7 func arrayAsParamTest() {
  8         fmt.Println("=========SliceTest.sliceAsParamTest()==========")
  9         arrayA := [2]int{1, 2}
 10         var arrayB [2]int
 11         arrayB = arrayA
 12         fmt.Printf("Address: %p.\n", &arrayA)
 13         fmt.Printf("Address: %p.\n", &arrayB)
 14         arrayAsParam(arrayA)
 15 }
=========SliceTest.sliceAsParamTest()==========
Address: 0xc420014390.
Address: 0xc4200143a0.
Address: 0xc4200143b0.

传参时用数组指针可以避免数据拷贝,但是如果原数组指针指向了新的数组,那么函数里面的指针也会随之变化,很容易引入bug。

slice是建立在数组基础之上的一种数据类型。slice本身只保存元数据,用户数据存储在底层array,slice用一个指针指向底层数组(可以指向底层数组任意元素,指向的元素也就是slice的首元素),并用length限定slice的读写区域,用capacity表示slice的容量,capacity减去length就是slice的剩余存储空间。

slice的创建

创建slice有以下几种用法:

1.通过make函数。make([]type, length, capacity),make函数在堆内存上分配底层数组。

2.用[]对array或者slice执行切片操作得到一个新的slice。

3.用字面值初始化一个slice。

4.创建一个nil slice,它的值就等于nil。

func slice() {
         fmt.Println("=========SliceTest.slice()========")
         //1
         a := make([]int, 5) //len = 5, cap = 5
         b := make([]int, 0, 5) //len = 0, cap = 5
         b1 := make([]int, 0)  //empty slice
 
         //2
         c := b[:2] //len = 2 , cap = 5
         d := c[2:5] //len = 3, cap = 3
     
         //3
         e := [3]bool{true, false, true}
         e1 := []bool{}  //empty slice

         //4, nil slice
         var f []int
}

上面demo中有两种特殊的slice,empty slice和nil slice,其区别在于:empty slice指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。函数发生异常,需要返回slice,这种情况通常返回的是nil slice。如果是查询函数,没有查询到结果,那么返回的通常是empty slice。

因此,在处理这类函数调用的结果时,经常先通过nil(if result == nil)判断函数执行是否出错,返回结果不等于nil再进行处理。

Slice常用操作

reslice

reslice其实就是在现有slice对象的基础上进行切片来创建新的slice对象,以便在capacity允许的范围内调整属性,并且共享底层数组。

append函数

我们可以通过append函数向slice尾部添加新的元素,有两种基本情况:

1.新增元素后slice的length不超出capacity,将对原底层数组的数据进行修改,如果该数组有多个切片,需要注意这种相互影响。

2.新增元素后slice的length超出capacity,将会申请新的底层数组并拷贝数据。

函数原型如下:

func append(s []T, vs ...T) []T

func printSlice(s string, x []int) {
        fmt.Printf("%s len=%d cap=%d adress=%p value=%v\n", s, len(x), cap(x), &x, x)
}
func SliceAppend() {
        fmt.Println("**********Test - Test append*********")
        var s []int
        printSlice("s", s)

        // append works on nil slices.
        s = append(s, 0)
        printSlice("s", s)

        // The slice grows as needed.
        s = append(s, 1)
        printSlice("s", s)

        // We can add more than one element at a time.
        s = append(s, 2)
        printSlice("s", s)

        s = append(s, 3, 4)
        printSlice("s", s)

        s = append(s, 5, 6)
        printSlice("s", s)
}
**********Test - Test append*********
s len=0 cap=0 adress=0xc00000a320 value=[]
s len=1 cap=1 adress=0xc00000a340 value=[0]
s len=2 cap=2 adress=0xc00000a380 value=[0 1]
s len=3 cap=4 adress=0xc00000a3c0 value=[0 1 2]
s len=5 cap=8 adress=0xc00000a400 value=[0 1 2 3 4]
s len=7 cap=8 adress=0xc00000a440 value=[0 1 2 3 4 5 6]

从上面的demo中我们可以发现,用append函数向slice压入新的元素时,如果slice底层的数组空间不足,将会分配一个新的数组,然后让slice的指针指向这个新的数组,capacity也修改为新数组的长度。看起来每次重新分配数组的时候,在满足slice的长度需求的同时,size都是之前的2倍,也就是会预留一部分空间。但是,下面我们稍微修改一下SliceAppend函数,value分别为2,3,4的三个元素一次append到slice:

func SliceAppend() {
        fmt.Println("**********Test - Test append*********")
        var s []int
        printSlice("s", s)

        // append works on nil slices.
        s = append(s, 0)
        printSlice("s", s)

        // The slice grows as needed.
        s = append(s, 1)
        printSlice("s", s)

        // We can add more than one element at a time.
        s = append(s, 2, 3, 4)
        printSlice("s", s)

        s = append(s, 5, 6)
        printSlice("s", s)
}
**********Test - Test append*********
s len=0 cap=0 adress=0xc00000a320 value=[]
s len=1 cap=1 adress=0xc00000a340 value=[0]
s len=2 cap=2 adress=0xc00000a380 value=[0 1]
s len=5 cap=6 adress=0xc00000a3c0 value=[0 1 2 3 4]
s len=7 cap=12 adress=0xc00000a400 value=[0 1 2 3 4 5 6]

在slice的元素个数为5时,重新分配的数组的size不再是8,而是6了;slice元素个数为7时,cap不再是8,而是12。相比第一份代码,仅仅是将中间两次append操作合并到一次而已,看来底层数组的分配算法并没有我们前面想象的那么简单。

那么在slice扩容时,底层数组的size到底是怎样去分配的呢?扩容策略如下:

1.如果新的size超出现有size的2倍,分配的大小就是新的size,如果新size是奇数还会加1(size为1是例外)。

2.否则;如果当前size小于1024,按每次2倍size分配空间,否则每次按当前size的四分之一扩容。

copy函数

 30 func sliceCopy() {
 31         fmt.Println("=========SliceTest.sliceCopy()==========")
 32         s := []string{"hunk", "jack", "bob"}
 33         d := make([]string, 3, 3)
 34         copy(d, s)
 35         fmt.Printf("s len=%d cap=%d adress=%p value=%v\n", len(s), cap(s), &s, s)
 36         fmt.Printf("d len=%d cap=%d adress=%p value=%v\n", len(d), cap(d), &d, d)
 37         for i, v := range d {
 38                 fmt.Printf("d[%d] is %s\n", i, v)
 39         }
 40 }
=========SliceTest.sliceCopy()==========
s len=3 cap=3 adress=0xc42000a440 value=[hunk jack bob]
d len=3 cap=3 adress=0xc42000a460 value=[hunk jack bob]
d[0] is hunk
d[1] is jack
d[2] is bob

copy函数会返回实际拷贝的元素个数,这也取决于源和目标两者长度较短的那一个。

range遍历

slice既然也是一组数据的集合,必然支持range对其进行遍历。

 46 /*
 47 When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
 48 */
 49 func sliceRange() {
 50         fmt.Println("=========SliceTest.sliceRange()=========")
 51         var pow = []int{1, 2, 4, 8}
 52 
 53         for i, v := range pow {
 54                 fmt.Printf("value = %d, value.address = %x, slice-address = %x.\n", v, &v, &pow[i])
 55         }
 56 
 57         for i := range pow {
 58                 fmt.Printf("%d\n", i)
 59         }
 60 
 61         for _, v := range pow {
 62                 fmt.Printf("%d\n", v)
 63         }
 64 }
=========SliceTest.sliceRange()=========
value = 1, value.address = c4200142e8, slice-address = c4200120c0.
value = 2, value.address = c4200142e8, slice-address = c4200120c8.
value = 4, value.address = c4200142e8, slice-address = c4200120d0.
value = 8, value.address = c4200142e8, slice-address = c4200120d8.

用range对slice进行遍历时,每次返回index和value两个数据,可以根据需要省略取值。需要注意的是,value是值拷贝的结果,并且每次的value都用了同一块存储区域,所以range遍历时修改返回的value无法修改到底层数组的数据,可以用slice[index]对其修改。

猜你喜欢

转载自blog.csdn.net/u012299594/article/details/82793125