数组是一组同一类数据
的集合,数组包含的元素个数被称为数组的长度,可用len(数组)来统计。
特点如下
- 1、数组内的元素可以被改变,但是数组的长度不可被改变。
- 2、数组是采用索引对应元素,索引从0开始,到len(数组)-1结束,不支持负向索引
- 3、数组是值类型、传递数组的开销比较大
数组的特点决定了其使用的局限性,因此在Go语言中很少直接使用数组。
和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列,slice的使用更为灵活,并且切片是引用类型、传递开销小,因此切片要比数组更为常用一些,但是要理解slice工作原理的话需要先理解数组。
| var 数组变量名 [元素数量]T |
| |
| |
| var a [3]int |
-
1、零值初始化:数组是值类,若声明时未初始化值,则依据其包含的元素类型填充对应零值。
| var a [3]int |
| fmt.Println(a) |
-
2、指定一组值来初始化数组:值个数等于数组长度
| var cityArray = [3]string{"北京", "上海", "深圳"} |
| fmt.Println(cityArray) |
-
3、指定一组值来初始化数组:值个数小于数组长度,缺失的则自动填充对应零值
| var numArray [3]int = [3]int{111, 222} |
| fmt.Println(numArray) |
-
4、用“…”省略号代替长度,数组的长度会根据初始化值的个数推导出来
| var names = [...]string{"egon", "EGON"} |
| fmt.Println(names) |
| |
| fmt.Println(len(names)) |
| fmt.Printf("%T\n",names) |
| |
| |
| type Currency int |
| |
| const ( |
| USD Currency = iota |
| EUR |
| GBP |
| RMB |
| ) |
| |
| symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"} |
| |
| fmt.Println(RMB, symbol[RMB]) |
-
5、我们还可以使用指定索引值的方式来初始化数组,缺失的则自动填充对应零值
| |
| nums := [...]int{1: 111, 5: 222} |
| fmt.Println(nums) |
| fmt.Printf("%T\n", nums) |
注意:
1、因为数组的长度需要在编译阶段确定,所以在声明时数组时,数组的长度必须是常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)
2、不能往数组里添加或删除值,数组一旦定义,长度不可改变。
3、数组属于复合类型,而复合类型所包含的元素个数(或称长度),还有位置,都是类型一部分,所以 [3]int
和[5]int
是不同的类型。
| var a [3]int |
| var b [5]int |
| a = b |
4、数组嵌套数组又称多维数组,多维数组只有第一层可以使用...
来让编译器推导数组长度。例如:
| |
| a := [...][2]string{ |
| {"林大牛", "林二牛"}, |
| {"李一蛋", "李二蛋"}, |
| {"王一炮", "王三炮"}, |
| } |
| |
| |
| b := [3][...]string{ |
| {"林大牛", "林二牛"}, |
| {"李一蛋", "李二蛋"}, |
| {"王一炮", "王三炮"}, |
| } |
仅限制用同类型数组之间进行,即数组长度、元素类型都必须一样,如下
| var x [3]int = [3]int{111,222,333} |
| var y [3]int = [3]int(x) |
错误案例
| |
| var x []int = []int{111,222,333} |
| var y [3]int = [3]int(x) |
| |
| |
| var y [3]int32 = [3]int32("hello") |
| |
| 思考,为何下面的转换就可以 |
| var y []int32 = []int32("hello") |
| fmt.Println(y) |
数组长度不可变,所以无法不能增、删,只能改、查,索引必须是非负数
| var cityArray = [3]string{"北京", "上海", "深圳"} |
| |
| |
| fmt.Println(cityArray[0]) |
| fmt.Println(cityArray[len(cityArray)-1]) |
| fmt.Println(cityArray[-1]) |
| |
| |
| cityArray[1] = "魔都" |
| fmt.Println(cityArray) |
| cityArray[666] = "魔都" |
(1)数组属于复合类型中的值类型,复合类型中的值类型支持进行相等性判断,但必备两个条件
I.待比较的两个数组本身,必须是相同的类型
II:待比较的两个数组中,对应的元素都是可进行相等性判断的
| |
| a := [2]int{1, 2} |
| b := [...]int{1, 2} |
| c := [2]int{1, 3} |
| fmt.Println(a == b, a == c, b == c) |
| |
| |
| a := [2]int{1, 2} |
| d := [3]int{1, 2} |
| fmt.Println(a == d) |
| |
| |
| var m [2][]int = [2][]int{ |
| {111, 222}, |
| {333, 444}, |
| } |
| |
| var n [2][]int = [2][]int{ |
| {111, 222}, |
| {333, 444}, |
| } |
| |
| fmt.Println(m) |
| fmt.Println(n) |
| |
| fmt.Println(m == n) |
(2)数组不可以比大小,若想比较,可以编写函数依次取出元素进行比较,就像字符串比大小的底层原理一样
遍历一维数组
| cityArray := [...]string{"北京", "上海", "深圳"} |
| |
| |
| for i := 0; i < len(cityArray); i++ { |
| fmt.Println(cityArray[i]) |
| } |
| |
| |
| for index, value := range cityArray { |
| fmt.Println(index, value) |
| } |
遍历多维数组
| |
| a := [3][2]string{ |
| {"林大牛", "林二牛"}, |
| {"李一蛋", "李二蛋"}, |
| {"王一炮", "王三炮"}, |
| } |
| |
| for _,v1:=range a{ |
| for _,v2:=range v1{ |
| fmt.Println(v2) |
| } |
| } |
| |
| ``` |
| 输出: |
| 林大牛 林二牛 |
| 李一蛋 李二蛋 |
| 王一炮 王三炮 |
| ``` |
在其他其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数,但在go语言中数组就是值传递,详细地说:关于数组的赋值和传参会复制整个数组,操作的都是副本的值,不会改变本身的值。
举例如下:
| package main |
| |
| import "fmt" |
| |
| func modifyArray(x [3]int) { |
| fmt.Printf("拷贝得到一个新数组,地址是:%p\n",&x) |
| x[0] = 100 |
| } |
| |
| func main() { |
| c := [3]int{10, 20, 30} |
| fmt.Printf("原数组,地址是:%p\n",&c) |
| modifyArray(c) |
| fmt.Println(c) |
| } |
小练习
| package main |
| |
| import "fmt" |
| |
| func modifyArray2(x [3][2]int) { |
| fmt.Printf("拷贝得到一个新数组,地址是:%p\n", &x) |
| x[2][0] = 100 |
| } |
| func main() { |
| |
| var a = 10 |
| b := a |
| b = 101 |
| fmt.Printf("a的内存地址是%p\n,b的内存地址是%p\n", a, &b) |
| |
| |
| d := [3][2]int{ |
| {1, 1}, |
| {1, 1}, |
| {1, 1}, |
| } |
| fmt.Printf("原数组,地址是:%p\n", &d) |
| modifyArray2(d) |
| fmt.Println(d) |
| } |
至此,我们可以总结一下数组因为是值类型带来的弊端
1、在函数参数中是值传递,导致传递大数组将是低效的
2、传递给函数后,任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。
当然,我们可以显式地传入一个数组指针来达到引用传递与修改原值的目的,如下
| func zero(ptr *[32]byte) { |
| *ptr = [32]byte{} |
| } |
| |
| 注意: |
| [n]*T:表示数组的元素由指针类型组成 |
| *[n]T:表示数组指针 |
虽然通过指针来传递数组参数是高效的,而且也允许在函数内部修改数组的值,但是数组依然是僵化的类型,因为数组的类型包含了僵化的长度信息,例如:上面的zero函数并不能接收指向[16]byte类型数组的指针,而且也没有任何添加或删除数组元素的方法。
由于这些原因,数组很少用作函数参数,我们一般使用slice来替代数组。接下来就让我们来介绍一下切片Slice吧