完整教程:【go 】数组的多种初始化方式与操作

在 Go 语言中,数组是一种固定长度的数据结构,用于存储相同类型的元素。以下是 Go 中数组的多种初始化方式,结合搜索结果整理如下:

(一)使用 var 关键字声明并初始化数组

使用 var 关键字声明数组时,可以指定数组的长度,数组的元素会被自动初始化为对应类型的零值。例如:

var arr [5]int // 声明一个长度为5的整型数组,元素默认初始化为0

这种方式适用于需要明确数组长度且元素初始值为零值的场景。

(二)声明时直接初始化数组

在声明数组的同时,可以直接指定数组的元素值。例如:

var arr = [5]int{
1, 2, 3, 4, 5
} // 声明并初始化一个长度为5的整型数组

这种方式适用于已知数组元素值的场景。

(三)使用短变量声明初始化数组

通过短变量声明 := 可以更简洁地初始化数组。例如:

arr := [5]int{
1, 2, 3, 4, 5
} // 使用短变量声明并初始化数组

这种方式适用于函数内部或需要快速声明和初始化数组的场景。

(四)部分初始化数组

在初始化数组时,可以只指定部分元素的值,未指定的元素会被初始化为零值。例如:

arr := [5]int{
1, 2
} // 初始化前两个元素为1和2,其余为0

这种方式适用于需要部分元素初始化的场景。

(五)使用 ... 自动推断数组长度

在初始化数组时,可以使用 ... 让编译器根据初始化值的个数自动推断数组长度。例如:

arr := [...]int{
1, 2, 3, 4, 5
} // 自动推断数组长度为5

这种方式适用于数组长度由初始化值决定的场景。

(六)指定索引初始化数组

在初始化数组时,可以指定某些索引位置的值,未指定的索引位置会被初始化为零值。例如:

arr := [5]int{
1: 10, 3: 30
} // 初始化索引1为10,索引3为30,其余为0

这种方式适用于需要指定特定索引位置值的场景。

总结

Go 语言提供了多种数组的初始化方式,包括使用 var 关键字、短变量声明、部分初始化、自动推断长度以及指定索引初始化等。这些方式可以根据实际需求灵活选择,以满足不同场景下的使用需求。


数组元素操作

在 Go 语言中,数组是一种固定长度同类型的集合。我们可以对数组元素进行访问、修改、遍历、拷贝等操作。下面详细介绍数组元素的常见操作方法。

一、声明和初始化数组

1. 声明数组

var arr [5]int // 声明一个长度为 5 的 int 数组,默认值为 0

2. 初始化数组

arr := [3]int{
1, 2, 3
} // 声明并初始化

也可以让编译器推断长度:

arr := [...]int{
1, 2, 3, 4
} // 长度自动推断为 4

二、访问数组元素

通过索引访问数组元素,索引从 0 开始:

arr := [3]int{
10, 20, 30
}
fmt.Println(arr[0]) // 输出 10
fmt.Println(arr[1]) // 输出 20

⚠️ 注意:索引超出范围会导致 panic:

fmt.Println(arr[3]) // panic: index out of range [3] with length 3

三、修改数组元素

通过索引直接赋值即可修改元素:

arr := [3]int{
10, 20, 30
}
arr[1] = 99
fmt.Println(arr) // 输出 [10 99 30]

四、遍历数组元素

1. 使用 for 循环

for i := 0; i <
len(arr); i++ {
fmt.Println(arr[i])
}

2. 使用 range(推荐)

for i, v := range arr {
fmt.Printf("index: %d, value: %d\n", i, v)
}

五、数组长度

使用 len() 获取数组长度:

arr := [3]int{
1, 2, 3
}
fmt.Println(len(arr)) // 输出 3

六、数组拷贝

Go 数组是值类型,赋值时会完整拷贝整个数组:

a := [3]int{
1, 2, 3
}
b := a
b[0] = 99
fmt.Println(a) // [1 2 3]
fmt.Println(b) // [99 2 3]

七、多维数组

Go 支持多维数组,例如二维数组:

var matrix [2][3]int
matrix[0] = [3]int{
1, 2, 3
}
matrix[1] = [3]int{
4, 5, 6
}
fmt.Println(matrix)
// 输出: [[1 2 3] [4 5 6]]

八、数组 vs 切片(slice)

特性数组(Array)切片(Slice)
长度固定动态变化
传递方式值拷贝引用传递
声明方式[n]T[]T
常用场景已知固定长度数据动态长度、灵活操作

九、示例代码汇总

package main
import "fmt"
func main() {
// 声明并初始化
arr := [3]int{
10, 20, 30
}
// 访问元素
fmt.Println(arr[0]) // 10
// 修改元素
arr[1] = 99
fmt.Println(arr) // [10 99 30]
// 遍历
for i, v := range arr {
fmt.Printf("index: %d, value: %d\n", i, v)
}
// 数组拷贝
b := arr
b[0] = 100
fmt.Println(arr) // [10 99 30]
fmt.Println(b) // [100 99 30]
}

十、总结

  • Go 数组是固定长度、同类型的集合。
  • 通过索引访问和修改元素。
  • 使用 range 遍历数组更方便。
  • 数组是值类型,赋值或传参时拷贝整个数组。
  • 如需动态长度,应使用切片(slice)。

存储不同类型的元素

在 Go 语言中,数组(array)只能存储相同类型的元素。这是 Go 类型系统的一个基本特性,它要求数组中的所有元素必须是同一类型,以确保类型安全和内存布局的一致性。


一、为什么数组不能存储不同类型的元素?

Go 是静态类型语言,数组的类型由其元素类型长度共同决定。例如:

var arr [3]int // 这是一个包含 3 个 int 类型元素的数组

如果你尝试将不同类型的值放入数组,比如:

arr[0] = 10
arr[1] = "hello" // 编译错误:cannot use "hello" (type untyped string) as type int

就会在编译时报错,因为类型不匹配。


二、如何实现存储不同类型的数据?

虽然 Go 数组不能存储不同类型,但你可以通过以下方式模拟存储多种类型的数据:

1. 使用 interface{}(空接口)

interface{} 是 Go 中的万能类型,可以表示任何类型。你可以声明一个 []interface{} 类型的切片(slice)来存储不同类型的值:

var data []interface{
} = []interface{
}{
42,
"hello",
3.14,
true,
}
for _, v := range data {
fmt.Println(v)
}

注意:使用 interface{} 会失去类型安全,访问时需要类型断言(type assertion):

val := data[1].(string) // 断言为 string 类型
fmt.Println(val)
2. 使用结构体(struct)

如果你希望更结构化地存储不同类型的数据,可以定义一个结构体,将不同类型作为字段:

type Item struct {
ID int
Name string
Price float64
}
items := [2]Item{
{
1, "Apple", 2.5
},
{
2, "Banana", 1.8
},
}
3. 使用联合体(C 风格)—— 不推荐

Go 没有像 C 语言那样的联合体(union),也不鼓励使用 unsafe 包来模拟,因为会破坏类型安全。

三、数组 vs 切片 vs 接口切片

类型是否可存多种类型特点
[n]T❌ 否固定长度,类型必须一致
[]T❌ 否动态长度,类型必须一致
[]interface{}✅ 是动态长度,可存任意类型

四、总结

  • Go 数组不能存储不同类型,必须保持元素类型一致。
  • 若需存储多种类型,推荐使用 []interface{} 或自定义结构体。
  • 使用 interface{} 时要注意类型断言,避免运行时错误。

使用数组作为函数参数示例

在 Go 语言中,数组可以作为函数参数传递,但由于数组是值类型,这意味着传递时会复制整个数组,而不是传递引用。下面我们详细讲解如何使用数组作为函数参数,包括传值、传指针、以及一些注意事项。

一、传值方式(默认行为)

1. 基本语法

当数组作为函数参数时,默认是值传递,即整个数组会被复制一份。

package main
import "fmt"
func modifyArray(arr [3]int) {
arr[0] = 100
fmt.Println("Inside function:", arr) // [100 2 3]
}
func main() {
arr := [3]int{
1, 2, 3
}
modifyArray(arr)
fmt.Println("Outside function:", arr) // [1 2 3]
}

2. 特点

  • 函数内对数组的修改不会影响原数组。
  • 如果数组很大,复制整个数组会影响性能。

二、传指针方式(避免复制)

1. 基本语法

为了避免复制整个数组,可以传递数组的指针:

package main
import "fmt"
func modifyArrayByPointer(arr *[3]int) {
(*arr)[0] = 100
fmt.Println("Inside function:", *arr) // [100 2 3]
}
func main() {
arr := [3]int{
1, 2, 3
}
modifyArrayByPointer(&arr)
fmt.Println("Outside function:", arr) // [100 2 3]
}

2. 特点

  • 传递的是数组的指针,不会复制整个数组。
  • 函数内对数组的修改会影响原数组。
  • 适用于大数组,提升性能。

三、使用切片代替数组(推荐)

在 Go 中,更常见的做法是使用**切片(slice)**作为函数参数,因为切片是引用类型,传递的是底层数组的指针,不会复制整个数组。

1. 基本语法

package main
import "fmt"
func modifySlice(s []int) {
s[0] = 100
fmt.Println("Inside function:", s) // [100 2 3]
}
func main() {
arr := [3]int{
1, 2, 3
}
slice := arr[:] // 将数组转换为切片
modifySlice(slice)
fmt.Println("Outside function:", arr) // [100 2 3]
}

2. 特点

  • 切片是引用类型,传递的是底层数组的指针。
  • 函数内对切片的修改会影响原数组。
  • 适用于动态长度的数据,更灵活。

四、多维数组作为参数

多维数组也可以作为函数参数,但需要明确指定每一维的长度(除了第一维)。

1. 基本语法

package main
import "fmt"
func printMatrix(matrix [3][2]int) {
for i := 0; i <
3; i++ {
for j := 0; j <
2; j++ {
fmt.Printf("%d ", matrix[i][j])
}
fmt.Println()
}
}
func main() {
matrix := [3][2]int{
{
1, 2
},
{
3, 4
},
{
5, 6
},
}
printMatrix(matrix)
}

2. 特点

  • 多维数组的长度必须严格匹配函数参数的声明。
  • 如果长度不固定,建议使用切片的切片([][]int)。

五、总结

方式特点适用场景
传值复制整个数组,函数内修改不影响原数组小数组,不希望原数组被修改
传指针传递数组指针,函数内修改影响原数组大数组,避免复制
传切片引用传递,函数内修改影响原数组动态长度数据,推荐使用
多维数组需明确指定长度,灵活性较低固定维度的矩阵数据

六、推荐做法

  1. 优先使用切片:除非明确需要固定长度的数组,否则推荐使用切片。
  2. 大数组传指针:如果必须使用数组且数组较大,建议传递指针。
  3. 避免多维数组:如果维度不固定,建议使用切片的切片([][]int)。

数组进行排序

在 Go 语言中,可以使用标准库中的 sort 包对数组进行排序。不过需要注意的是,Go 的 sort 包主要针对的是切片(slice),而不是数组(array)。因此,通常我们会先将数组转换为切片,再进行排序操作。
下面我们详细讲解如何对数组进行排序,包括升序、降序、自定义排序等场景。

一、基本排序(升序)

1. 将数组转为切片并排序

Go 的 sort.Intssort.Float64ssort.Strings 等函数只能对切片操作,因此我们需要先将数组转为切片。

package main
import (
"fmt"
"sort"
)
func main() {
arr := [5]int{
5, 2, 6, 3, 1
}
slice := arr[:] // 转为切片
sort.Ints(slice) // 升序排序
fmt.Println("Sorted slice:", slice) // [1 2 3 5 6]
fmt.Println("Original array:", arr) // [1 2 3 5 6]
}

注意:由于切片是对底层数组的引用,排序后原数组也会被修改。


二、降序排序

Go 的 sort 包没有直接提供降序排序的函数,但可以使用 sort.Reverse 实现降序排序。

package main
import (
"fmt"
"sort"
)
func main() {
arr := [5]int{
5, 2, 6, 3, 1
}
slice := arr[:]
sort.Sort(sort.Reverse(sort.IntSlice(slice))) // 降序排序
fmt.Println("Sorted slice (desc):", slice) // [6 5 3 2 1]
}

三、自定义排序

如果数组元素是结构体,或者需要按照自定义规则排序,可以实现 sort.Interface 接口。

1. 示例:按结构体字段排序

package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
type ByAge []Person
func (a ByAge) Len() int {
return len(a)
}
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i]
}
func (a ByAge) Less(i, j int) bool {
return a[i].Age < a[j].Age
}
func main() {
people := []Person{
{
"Alice", 30
},
{
"Bob", 25
},
{
"Charlie", 35
},
}
sort.Sort(ByAge(people)) // 按 Age 升序排序
fmt.Println("Sorted by age:", people)
// Output: [{Bob 25} {Alice 30} {Charlie 35}]
}

四、稳定性排序

Go 的 sort 包提供了稳定排序 sort.Stable(),保证相等元素的相对顺序不变。

package main
import (
"fmt"
"sort"
)
func main() {
arr := [5]int{
5, 2, 6, 2, 1
}
slice := arr[:]
sort.Stable(sort.IntSlice(slice)) // 稳定排序
fmt.Println("Stable sorted slice:", slice) // [1 2 2 5 6]
}

五、总结

排序方式方法适用场景
升序排序sort.Ints() / sort.Float64s() / sort.Strings()基本类型排序
降序排序sort.Reverse(sort.IntSlice())需要降序时
自定义排序实现 sort.Interface结构体或复杂规则排序
稳定排序sort.Stable()需要保持相等元素顺序

六、注意事项

  1. 数组 vs 切片:Go 的 sort 包主要针对切片,数组需要先转为切片。
  2. 引用影响:切片排序会影响原数组,因为切片是对数组的引用。
  3. 性能考虑:对于大型数组,排序是 O(n log n) 的时间复杂度,合理使用。

posted @ 2025-08-07 10:43  yjbjingcha  阅读(54)  评论(0)    收藏  举报