乐趣区

关于golang:Go语言系列三之数组和切片

《Go 语言系列文章》

  1. Go 语言系列 (一) 之 Go 的装置和应用
  2. Go 语言系列 (二) 之根底语法总结

1. 数组

数组 用于存储若干个雷同类型的变量的 汇合 。数组中每个变量称为 数组的元素 ,每个元素都有一个数字编号—— 数组下标 ,该下标从 0 开始,用于区别各个元素。数组中可包容的元素个数称为 数组的长度

1.1. 申明

Go 语言中数组的申明形式:

var arr_name [length]type

var:不用多说,申明变量时都会用到该关键字。

arr_name:数组名称,实质是个变量

length:数组的长度

type:数组的类型

[]:通过它来进行对数组元素的读取、赋值

上面是一个例子:

package main

import "fmt"

func main() {var a [2]string // 申明一个长度为 2 的 string 数组
    a[0] = "我是"       // 赋值
    a[1] = "行小观"
    fmt.Println(a[0], a[1]) // 获取元素
    fmt.Println(a)
}

1.2. 初始化

在《Go 语言系列 (二) 之根底语法总结》这篇文章中提过:若咱们在申明变量时,不给变量赋初始值,则这些变量会被赋予“零值”。

数组中也是这样,如果不初始化,则数组中的所有元素值都为“零值”。如下例:

package main

import "fmt"

func main() {var a [3]int
    var b [3]string
    var c [3]bool

    fmt.Println(a) //[0 0 0]
    fmt.Println(b) //[ ]
    fmt.Println(c) //[false false false]
}

对数组元素进行初始化:

package main

import "fmt"

func main() {var a = [5]int {1, 2, 3}
    fmt.Println(a) //[1 2 3 0 0]
}

只初始化了局部元素,残余的仍是零值。

如果咱们在申明数组时同时初始化了,能够应用 ... 而不指定数组的长度,Go 会主动计算数组长度:

var a = [...]int {1, 2, 3} // 初始化,数组长度为 3 

1.3. 短变量形式申明

当然,咱们能够应用短变量申明的形式申明数组。留神:应用该形式就必须在申明的时候同时初始化

如果你只是想应用这种形式来申明一个数组,但并不初始化,能够这样做,然而必须带上{}

package main

import "fmt"

func main() {a := [5]int {1, 2, 3} // 初始化
    b := [3]int {}
    c := [...]int {1, 2, 3}

    fmt.Println(a) //[1 2 3 0 0]
       fmt.Println(b) //[0 0 0]
    fmt.Println(c) //[1 2 3]
}

1.4. 非凡之处

留神:在 Go 语言中,数组的长度是其类型的一部分。 所以 Go 中的数组不能扭转长度。

怎么了解?上面申明了两个数组:

var a [4]int // 将变量 a 申明为领有 4 个整数的数组

var b [5]int // 将变量 b 申明为领有 5 个整数的数组

变量 ab 的类型别离为 [4]int[5]int,是不同的类型。

1.5. 二维数组

二维数组当中的元素仍是数组:

var ab = [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}
或
ab := [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}

能够省去数组元素的类型:

var ab = [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}
或
ab := [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}

1.6. 遍历数组

(一)应用数组长度

能够应用 len(slice) 函数获取数组长度,而后遍历。

arr := [5]string {"a", "b", "c", "d", "e"}

bc := [2][4]int {{1, 2, 3, 4}, 
    {5, 6, 7, 8},
}

for i := 0; i < len(arr); i++ {// 遍历一维数组
    fmt.Println(arr[i])
}

for i := 0; i < len(bc); i++ {// 遍历二维数组的元素
    fmt.Println(bc[i])
}

for i := 0; i < len(bc); i++ {// 遍历二维数组的元素的元素
    for j := 0; j < len(bc[0]); j++ {fmt.Println(bc[i][j])
    }
}

(二)应用 range 关键字

range关键字用于 for 循环中遍历数组时,每次迭代都会返回两个值,第一个值为以后元素的下标,第二值为该下标所对应的元素值。如果这两个值的其中一个你不须要,只需应用下划线 _ 代替即可。

arr := [5]string {"a", "b", "c", "d", "e"}

bc := [2][4]int {{1, 2, 3, 4},
    {5, 6, 7, 8},
}

for i, v := range arr {// 遍历一维数组
    fmt.Println(i, v)
}

for i := range arr {// 遍历时只获取下标
    fmt.Println(i)
}

for _, v := range arr{// 遍历时只获取元素值
    fmt.Println(v)
}

for _, v := range bc {// 遍历二维数组
    for _, w := range v{fmt.Println(w)
    }
}

2. 切片(slice)

后面提到:Go 中的数组的长度是固定的。这样就会在理论利用中带来不不便,因为很多时候在申明数组前并不明确该数组要存储多少个元素。申明太多,节约;申明太少,不够用。

而切片就为咱们提供了“动静数组”。

2.1. 应用

申明切片和申明数组相似,然而 不指定长度

var sli_name []type

比方,申明一个 int 类型、名为 a 的切片:

var a []int

能够在申明它的时候间接初始化:

var a = []int {1, 2, 3, 4}

当然,也能够应用短变量的形式申明:

a := []int {1, 2, 3, 4}

能够从一个已有的数组或者已有的切片中获取切片。

获取切片的形式是通过两个下标来获取,即开始下标(startIndex)和完结下标(endIndex),二者以冒号分隔。包含startIndex,不包含endIndex

a[startIndex : endIndex]

上面是一个例子:

a := [5]string {"a", "b", "c", "d", "e"} // 数组
b := []int {1, 2, 3, 4} // 切片

sliA := a[2:4]
sliB := b[1:3]

fmt.Println(sliA) //
fmt.Println(sliB) //[2 3]

2.2. 切片与数组

后面提到:切片为咱们提供了“动静数组”。但该“动静数组”并不是真正意义上的能扩大长度的动静数组。

切片并不存储任何数据,它只是一个援用类型,切片总是指向一个底层的数组,形容这个底层数组的一段。

所以咱们在申明数组时须要指定长度,而申明切片时不须要:

var arr = [4]int {1, 2, 3, 4} // 申明数组

var slice = []int {1, 2, 3, 4} // 申明切片

因为切片的底层援用的是数组,所以 更改切片中的元素会批改其底层数组中对应的元素,如果还有其余切片也援用了该底层数组,那么这些切片也能观测到这些批改。如图:

上面是一个例子:

package main

import "fmt"

func main() {array := [5]string {"aa", "bb", "cc", "dd", "ee"} // 数组
    fmt.Println(array) //[aa bb cc dd ee]

    slice1 := array[0:2] // 切片 1
    slice2 := array[1:3] // 切片 2
    slice3 := array[2:5] // 切片 3

    fmt.Println(slice1) //[aa bb]
    fmt.Println(slice2) //[bb cc]
    fmt.Println(slice3) //[cc dd ee]

    slice1[0] = "xx" // 批改切片 1 中的值
    slice2[1] = "yy" // 批改切片 2 中的值
    slice3[2] = "zz" //// 批改切片 3 中的值

    fmt.Println(array) //[xx bb yy dd zz]
    fmt.Println(slice1) //[xx bb]
    fmt.Println(slice2) //[bb yy]
    fmt.Println(slice3) //[yy dd zz]
}

2.3. 切片的相干操作

(一)长度

切片的长度指切片所蕴含的元素个数。通过函数 len(s) 获取切片 s 的长度。

(二)容量

切片的容量指切片的第一个元素到其底层数组的最初一个元素的个数。通过函数 cap(s) 获取切片 s 的容量。

上面是一个例子:

package main

import "fmt"

func main() {arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
    s := arr[2:5] // 创立切片 s

    fmt.Println(arr) //[a b c d e f g h i j]
    fmt.Println(s) //

    fmt.Println(len(s)) //3
    fmt.Println(cap(s)) //8
}

上面是长度和容量的示意图:

有了容量这个概念,咱们就能够通过从新切片来扭转切片的长度:

package main

import "fmt"

func main() {arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
    s := arr[2:5]
    fmt.Printf("s 的长度为 %d,s 的容量为 %d\n", len(s), cap(s))
    s = s[2:8]
    fmt.Printf("s 的长度为 %d,s 的容量为 %d\n", len(s), cap(s))
    s = s[0:2]
    fmt.Printf("s 的长度为 %d,s 的容量为 %d\n", len(s), cap(s))
}

(三)追加元素

应用 func append(slice []Type, elems ...Type) []Type 能够向切片 slice 的开端追加类型为 Type 的元素elems

该函数的后果是一个蕴含原切片所有元素加上新增加元素的切片。因为扭转切片内容了,所以底层数组也会被扭转。

package main

import "fmt"

func main() {s := []string {"a", "b", "c", "d"}
    s = append(s, "e") // 追加 1 个
    s = append(s, "f", "g", "h") // 追加 3 个
    fmt.Println(s)
}

当切片中容量曾经用完时(len(s) == cap(s)),也即底层数组包容不了追加的元素时,Go 会调配一个更大的底层数组,返回的切片指向这个新调配的数组,原数组的内容不变。

package main

import "fmt"

func main() {arr := [5]string {"a", "b", "c", "d", "e"}
    slice1 := arr[0:2]
    fmt.Println(slice1) //[a b]
    // 追加 3 个元素,slice1 的容量已满
    slice1 = append(slice1, "1", "2", "3")
    fmt.Println(slice1) //[a b 1 2 3]
    // 底层数组跟着扭转
    fmt.Println(arr) //[a b 1 2 3]
    // 持续追加
    slice1 = append(slice1, "4", "5")
    // 指向新的底层数组
    fmt.Println(slice1) //[a b 1 2 3 4 5]
    // 原底层数组不变
    fmt.Println(arr) //[a b 1 2 3]
}

(四)复制切片

func copy(dst []Type, src []Type) int

dst是指标切片,src是源切片,该函数会将 src 中的元素复制到 dst 中,并返回复制的元素个数(该返回值是两个切片长度中的小值)

package main

import "fmt"

func main() {slice1 := []string {"a", "b"}
    slice2 := []string {"1", "2", "3"}
    length := copy(slice2, slice1)
    //length := copy(slice1, slice2)
    fmt.Println(length)
    fmt.Println(slice1)
    fmt.Println(slice2)
}

(五)切片的默认行为

切片的默认开始下标是 0,默认完结下标是切片的长度。

对于数组:

var a [10]int

上面几个切片是等价的:

a[0:10]
a[:10]
a[0:]
a[:]

2.4. 非凡切片

(一)nil 切片

切片的零值是 nil,当申明一个切片,但不出初始化它,该切片便为 nil 切片。nil切片的长度和容量为 0 且没有底层数组。

func main() {var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {fmt.Println("s 切片是 nil 切片")
    }
}

(二)切片的切片

切片中的元素能够是切片

package main

import "fmt"

func main() {ss := [][]int {[]int {1, 2, 3}, // 切片元素的类型能够省去
        []int {4, 5, 6},
        []int {7, 8, 9},
    }

    for i := 0; i < len(ss); i++ {fmt.Println(ss[i])
    }
}

2.5. 应用 make 函数创立切片

应用 make 函数能够在创立切片时指定长度和容量。make函数会调配一个元素为零值的数组并返回一个援用了它的切片

该函数承受三个参数,别离用来指定切片的类型、长度、容量。当不传入容量参数时,容量默认和长度雷同。容量参数不能小于长度参数。

package main

import "fmt"

func main() {a := make([]int, 5)
    fmt.Println(a, len(a), cap(a)) //[0 0 0 0 0] 5 5

    b := make([]int, 5, 6)
    fmt.Println(b, len(b), cap(b)) //[0 0 0 0 0] 5 6

    //c := make([]int, 5, 4) 
    //fmt.Println(c, len(c), cap(c))// 报错:len larger than cap in make([]int)
}

2.6. 遍历切片

因为切片是对数组的援用,所以遍历切片也就是在遍历数组。

3. 对于我

退出移动版