1 序


图片名称 在Go语言中,数组是数据管理中至关重要的组件。它们作为核心数据结构,为开发者提供了灵活性、性能和便利性,本文将带您了解Go语言复合数据类型的数组。并深入探讨它的特性、用法和常见操作。

在使用数组的时候,我们会使用一些函数来实现,这里我们将介绍Go语言中的内置函数。

2 内置函数

Go语言提供了一些内置函数(Built-in Functions),这些函数是在编译器中实现的,并且可以直接使用,无需导入任何包。以下是一些常用的Go内置函数:

1
2
3
4
5
6
7
8
9
10
1. len(): 返回字符串、数组、切片、字典或通道的长度。
2. cap(): 返回数组、切片或通道的容量。
3. make(): 用于创建切片、映射或通道。
4. new(): 用于创建某种类型的指针,并返回其地址。
5. append(): 用于向切片追加元素,可以同时追加一个或多个元素。
6. copy(): 用于将源slice的元素复制到目标slice,并返回复制的元素个数。
7. delete(): 用于从字典中删除指定键的元素。
8. close(): 用于关闭通道。
9. panic()和recover(): 用于处理错误和异常情况。
10. print()和println(): 用于在控制台打印输出。

除了以上列出的内置函数,Go语言还提供了其他很多有用的内置函数,涵盖了各种操作和功能,例如类型转换、数学计算、字符串处理等。你可以通过查看官方文档来获取完整的内置函数列表和详细的使用说明。

需要注意的是,虽然这些函数是内置的,但它们也可以被重新定义为普通的标识符。所以,如果你在自己的代码中使用了跟内置函数同名的标识符,那么内置函数将会被覆盖。

3 数组底层原理

在Go语言中,数组是一种值类型。当你创建一个数组时,Go会在内存中为该数组分配一个连续的内存块,每个元素都占据该内存块中的一部分。数组中的每个元素都可以通过其索引直接访问。因为这种内存布局,数组的访问速度非常快。

下面是一个简单的图解,展示了一个长度为3,元素类型为int的数组在内存中的布局。

内存地址 0x00 0x04 0x08
索引 0 1 2
arr[0] arr[1] arr[2]

当你将一个数组赋值给另一个数组时,Go会创建一个新的内存块,并将原数组的所有元素值复制到新的内存块。这就是为什么说Go中的数组是值类型,而不是引用类型。这也意味着如果你在函数中修改了一个数组,原数组不会被修改,除非你使用指针或者切片。

4 数组特点

数组是一种固定长度的数据结构,用于存储相同类型的元素序列。声明数组时,需要指定其长度,这意味着数组的大小在创建后不可更改。数组的索引从0开始,提供了快速访问元素的方式。然而,由于其固定长度的特性,数组在某些场景下的灵活性受到限制。

数组具有以下特点:

  • 数组长度不可变,且它不是引用类型。
  • 数组里数据是相同类型数据。
  • 数组中元素可以是任意的原始类型,比如int、string等。
  • 一个数组中元素的个数被称为数组长度。
  • 数组的长度属于类型一部分,也就是[5]int和[10]int属于不同类型。
  • 数组占用内存连续性,也就是数组中的元素被分配到连续内存地址中,因而索引数组元素速度非常快。

5 定义数组

Go语言中,数组的声明和实现可以通过以下方式进行:

5.1 var关键字声明

在Go语言中,可以使用var关键字来声明一个数组。数组的声明语法如下:

1
var arrayName [size]dataType

其中,arrayName是数组的名称,size是数组的大小, 而且size是必须要指定的,dataType是数组中元素的数据类型。

例如,声明一个包含5个整数的数组:

1
var numbers [5]int

在这个例子中,数组的名称是numbers,大小为5个整数元素,即[5]int。

声明完该数组,我们可以后面再针对该数组进行赋值。一般是直接用{}大括号赋值,或者通过索引设置数组元素。

1
2
3
4
5
6
7
8
9
var numbers [5]int
// 直接通过{}大括号实现
numbers = [5]int{1, 2, 3, 4, 5}
// 或者通过索引依次赋值
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5

5.2 :=声明并初始化数组

在声明数组的同时,可以使用大括号{}来初始化数组的元素。例如:

1
2
// 声明并初始化值
var numbers [5]int = [5]int{1, 2, 3, 4, 5}

或者可以使用简化的声明和初始化方式:

1
2
// 声明并初始化值
numbers := [5]int{1, 2, 3, 4, 5}

如果你不确定数组的长度,可以让Go编译器自动计算数组的长度,在初始化的时候,可以使用

1
2
3
4
// 数组大小可以根据数组内容自动推断
var numbers1 = [...]int{1, 2, 3, 4, 5}
// 数组大小可以根据数组内容自动推断
numbers2 := [...]int{1, 2, 3, 4, 5}

这样,数组numbers就被声明并初始化为包含元素1到5的整数数组。

6 访问数组

可以通过索引来访问数组中的元素。索引从0开始,逐个递增。例如,要访问上面声明的数组numbers的第一个元素,可以使用numbers[0]。例如:

1
2
3
4
5
6
7
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
first := numbers[0]
fmt.Println("First element: ", first) // Output: First element: 1
}

我们还可以通过len()cap()函数计算数组的长度和容量

1
2
3
4
5
6
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
fmt.Printf("Value=%d,Length=%d,Capacity=%d\n", numbers, len(numbers), cap(numbers)) // Output: Value=[1 2 3 4 5],Length=5,Capacity=5
}

7 遍历数组

有两种主要的方法可以遍历Go中的数组。你可以使用传统的for循环,也可以使用range关键字。

7.1 For关键字遍历

首先可以通过for关键字遍历,其中需要借助len()函数计算数组长度。

1
2
3
4
5
6
7
8
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
}

7.2 Range关键字遍历

也可以通过range关键字遍历数组,例如:

1
2
3
4
5
6
7
8
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}

如果不需要使用索引,可以通过下划线’_’代替:

1
2
3
4
5
6
7
8
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
for _, value := range numbers {
fmt.Println(value)
}
}

通常来说,我们使用for range方式迭代可能会好一点,因为这种迭代可以保证不会出现数组越界的情况,每次迭代对数组的访问可以省略对下标越界判断,当然具体使用,因实际情况不同而不同。

8 修改数组元素

可以通过索引来修改数组中的元素。例如,要将上面声明的数组numbers的第一个元素修改为0,可以使用numbers[0] = 0

1
2
3
4
5
6
7
import "fmt"

func main() {
numbers := [...]int{1, 2, 3, 4, 5}
numbers[0] = 0
fmt.Println(numbers) // Output: [0,2,3,4,5]
}

当你把一个数组作为参数传给函数,然后在函数中修改数组的其中一个元素,这并不会改变原数组的值,因为参数值实际上是该数组的副本,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import "fmt"

func main() {
arr1 := [3]int{1, 2, 3}
arr2 := changeElement(arr1) // 修改数组元素
fmt.Println(arr1) // Output: [1 2 3]
fmt.Println(arr2) // Output: [10 2 3]
}

// 修改数组第一个元素的值为10,并返回修改后的数组
func changeElement(array [3]int) [3]int {
array[0] = 10
return array
}

如果要想在函数中修改数组的值,可以通过指针实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "fmt"

func main() {
arr1 := [3]int{1, 2, 3}
arr2 := changeElement(&arr1) // 修改数组元素
fmt.Println(arr1) // Output: [10 2 3]
fmt.Println(arr2) // Output: [10 2 3]
}

// 修改数组第一个元素的值为10
func changeElement(array *[3]int) [3]int {
// 获取第一个元素的地址
ptr := &array[0]
fmt.Println("第一个元素值为:", *ptr) // 通过指针访问元素的值 Output: 第一个元素值为: 1
// 通过指针修改数组元素的值
*ptr = 10
return *array
}

9 数组复制

在Go语言中,数组是值类型。当你将一个数组赋值给另一个变量时,会复制整个数组的内容,而不是复制对数组的引用。这意味着对一个数组的修改不会影响到另一个数组,例如

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
arr1 := [3]int{1, 2, 3}
arr2 := arr1 // 复制arr1到arr2
arr1[0] = 10 // 修改arr1的第一个元素
fmt.Println(arr1) // Output: [10 2 3]
fmt.Println(arr2) // Output: [1 2 3]
}

这个例子中,虽然修改了arr1的第一个元素,但arr2并不受影响,因为在赋值时是将arr1的内容复制给了arr2,它们是完全独立的数组。

10 数组比较

在go语言中,可以使用比较运算符“==”或“!=”来进行数组比较,判断两个数组是否相等。

注意:只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。

1
2
3
4
5
6
7
8
import "fmt"

func main() {
// 通过 == 来比较数组
arr1 := [3]string{"Hello", "Ratel"}
arr2 := [3]string{"Hello", "Ratel"}
fmt.Println("arr1 == arr2 ", arr1 == arr2) // Output: arr1 == arr2 true
}

10.1 数组长度不同

根据数组特点我们知道,即便是元素类型相同,数组长度只要不同,编译器都认为这是两种不同的数据类型,所以数组无法通过==比较,编译器会直接报错。

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
// 数组长度不同,不可以通过 == 来比较数组
arr1 := [3]string{"Hello", "Ratel", "Wu"}
arr2 := [2]string{"Hello", "Ratel"}
fmt.Println("arr1 == arr2 ", arr1 == arr2)
// invalid operation: arr1 == arr2 (mismatched types [3]string and [2]string)
}

10.2 数组元素类型不同

如果数组的长度相同,数据类型不同,编译器同样认为这是两种不同的数据类型,所以数组也无法通过==比较,编译器会直接报错。

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
// 数组元素数据类型不同,不可以通过 == 来比较数组
arr1 := [2]int{10, 20}
arr2 := [2]string{"Hello", "Ratel"}
fmt.Println("arr1 == arr2 ", arr1 == arr2)
// invalid operation: arr1 == arr2 (mismatched types [2]int and [2]string)
}

11 总结

需要注意的是,在Go中数组的长度是固定的,因此无法直接删除或调整数组的大小。如果需要删除数组中的元素,可以使用切片来实现类似的效果,我们将在下一章节中介绍。

在Go语言中,数组类型是非常重要的类型,数组本身的赋值和函数传参都是通过复制的方式处理的,理解数组的底层原理有助于更好的使用数组,但是Go语言中很少直接使用数组,原因就是不同长度的数组因为类型不同无法直接赋值,我们下章节将介绍切片,看看切片的操作使用,欢迎阅读。