3.4.7 复合类型的变量

前面介绍的几种类型的变量叫作变量的“基本类型”,变量在这几种基本类型的基础上还可组合形成“复合类型”。在一般计算机语言中常用的复合类型的变量有数组和字典两种,其中数组又可以分为固定长度的数组和可变长度的数组。Go语言中也具有数组类型,但更多是通过类似可变长度数组的称作切片(slice)的类型来表示一般语言中的数组类型,而映射(map)则被用来表示字典这种变量类型。

1. 切片

切片(slice)用于在Go语言中表示其他语言中常称作数组(array)的复合数据类型,而“数组”顾名思义就是表示一组数值。Go语言的切片类型融合了其他语言中的静态(固定长度)数组和动态(可变长度)数组的一些特性,更轻量和高效一些。

代码3-16显示了Go语言中声明并使用切片类型变量的一般方法。

  var ary []int

  ary = make([]int, 3)

  ary[0] = 2
  ary[1] = 4
  ary[2] = 8

  fmt.Println(ary)

代码3-16 切片类型的基本用法

其中,var ary []int这条语句声明了一个切片类型的变量ary,并且这个切片中的每一个元素都是int类型的数值,可以简单地说这条语句“声明了一个int类型的切片”。方括号“[]”加上某个基本数据类型后,可以表示某种类型的切片,例如var ary []string可以声明一个字符串类型的切片,其中每一个元素都是一个字符串。

声明切片类型的变量并不能为其分配所需的内存,如果直接使用会产生错误,因此第二条语句立即使用了Go语言中常见的内置函数make为变量分配所需的内存。ary = make([]int, 3)的意思是为变量ary分配一个可以容纳3个int类型数据的切片,或者说令变量ary等于一个容量为3的int类型切片。我们也常将切片的容量称为该切片的“长度”,它表达了该切片中可以存放的数值项(元素)的最大个数。

如果用变量声明和赋值的简化形式,上述两条语句可以用下面等价的一条语句来表示,效果是相同的。

ary := make([]int, 3)

切片中表示不同位置的值是用“索引”来进行的,索引从0开始(注意,不是从1开始),然后按1,2,3…顺序递增,索引在变量名称之后紧跟着用方括号括起来,例如,ary[0]表示切片变量ary的第1项。因此,make语句之后的三条语句是对切片中每一个索引位置的数值项进行赋值。ary[0] = 2是将ary这个int类型的切片变量的第1项(索引值为0)赋值为2,ary[1] = 4是将ary的第2项(索引值为1)赋值为4,ary[2] = 8是将ary这个int类型的切片变量的第3项(索引值为2)赋值为8。

编译并执行该代码(注意要自行加上框架代码),得到的结果如下:

[2 4 8]

可以看出用fmt.Println函数输出变量ary的结果是一个切片(输出信息中用方括号括起来表示是切片),并且数据项有3项,按顺序分别是2、4、8,和预期一致。

切片变量可以用“截取”的方式获得它的一部分,称作“子切片”,子切片本身也是一个切片。截取子切片的方式见代码3-17。

  var ary []int

  ary = make([]int, 3)

  ary[0] = 2
  ary[1] = 4
  ary[2] = 8

  fmt.Println(ary)

  var subAry []int

  subAry = ary[1:3]

  fmt.Println(subAry)

代码3-17 从切片中截取子切片

代码3-17中用subAry = ary[1:3] 这条语句截取切片变量ary中的后两个数字,成为一个新的切片(也就是ary的子切片),并赋值给切片变量subAry。注意其中方括号内用冒号“:”分隔开的两个数字分别代表从切片ary中开始截取的索引和结束截取的索引加1的数值。因此,由于我们要截取后两个数字索引分别为1和2,则方括号内的起始和结束索引值分别为1和3。代码3-17的执行结果为:

[2 4 8]
[4 8]

可以看出,后面输出的子切片subAry内确实是切片ary的后两个数值。

另外,在切片截取中可以省略起始索引值,这时候代表从切片第一个数值开始截取,例如ary[:3]代表从第一个数值截取到索引为2的数值,等同于ary[0:3]。与此类似,也可以省略结束索引,代表截取到切片最后一个数值。同时省略这两个索引值也是可以的,这时候代表截取整个切片中的所有元素,即ary[:]的效果相当于复制了一份ary切片。

Go语言向切片中增加数值项需要用到内置函数append,例如代码3-17中如果增加一句ary=append(ary,9),将在切片ary的末尾增加一个整数9,此时ary将变成具有4个数值项的切片。

2. 数组

Go语言中也有固定长度的数组类型(array),但相较于切片来说不太常用。数组类型可以看作是固定长度的切片,与切片的不同之处主要有两点:一是定义的时候要有指定的长度,例如var a [8]int这条语句定义了一个长度固定为8的int类型的数组;二是数组无法像切片那样添加数值项,因为数组的长度是固定的,如果对数组使用append函数则会发生错误。数组可以像切片一样进行截取操作,但是截取之后也就变成了切片类型。数组类型在声明后就被分配了空间,无须再使用make函数来分配。

3. 映射

映射(map)用于在Go语言中表示其他语言中常称作字典(dictionary)的复合数据类型。与我们日常使用的外语字典相同,而与切片数组使用顺序数字进行索引不同,字典类型一般是由字符串进行索引的,这个字符串被称为“键(key)”,而由该键索引的位置的具体数据就叫作“值(value)”。对一个映射变量来说,一个“键”就对应着该映射变量中该位置的一个“值”,因此我们常说映射(字典)是由很多个“键-值对”(key-value pair)组成的。实际上Go语言的映射类型中,键和值的类型并不仅仅是字符串,可以是大多数基本类型。

  var map1 map[string]string

  map1 = make(map[string]string, 3)

  map1["Name"] = "张三"
  map1["Gender"] = " 男"

  fmt.Println(map1)

  map2 :=  make(map[int]string, 3)

  map2[1] = "李四"
  map2[5] = "女"

  fmt.Println(map2)

上述代码中定义了一个映射类型的变量map1,声明的变量类型是map[string]string,其中方括号内指定的是键的类型,也就是索引的类型,这里指定的是字符串类型;方括号后紧跟的是该映射变量中每个值的类型,这里指定的也是字符串类型。与切片类型类似,映射类型也需要在变量声明后用make函数分配空间,make函数的第一个参数是该映射的类型map[string]string;第二个参数是一个预估的数字,用来表明为它保留多少个数值项的空间,如果实际数据项超过这个空间,则需要重新分配该变量的内存。这个参数值如果无法预估,可以暂时填为0。

后面我们还用简化方法定义了另一个映射变量map2,它的索引类型是int,值类型仍然是string。

映射变量在给其中的数据项赋值时与切片类似,都是用方括号表示索引,在方括号中填入索引即可。不同的是映射变量的索引不一定是数字,即使是数字也不是按顺序索引的,注意map1中因为索引类型是字符串,所以方括号内的索引值是双引号括起来的字符串值。

执行这段程序可以得到下面的输出结果:

map[Name:张三 Gender:男]
map[1:李四 5:女]

fmt.Println函数会将映射变量按照上述格式输出,可以看到map1变量中Name键的值是“张三”,Gender键的值是“男”;而map2变量中数字1的键对应的值是“李四”,数字5的键对应的值是“女”。用户需要理解映射变量的这种输出格式,也需要注意变量map2中虽然以数字作为索引,但与切片不同,并不需要数字是连续的,map2中就只有数字1和5两项索引。

Go语言中常用的复合类型还有通道(channel)类型,用关键字chan来定义,主要用于并发编程中共享数据。通道类型将在本书后面有关并发编程的章节中详细介绍。

另外,Go语言中还可以自定义数据类型,也将在后面的章节中进行介绍。