Go容器
变量在一定程度上能满足函数及代码要求。如果编写一些复杂算法、结构和逻辑,就需要更复杂的类型来实现。这类复杂类型一般情况下具有各种形式的存储和处理数据的功能,将它们称为“容器”。
容器概述
在很多语言里,容器是以标准库的方式提供,你可以随时查看这些标准库的代码,了解如何创建,删除,维护内存。
- C语言没有提供容器封装,开发者需要自己根据性能需求进行封装,或者使用第三方提供的容器。
- C++语言的容器通过标准库提供,如vector对应数组,list对应双链表,map对应映射等。
- C#语言通过.NET框架提供,如List对应数组,LinkedList对应双链表,Dictionary对应映射。
- Lua语言的table实现了数组和映射的功能,Lua语言默认没有双链表支持。
数组——固定大小的连续空间
数组是一段固定长度的连续内存区域。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。
提示:C语言和Go语言中的数组概念完全一致。C语言的数组也是一段固定长度的内存区域,数组的大小在声明时固定下来。 下面演示一段C语言的数组:
int a[10]={ 0,1,2,3,4,5,6,7,8,9 };
int b[4];
此时,a和b类型都是int*,也就是整型指针。而C语言中,也可以使用malloc()函数动态地分配一段内存区域。C++语言中可以使用new()函数。例如:
int* a = (int*)malloc(10);
int* b = new int(4);
此时,a和b的类型也是int*。a和b此时分配内存的方式类似于Go语言的切片。 Go的数组和切片都是从C语言延续过来的设计。
声明数组
数组的写法如下:
var 数组变量名 [元素数量]T
其中
- 数组变量名:数组声明及使用时的变量名。
- 元素数量:数组的元素数量。可以是一个表达式,但最终通过编译期计算的结果必须是整型数值。也就是说,元素数量不能含有到运行时才能确认大小的数值。
- T可以是任意基本类型,包括T为数组本身。但类型为数组本身时,可以实现多维数组。
下面是一段数组的演示例子:
01 var team [3]string 02 team[0] = "hammer" 03 team[1] = "soldier" 04 team[2] = "mum" 05 06 fmt.Println(team)代码输出如下:
[hammer soldier mum]代码说明如下:
- 第1行,将team声明为包含3个元素的字符串数组。
- 第2~4行,为team的元素赋值。
初始化数组
数组可以在声明时使用初始化列表进行元素设置,参考下面的代码:
var team = [3]string{"hammer", "soldier", "mum"}
这种方式编写时,需要保证大括号后面的元素数量与数组的大小一致。但一般情况下,这个过程可以交给编译器,让编译器在编译时,根据元素个数确定数组大小。
var team = [...]string{"hammer", "soldier", "mum"}
“…”表示让编译器确定数组大小。上面例子中,编译器会自动为这个数组设置元素个数为3。
遍历数组——访问每一个数组元素
遍历数组也和遍历切片类似,看下面代码:
01 var team [3]string
02 team[0] = "hammer"
03 team[1] = "soldier"
04 team[2] = "mum"
05
06 for k, v := range team {
07 fmt.Println(k, v)
08 }
代码输出如下:
0 hammer
1 soldier
2 mum
代码说明如下:
- 第6行,使用for循环,遍历team数组,遍历出的键k为数组的索引,值v为数组的每个元素值。
- 第7行,将每个键值打印出来
切片(slice)——动态分配大小的连续空间
Go语言切片的内部结构包含地址、大小和容量。切片一般用于快速地操作一块数据集合。如果将数据集合比作切糕的话,切片就是你要的“那一块”。切的过程包含从哪里开始(这个就是切片的地址)及切多大(这个就是切片的大小)。容量可以理解为装切片的口袋大小。
从数组或切片生成新的切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:
slice [开始位置:结束位置]
- slice表示目标切片对象。
- 开始位置对应目标切片对象的索引。
- 结束位置对应目标切片的结束索引。
从数组生成切片,代码如下:
var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])
a是一个拥有3个整型元素的数组,被初始化数值1到3。使用a[1:2]可以生成一个新的切片。代码运行结果如下:
[1 2 3] [2]
[2]就是a[1:2]切片操作的结果。
从数组或切片生成新的切片拥有如下特性。
- 取出的元素数量为:结束位置-开始位置。
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用slice[len(slice)]获取。
- 当缺省开始位置时,表示从连续区域开头到结束位置。
- 当缺省结束位置时,表示从开始位置到整个连续区域末尾。
- 两者同时缺省时,与切片本身等效。
- 两者同时为0时,等效于空切片,一般用于切片复位。
- 根据索引位置取切片slice元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误。生成切片时,结束位置可以填写len(slice)但不会报错。
下面在具体的例子中熟悉切片的特性。
- 从指定范围中生成切片
切片和数组密不可分。如果将数组理解为一栋办公楼,那么切片就是把不同的连续楼层出租给使用者。出租的过程需要选择开始楼层和结束楼层,这个过程就会生成切片。示例代码如下:
01 var highRiseBuilding [30]int 02 03 for i := 0; i < 30; i++ { 04 highRiseBuilding[i] = i + 1 05 } 06 07 // 区间 08 fmt.Println(highRiseBuilding[10:15]) 09 10 // 中间到尾部的所有元素 11 fmt.Println(highRiseBuilding[20:]) 12 13 // 开头到中间的所有元素 14 fmt.Println(highRiseBuilding[:2])