写在前面
嗯,学习GO
,所以有了这篇文章
博文内容为《GO语言实战》
读书笔记之一
主要涉及 GO 学习环境配置数组相关知识
「 世上除了爹娘,再没有人是理所应当对你好的。 ——烽火戏诸侯《剑来》」
GO 学习环境配置
GO 环境下载:https://go.dev/doc/install
┌──[root@liruilongs.github.io]-[/]
└─$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.7.linux-amd64.tar.gz
┌──[root@liruilongs.github.io]-[/]
└─$ export PATH=$PATH:/usr/local/go/bin
┌──[root@liruilongs.github.io]-[/]
└─$ go version
go version go1.17.7 linux/amd64
┌──[root@liruilongs.github.io]-[/]
└─$
复制代码
数组的内部实现和基础功能
数组在GO
里是切片
和映射
的基础数据结构
,学过其他语言的小伙伴对数组应该都不陌生。基本上所有的语言都会有数组的概念。
数组(Array)
是一种线性
数据结构。它用一组连续
的内存空间
,来存储一组具有相同类型的数据
。
内部实现
在Go
语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块。数组存储的类型可以是内置类型,如整型
或者字符串
,也可以是某种结构类型
。
「数组因为其占用的内存是连续分配的。CPU 能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速迭代数组里的所有元素。数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离。」
关于上面这句话我个人理解:
数组结构上内存连续分配
,所以很容易的计算索引
(元素的相对地址),等差数列
,同时内存连续
,可以很好的使用CUP的缓存
,当CPU
访问首地址
时,会自动的从内存中加载当前数组其他元素到CUP Cache
,加载多少由CPU Cache Line
决定,所以CPU
能把正在使用的数据缓存更久的时间。当内存不连续时,就无法读到 CPU Cache
,只能重复的从内存读取数据元素,也就不能充分利用到了CPU Cache
的特性
通过索引可以快速的迭代数组元素,如果用 a 代表数组的首地址,a[0]
就是偏移量为0
的位置,也就是首地址,a[k]
就表示偏移量k
个type_size
的位置,所以计算a[k]
的内存地址只需要用这个公式a[k]_address = base_address + k * type_size
,所以说可以通过索引确认内存地址快速迭代,理论上时间复制度为常量级。
声明和初始化
声明数组
时需要指定内部存储的数据的类型
,以及需要存储的元素的数量
arrays := [5]int{10,12,13}
复制代码
array := [...]int{10, 20,30, 40, 50}
复制代码
array := [5]int{1: 10, 2: 20}
复制代码
使用数组
内存布局是连续的,所以数组是效率很高的数据结构,在访问数组里任意元素的时候,使用[]
运算符
//声明一个包含 5 个元素的整型数组
array := [5]int{10, 20, 30, 40, 50}
// 修改索引为 2 的元素的值
array[2] = 35
复制代码
声明一个所有元素都是指针的数组
。使用*运算符
就可以访问元素指针所指向的值
// 声明包含 5 个元素的指向整数的数组
// 用整型指针初始化索引为 0 和 1 的数组元素
array := [5]*int{0: new(int), 1: new(int)}
// 为索引为 0 和 1 的元素赋值
*array[0] = 10
*array[1] = 20
复制代码
在 Go
语言里,数组是一个值。这意味着数组可以用在赋值操作中。变量名代表整个数组,同样类型的数组可以赋值给另一个数组
// 声明第一个包含 5 个元素的字符串数组
var array1 [5]string
// 声明第二个包含 5 个元素的字符串数组
// 用颜色初始化数组
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 把 array2 的值复制到 array1
array1 = array2
复制代码
package main
import "fmt"
func main() {
fmt.Println("你好,世界")
// 声明第一个包含 4 个元素的字符串数组
var array1 [4]string
// 声明第二个包含 5 个元素的字符串数组
// 使用颜色初始化数组
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 将 array2 复制给 array1
array1 = array2
}
复制代码
go vet
检查
┌──[root@liruilongs.github.io]-[/usr/local/go/src/demo]
└─$go fmt array.go
array.go
┌──[root@liruilongs.github.io]-[/usr/local/go/src/demo]
└─$vim array.go
┌──[root@liruilongs.github.io]-[/usr/local/go/src/demo]
└─$go vet array.go
# command-line-arguments
vet: ./array.go:15:11: cannot use array2 (variable of type [5]string) as [4]string value in assignment
┌──[root@liruilongs.github.io]-[/usr/local/go/src/demo]
└─$
复制代码
多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。多维数组很容易管理具有父子关系的数据或者与坐标系相关联的数据
// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为 1 个和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
复制代码
// 声明一个 2×2 的二维整型数组
var array [2][2]int
// 设置每个元素的整型值
array[0][0] = 10
复制代码
只要类型一致,就可以将多维数组互相赋值
// 声明两个不同的二维整型数组
var array1 [2][2]int
var array2 [2][2]int
// 为每个元素赋值
array2[0][0] = 10
array2[0][1] = 20
array2[1][0] = 30
array2[1][1] = 40
复制代码
// 将 array2 的值复制给 array1
array1 = array2
复制代码
// 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
var array3 [2]int = array1[1]
// 将外层数组的索引为 1、内层数组的索引为 0 的整型值复制到新的整型变量里
var value int = array1[1][0]
复制代码
在函数间传递数组
根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的
。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。
// 声明一个需要 8 MB 的数组,创建一个包含 100 万个 int 类型元素的数组
var array [1e6]int
// 将数组传递给函数 foo
foo(array)
// 函数 foo 接受一个 100 万个整型值的数组
func foo(array [1e6]int) {
...
}
复制代码
每次函数foo
被调用时,必须在栈上分配8 MB
的内存
还有一种更好且更有效的方法来处理这个操作。可以只传入指向数组的指针,这样只需要复制8
字节的数据而不是8 MB
的内存数据到栈上
// 分配一个需要 8 MB 的数组
var array [1e6]int
// 将数组的地址传递给函数 foo
foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针
func foo(array *[1e6]int) {
...
}
复制代码
将数组的地址传入函数,只需要在栈上分配 8 字节的内存给指针就可以.
评论