value type
)。[大小]T
,比如[5]int
表示拥有5个int元素的数组。定义方式如下:
var arr [N]T
// 或者使用短变量申明
arr := [N]T{}
这里我们声明了一个数组变量 arr
,其中:
arr
为数组变量名N
表示数组长度T
表示数组存储类型**如果两个数组类型的元素类型 T 与数组长度 N 都是一样的,那么这两个数组类型是等价的,如果有一个属性不同,它们就是两个不同的数组类型。**下面这个示例很好地诠释了这一点:
func foo(arr [5]int) {}
func main() {
var arr1 [5]int
var arr2 [6]int
var arr3 [5]string
foo(arr1) // ok
foo(arr2) // 错误:[6]int与函数foo参数的类型[5]int不是同一数组类型
foo(arr3) // 错误:[5]string与函数foo参数的类型[5]int不是同一数组类型
}
在这段代码里,arr2 与 arr3 两个变量的类型分别为[6]int 和 [5]string,前者的长度属性与[5]int 不一致,后者的元素类型属性与[5]int 不一致,因此这两个变量都不能作为调用函数 foo 时的实际参数。
var a [5]byte //长度为5的数组,每个元素为一个字节
var b [2*N] struct { x, y int5 } //复杂类型数组
var c [5]*int // 指针数组
var d [2][3]int //二维数组
var e [2][3][4]int //等同于[2]([3]([4]int))
这种方式在声明数组的同时,通过提供初始值列表来初始化数组元素。如果没有为数组的每个元素提供初始值,剩余的元素将会使用默认值。对于数值类型(如int),默认值为0;对于字符串类型(如string),默认值为空字符串。
var testArray [3]int //数组会初始化为int类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var strArray = [3]string{}
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2 0]
fmt.Println(strArray) //[ ] 默认值空字符串
在这种方式下,你可以在声明数组时省略长度,并使用...
操作符,编译器会根据提供的初始值的个数自动推断数组的长度。这使得代码更加简洁,不需要显式指定数组的长度。
arr := [...]int{1, 2, 3} // [1 2 3]
fmt.Println(arr) // [1 2]
fmt.Printf("type of numArray:%T\n", arr) // type of numArray:[3]int
这种方式允许你在数组的指定索引位置提供初始值,其他位置会被初始化为默认值。在示例中,a[1]
被初始化为1,a[3]
被初始化为5,其他位置默认为0。
func main() {
a := [...]int{1: 1, 3: 5}
fmt.Println(a) // [0 1 0 5]
fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}
遍历数组有两种方法,使用for
循环和使用for range
语句
for
循环遍历var a = [...]string{"贾", "维", "斯"}
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
这是传统的for
循环遍历数组的方式,它使用一个循环变量i
来迭代数组的索引,然后使用a[i]
来访问数组的元素。这种方式适用于需要访问数组索引或按照索引进行操作的情况。
for range
遍历 var a = [...]string{"贾", "维", "斯"}
for index, value := range a {
fmt.Println(index, value)
}
for range
语句更加简洁和直观。它会返回数组的索引和对应的值,这使得遍历数组变得非常方便。通常情况下,使用for range
遍历数组更加推荐,特别是当你只需要访问数组的值而不需要索引时。
需要注意的是,for range
遍历数组会创建一个值的拷贝,而不是原始数组的引用。如果你需要在循环内修改数组元素的值,并且希望这些修改在循环结束后对原始数组生效,那么你应该使用for
循环,因为它允许你直接访问数组的元素。
在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用Go语言的内置函数len()来获取。
arrLength := len(arr)
举个例子:
arr := [5]int{10, 20, 30, 40, 50}
length := len(arr) // 获取数组的长度,length的值为5
数组的下标值是从 0 开始的
使用数组变量名加索引下标的方式就可以访问数组对应位置的元素。
var arr = [6]int{11, 12, 13, 14, 15, 16}
fmt.Println(arr[0], arr[5]) // 11 16
fmt.Println(arr[-1]) // 错误:下标值不能为负数
fmt.Println(arr[8]) // 错误:小标值超出了arr的长度范围
arr := [5]int{1, 2, 3, 4, 5}
arr[0] = 100 // 修改数组第一个元素
arr[1] = 200 // 修改数组第二个元素
fmt.Println(arr) // 输出:[100 200 3 4 5]
使用切片来从数组中创建一个动态长度的子集。切片是对数组的引用,因此它们与原始数组共享底层数据。
arr := [5]int{10, 20, 30, 40, 50}
slice := arr[1:4] // 创建一个包含arr的索引1到3的切片,slice的值为{20, 30, 40}
你可以使用==
运算符来比较两个数组是否相等。两个数组相等的条件是它们的长度和元素都相同。
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
isEqual := arr1 == arr2
fmt.Println(isEqual) // isEqual为true
数组是值类型,当它作为函数参数传递时,会复制整个数组。这意味着在函数内对数组的修改不会影响原始数组。
func modify(arr [3]int) {
arr[0] = 100
}
func main() {
a := [3]int{1, 2, 3}
modify(a)
fmt.Println(a) // 输出[1, 2, 3]
}
// 在modify函数中,我们把数组arr的第一个元素修改为了100。但是回到main函数后,打印数组a时,它的第一个元素仍然是1。
如果需要在函数内修改数组,需要传入数组指针:
func modify(arr *[3]int) {
(*arr)[0] = 100
}
func main() {
a := [3]int{1, 2, 3}
modify(&a)
fmt.Println(a) // 输出[100 2 3]
}
了解了数组类型的定义和操作后,我们再来看看数组类型在内存中的实际表示是怎样的,这是数组区别于其他类型,也是我们区分不同数组类型的根本依据。
**数组类型不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。**Go 编译器在为数组类型的变量实际分配内存时,会为 Go 数组分配一整块、可以容纳它所有元素的连续内存,如下图所示:
我们从这个数组类型的内存表示中可以看出来,这块内存全部空间都被用来表示数组元素,所以说这块内存的大小,就等于各个数组元素的大小之和。如果两个数组所分配的内存大小不同,那么它们肯定是不同的数组类型。Go 提供了预定义函数 len 可以用于获取一个数组类型变量的长度,通过 unsafe 包提供的 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 个字节。
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
func modifyArray(x [3]int) {
x[0] = 100
}
func modifyArray2(x [3][2]int) {
x[2][0] = 100
}
func main() {
a := [3]int{10, 20, 30}
modifyArray(a) //在modify中修改的是a的副本x
fmt.Println(a) //[10 20 30]
b := [3][2]int{
{1, 1},
{1, 1},
{1, 1},
}
modifyArray2(b) //在modify中修改的是b的副本x
fmt.Println(b) //[[1 1] [1 1] [1 1]]
}
注意:
[n]*T
表示指针数组,*[n]T
表示数组指针 。组是最简单的多维数组,二维数组本质上是由一维数组组成的。二维数组定义方式如下:
var arrayName [ x ][ y ] variable_type
variable_type 为 Go 语言的数据类型,arrayName 为数组名,二维数组可认为是一个表格,x 为行,y 为列,下图演示了一个二维数组 a 为三行四列:
举个栗子,二维数组定义并初始化
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
fmt.Println(a[2][1]) //支持索引取值:重庆
}
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s\t", v2)
}
fmt.Println()
}
}
输出:
北京 上海
广州 深圳
成都 重庆
注意: 多维数组只有第一层可以使用...
来让编译器推导数组长度。例如:
//支持的写法
a := [...][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
//不支持多维数组的内层使用...
b := [3][...]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
多维数组是一种数组的扩展,它允许在一个数组中存储多个维度的数据。在许多编程语言中,通常可以创建二维数组、三维数组,甚至更高维度的数组。多维数组在处理具有多个维度的数据集时非常有用,比如矩阵、图像等。
多维数组的基本思想是使用多个索引来引用数组中的元素。例如,二维数组可以看作是一个表格,需要两个索引来定位某个元素,第一个索引表示行号,第二个索引表示列号。三维数组则需要三个索引,依此类推。以下是多维数组的一些基本概念:
Go 语言支持多维数组,以下为常用的多维数组声明方式:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
比如下面的变量 mArr 的类型就是一个多维数组[2][3][4]int
:
var mArr [2][3][4]int
多维数组也不难理解,我们以上面示例中的多维数组类型为例,我们从左向右逐维地去看,这样我们就可以将一个多维数组分层拆解成这样:
我们从上向下看,首先我们将 mArr 这个数组看成是一个拥有两个元素,且元素类型都为[3] [4]int
的数组,就像图中最上层画的那样。这样,mArr
的两个元素分别为 mArr[0]
和 mArr [1]
,它们的类型均为[3] [4]int
,也就是说它们都是二维数组。
而以 mArr[0]为例,我们可以将其看成一个拥有 3 个元素且元素类型为[4]int 的数组,也就是图中中间层画的那样。这样 mArr[0]
的三个元素分别为 mArr[0][0]
、mArr[0][1]
以及 mArr[0][2]
,它们的类型均为[4]int
,也就是说它们都是一维数组。
图中的最后一层就是 mArr[0]
的三个元素,以及 mArr[1]
的三个元素的各自展开形式。以此类推,你会发现,无论多维数组究竟有多少维,我们都可以将它从左到右逐一展开,最终化为我们熟悉的一维数组。
不过,虽然数组类型是 Go 语言中最基础的复合数据类型,但是在使用中它也会有一些问题。数组类型变量是一个整体,这就意味着一个数组变量表示的是整个数组。这点与 C 语言完全不同,在 C 语言中,数组变量可视为指向数组第一个元素的指针。这样一来,无论是参与迭代,还是作为实际参数传给一个函数 / 方法,Go 传递数组的方式都是纯粹的值拷贝,这会带来较大的内存拷贝开销。
这时,你可能会想到我们可以使用指针的方式,来向函数传递数组。没错,这样做的确可以避免性能损耗。其实,Go 语言为我们提供了一种更为灵活、更为地道的方式 ,切片,来解决这个问题。
在Go语言中,数组和一般认知中的数组(如C、C++等语言中的数组)有一些重要区别和特点。下面是关于Go语言中数组的一些特点和区别:
var a [len]Type
,其中len
表示数组的长度,Type
表示数组元素的类型。例如,var a [5]int
定义了一个包含5个整数的数组。[5]int
和[10]int
是不同的类型。这意味着不能将一个长度为5的数组赋值给一个长度为10的数组,它们是不兼容的。len-1
。可以使用for
循环或range
来遍历数组。==
)和不等(!=
)操作符,因为数组在定义后会被初始化,所以它们是可比较的。