关于golang:Go数据结构之数组与切片

3次阅读

共计 5797 个字符,预计需要花费 15 分钟才能阅读完成。

前言

数组的长度是申明的时候就固定好的,前面不可能变大,而且长度和容量相等。

切片的长度和容量前面能够随着元素增多而增长,然而容量不可能小于长度。

注释

申明 & 初始化

在 Go 中申明即初始化,如果在申明的时候没有初始化值,那么就会赋值为申明类型的「零值」。

func TestDemo1(t *testing.T) {
    // 数组
    var array1 [5]int        // 只需设置长度,前面不可变
    var array2 = new([5]int) // 返回指针

    // 切片
    var slice1 []int
    var slice2 = make([]int, 5, 5) // 设置长度、容量,前面可变

    t.Log("array1 val:", array1)      // [0 0 0 0 0]
    t.Log("array1 len:", len(array1)) // 5
    t.Log("array1 cap:", cap(array1)) // 5

    fmt.Println("")

    t.Log("array2 val:", array2)      // &[0 0 0 0 0]
    t.Log("array2 len:", len(array2)) // 5
    t.Log("array2 cap:", cap(array2)) // 5

    fmt.Println("")

    t.Log("slice1 val:", slice1)      // []
    t.Log("slice1 len:", len(slice1)) // 0
    t.Log("slice1 cap:", cap(slice1)) // 0

    fmt.Println("")

    t.Log("slice2 val:", slice2)      // [0 0 0 0 0]
    t.Log("slice2 len:", len(slice2)) // 5
    t.Log("slice2 cap:", cap(slice2)) // 5
}

在申明的时候就初始化:

func TestDemo2(t *testing.T) {
    // 数组
    var array1 = [5]int{4: 1, 2: 5}
    var array2 = [...]int{4: 1, 2: 5}

    // 切片
    var slice1 = []int{4: 1, 2: 5}
    var slice2 = array1[:] // 从数组截取来的切片

    t.Log("array1 val:", array1)      // [0 0 5 0 1]
    t.Log("array1 len:", len(array1)) // 5
    t.Log("array1 cap:", cap(array1)) // 5

    fmt.Println("")

    t.Log("array2 val:", array2)      // [0 0 5 0 1]
    t.Log("array2 len:", len(array2)) // 5
    t.Log("array2 cap:", cap(array2)) // 5

    fmt.Println("")

    t.Log("slice1 val:", slice1)      // [0 0 5 0 1]
    t.Log("slice1 len:", len(slice1)) // 5
    t.Log("slice1 cap:", cap(slice1)) // 5

    fmt.Println("")

    t.Log("slice2 val:", slice2)      // [0 0 5 0 1]
    t.Log("slice2 len:", len(slice2)) // 5
    t.Log("slice2 cap:", cap(slice2)) // 5
}

增加 & 更新元素值

数组因为长度固定,且的值都是初始化好了的,所以只有更新。

切片更新操作和数据一样,只不过新增元素只能通过 append() 办法。

append():将元素追加大切片的开端,如果容量不够,会进行扩容。

func TestDemo3(t *testing.T) {
    // 数组
    var array1 = [5]int{4: 1, 2: 5}
    array1[0] = 100 // 更新
    array1[4] = 100 // 更新

    // 切片
    var slice1 = []int{4: 1, 2: 5}
    array1[4] = 100 // 更新
    //array1[5] = 100 // 报错
    slice1 = append(slice1, 1) // 切片减少元素只能应用此办法

    t.Log("array1 val:", array1)      // [100 0 5 0 100]
    t.Log("array1 len:", len(array1)) // 5
    t.Log("array1 cap:", cap(array1)) // 5

    fmt.Println("")

    t.Log("slice1 val:", slice1)      // [0 0 5 0 1 1]
    t.Log("slice1 len:", len(slice1)) // 6
    t.Log("slice1 cap:", cap(slice1)) // 10
}

表达式

数组与切片,都能够应用表达式截取,截取之后的数据它的类型为切片。

func TestDemo4(t *testing.T) {array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}

    slice1 := array1[3:]              // 从 index 3 取到 index end
    t.Log("slice1 val:", slice1)      // [3 0 5 6 2 8 4]
    t.Log("slice1 len:", len(slice1)) // 7
    t.Log("slice1 cap:", cap(slice1)) // 7

    fmt.Println("")

    slice2 := array1[3:4]             // 从 index 3 取到 index 4
    t.Log("slice2 val:", slice2)      // [3]
    t.Log("slice2 len:", len(slice2)) // 1
    t.Log("slice2 cap:", cap(slice2)) // 7

    fmt.Println("")

    slice3 := array1[3:6:6]           // 从 index 3 取到 index 6,容量取到 index 6
    t.Log("slice3 val:", slice3)      // [3 0 5]
    t.Log("slice3 len:", len(slice3)) // 3
    t.Log("slice3 cap:", cap(slice3)) // 3

    fmt.Println("")

    slice4 := array1[3:6:9]           // 从 index 3 取到 index 6,容量取到 index 9
    t.Log("slice4 val:", slice4)      // [3 0 5]
    t.Log("slice4 len:", len(slice4)) // 3
    t.Log("slice4 cap:", cap(slice4)) // 6
}

遍历

应用 for、range

func TestDemo5(t *testing.T) {array1 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
    slice1 := make([]int, 5, 5)
    for k, v := range array1 {fmt.Println(k, "-", v)
    }
    fmt.Println()
    for k, v := range slice1 {fmt.Println(k, "-", v)
    }
}

比拟

数组与数组能够应用 == 比拟,不能与 nil 比拟

切片与切片不能应用 == 比拟,能够应用 reflect.DeepEqual 比拟,能够与 nil 比拟

func TestDemo6(t *testing.T) {array1 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
    array2 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 9}
    array3 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 9}

    t.Logf("array1 == array2 %t\n", array1 == array2) // false
    t.Logf("array2 == array3 %t\n", array2 == array3) // true
    //t.Logf("%t\n", array2 == nil) // 会报错,数组不能与 nil 比

    slice1 := make([]int, 5, 5)
    var slice2 []int
    slice3 := []int{4: 0}

    // t.Logf("%t\n", slice1 == slice2) // 会报错,切片与切片不能比
    t.Logf("slice1 == nil %t\n", slice1 == nil) // false
    t.Logf("slice2 == nil %t\n", slice2 == nil) // true
    t.Logf("slice3 == nil %t\n", slice3 == nil) // false

    t.Logf("slice1 == slice2 %t\n", reflect.DeepEqual(slice1, slice2)) // false
    t.Logf("slice2 == slice3 %t\n", reflect.DeepEqual(slice2, slice3)) // false
    t.Logf("slice1 == slice3 %t\n", reflect.DeepEqual(slice1, slice3)) // true
}

删除

需应用 append()、切片表达式 联合来实现

func TestDemo7(t *testing.T) {slice1 := []int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
    slice1 = append(slice1[:2], slice1[3:]...)
    t.Log(slice1)
}

扩大

数组与切片的关系

数组为值类型,切片为援用类型,他们又有何关系呢?

程序示例:

func TestDemo8(t *testing.T) {array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}

    slice1 := array1[:]

    t.Log("slice1 val:", slice1)      // [9 1 7 3 0 5 6 2 8 4]
    t.Log("slice1 len:", len(slice1)) // 10
    t.Log("slice1 cap:", cap(slice1)) // 10

    array1[9] = 96969696 // array1 的批改会影响到 slice1

    fmt.Println("")

    t.Log("slice1 val:", slice1)      // [9 1 7 3 0 5 6 2 8 96969696]
    t.Log("slice1 len:", len(slice1)) // 10
    t.Log("slice1 cap:", cap(slice1)) // 10
}

在这个示例程序中,能够说 slice1 是 array1 的援用。

不光是在示例程序中,这种在数组上通过表达式截取出的切片,为数组的援用,就算在程序中,间接申明一个新切片(var slice1 []int),在切片的底层实现,其实也是援用了一个数组。

他们的关系就是:数组是切片的底层实现,切片是数组的援用。

切片扩容

在示例程序 TestDemo8 中,slice1 会始终援用 array1 么?

个别状况下是这样,但有种状况下援用会发生变化,就是在 slice 产生扩容的状况下

func TestDemo9(t *testing.T) {array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
    slice1 := array1[:] // 从 array1 截取出 slice1

    t.Log("slice1 val:", slice1)      // [9 1 7 3 0 5 6 2 8 4]
    t.Log("slice1 len:", len(slice1)) // 10
    t.Log("slice1 cap:", cap(slice1)) // 10

    slice1 = append(slice1, 9) // 进行扩容后,slice1 指向了新的底层数组,不在是 array1 的援用
    array1[9] = 96969696

    fmt.Println("")

    t.Log("slice1 val:", slice1)      // [9 1 7 3 0 5 6 2 8 4 9]
    t.Log("slice1 len:", len(slice1)) // 11
    t.Log("slice1 cap:", cap(slice1)) // 20
}

当切片增加新元素,发现容量不够时,会开拓一个新的底层数组,而后把旧数组的数据和增加的新元素一并拷贝到新数组中。

扩容策略:

  • 首先判断,如果新申请容量(cap)大于 2 倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
  • 否则判断,如果旧切片的长度小于 1024,则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍,即(newcap=doublecap)
  • 否则判断,如果旧切片长度大于等于 1024,则最终容量(newcap)从旧容量(old.cap)开始循环减少原来的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)

深复制

靠扩容解决援用问题,显得不是那么优雅。

能够应用 copy() 进行深复制

func TestDemo10(t *testing.T) {array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}

    var slice1 = make([]int, 10, 10)
    copy(slice1, array1[:]) // 深复制,slice1 不会援用 array1
    array1[9] = 96969696

    t.Log("slice1 val:", slice1)      // [9 1 7 3 0 5 6 2 8 4]
    t.Log("slice1 len:", len(slice1)) // 10
    t.Log("slice1 cap:", cap(slice1)) // 10

    fmt.Println("")

    t.Log("array1 val:", array1)      // [9 1 7 3 0 5 6 2 8 96969696]
    t.Log("array1 len:", len(array1)) // 10
    t.Log("array1 cap:", cap(array1)) // 10
}

数组

一段间断的内存空间。

make

make 只能用于 slice、map、channel,返回的初始化后的(非零)值。

援用类型

  • 切片
  • 字典
  • 通道
  • 函数

值类型

  • 数组
  • 根底数据类型
  • 构造体类型

总结

  1. 切片是数组的援用,数组是切片的底层实现。
  2. 数组的长度 (len) 等于容量 (cap),切片的长度(len) 小于等于容量(cap)。
  3. 数组申明的时候默认就会初始化,值为类型的「零值」;切片申明的时候不会默认初始化,值是 nil。
  4. 应用 copy() 深复制解决援用问题。

文章示例代码

Sown 专栏地址:https://segmentfault.com/blog/sown

正文完
 0