本文共 7228 字,大约阅读时间需要 24 分钟。
[32] byte // 长度为 32的数组,每个元素为一个字节[2*N] struct { x, y int32 } // 复杂类型数组[1000]*float64 // 指针数组[3][5] int // 二维数组[2][2][2] float64 // 等同于[2]([2]([2]float64))从以上类型也可以看出,数组可以是多维的,比如[3][5]int就表达了一个3行5列的二维整型数组,总共可以存放15个整型元素。 在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用Go语言的内置函数len() 来获取。下面是一个获取数组arr元素个数的写法:
for i := 0; i < len(array); i++ { fmt.Println("Element", i, "of array is", array[i])}for i := 0; i < len(array); i++ { fmt.Println("Element", i, "of array is", array[i])}Go语言还提供了一个关键字range,用于便捷地遍历容器中的元素。当然,数组也是range的支持范围。上面的遍历过程可以简化为如下的写法:
for i, v := range array { fmt.Println("Array element[", i, "]=", v)}在上面的例子里可以看到, range具有两个返回值,第一个返回值是元素的数组下标,第二个返回值是元素的值。 2. 值类型 需要特别注意的是,在Go语言中数组是一个值类型( value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。 下面用例子来说明这一特点:
package mainimport "fmt"func modify(array [10] int) { array[0] = 10 // 试图修改数组的第一个元素 fmt.Println("In modify(), array values:", array)}func main() { array := [5] int{1,2,3,4,5} // 定义并初始化一个数组 modify(array) // 传递给一个函数,并试图在函数体内修改这个数组内容 fmt.Println("In main(), array values:", array)}该程序的执行结果为:
In modify(), array values: [10 2 3 4 5]In main(), array values: [1 2 3 4 5]从执行结果可以看出, 函数modify() 内操作的那个数组跟main() 中传入的数组是两个不同的实例。那么,如何才能在函数内操作外部的数据结构呢?我们将在2.3.6节中详细介绍如何用数组切片功能来达成这个目标。
package mainimport "fmt"func main() { // 先定义一个数组 var myArray [10] int = [10] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 基于数组创建一个数组切片 var mySlice [] int = myArray[:5] fmt.Println("Elements of myArray: ") for _, v := range myArray { fmt.Print(v, " ") } fmt.Println("\nElements of mySlice: ") for _, v := range mySlice { fmt.Print(v, " ") } fmt.Println()}运行结果为:Elements of myArray:1 2 3 4 5 6 7 8 9 10Elements of mySlice:1 2 3 4 5已经注意到, Go语言支持用myArray[first:last] 这样的方式来基于数组生成一 个数组切片,而且这个用法还很灵活,比如下面几种都是合法的。 基于myArray的所有元素创建数组切片:
mySlice = myArray[:]基于myArray的前5个元素创建数组切片:
mySlice = myArray[:5]基于从第5个元素开始的所有元素创建数组切片:
mySlice = myArray[5:] 直接创建 并非一定要事先准备一个数组才能创建数组切片。 Go语言提供的内置函数make() 可以用于 灵活地创建数组切片。下面的例子示范了直接创建数组切片的各种方法。 创建一个初始元素个数为5的数组切片,元素初始值为0:
mySlice1 := make([] int, 5)创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
mySlice2 := make([] int, 5, 10)直接创建并初始化包含5个元素的数组切片:
mySlice3 := [] int{1, 2, 3, 4, 5}当然,事实上还会有一个匿名数组被创建出来,只是不需要我们来操心而已。 2. 元素遍历 操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用len() 函数获取元素个数,并支持使用range关键字来快速遍历所有元素。 传统的元素遍历方法如下:
for i := 0; i使用range关键字可以让遍历代码显得更整洁。 range表达式有两个返回值,第一个是索引, 第二个是元素的值:
for i, v := range mySlice { fmt.Println("mySlice[", i, "] =", v)}对比上面的两个方法,我们可以很容易地看出使用range的代码更简单易懂。 3. 动态增减元素 可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数组切片多了一个存储能 力( capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的 值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。 假如你明确知道当前创建的数组切片最多可能需要存储的元素个数为50,那么如果你设置的 存储能力小于50,比如20,那么在元素超过20时,底层将会发生至少一次这样的动作——重新分 配一块“够大”的内存,并且需要把内容从原来的内存块复制到新分配的内存块,这会产生比较 明显的开销。给“够大”这两个字加上引号的原因是系统并不知道多大才是够大,所以只是一个 简单的猜测。比如,将原有的内存空间扩大两倍,但两倍并不一定够,所以之前提到的内存重新 分配和内容复制的过程很有可能发生多次,从而明显降低系统的整体性能。但如果你知道最大是 50并且一开始就设置存储能力为50,那么之后就不会发生这样非常耗费CPU的动作,从而达到空间换时间的效果。 数组切片支持Go语言内置的cap() 函数和len() 函数,代码清单2-2简单示范了这两个内置 函数的用法。可以看出, cap() 函数返回的是数组切片分配的空间大小,而len() 函数返回的是 数组切片中当前所存储的元素个数。
package mainimport "fmt"func main() { mySlice := make([] int, 5, 10) fmt.Println("len(mySlice):", len(mySlice)) fmt.Println("cap(mySlice):", cap(mySlice))}该程序的输出结果为:
len(mySlice): 5cap(mySlice): 10如果需要往上例中mySlice已包含的5个元素后面继续新增元素,可以使用append() 函数。 下面的代码可以从尾端给mySlice加上3个元素,从而生成一个新的数组切片:
mySlice = append(mySlice, 1, 2, 3)函数append() 的第二个参数其实是一个不定参数,我们可以按自己需求添加若干个元素, 甚至直接将一个数组切片追加到另一个数组切片的末尾:
mySlice2 := [] int{8, 9, 10}// 给mySlice后面添加另一个数组切片mySlice = append(mySlice, mySlice2...)需要注意的是,我们在第二个参数mySlice2 后面加了三个点,即一个省略号,如果没有这个省 略号的话,会有编译错误,因为按append() 的语义,从第二个参数起的所有参数都是待附加的 元素。因为mySlice中的元素类型为int,所以直接传递mySlice2 是行不通的。加上省略号相 当于把mySlice2 包含的所有元素打散后传入。 上述调用等同于:
mySlice = append(mySlice, 8, 9, 10)数组切片会自动处理存储空间不足的问题。如果追加的内容长度超过当前已分配的存储空间 (即cap() 调用返回的信息),数组切片会自动分配一块足够大的内存。 4. 基于数组切片创建数组切片 类似于数组切片可以基于一个数组创建,数组切片也可以基于另一个数组切片创建。下面的 例子基于一个已有数组切片创建新数组切片:
oldSlice := [] int{1, 2, 3, 4, 5}newSlice := oldSlice[:3] // 基于oldSlice的前3个元素构建新数组切片有意思的是,选择的oldSlicef元素范围甚至可以超过所包含的元素个数,比如newSlice 可以基于oldSlice的前6个元素创建,虽然oldSlice只包含5个元素。只要这个选择的范围不超 过oldSlice存储能力(即cap() 返回的值),那么这个创建程序就是合法的。 newSlice中超出 oldSlice元素的部分都会填上0。 5. 内容复制 数组切片支持Go语言的另一个内置函数copy() ,用于将内容从一个数组切片复制到另一个 数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行 复制。下面的示例展示了copy() 函数的行为:
slice1 := [] int{1, 2, 3, 4, 5}slice2 := [] int{5, 4, 3}copy(slice2, slice1) // 只会复制slice1的前3个元素到 slice2中copy(slice1, slice2) // 只会复制slice2的 3个元素到 slice1的前3个位置
package mainimport "fmt"// PersonInfo是一个包含个人详细信息的类型type PersonInfo struct { ID string Name string Address string}func main() {var personDB map[ string] PersonInfo personDB = make( map[ string] PersonInfo) // 往这个map里插入几条数据 personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,..."} personDB["1"] = PersonInfo{"1", "Jack", "Room 101,..."} // 从这个map查找键为 "1234" 的信息 person, ok := personDB["1234"]上面这个简单的例子基本上已经覆盖了map的主要用法,下面对其中的关键点进行细述。1. 变量声明map的声明基本上没有多余的元素,比如:// ok是一个返回的bool型,返回true表示找到了对应的数据 if ok { fmt.Println("Found person", person.Name, "with ID 1234.") } else { fmt.Println("Did not find person with ID 1234.") }}
var myMap map[ string] PersonInfomyMap = make( map[ string] PersonInfo)其中, myMap是声明的map变量名, string是键的类型, PersonInfo则是其中所存放的值类型。2. 创建我们可以使用Go语言内置的函数make() 来创建一个新map。下面的这个例子创建了一个键类型为string、值类型为PersonInfo的map:
myMap = make( map[ string] PersonInfo)也可以选择是否在创建时指定该map的初始存储能力,下面的例子创建了一个初始存储能力 为100的map:
myMap = make( map[ string] PersonInfo, 100)创建并初始化map的代码如下:
myMap = map[ string] PersonInfo{ "1234": PersonInfo{"1", "Jack", "Room 101,..."},}3. 元素赋值 赋值过程非常简单明了,就是将键和值用下面的方式对应起来即可:
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}4. 元素删除 Go语言提供了一个内置函数delete() ,用于删除容器内的元素。下面我们简单介绍一下如 何用delete() 函数删除map内的元素:
delete(myMap, "1234")5. 元素查找 在Go语言中, map的查找功能设计得比较精巧。而在其他语言中,我们要判断能否获取到一个值不是件容易的事情。判断能否从map中获取一个值的常规做法是: (1) 声明并初始化一个变量为空; (2) 试图从map中获取相应键的值到该变量中; (3) 判断该变量是否依旧为空,如果为空则表示map中没有包含该变量。 这种用法比较啰唆,而且判断变量是否为空这条语句并不能真正表意(是否成功取到对应的 值),从而影响代码的可读性和可维护性。有些库甚至会设计为因为一个键不存在而抛出异常, 让开发者用起来胆战心惊,不得不一层层嵌套try-catch语句,这更是不人性化的设计。在Go 语言中,要从map中查找一个特定的键,可以通过下面的代码来实现
value, ok := myMap["1234"]if ok { // 找到了 // 处理找到的value}判断是否成功找到特定的键,不需要检查取到的值是否为nil,只需查看第二个返回值ok, 这让表意清晰很多。配合:=操作符,让你的代码没有多余成分,看起来非常清晰易懂。
转载地址:http://vkrab.baihongyu.com/