Go中复合数据类型:array, slice, map, struct
4.1 数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
内置的len函数将返回数组中元素的个数。
默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。我们也可以使用数组字面值语法用一组值来初始化数组:
var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"
在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
可以指定一个索引和对应值列表的方式初始化,就像下面这样:
r := [...]int{99: -1}
如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。
当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。数组在调用中也会被完整复制。
4.2 Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。
slice唯一合法的比较操作是和nil比较。
如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
除了和nil相等比较外,一个nil值的slice的行为和其它任意0产长度的slice一样。
内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。在底层,make创建了一个匿名的数组变量,然后返回一个slice。
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
内置的append函数用于向slice追加元素。
4.3 Map
一个map就是一个哈希表的引用,map类型可以写为map[K]V。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。
内置的make函数可以创建一个map:
ages := make(map[string]int) // mapping from strings to ints
也可以用map字面值的语法创建map:
ages := map[string]int {
"alice" : 31,
"charlie" : 34,
}
使用内置的delete函数可以删除元素:
delete(ages, "alice") // remove element ages["alice"]
如果一个查找失败将返回value类型对应的零值。
不能对map的元素进行取址操作。
要想遍历map中全部的key/value对的话,可以使用range风格的for循环实:
for name, age := range ages {
fmt.Printf("%s\t%d\n", name, age)
}
遍历的顺序是随机的,每一次遍历的顺序都不相同。
map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常。
map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在:
if age, ok := ages["bob"]; !ok { /* ... */ }
map之间也不能进行相等比较,唯一的例外是和nil进行比较。
4.4 结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体,field之间没有逗号:
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var dilbert Employee
结构体变量的成员可以通过点操作符访问;或者是对成员取地址,然后通过指针访问。点操作符也可以和指向结构体的指针一起工作:
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"
相当于
(*employeeOfTheMonth).Position += " (proactive team player)"
如果结构体成员名字是以大写字母开头的,那么该成员就是导出的。
一个命名为S的结构体类型将不能再包含S类型的成员,但是S类型的结构体可以包含*S 指针类型的成员。
结构体类型的零值是每个成员都对是零值。如果结构体没有任何成员的话就是空结构体,写作struct{}。
结构体字面值有两种语法,都只能赋值导出的成员
1. 以结构体成员定义的顺序为每个结构体成员指定一个字面值:
type Point struct { X, Y int}
p := Point{1, 2}
2. 以成员名字和相应的值来初始化:
p := Point{X: 1, Y: 2}
结构体可以作为函数的参数和返回值,从效率考虑,可以通过指针传入。
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较。可比较的结构体类型和其他可比较的类型一样,可以用于map的key类型。
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。通过匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径。
结构体字面值并没有简短表示匿名成员的语法,必须遵循形状类型声明时的结构:
w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
Point: Point{ X : 8, Y :8},
Radius: 5,
},
Spokes: 20 , // NOTE: trailing comma necessary here (and at Radius)
}
因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突。