写在文章开头
近期笔者对Go语言大感兴趣,所以找了一些不错的文档过一下Go语言的基础知识并加以整理,私以为学习一门语言最快的上手方式就是找一些不错的示例进行实践并理解。而这篇文章就是笔者整理的关于数组的一些核心知识点和实践。
Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。
详解Go语言数组
数组内部实现
数组内部是一段连续的空间,所以CPU
通过局部性原理可以一次性加载连续一段的空间到缓存中,使得其访问效率非常高,唯一的缺陷就是地址空间大小固定不变,如果希望追加更多的元素就可能需要重新声明一个全新的数组。
数组的声明和初始化
对于数组的声明,其语法格式如下所示,可以看到变量名在前,类型在后,与Java数组声明不同的是,Go语言的数组声明格式为[size]type
,注意在Go语言
中严格要求size
是必传的,否者声明的就是切片,关于切片的知识点笔者会在后续的文章中推出,建议感兴趣的读者留意文章的联系方式实时关注笔者推送的系列专栏。
csharp
func main() {
//声明一个长度为5的数组,声明完成之后长度就不可变
var arr [5]int
fmt.Println(arr)
}
对应的输出结果如下,可以看到go语言的遍历都是由默认值的,以int为例,每个数组空间的值都是默认为0。
csharp
[0 0 0 0 0]
当然也可以你也可以使用字面量的方式进行初始化,Go语言会在编译时识别类型完成空间分配:
go
func main() {
//短变量直接显示初始化数组
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
}
输出结果如下,可以看到初始化后的值都显示的打印出来了:
csharp
[1 2 3 4 5]
当然,如果对于长度未知,但又明确大小空间的情况下,我们更推荐使用下面这种语法进行声明并初始化数组:
go
func main() {
//短变量直接显示初始化数组,长度由go编译器自行计算
arr := [...]int{1, 2, 3, 4, 5}
fmt.Println(arr)
}
对应的输出结果也和上一个初始化是一致的:
csharp
[1 2 3 4 5]
数组的访问
因为地址空间连续,所以通过索引访问数组非常高效,语法也和其他编程语言一样,通过arr[index]
即可访问:
go
func main() {
//短变量直接显示初始化数组,长度由go编译器自行计算
arr := [...]int{1, 2, 3, 4, 5}
//访问数组元素
fmt.Println("arr[1]:", arr[1])
}
对应的输出结果如下所示:
css
arr[1]: 2
指针数组
有时候我们希望数组中存放的是指针,我们可以通过字面量声明的方式创建并初始化一个指针,如下代码所示,其语法格式为:
- 创建变量名arr。
- := 赋值。
- 指定数组大小为5,即
[5]
。 - 重点来了,类型为
*int
,即整型指针。 - 显示初始化索引0位置的指针空间。
自此我们完成了一个大小为5的指针空间创建,如果我们希望对索引0的指针空间赋值,则需要* arr[0]
,*
的地址访问符
,这段代码含义就是访问arr[0]
指针的地址空间,然后设置为15。
go
func main() {
//短变量直接显示初始化数组,长度由go编译器自行计算
arr := [5]*int{0: new(int)}
//访问数组元素
*arr[0] = 15
fmt.Println("arr[0]的指针", arr[0], "arr[0]处的值", *arr[0])
}
基于上述代码我们给出一段Go语言
操作指针时底层的工作图解:
数组的赋值
其实数组也是一个值,所以也是可以进行赋值的,语法如下,可以看到可以通过:=
直接完成数组拷贝。
go
func main() {
//数组复制
arr0 := [...]int{1, 2, 3}
arr1 := arr0
fmt.Println(arr0)
fmt.Println(arr1)
}
从输出的结果来看,数组的复制操作是成功了:
csharp
[1 2 3]
[1 2 3]
此时可能有读者可能认为复制的是指针,所以我们直接通过取地址运算符&
打印两个数组的第一个元素的地址:
css
fmt.Println(&arr0[0])
fmt.Println(&arr1[0])
可以看到打印的地址是不一样的,所以:=数组赋值是对数组的所有元素进行拷贝操作的:
0xc000010138
0xc000010150
同样的指针数组也可以进行复制操作:
scss
func main() {
//指针数组复制
arr0 := [...]*int{new(int), new(int), new(int)}
*arr0[0] = 10
*arr0[1] = 20
*arr0[2] = 30
arr1 := arr0
fmt.Println("arr0[0]的指针", arr0[0], "arr0[0]处的值", *arr0[0])
fmt.Println("arr1[0]的指针", arr1[0], "arr1[0]处的值", *arr1[0])
}
但是需要注意的是指针复制的都是每个元素的指针,也就意味着我们修改任意一个数组元素指针所指向的地址空间,都会影响到另一个数组指针指向的值。
对此我们给出下面这样一段指针赋值后,操作新数组指针空间的代码:
scss
func main() {
//指针数组复制
arr0 := [...]*int{new(int), new(int), new(int)}
*arr0[0] = 10
*arr0[1] = 20
*arr0[2] = 30
arr1 := arr0
fmt.Println("arr0[0]的指针", arr0[0], "arr0[0]处的值", *arr0[0])
fmt.Println("arr1[0]的指针", arr1[0], "arr1[0]处的值", *arr1[0])
*arr1[0] = 100
fmt.Println("arr0[0]的指针", arr0[0], "arr0[0]处的值", *arr0[0])
fmt.Println("arr1[0]的指针", arr1[0], "arr1[0]处的值", *arr1[0])
}
从最终的输出结果可以看出修改一处的指针,确实影响到了另一个数组指针所指向的内存空间
css
arr0[0]的指针 0xc0000a6058 arr0[0]处的值 10
arr1[0]的指针 0xc0000a6058 arr1[0]处的值 10
arr0[0]的指针 0xc0000a6058 arr0[0]处的值 100
arr1[0]的指针 0xc0000a6058 arr1[0]处的值 100
其底层操作图解如下:
多维数组
go语言也同样支持多维数组的赋值,三种初始化的语法如下所示:
go
import "fmt"
func main() {
//二维数组的声明
var arr0 [2][2]int
fmt.Println(arr0)
//二维数组的全部初始化
arr1 := [2][2]int{{1, 2}, {3, 4}}
fmt.Println(arr1)
//二维数组的部分初始化
arr2 := [2][2]int{1: {3, 4}}
fmt.Println(arr2)
//二维数组的个性初始化
arr3 := [2][2]int{1: {1: 4}}
fmt.Println(arr3)
}
对应的输出结果如下:
lua
[[0 0] [0 0]]
[[1 2] [3 4]]
[[0 0] [3 4]]
[[0 0] [0 4]]
对于多维数组,支持赋值某一个维度的数组复制到别的数组上,如下所示我们创建了一个2维,每一维度空间大小为2的数组。然后将这个数组arr1的第一个维度的数组空间赋值给arr2:
对应的我们给出代码示例:
go
func main() {
arr := [2][2]int{{1, 2}, {3, 4}}
fmt.Println(arr)
//获取二维数组某个维度
arr2 := arr[0]
fmt.Println(arr2)
}
输出结果如下,可以看到arr2
拿到了arr[0]
这个维度的两个元素:
lua
[[1 2] [3 4]]
[1 2]
如何高效的在函数间传递数组
在调用方法时,传入数组是存在拷贝所有元素开销的,所以最佳实践是通过指针传递数组的地址给函数操作,避免调用函数时产生拷贝大数组的开销:
scss
func main() {
//初始化一个大数组
var arr [1e6]int
arr[0] = 18
arr[1] = 19
//通过取地址运算符&传递数组
foo(&arr)
fmt.Println("arr[0]修改后的结果:",arr[0])
}
// 入参为数组的指针
func foo(arr *[1e6]int) {
arr[0] = 20
}
输出结果:
css
arr[0]修改后的结果: 20
数组的缺陷
数组缺陷在上文中我们也提及了,空间大小固定,不支持动态扩容等动态添加和删除操作,所以我们下一篇就会讨论一个Go语言中对数组封装的引用------切片。
小结
本文通过大量的例子带读者们了解了:
- 数组声明和初始化的基本语法
- 如何访问数组
- 指针数组的声明和使用
- 普通数组和指针数组复制时的区别
- 多维数组的创建和不同维度的复制
- 如何高效传递数组
我是 sharkchili ,CSDN Java 领域博客专家 ,开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。
参考
《Go语言实战》
本文使用 markdown.com.cn 排版