数组
数组一旦声明,里面存储的数据类型和数组长度都不能改变了(单个元素值可以通过下标修改(但元素值的类型不能更改))。如果需要存储更多的元素,就需要先创建一个更长的数组,再把原来数组里的值复制到新数组里。
数组是具有固定长度且拥有0个或者多个相同数据类型元素的序列。由于数组长度固定,所以在Go里面很少直接使用。slice的长度可以增长和缩短,在很多场合下使用的更多。
默认情况下,一个新数组中的元素初始值为元素类型的零值,对于数字来说,就是0。
也可以使用数组字面量根据已组织来初始化一个数组:
数组长度由初始化数组元素个数决定的方式:
数组的长度是数组类型的一部分,所以[3]int 和 [4]int 是两种不同的数组类型。数组的长度必须是常量表达式,也就是说,这个表达式的值在程序编译时就可以确定。
创建和初始化
|
|
使用数组
|
|
在函数建传递数组
根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的。如果这个变量时一个数组,意味着整个数组,不管有多长,都会完整赋值,并传递给函数。所以我们可以只传入指向数组的指针就好:
数组优缺点
- 优点
占用内存是连续的,元素类型相同,数据索引迭代快 - 缺点
长度和数据类型固定
slice
指针、长度、容量
slice表示一个拥有相同类型元素的可变长度的序列。slice通常写成[]T,其中元素的类型都是T; 看上去就像是没有长度的数组类型
数组和slice是紧密关联的。slice是一种轻量级的数据结构,可以用来访问数组的部分或者全部的元素,而这个数组称为slice的底层数组。slice有三个属性:指针、长度和容量
slice操作符s[i:j] 会创建一个新的slice
slice空判断:len(s) == 0
slice追加:
创建和初始化
切片的容量必须大于等于长度
使用 make 或者 字面量 创建切片
使用切片
|
|
说明:
对于底层数组容量是k的切片slice[i:j]来说:
长度:j - i
容量:k - i
所以上面新切片的长度为2(3-1),容量为4(5-1)
由于两个切片共享同一个底层数组。如果一个切片修改了该底层数组的共享部分,另外一个切片也能感知:
切片只能访问到其长度内的元素。试图访问超出其长度的元素将会导致语言运行异常:
切片增长:
如果切片的底层数组没有足够的可用容量,append函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值:
当这个append操作完成后,newSlice拥有一个全新的底层数组,与原有的底层数组分离(操作新切片不会影响原来的),这个数组的容量是原来的两倍
切片增长规则:
函数append会智能的处理底层数组的容量增长。在切片的容量小于1000个元素的时候,总是会成倍的增加容量。一旦元素个数超过1000,容量的增长因子会设为1.25,也就是会每次增加25%的容量。
创建切片时的3个索引:
第三个索引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。
切片slice[i:j:k] 或[2:3:4]来说:
长度:j - i 或 3 - 2 = 1
容量:k - i 或 4 - 2 = 2
如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个append操作创建新的底层数组,与原有的底层数组分离。新切片与原有的底层数组分离后,就可以安全的进行后续修改:
如果不加第三个索引,由于剩余的所有容量都属于slice,向slice追加Kiwi会改变原有底层数组索引为3的元素的值Banana。不过这里我们限制了slice的容量为1.当我们第一次对slice调用append的时候,会创建一个新的底层数组,这个数组包括2个元素,并将水果Plum复制进来,再追加新水果Kiwi,并返回一个引用了这个底层数组的的新切片.
内置函数append也是一个可变参数的函数,这意味着可以在一次调用传递多个追加的值。如果使用…运算符,可以将一个切片的所有元素追加到另外一个切片里:
迭代切片:
在函数间传递切片
在函数间的传递切片就是要在函数间以值的方式传递切片。由于切片的尺寸很小,在函数建复制和传递切片成本也很小。由于与切片关联的数据包含在底层数组里,不属于切片本身,所以讲切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会复制切片本身,不会涉及底层数组。
slice优缺点
- 优点
可按需自动增长和缩小, 底层分配的内存连续,索引迭代快; 函数调用时的值传递内存占用小、效率高(只复制切片本身,不需要复制底层数组) - 缺点
固定类型,
map
无序键值对
在Go语言中,map是散列表的引用,map的类型是map[K]V, 其中K和V是字典的键和值对应的数据类型。map中所有的键都拥有相同的数据类型,同时所有的值也都拥有相同的数据类型,但是键的数据类型和值的数据类型不一定相同。
键的类型K,必须是可以通过操作符==来进行比较的数据类型,所以map可以检测某一个键是否已经存在。虽然浮点型是可以比较的,但是比较浮点型的相等性不是一个好主意; 切片、函数以及包含切片的结构类型不能作为映射的键
创建和初始化
通过 内置函数make和映射(map)字面量创建
新的空map的另外一种表达式:map[string]int{}
使用映射
map类型的零值是nil
如果向未初始化的map(即零值map)设置元素会报错:
所以设置元素之前必须初始化map
判断元素是否在map中:
通常这两条语句合并成一条语句:
和slice一样,map不可比较,唯一合法的比较就是和nil做比较。为了判断两个map是否拥有相同的键和值,必须写一个循环:
注意这里使用!ok 来区分 “元素不存在” 和 “元素存在但值为零” 的情况。如果简单的写成了 xv != yv, 那么如下调用将错误的报告两个map是相等的:
delete(ages, “alice”) //移除元素 ages[“alice”]
ages[“bob”] = ages[“bob”] + 1] // 1
ages[“bob”] += 1
// or
ages[“bob”]++
_ = &ages[“bob”] //编译错误,无法获取map元素的地址
在函数建传递映射
在函数建传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改
Go没有提供集合类型,有map的键都是唯一的,就可以使用map来实现这个功能
小结
- 数组是构造切片和映射的基石
- Go语言里切片经常用来处理数据的集合,映射用来处理具有键值对结构的数据
- 内置函数make可以创建切片和映射,并指定原始的长度和容量。也可以直接使用切片和映射字面量,或者使用字面量作为变量的初始值
- 切片有容量限制,不过可以使用内置的append函数扩展容量
- 映射的增长没有容量或者任何限制
- 内置函数len可以用来获取切片或者映射的长度
- 内置函数cap只能用于切片
- 通过组合,可以创建多维数组和多维切片。也可以使用切片或者其他映射作为映射的值。但是切片不能用作映射的键
- 将切片或者映射传递给函数成本很小,并且不会复制底层的数据结构
- 切片进行append的时候,如果容量已经满了,则会创建一个新的底层数组