数组

2023/1/15

# 数组定义

Go语言中数组类型变量的声明:var arr [N]T 这里我们声明了一个数组变量arr,它的类型为[N]T,其中元素的类型为T,数组的长度为N。且数组的长度必须在声明数组变量时提供 初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小,这就是切片。

// 数组
var balance [10] float32
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 切片
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}

balance[4] = 50.0

数组的其他创建方式:

var a [4] float32 // 等价于:var arr2 = [4]float32{}
fmt.Println(a) // [0 0 0 0]
var b = [5] string{"ruby", "王二狗", "rose"}
fmt.Println(b) // [ruby 王二狗 rose  ]
var c = [5] int{'A', 'B', 'C', 'D', 'E'} // byte
fmt.Println(c) // [65 66 67 68 69]
d := [...] int{1,2,3,4,5}// 根据元素的个数,设置数组的大小
fmt.Println(d)//[1 2 3 4 5]
e := [5] int{4: 100} // [0 0 0 0 100]
fmt.Println(e)
f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
fmt.Println(f)

数组类型不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。Go编译器在为数组类型的变量实际分配内存时,会为Go数组分配一整块、可以容纳它所有元素的连续内存.

# 通过使用下标赋值的方式对数组某元素进行初始化

var arr4 = [...]int{
 99: 39, // 将第100个元素(下标值为99)的值赋值为39,其余元素值均为0
}
fmt.Printf("%T\n", arr4) // [100]int

# 访问数组元素

数组的下标值是从0开始的。如果下标值超出数组长度范畴,或者是负数,那么Go编译器会给出错误提示,防止访问溢出

package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,j int

   /* 为数组 n 初始化元素 */         
   for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}

# 数组的长度

通过将数组作为参数传递给len函数,可以获得数组的长度。

package main
import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    fmt.Println("length of a is",len(a))

}

您甚至可以忽略声明中数组的长度并将其替换为…让编译器为你找到长度。这是在下面的程序中完成的。

# Sizeof函数获得一个数组变量的总大小

var arr = [6]int{1, 2, 3, 4, 5, 6}
fmt.Println("数组长度:", len(arr)) // 6
fmt.Println("数组大小:", unsafe.Sizeof(arr)) // 48

数组大小就是所有元素的大小之和,这里数组元素的类型为int。在64位平台上,int类型的大小为8,数组arr一共有6个元素,因此它的总大小为6x8=48个字节。

# 多维数组

当数组元素的类型也是数组类型时,会出现多维数组。我们只需要按照变量声明从左到右、按维度分层拆解,直到出现一元数组就好了 var mArr [2][3][4]int

# 切片-Slice

与数组声明相比,切片声明仅仅是少了一个“长度”属性。去掉“长度”这一束缚 后,切片展现出更为灵活的特性。

var nums = []int{1, 2, 3, 4, 5, 6}
fmt.Println(len(nums)) // 6
nums = append(nums, 7) // 切片变为[1 2 3 4 5 6 7]
fmt.Println(len(nums)) // 7

通过len函数获得切片类型变量的长度,比如上面那个切片变量的长度就是6 而且,通过Go内置函数append,我们可以动态地向切片中添加元素。当然,添加后切片的 长度也就随之发生了变化。

var s []int
s = append(s, 11) 
fmt.Println(len(s), cap(s)) //1 1
s = append(s, 12) 
fmt.Println(len(s), cap(s)) //2 2
s = append(s, 13) 
fmt.Println(len(s), cap(s)) //3 4
s = append(s, 14) 
fmt.Println(len(s), cap(s)) //4 4
s = append(s, 15) 
fmt.Println(len(s), cap(s)) //5 8

append会根据切片的需要,在当前底层数组容量无法满足的情况下,动态分配新的数组,新数组长度会按一定规律扩展。在上面这段代码中,针对元素是int型的数组,新数组的容量是当前数组的2倍。新数组建立后,append会把旧数组中的数据拷贝到新数组中,之后新数组便成为了切片的底层数组,旧数组会被垃圾回收掉。不过append操作的这种自动扩容行为,有些时候会给我们开发者带来一些困惑,比如基于一个已有数组建立的切片,一旦追加的数据操作触碰到切片的容量上限(实质上也是数组容量的上界),切片就会和原数组解除“绑定”,后续对切片的任何修改都不会反映到原数组中了。

# 初始化切片

  • 方法一:通过make函数来创建切片,并指定底层数组的长度。
sl := make([]byte, 6, 10) // 其中10为cap值,即底层数组长度,6为切片的初始长度
// 如果没有在make中指定cap参数,那么底层数组长度cap就等于len,比如:
sl := make([]byte, 6) // cap = len = 6
  • 方法二:采用array[low:high:max]语法基于一个已存在的数组创建切片。这种方式被称为数组的切片化
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sl := arr[3:7:9]
  • 方法三:基于切片创建切片

Go值传递的机制让数组在各个函数间传递起来比较“笨重”,开销较大,且开销随数 组长度的增加而增加。为了解决这个问题,Go引入了切片这一不定长同构数据类型。 切片可以看成是数组的“描述符”,为数组打开了一个访问与修改的“窗口”。 切片在Go运行时中被实现为一个“三元组(array, len, cap)”,其中的array是指向底层数组的指针,真正的数据都存储在这个底层数组中;len表示切片的长度;而cap则是切片底层数组的容量。我们可以为一个数组建立多个切片,这些切片由于共享同一个底层数组,因此我们通过任一个切片对数组的修改都会反映到其他切片中。 切片是不定长同构复合类型,这个不定长体现在Go运行时对它提供的动态扩容的支撑。当切片的cap值与len值相等时,如果再向切片追加数据,Go运行时会自动对切片的底层数组进行扩容,追加数据的操作不会失败。 在大多数场合,我们都会使用切片以替代数组,原因之一是切片作为数组“描述符”的轻量 性,无论它绑定的底层数组有多大,传递这个切片花费的开销都是恒定可控的;另外一个原因是切片相较于数组指针也是有优势的,切片可以提供比指针更为强大的功能,比如下标访问、边界溢出校验、动态扩容等。而且,指针本身在Go语言中的功能也受到的限制,比如不支持指针算术运算。