AI智能
改变未来

Go语言入门系列(三)之数组和切片

《Go语言入门系列文章》

  1. Go语言入门系列(一)之Go的安装和使用
  2. Go语言入门系列(二)之基础语法总结

1. 数组

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

1.1. 声明

Go语言中数组的声明方式:

var arr_name [length]type

var

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

arr_name

:数组名称,本质是个变量

length

:数组的长度

type

:数组的类型

[]

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

下面是一个例子:

package mainimport "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 mainimport "fmt"func main() {var a [3]intvar b [3]stringvar c [3]boolfmt.Println(a) //[0 0 0]fmt.Println(b) //[  ]fmt.Println(c) //[false false false]}

对数组元素进行初始化:

package mainimport "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 mainimport "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个整数的数组

变量

a

b

的类型分别为

[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) //[c d]fmt.Println(sliB) //[2 3]

2.2. 切片与数组

前面提到:切片为我们提供了“动态数组”。但该“动态数组”并不是真正意义上的能扩展长度的动态数组。

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

所以我们在声明数组时需要指定长度,而声明切片时不需要:

var arr = [4]int {1, 2, 3, 4} //声明数组var slice = []int {1, 2, 3, 4} //声明切片

由于切片的底层引用的是数组,所以更改切片中的元素会修改其底层数组中对应的元素,如果还有其他切片也引用了该底层数组,那么这些切片也能观测到这些修改。如图:

下面是一个例子:

package mainimport "fmt"func main() {array := [5]string {"aa", "bb", "cc", "dd", "ee"} //数组fmt.Println(array) //[aa bb cc dd ee]slice1 := array[0:2] //切片1slice2 := array[1:3] //切片2slice3 := array[2:5] //切片3fmt.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 mainimport "fmt"func main() {arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}s := arr[2:5] //创建切片sfmt.Println(arr) //[a b c d e f g h i j]fmt.Println(s) //[c d e]fmt.Println(len(s)) //3fmt.Println(cap(s)) //8}

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

有了容量这个概念,我们就可以通过重新切片来改变切片的长度:

package mainimport "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 mainimport "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 mainimport "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 mainimport "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 []intfmt.Println(s, len(s), cap(s))if s == nil {fmt.Println("s切片是nil切片")}}

(二)切片的切片

切片中的元素可以是切片

package mainimport "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 mainimport "fmt"func main() {a := make([]int, 5)fmt.Println(a, len(a), cap(a)) //[0 0 0 0 0] 5 5b := 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. 关于我

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Go语言入门系列(三)之数组和切片