您现在的位置是:网站首页> 编程资料编程资料

Go语言中的Array、Slice、Map和Set使用详解_Golang_

2023-05-26 461人已围观

简介 Go语言中的Array、Slice、Map和Set使用详解_Golang_

Array(数组)

内部机制

在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元素可以通过唯一的索引值访问,从 0 开始。

数组是很有价值的数据结构,因为它的内存分配是连续的,内存连续意味着可是让它在 CPU 缓存中待更久,所以迭代数组和移动元素都会非常迅速。

数组声明和初始化

通过指定数据类型和元素个数(数组长度)来声明数组。

复制代码 代码如下:

// 声明一个长度为5的整数数组
var array [5]int

一旦数组被声明了,那么它的数据类型跟长度都不能再被改变。如果你需要更多的元素,那么只能创建一个你想要长度的新的数组,然后把原有数组的元素拷贝过去。

Go 语言中任何变量被声明时,都会被默认初始化为各自类型对应的 0 值,数组当然也不例外。当一个数组被声明时,它里面包含的每个元素都会被初始化为 0 值。

一种快速创建和初始化数组的方法是使用数组字面值。数组字面值允许我们声明我们需要的元素个数并指定数据类型:

复制代码 代码如下:

// 声明一个长度为5的整数数组
// 初始化每个元素
array := [5]int{7, 77, 777, 7777, 77777}

如果你把长度写成 ...,Go 编译器将会根据你的元素来推导出长度:
复制代码 代码如下:

// 通过初始化值的个数来推导出数组容量
array := [...]int{7, 77, 777, 7777, 77777}

如果我们知道想要数组的长度,但是希望对指定位置元素初始化,可以这样:

复制代码 代码如下:

// 声明一个长度为5的整数数组
// 为索引为1和2的位置指定元素初始化
// 剩余元素为0值
array := [5]int{1: 77, 2: 777}

使用数组

使用 [] 操作符来访问数组元素:

复制代码 代码如下:

array := [5]int{7, 77, 777, 7777, 77777}
// 改变索引为2的元素的值
array[2] = 1

我们可以定义一个指针数组:

复制代码 代码如下:

array := [5]*int{0: new(int), 1: new(int)}

// 为索引为0和1的元素赋值
*array[0] = 7
*array[1] = 77


在 Go 语言中数组是一个值,所以可以用它来进行赋值操作。一个数组可以被赋值给任意相同类型的数组:

复制代码 代码如下:

var array1 [5]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2

注意数组的类型同时包括数组的长度和可以被存储的元素类型,数组类型完全相同才可以互相赋值,比如下面这样就不可以:
复制代码 代码如下:

var array1 [4]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2

// 编译器会报错
Compiler Error:
cannot use array2 (type [5]string) as type [4]string in assignment


拷贝一个指针数组实际上是拷贝指针值,而不是指针指向的值:
复制代码 代码如下:

var array1 [3]*string
array2 := [3]*string{new(string), new(string), new(string)}
*array2[0] = "Red"
*array2[1] = "Blue"
*array2[2] = "Green"

array1 = array2
// 赋值完成后,两组指针数组指向同一字符串

多维数组

数组总是一维的,但是可以组合成多维的。多维数组通常用于有父子关系的数据或者是坐标系数据:

复制代码 代码如下:

// 声明一个二维数组
var array [4][2]int

// 使用数组字面值声明并初始化
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}

// 指定外部数组索引位置初始化
array := [4][2]int{1: {20, 21}, 3: {40, 41}}

// 同时指定内外部数组索引位置初始化
array := [4][2]int{1: {0: 20}, 3: {1: 41}}

同样通过 [] 操作符来访问数组元素:

复制代码 代码如下:

var array [2][2]int

array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3

也同样的相同类型的多维数组可以相互赋值:

复制代码 代码如下:

var array1 = [2][2]int
var array2 = [2][2]int

array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3

array1 = array2

因为数组是值,我们可以拷贝单独的维:

复制代码 代码如下:

var array3 [2]int = array1[1]
var value int = array1[1][0]

在函数中传递数组

在函数中传递数组是非常昂贵的行为,因为在函数之间传递变量永远是传递值,所以如果变量是数组,那么意味着传递整个数组,即使它很大很大很大。。。

举个栗子,创建一个有百万元素的整形数组,在64位的机器上它需要8兆的内存空间,来看看我们声明它和传递它时发生了什么:

复制代码 代码如下:

var array [1e6]int
foo(array)
func foo(array [1e6]int) {
  ...
}

每一次 foo 被调用,8兆内存将会被分配在栈上。一旦函数返回,会弹栈并释放内存,每次都需要8兆空间。

Go 语言当然不会这么傻,有更好的方法来在函数中传递数组,那就是传递指向数组的指针,这样每次只需要分配8字节内存:

复制代码 代码如下:

var array [1e6]int
foo(&array)
func foo(array *[1e6]int){
  ...
}

但是注意如果你在函数中改变指针指向的值,那么原始数组的值也会被改变。幸运的是 slice(切片)可以帮我们处理好这些问题,来一起看看。

Slice(切片)

内部机制和基础

slice 是一种可以动态数组,可以按我们的希望增长和收缩。它的增长操作很容易使用,因为有内建的 append 方法。我们也可以通过 relice 操作化简 slice。因为 slice 的底层内存是连续分配的,所以 slice 的索引,迭代和垃圾回收性能都很好。

slice 是对底层数组的抽象和控制。它包含 Go 需要对底层数组管理的三种元数据,分别是:

1.指向底层数组的指针
2.slice 中元素的长度
3.slice 的容量(可供增长的最大值)

创建和初始化

Go 中创建 slice 有很多种方法,我们一个一个来看。

第一个方法是使用内建的函数 make。当我们使用 make 创建时,一个选项是可以指定 slice 的长度:

复制代码 代码如下:

slice := make([]string, 5)

如果只指定了长度,那么容量默认等于长度。我们可以分别指定长度和容量:

复制代码 代码如下:

slice := make([]int, 3, 5)

当我们分别指定了长度和容量,我们创建的 slice 就可以拥有一开始并没有访问的底层数组的容量。上面代码的 slice 中,可以访问3个元素,但是底层数组有5个元素。两个与长度不相干的元素可以被 slice 来用。新创建的 slice 同样可以共享底层数组和已存在的容量。

不允许创建长度大于容量的 slice:

复制代码 代码如下:

slice := make([]int, 5, 3)

Compiler Error:
len larger than cap in make([]int)

惯用的创建 slice 的方法是使用 slice 字面量。跟创建数组很类似,不过不用指定 []里的值。初始的长度和容量依赖于元素的个数:

复制代码 代码如下:

// 创建一个字符串 slice
// 长度和容量都是 5
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}

在使用 slice 字面量创建 slice 时有一种方法可以初始化长度和容量,那就是初始化索引。下面是个例子:

复制代码 代码如下:

// 创建一个字符串 slice
// 初始化一个有100个元素的空的字符串 slice
slice := []string{99: ""}

nil 和 empty slice

有的时候我们需要创建一个 nil slice,创建一个 nil slice 的方法是声明它但不初始化它:

复制代码 代码如下:

var slice []int

创建一个 nil slice 是创建 slice 最基本的方法,很多标准库和内建函数都可以使用它。当我们想要表示一个并不存在的 slice 时它变得非常有用,比如一个返回 slice 的函数中发生异常的时候。

创建 empty slice 的方法就是声明并初始化一下:

复制代码 代码如下:

// 使用 make 创建
silce := make([]int, 0)

// 使用 slice 字面值创建
slice := []int{}

empty slice 包含0个元素并且底层数组没有分配存储空间。当我们想要表示一个空集合时它很有用处,比如一个数据库查询返回0个结果。

不管我们用 nil slice 还是 empty slice,内建函数 append,len和cap的工作方式完全相同。

使用 slice

为一个指定索引值的 slice 赋值跟之前数组赋值的做法完全相同。改变单个元素的值使用 [] 操作符:

复制代码 代码如下:

slice := []int{10, 20, 30, 40, 50}
slice[1] = 25

我们可以在底层数组上对一部分数据进行 slice 操作,来创建一个新的 slice:

复制代码 代码如下:

// 长度为5,容量为5
slice := []int{10, 20, 30, 40, 50}

// 长度为2,容量为4
newSlice := slice[1:3]

在 slice 操作之后我们得到了两个 slice,它们共享底层数组。但是它们能访问底层数组的范围却不同,newSlice 不能访问它头指针前面的值。

计算任意 new slice 的长度和容量可以使用下面的公式:

复制代码 代码如下:

对于 slice[i:j] 和底层容量为 k 的数组
长度:j - i
容量:k - i

必须再次明确一下现在是两个 slice 共享底层数组,因此只要有一个 slice 改变了底层数组的值,那么另一个也会随之改变:

复制代码 代码如下:

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[1] = 35

改变 newSlice 的第二个元素的值,也会同样改变 slice 的第三个元素的值。

一个 slice 只能访问它长度范围内的索引,试图访问超出长度范围的索引会产生一个运行时错误。容量只可以用来增长,它只有被合并到长度才可以被访问:

复制代码 代码如下:

slice := []int{10, 20, 30, 40, 5

-六神源码网