search.png
关于我
menu.png
go数组和切片

概述

go的数组和切片是很相似很容易混淆的概念。

数组Array

  • 声明
    var a [10] int
    var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 自动长度
    
  • 是同一种数据类型的固定长度的序列。
  • 数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
  • 声明之后每个元素都会初始化其数值的默认值
  • 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此在函数改变参数中数组的值,不会改变本身的值。
  • 指针数组 [n]*T,数组指针 *[n]T。
  • 支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
  • 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。

切片Slice

  • 声明
    //   slice  := make([]type, len, cap)
    var a = make([]int, 10) // 声明同时初始化
    var aa := make([]int, 6) // 省略 cap,相当于 cap = len。
    var aaa := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
    var b []int // 此时是nil
    var c = []int {1,2,3}
    var arr [10]int
    var d = arr[:] // 从数组切片
    var f = arr[1:2] // 前包后不包,实际只有一个值
    
  • 类似java的List,它通过内部指针和相关属性引用数组片段,以实现变长方案。
  • 切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
  • 切片的长度是其内部的属性,因此可以改变,简单理解切片是一个可变的数组。
  • 如果 slice == nil,那么 len、cap 结果都等于 0。


数组的range遍历是值传递的

    type A struct {
        a string
    }
    arr := [...]A {
        {a:"1"},
        {a:"2"},
    }
    fmt.Println(arr)
    for i, v := range arr {
        // range 是值传递,因此无法直接修改对象的值,对比地址的结果是false
        fmt.Println(&v == &arr[i])
        v.a = "3"
    }
    fmt.Println(arr)

    // 通过索引修改没有问题
    for i,_ := range arr {
        arr[i].a = "3"
    }
    fmt.Println(arr)

    // 指针数组,遍历的是指针,如此修改值生效
    arr2 := [...]*A {
        {a:"1"},
        {a:"2"},
    }
    fmt.Println(*arr2[0], *arr2[1])
    for i, v := range arr2 {
        fmt.Println(v == arr2[i])
        v.a = "3"
    }
    fmt.Println(*arr2[0], *arr2[1])

结果:

[{1} {2}]
false
false
[{1} {2}]
[{3} {3}]
{1} {2}
true
true
{3} {3}

数组的指针传递的深度理解

package main

import (
    "fmt"
)

// 数组是值传递的
func changeArr(a [10]int,index int, val int) {
    a[index] = val
}
// 修改数组指针,生效
func changeArrPtr(a *[10]int,index int, val int) {
    a[index] = val
}

// 修改
func changePtrArr(a [1]*int,index int, val *int) {
    a[index] = val
}
func changePtrArrPtr(a *[1]*int,index int, val *int) {
    a[index] = val
}


func main() {
    arr := [10]int{1, 2, 3, 4, 5}
    // 修改数组,生效,2
    arr[0] = 2
    fmt.Println(arr[0])
    arr[0] = 1
    // 值传递修改数组,不生效,1
    changeArr(arr, 0, 2)
    fmt.Println(arr[0])
    // 指针传递修改数组,生效,2
    changeArrPtr(&arr, 0, 2)
    fmt.Println(arr[0])
    arr[0] = 1

    // 指针数组
    var a = 1
    var b = 2
    var c = &b
    arrptr := [1]*int{&a}
    // 同样是值传递,因此不生效,1
    changePtrArr(arrptr, 0, c)
    fmt.Println(*arrptr[0])
    // 指针传递,生效,2
    changePtrArrPtr(&arrptr, 0, c)
    fmt.Println(*arrptr[0])

}


结果:

2
1
2
1
2

切片复制时并不会改变内存地址

    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[0:2]
    s2 := []int{66, 66, 66}
    copy(s1, s2)

    fmt.Println(s1, arr)

结果:

[66 66] [66 66 3 4 5]

切片的切片

    arr := [5]int{1,2,3,4,5}
    s1 := arr[1:2]
    s2 := s1[0:3]
    fmt.Println(s2)

结果:

[2 3 4]

切片的内存分配深度理解

package main

import (
    "fmt"
)


// 切片是引用传递的
func changeSplit(s []int, index int, val int) {
    s[index] = val
}

func main() {
    arr := [10]int{1, 2, 3, 4, 5}

    s1 := arr[:2:5]
    // cap=5 len=2 val=1,2
    fmt.Println(cap(s1), len(s1), s1)
    // 修改切片的值,会同步修改其指向的数组的值!!!
    // 此处修改0索引的值为5,数组的0索引也同步的改为了5
    // 因此对数组进行切片,并不会复制数组,而是用在指针指向数组
    s1[0] = 5
    fmt.Println(s1, arr)
    // 通过函数传递的是切片的引用,因此在函数内修改切片也会同样的修改原数组!!!
    // 此处修改0的索引值为1,数组的0索引也同步的改为了1
    changeSplit(s1, 0, 1)
    fmt.Println(s1, arr)

    s1 = append(s1, 123)
    // 未超出容量,append 修改切片也会同步的修改原数组,这也是因为切片底层有对数组的指针
    fmt.Println(s1, arr)
    // 两个指针是一样的
    fmt.Println(&s1[0], &arr[0], &arr[0]==&s1[0])

    // 起始;终止;容量
    s2 := arr[0:2:2]
    fmt.Println(cap(s2), len(s2), s2)
    // 修改另一个切片,同时会修改第一个切片,因为其底层数组是一样的
    s2[0] = 666
    // 都是666
    fmt.Println(s2[0], s1[0], arr[0])
    // true
    fmt.Println(&s1[0] == &s2[0])
    s2 = append(s2, 233)
    // 超出容量(2)重新分配切片空间,此时指向的已经不是原数组,因此修改切片不会修改原数组
    fmt.Println(s2, arr)
    // 指针已经不一样
    fmt.Println(&s2[0], &arr[0],  &arr[0]==&s2[0])
}



结果:

5 2 [1 2]
[5 2] [5 2 3 4 5 0 0 0 0 0]
[1 2] [1 2 3 4 5 0 0 0 0 0]
[1 2 123] [1 2 123 4 5 0 0 0 0 0]
0xc00001a140 0xc00001a140 true
2 2 [1 2]
666 666 666
true
[666 2 233] [666 2 123 4 5 0 0 0 0 0]
0xc000012240 0xc00001a140 false

字符串切片

  str := "你好,golang"
    // 默认是byte[]
    for i := 0; i < len(str); i++ {
        fmt.Printf("%c(%v)", str[i], str[i])
    }
    fmt.Println()
    // 使用rune遍历
    runes := []rune(str)
    for i := 0; i < len(runes); i++ {
        fmt.Printf("%c(%v)", runes[i], runes[i])
    }
    fmt.Println()
    // 直接用range遍历
    for _,v := range str {
        fmt.Printf("%c(%v)", v, v)
    }
    fmt.Println()
    // 默认切片采用的时byte[],因此存在问题
    hello := str[0:2]
    fmt.Println(hello)
    // 转成rune切片
    hello2 := ([]rune(str))[0:2]
    fmt.Println(string(hello2))

结果:

ä(228)½(189) (160)å(229)¥(165)½(189)ï(239)¼(188)Œ(140)g(103)o(111)l(108)a(97)n(110)g(103)
你(20320)好(22909),(65292)g(103)o(111)l(108)a(97)n(110)g(103)
你(20320)好(22909),(65292)g(103)o(111)l(108)a(97)n(110)g(103)
�
你好

版权声明

知识共享许可协议 本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
发布时间:2021年03月08日 06:10:54

评论区#

还没有评论哦,期待您的评论!

关闭特效