【Go 基础】array && slice

array && slice

问:slicearray的区别是什么?

  • 数组的零值是元素类型的零值,切片的零值是 nilnil 也是唯一可以和切片类型作比较的值;
  • 数组的长度固定,不能动态变化,而切片是一个可以动态变化的数组。数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化,否则会报越界;切片是一种可变长度的数组;
  • 数组默认是值传递,而切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。

切片和数组的零值:

package main

import (
    "fmt"
)

func main() {
    var arr = [2]int{}
    // invalid operation: arr == nil (mismatched types [2]int and nil)
    // if arr == nil {
    //     fmt.Println("arr nil")
    // }
    fmt.Println("arr=", arr) // arr = [0 0]
    var slice []int
    if slice == nil {
        fmt.Println("slice nil") // slice= []
    }
    fmt.Println("slice=", slice) // slice= []
}

array 细节,数组定义的基本语法::

var 数组名 [数组大小]数据类型 
var a [3]int

数组代码演示示例:

package main
import (
    "fmt"
)

func main() {

    var intArr [3]int // int占8个字节
    // 当我们定义完数组后,其实数组的各个元素有默认值 0
    fmt.Println(intArr) // [0 0 0]
    intArr[0] = 10
    intArr[1] = 20
    intArr[2] = 30
    fmt.Println(intArr) // [10 20 30]
    // intArr的地址=0xc000016200 intArr[0] 地址0xc000016200 intArr[1] 地址0xc000016208 intArr[2] 地址0xc000016210
    fmt.Printf("intArr的地址=%p intArr[0] 地址%p intArr[1] 地址%p intArr[2] 地址%p\n", 
        &intArr, &intArr[0], &intArr[1], &intArr[2]) 

数组的底层结构示意图:

image-20211031165038469

上图总结:

  • 数组的地址可以通过数组名来获取 &intArr
  • 数组的第一个元素的地址,就是数组的首地址。
  • 数组的各个元素的地址间隔是依据数组的类型决定,int占8个字节,比如 int64 -> 8 int32->4...

image-20211031170052024

Go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会相互影响:

image-20211031171223947

如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式):

image-20211031171952476

长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度:

image-20211031172034637

slice 细节,切片定义的基本语法:

var 切片名 []类型 
var a [] int

切片示例代码演示:

package main
import (
    "fmt"
)

func main() {

    // 演示切片的基本使用
    var intArr [5]int = [...]int{1, 22, 33, 66, 99}
    // 声明/定义一个切片
    // slice := intArr[1:3]
    // 1. slice 就是切片名。
    // 2. intArr[1:3] 表示 slice 引用到 intArr 这个数组。
    // 3. 引用intArr数组的起始下标为 1 , 最后的下标为3(但是不包含3)。
    // 4. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
    slice := intArr[1:3] 
    fmt.Println("intArr=", intArr) // [1 22 33 66 99]
    fmt.Println("slice 的元素是 =", slice) //  22, 33
    fmt.Println("slice 的元素个数 =", len(slice)) // 2
    fmt.Println("slice 的容量 =", cap(slice)) // 切片的容量是可以动态变化  

    fmt.Printf("intArr[1]的地址=%p\n", &intArr[1]) // 0xc042060038
    // 0xc042060038 slice[0==22
    fmt.Printf("slice[0]的地址=%p slice[0==%v\n", &slice[0], slice[0])
    slice[1] = 34
    fmt.Println()
    fmt.Println("intArr=", intArr) // intArr= [1 22 34 66 99]
    fmt.Println("slice 的元素是 =", slice) //  slice 的元素是 = [22 34]
}

切片的底层结构示意图:

image-20211031173315782

上图总结:

  • slice 是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  • slice 从底层来说,其实就是一个数据结构(struct 结构体)。
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

基础使用:

① 方式一:定义一个切片,然后让切片去引用一个已经创建好的数组。

var intArr [5]int = [...]int{1, 22, 33, 66, 99}
slice := intArr[1:3]

② 方式二:通过 make来创建切片。基本语法:

var 切片名 []type = make([]type, len, [cap])
// 参数说明: 
// type: 数据类型 
// len : 大小 
// cap : 指定切片容量,可选,如果分配了 cap,则要求 cap>=len.

案例演示图:

image-20211031175006465

③ 方式三:定义一个切片,直接就指定具体数组,使用原理类似 make 的方式。

var slice = []int {1, 2, 3, 4}
var strSlice = []string{"w","x","i","o","n","g"}

方式一和方式二的区别:方式一直接引用数组,这个数组事先是已经存在的;方式②是通过make来创建切片,而make也会在底层去创建一个数组。

注意事项:

① 切片初始化时 var slice = arr[startIndex:endIndex],从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。

② 切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但是可以动态增长。

var slice = arr[0:end] 等价于 var slice = arr[:end]
var slice = arr[start:len(arr)] 等价于 var slice = arr[start:]
var slice = arr[0:len(arr)] 等价于 var slice = arr[:]

cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。

④ 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用。

⑤ 切片可以继续切片。

package main

import (
    "fmt"
)

func main() {

    // 使用常规的for循环遍历切片
    var arr [5]int = [...]int{10, 20, 30, 40, 50}
    // slice := arr[1:4] // 20, 30, 40
    slice := arr[1:4]
    for i := 0; i < len(slice); i++ {
        // slice[0]=20 slice[1]=30 slice[2]=40
        fmt.Printf("slice[%v]=%v ", i, slice[i])
    }

    fmt.Println()
    // 使用for--range 方式遍历切片
    for i, v := range slice {
        fmt.Printf("i=%v v=%v \n", i, v)
    }

    slice2 := slice[1:2] //  slice [ 20, 30, 40]    [30]
    slice2[0] = 100      // 因为arr , slice 和slice2 指向的数据空间是同一个,因此slice2[0]=100,其它的都变化

    fmt.Println("slice2=", slice2) // slice2= [100]
    fmt.Println("slice=", slice)   // slice= [20 100 40]
    fmt.Println("arr=", arr)       // arr = [10 20 100 40 50]
}  

⑥ 用 append 内置函数,可以对切片进行动态追加。

package main

import (
    "fmt"
)

func main() {
    // 用 append 内置函数,可以对切片进行动态追加
    var slice3 []int = []int{100, 200, 300}
    // 通过append直接给slice3追加具体的元素
    slice3 = append(slice3, 400, 500, 600)
    fmt.Println("slice3", slice3) // 100, 200, 300,400, 500, 600

    // 通过 append 将切片slice3追加给slice3
    slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300,400, 500, 600
    fmt.Println("slice3", slice3)
}

image-20211031210658743

append操作的本质就是对数组扩容:go 底层会创建一个新的数组 newArr(按照扩容后大小) 将 slice 原来包含的元素拷贝到新的数组 newArrslice 重新引用到 newArr

⑦ 切片的拷贝操作。下面代码中,slice4slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999slice5[0] 仍然是 1。

package main

import (
    "fmt"
)

func main() {
    // 切片的拷贝操作
    // 切片使用copy内置函数完成拷贝
    fmt.Println()
    var slice4 []int = []int{1, 2, 3, 4, 5}
    var slice5 = make([]int, 10)
    // func copy(dst, src []Type) int
    copy(slice5, slice4)
    fmt.Println("slice4=", slice4) // 1, 2, 3, 4, 5
    fmt.Println("slice5=", slice5) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0
}

⑧ 切片是引用类型,所以在传递时,遵守引用传递机制。

image-20211031180114277

相关推荐

微信扫一扫,分享到朋友圈

【Go 基础】array && slice
返回顶部

显示

忘记密码?

显示

显示

获取验证码

Close