数组与切片

数组

数组的使用和以前基本差不多,就写一些他们之间的不同点

数组在内存中的布局

  • 可以使用&数组名获得数组的内存地址,数组的第一个元素的地址,就是数组的首地址
  • 数组之间的地址间隔根据数组元素的类型来决定,比如int32他们就是4个字节的差距

四种初始化数组的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//原始办法
var a [4]int = [4]int{1, 2, 3, 4}
var b = [4]int{1, 2, 3, 4}
//使用[...]
var c = [...]int{1, 2, 3, 4}
//使用下标,没有序号的自动给上初始值
var d = [...]int{1: 2, 2: 4, 3: 6, 4: 8}
//类型推导
e:=[...]int{1,2,3,4,5,6}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
fmt.Println(e)

结果:

1
2
3
4
5
[1 2 3 4]
[1 2 3 4]
[1 2 3 4]
[0 2 4 6 8]
[1 2 3 4 5 6]

切片

切片就是没有固定容量的数组; 切片是一个很小的对象,它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法。切片是一个有三个字段的数据结构,这些数据结构包含 Golang 需要操作底层数组的元数据:

1
2
3
4
5
a:=[...]int{1,2,3,4,5,6,7,8,9}
//使用数组名 数组名[n:m],就是获得n-m的数据,包括n不包括m
slice:=a[1:8]
fmt.Println(slice) //[2 3]
fmt.Println(cap(slice)) //5

image-20201207223043849

最重要的是切片是一个引用数据类型,就是说我们使用 slice:=a[1:8] 进行的行为是==引用传递==,简单来说,就是我们切片存储的不是值,而是我们的引用数组元素的地址,再由地址指向元素,换句话说,如果数组变了那么切片的数据也会跟着改变

例子

1
2
3
4
5
6
7
a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
//使用数组名 数组名[n:m],就是获得n-m的数据,包括n不包括m
slice := a[:3]
fmt.Println(slice) //[1 2 3]
a[0]=111
fmt.Println(slice) //[111 2 3]
fmt.Println(cap(slice)) //5

结果:

1
2
3
[1 2 3]
[3 2 3]
9

创建切片的3种方式

  • 第一种,使用已有的数组

    1
    2
    3
    a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    //使用数组名 数组名[n:m],就是获得n-m的数据,包括n不包括m
    slice := a[:3]
  • 使用make来获得一个切片

    1
    2
    slice:=make([]int,5,10)
    fmt.Println(slice)

    使用make有3个属性

    • type:类型, 使用[]类型表示
    • len,切片现在的长度,就是初始化里面的元素数量
    • cap:切片的容量,可以动态扩充,如果选择了填写cap,那么cap>=len切片对应的底层数组的大小为指定的容量
    • 如果不对切片赋值,那么所有的都是默认值
  • 直接赋值,直接指明数据

    1
    2
    3
    4
    slice:=[]int{1,2,3}
    fmt.Println(slice)
    fmt.Println(len(slice)) //3
    fmt.Println(cap(slice)) //3

    如果没有指明容量,那么这个切片的容量和长度一样;我们只可以访问我们在这个长度以内的元素,超出切片长度却又没有超出切片容量的下标,我们是无法访问的,因为 容量代表着底层数组的大小,但是超出大小的部分对我们不可见

  • 一个切片可以以其他切片为主,复制其他的切片

    1
    2
    slice:=make([]int,2,10)
    slice1:=slice[1:2]//从第一号开始复制,那么他在底层相对于slice就有了一个偏移量1 因为他是从一开始的
    • 需要记住的是,现在两个切片共享同一个底层数组。如果一个切片修改了该底层数组的共享部分,另一个切片也能感知到
    • 子切片的容量为底层数组的长度减去切片在底层数组的开始偏移量

    基于第一点,可以使用其他切片作为我们的父切片,且他们之间共享同一个底层数组,意思就是可以被感知到数据的变化,但是当我们对其中的子切片扩容超出自己的容量,那么他就会脱离这个群体,自己成一个数组。这个时候,他对自己数据的改变已经对家庭没有影响了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    slice := make([]int, 2, 10)
    fmt.Println(cap(slice))
    slice1 := slice[:2]
    fmt.Println(slice1)
    fmt.Println(cap(slice1))
    slice2 := append(slice1, 1)
    fmt.Println(cap(slice2))
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2 = append(slice2, 1)
    slice2[0] = 10001
    fmt.Println(slice)
    fmt.Println(slice1)
    fmt.Println(cap(slice2))

我们知道原始的 slice2 对应的底层数组的容量为 9,经过我们一系列的 append 操作,原始的底层数组已经无法容纳更多的元素了,此时 Go 会分配另外一块内存,把原始切片从位置 1 开始的内存复制到新的内存地址中,也就是说现在的 slice2 切片对应的底层数组和 slice 切片对应的底层数组完全不是在同一个内存地址,所以当你此时更改 slice2 中的元素时,对 slice 已经来说,反之,slice 操作对slice2没有一点作用,一点儿关系都没有。两个切片的地址就不一样,就是两个完全不一样的切片

二维数组

创建方法和基本使用和一维数组一样

1
2
3
4
5
6
a := [][]int{{1, 2}, {3, 4}}
for _,value:=range(a){
for _,value1:=range(value){
fmt.Println(value1)
}
}

在内存中的存储

存储两个地址,分别指向不同地方

image-20201208135244220