【九】Golang数组
- 人工智能
- 2025-09-05 00:00:01

💢欢迎来到张胤尘的技术站 💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥
文章目录 数组数组初始化默认初始化显式初始化省略长度初始化索引号初始化 内存布局内存分配 常见操作访问数组元素修改数组元素删除数组元素获取数组长度遍历数组数组切片数组排序基本类型排序结构体类型排序实现`sort.Interface`接口进行自定义排序直接切片排序 手动实现排序 数组合并数组反转数组去重数组过滤 数组作为函数参数指针传递数组使用切片 多维数组声明和初始化访问和修改多维数组遍历多维数组 数组在 golang 中,数组是一种复合数据类型。数组的长度是一个常量,表示数组的大小,类型是数组中元素的数据类型,定义如下所示:
var 数组名 [长度]类型定义一个长度为5的整型数组,代码如下所示:
var arr [5]int另外在开发过程中使用数组需要注意以下规则:
数组长度固定:数组的长度在声明时必须确定,并且在程序运行过程中不能改变。数组元素类型相同:数组中的所有元素必须是相同的数据类型。 数组初始化 默认初始化如果在声明数组时没有初始化,数组中的所有元素都会被初始化为该类型的零值,代码如下所示:
不同数据类型的零值不同,具体请查看 《Golang 变量和常量》章节内容。
package main import "fmt" func main() { var arr [5]int fmt.Println(arr) // [0 0 0 0 0] } 显式初始化可以在声明时直接初始化数组元素,代码如下所示:
package main import "fmt" func main() { var arr [5]int = [5]int{1, 2, 3, 4, 5} fmt.Println(arr) // [1 2 3 4 5] }或者采用短声明的方式初始化数组,代码如下所示:
package main import "fmt" func main() { arr := [5]int{1, 2, 3, 4, 5} fmt.Println(arr) // [1 2 3 4 5] }如果初始化时提供的元素个数少于数组的长度,剩余的元素会被自动初始化为零值。如果初始化时提供的元素个数大于数组的长度则编译无法通过,提示错误。
package main import "fmt" func main() { arr1 := [5]int{1, 2, 3, 4} // 只提供了4个元素,但是数组长度是5,最后一个元素初始化为0值 fmt.Println(arr1) // [1 2 3 4 0] // arr2 := [5]int{1, 2, 3, 4, 5, 6} // fmt.Println(arr2) 报错:golang/code/test.go:7:31: index 5 is out of bounds (>= 5) } 省略长度初始化如果在初始化数组时使用...,编译器则会根据初始化的元素个数自动推导数组的长度。
package main import "fmt" func main() { arr := [...]int{1, 2, 3} fmt.Println(arr) // [1 2 3] fmt.Println("数组长度:", len(arr)) // 数组长度: 3 } 索引号初始化golang 中支持在声明数组时,为数组的某些特定索引位置指定初始值,语法格式如下:
[长度]类型{索引号1: 值1, 索引号2: 值2, ...}或者不指定长度,采用 ... 省略长度初始化,让编译器进行自动类型推导,语法格式如下:
[...]类型{索引号1: 值1, 索引号2: 值2, ...}例如:
package main import "fmt" func main() { numbers1 := [...]int{0: 10, 2: 30, 4: 50} fmt.Println(numbers1) // [10 0 30 0 50] fmt.Println(len(numbers1)) // 5 numbers2 := [...]int{0: 10, 5: 30, 4: 50} fmt.Println(numbers2) // [10 0 0 0 50 30] fmt.Println(len(numbers2)) // 6 }但是在使用过程中需要注意规则:
索引号必须是有效的:索引号必须是非负整数,并且不能超过数组的最大索引(即 长度 - 1)。否则,编译器会报错。未指定的元素默认为零值:如果某些索引位置没有指定值,这些位置的元素会被自动初始化为对应类型的零值。数组长度的确定:如果使用 ... 自动推断长度,编译器会根据最大的索引号加 1 来确定数组的长度。因此,初始化时必须确保索引号连续且不跳过。 内存布局数组在内存中以连续的方式存储其元素,每个元素的内存地址是连续的并且可以通过内存地址加上偏移量进行计算。
同样的,数组的大小在编译时就已经确定,因此其所占内存大小也是固定的。
package main import "fmt" func main() { arr := [5]int{1, 2, 3, 4, 5} fmt.Printf("数组地址:%p\n", &arr) // 数组地址:0xc00001c210 fmt.Printf("数组首元素地址:%p\n", &arr[0]) // 数组首元素地址:0xc00001c210 }从上面代码打印结果可以看出,数组的地址和数组首元素的地址相等,那么也就是说数组第一个元素的地址,即数组的起始地址。
下面通过两种方式获取数组中每个元素的内存地址:直接遍历数组获取每个元素的地址、通过偏移量计算数组中每个元素的地址。通过两种方式可以更直观地验证数组元素在内存中的连续性,代码如下所示:
直接遍历数组获取每个元素的地址 package main import ( "fmt" "unsafe" ) func main() { arr := [5]int{1, 2, 3, 4, 5} // 数组首元素地址 baseAddress := unsafe.Pointer(&arr[0]) size := unsafe.Sizeof(arr[0]) fmt.Println("数组元素字节大小:", size) // 数组元素字节大小: 8 // 元素下标:0,地址:0xc00001c210 // 元素下标:1,地址:0xc00001c218 // 元素下标:2,地址:0xc00001c220 // 元素下标:3,地址:0xc00001c228 // 元素下标:4,地址:0xc00001c230 // 遍历数组,打印每个元素的地址 for i := 0; i < len(arr); i++ { // 直接打印每个元素的地址 fmt.Printf("元素下标:%d,地址:%p\n", i, &arr[i]) } } 通过偏移量计算数组中每个元素的地址 package main import ( "fmt" "unsafe" ) func main() { arr := [5]int{1, 2, 3, 4, 5} // 数组首元素地址 baseAddress := unsafe.Pointer(&arr[0]) size := unsafe.Sizeof(arr[0]) fmt.Println("数组元素字节大小:", size) // 数组元素字节大小: 8 // 元素下标:0,地址:0xc00001c210 // 元素下标:1,地址:0xc00001c218 // 元素下标:2,地址:0xc00001c220 // 元素下标:3,地址:0xc00001c228 // 元素下标:4,地址:0xc00001c230 for i := 0; i < len(arr); i++ { offset := uintptr(i) * uintptr(size) address := unsafe.Pointer(uintptr(baseAddress) + offset) fmt.Printf("元素下标:%d,地址:%p\n", i, address) } } 内存分配golang 的运行时系统会根据数组的大小和作用域自动选择栈分配或堆分配:
小数组(小于64 KB):通常分配在栈上,以减少内存分配的开销。大数组或全局数组:分配在堆上,以避免栈溢出。局部变量:如果数组是函数的局部变量且大小较小,通常分配在栈上。全局变量:全局数组通常分配在堆上。golang 的内存分配机制基于垃圾回收(GC),不需要手动管理堆内存,简化了内存管理。
更多关于 golang 的内存机制请参考 《Golang 内存模型》章节。
常见操作 访问数组元素通过索引访问数组中的元素,数组的索引从0开始。
package main import "fmt" func main() { arr := [5]int{10, 20, 30, 40, 50} element := arr[2] // 访问索引为2的元素,值为30 fmt.Println(element) // 30 } 修改数组元素通过索引直接修改数组中的元素。
package main import "fmt" func main() { arr := [5]int{10, 20, 30, 40, 50} arr[2] = 300 // 修改索引为2的元素为300 fmt.Println(arr) // [10 20 300 40 50] } 删除数组元素在 golang 中,数组的大小是固定的,无法直接删除数组中的元素。通常需要借助切片的特性来实现类似“删除元素”的操作。
package main import "fmt" func main() { arr := []int{1, 2, 3, 4, 5} // 删除的目标索引 index := 2 arr = append(arr[:index], arr[index+1:]...) fmt.Println(arr) // [1 2 4 5] }或者采用数组拷贝删除法,创建一个新的数组,将原数组中不需要删除的元素拷贝到新数组中。代码如下所示:
package main import "fmt" func main() { array := [5]int{0, 1, 2, 3, 4} // 删除的目标索引 index := 2 newArray := [4]int{} for i, j := 0, 0; i < len(array); i++ { if i != index { newArray[j] = array[i] j++ } } fmt.Println(newArray) // [0 1 3 4] }或者直接通过 copy 函数将原数组中不需要删除的元素拷贝到新的切片中。代码如下所示:
package main import "fmt" func main() { arr := []int{1, 2, 3, 4, 5} // 删除的目标索引 index := 2 newarr := make([]int, len(arr)-1) // 创建一个新的切片 copy(newarr, arr[:index]) copy(newarr[index:], arr[index+1:]) fmt.Println(newarr) // [1 2 4 5] } 获取数组长度使用len() 或者 cap() 函数获取数组的长度。
package main import "fmt" func main() { arr := [5]int{10, 20, 30, 40, 50} fmt.Println(len(arr)) // 5 fmt.Println(cap(arr)) // 5 }在 golang 语言中,len 和 cap 是用于描述数组和切片的两个重要函数,但是两者之间的含义和用途有所不同,尤其是在切片中,这种区别更为明显。
对于数组而言,len 和 cap 的值是相同的,因为数组的长度是固定的,其容量就是数组的长度。在 《Golang 切片》章节中进行详细讨论
遍历数组可以使用 for 循环或 for range 循环遍历数组。
package main import "fmt" func main() { arr := [5]int{10, 20, 30, 40, 50} // 10 // 20 // 30 // 40 // 50 // 使用 for 循环 for i := 0; i < len(arr); i++ { fmt.Println(arr[i]) } // Index: 0, Value: 10 // Index: 1, Value: 20 // Index: 2, Value: 30 // Index: 3, Value: 40 // Index: 4, Value: 50 // 使用 for-range 循环 for index, value := range arr { fmt.Printf("index: %d, value: %d\n", index, value) } } 数组切片通过切片操作可以获取数组的一个子集。
package main import "fmt" func main() { arr := [5]int{10, 20, 30, 40, 50} sub := arr[1:3] // 获取从索引1到索引3(不包括3)的子数组 fmt.Println(sub) // [20 30] } 数组排序golang 中数组本身没有直接排序的方法,但是 sort 包下提供了强大的排序的功能,适用于基本类型(如int、string等)和复杂类型(如结构体数组)的排序。
基本类型排序 package main import ( "fmt" "sort" ) func main() { arr := [5]int{10, 20, 30, 40, 50} sort.Ints(arr[:]) // 将数组转换为切片后排序 fmt.Println(arr) // [10 20 30 40 50] }另外 sort 包中提供了 sort.Slice 函数允许通过自定义比较函数来实现排序逻辑。代码如下所示:
package main import ( "fmt" "sort" ) func main() { arr := [5]int{10, 20, 30, 40, 50} sort.Slice(arr[:], func(i, j int) bool { // Slice函数的第二个参数中传递自定义排序函数 return arr[i] > arr[j] // 降序排序 }) fmt.Println(arr) // [50 40 30 20 10] } 结构体类型排序 实现sort.Interface接口进行自定义排序 package main import ( "fmt" "sort" ) type Person struct { Name string Age int } type ByAgeDesc []Person func (a ByAgeDesc) Len() int { return len(a) } func (a ByAgeDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByAgeDesc) Less(i, j int) bool { return a[i].Age > a[j].Age } // 降序排序 func main() { people := [3]Person{ {"Alice", 30}, {"Bob", 25}, {"Charlie", 35}, } sort.Sort(ByAgeDesc(people[:])) fmt.Println(people) // 输出: [{Charlie 35} {Alice 30} {Bob 25}] }首先定义了一个 Person 结构体,包含两个字段:Name(字符串类型)和 Age(整型)。接着定义了一个新的类型ByAgeDesc,它是[]Person(Person结构体切片)的别名。这个类型将实现sort.Interface接口,从而可以使用sort.Sort函数对Person切片进行排序。
为了使用sort.Sort函数,需要实现sort.Interface接口的三个方法:Len、Swap和Less。
Len():返回切片的长度,即Person结构体切片中元素的数量。Swap():交换切片中索引为i和j的两个元素,用于在排序过程中交换元素的位置。Less():这个方法决定了排序的顺序,如果索引为 i 的元素的 Age 字段大于索引为 j 的元素的 Age 字段返回 true,否则返回 false最后在主函数中创建了一个 Person 结构体数组 people,并初始化了三个 Person 元素。调用 sort.Sort 函数,将 people 通过 [:] 转换为切片后再转换为 ByAgeDesc 类型,sort.Sort 会根据 ByAgeDesc 类型实现的 sort.Interface 接口方法(Len、Swap和Less)对切片进行排序。
直接切片排序直接操作切片,通过一个比较函数实现排序逻辑。代码简洁,适合快速实现排序需求。
package main import ( "fmt" "sort" ) type Person struct { Name string Age int } func main() { people := [4]Person{ {"Alice", 30}, {"Bob", 25}, {"Charlie", 35}, {"David", 30}, } sort.Slice(people[:], func(i, j int) bool { // 自定义排序函数 if people[i].Age == people[j].Age { return people[i].Name < people[j].Name // 名字升序 } return people[i].Age > people[j].Age // 年龄降序 }) fmt.Println(people) // [{Charlie 35} {Alice 30} {David 30} {Bob 25}] } 手动实现排序该小结省略,后续在 《数据结构与算法》中针对常见的排序算法进行总结。
数组合并在 golang 中由于数组的长度是固定的,因此不能直接合并两个数组。但可以通过切片和 append() 来实现类似的效果。代码如下所示:
package main import "fmt" func main() { arr1 := [3]int{1, 2, 3} arr2 := [2]int{4, 5} merged := append(arr1[:], arr2[:]...) fmt.Println(merged) // [1 2 3 4 5] } 数组反转golang 中数组没有直接提供反转元素的能力,通过遍历数组并交换元素来实现数组的反转。代码如下所示:
package main import ( "fmt" ) func main() { arr := [5]int{1, 2, 3, 4, 5} for i := 0; i < len(arr)/2; i++ { arr[i], arr[len(arr)-1-i] = arr[len(arr)-1-i], arr[i] } fmt.Println(arr) // [5 4 3 2 1] } 数组去重golang 中数组没有直接提供去重元素的能力,但是可以通过切片和映射来实现。代码如下所示:
package main import "fmt" func main() { arr := [5]int{1, 2, 2, 3, 4} unique := make(map[int]bool) // 创建一个映射 var result []int for _, v := range arr { if !unique[v] { // 只有当映射中不存在时,才会将元素加入到结果集中 unique[v] = true result = append(result, v) } } fmt.Println(result) // [1 2 3 4] }映射相关的知识点后续在 《Golang 映射》中进行总结。
数组过滤通过遍历数组并根据条件筛选元素。代码如下所示:
package main import "fmt" func main() { arr := [5]int{1, 2, 3, 4, 5} var filtered []int for _, v := range arr { if v%2 == 0 { // 只获取偶数的元素 filtered = append(filtered, v) } } fmt.Println(filtered) // [2 4] } 数组作为函数参数在 golang 中,数组作为函数参数时的行为与其他类型有所不同。数组是值类型,这意味着将数组作为参数传递给函数时,函数内部会接收到数组的一个副本,而不是原始数组的引用。因此,对数组副本的修改不会影响原始数组。
以下是一个示例代码,展示数组作为函数参数时的行为:
package main import "fmt" func modifyArray(arr [5]int) { arr[0] = 100 // 修改数组的第一个元素 fmt.Println(arr) // [100 2 3 4 5] } func main() { numbers := [5]int{1, 2, 3, 4, 5} fmt.Println(numbers) // [1 2 3 4 5] modifyArray(numbers) fmt.Println(numbers) // [1 2 3 4 5] }在 modifyArray 函数中,arr 是 numbers 数组的一个副本。对 arr 的修改不会影响原始数组 numbers。在 main 函数中,numbers 数组的内容保持不变,即使在 modifyArray 中对其副本进行了修改。
如果希望在函数中修改原始数组,可以通过以下两种方式实现:
指针传递数组通过传递数组的指针,函数可以直接修改原始数组。代码如下所示:
package main import "fmt" func modifyArray(arr *[5]int) { arr[0] = 100 // 修改数组的第一个元素 fmt.Println(*arr) // [100 2 3 4 5] } func main() { numbers := [5]int{1, 2, 3, 4, 5} fmt.Println(numbers) // [1 2 3 4 5] modifyArray(&numbers) fmt.Println(numbers) // [100 2 3 4 5] } 使用切片切片是引用类型,传递切片时,函数内部对切片的修改会直接影响原始切片。如果需要修改数组的内容,可以先将数组转换为切片,然后传递切片。代码如下所示:
package main import "fmt" func modifySlice(slice []int) { slice[0] = 100 // 修改切片的第一个元素 fmt.Println(slice) // [100 2 3 4 5] } func main() { numbers := [5]int{1, 2, 3, 4, 5} fmt.Println(numbers) // [1 2 3 4 5] modifySlice(numbers[:]) fmt.Println(numbers) // [100 2 3 4 5] } 多维数组在 golang 中,多维数组是通过数组的数组来实现的。维数组的每个维度都有固定的大小,同样的多维数组在内存中是连续存储。
多维数组很多特性、操作和一维数组一致,不再赘述。下面只针对重点进行详细说明。
声明和初始化声明一个 3x3 的二维数组:
var matrix [3][3]int声明一个 2x3x2 的三维数组:
var arr [2][3][2]int同样可以使用短声明的方式声明多维数组,让编译器进行自动类型推导:
package main import "fmt" func main() { matrix := [3][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } fmt.Println(matrix) // [[1 2 3] [4 5 6] [7 8 9]] } 访问和修改多维数组访问和修改多维数组的元素可以通过索引来进行操作。
package main import "fmt" func main() { matrix := [3][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } // 访问元素 fmt.Println(matrix[1][2]) // 6 // 修改元素 matrix[1][2] = 100 fmt.Println(matrix[1][2]) // 100 fmt.Println(matrix) // [[1 2 3] [4 5 100] [7 8 9]] } 遍历多维数组通过嵌套的 for 循环来遍历多维数组。例如:
package main import "fmt" func main() { matrix := [3][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } // matrix[0][0] = 1 // matrix[0][1] = 2 // matrix[0][2] = 3 // matrix[1][0] = 4 // matrix[1][1] = 5 // matrix[1][2] = 6 // matrix[2][0] = 7 // matrix[2][1] = 8 // matrix[2][2] = 9 // 遍历二维数组 for i := 0; i < len(matrix); i++ { for j := 0; j < len(matrix[i]); j++ { fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]) } } }通过嵌套的 for range 循环来遍历多维数组。例如:
package main import "fmt" func main() { matrix := [3][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } // matrix[0][0] = 1 // matrix[0][1] = 2 // matrix[0][2] = 3 // matrix[1][0] = 4 // matrix[1][1] = 5 // matrix[1][2] = 6 // matrix[2][0] = 7 // matrix[2][1] = 8 // matrix[2][2] = 9 // 遍历二维数组 for i, arr := range matrix { for j, e := range arr { fmt.Printf("matrix[%d][%d] = %d\n", i, j, e) } } }🌺🌺🌺撒花!
如果本文对你有帮助,就点关注或者留个👍 如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。
【九】Golang数组由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【九】Golang数组”