1. Array
- 数组是值类型,赋值和传参都会拷贝整个数组;
- 数组长度必须是常量,且是类型的组成部分,
[2]int
和[3]int
是不同类型; - 支持
==
和!=
操作符; - 指针数组
[10]*int
,数组指针*[10]int
; -
len
和cap
都返回数组长度; - 可用复合语句初始化;
a := [3]int{1, 2} // 未初始化的元素值为0
b := [...]int{1, 2, 3, 4} // 通过初始化值确定长度
c := [5]int{2: 100, 3: 10} //使用索引号初始化元素
d := [...]struct {
name string
age int
}{
{"Jim", 10},
{"Jack", 11}, // 别忘了最后一行的逗号
}
e := [2][3]int{{1, 2, 3}, {4, 5, 6}}
f := [...][3]int{{1, 2, 3}, {4, 5, 6}} // 第二维不可用...
值拷贝会造成性能问题,通常建议用slice
或者数组指针。
2. slice
slice
并不是数组或者数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
- 引用类型,自身是结构体,值拷贝传递;
type slice struct {
array unsafe.Pointer
len int
cap int
}
- 属性
len
表示可用元素数量,读写操作不能够超过该限制; - 属性
cap
表示最大容量,不能超过数组限制; - 如果
slice == nil
,那么len
和cap
都为0;
创建slice
时,索引不能超过数组限制,读写slice
操作的是底层数组。
a := [...]int{1, 2, 3, 4, 5, 6, 7, 8}
// s := a[1:9:9] // invalid slice index 9 索引不能超过数组限制
s := a[1:4:5]
s[2] = 10
fmt.Println(a, s) // [1 2 3 10 5 6 7 8] [2 3 10]
- 可直接创建
slice
对象,自动分配底层数组。
a := []int{1, 2, 3, 4, 8: 9} // 初始化创建,可使用索引号
fmt.Println(a, len(a), cap(a)) // [1 2 3 4 0 0 0 0 9] 9 9
b := make([]int, 6) // make创建,省略cap,相当于len = cap
fmt.Println(b, len(b), cap(b)) // [0 0 0 0 0 0] 6 6
c := make([]int, 6, 8)
fmt.Println(c, len(c), cap(c)) // [0 0 0 0 0 0] 6 8
-
reslice
是基于已有slice
创建新slice
对象
2.1. append
向slice
尾部添加数据,返回新的slice
对象。一旦slice
超出cap
限制,就会重新分配底层数组,即便原数组并未填满。
data := []int{1, 2, 3, 4, 8: 9}
s := data[0:2:3]
fmt.Println(&s[0], &data[0]) // 0xc000010280 0xc000010280
s = append(s, 100, 200) // 一次append两个值,超出s.cap限制
fmt.Println(&s[0], &data[0]) // 0xc00000a2d0 0xc000010280
通常以2倍容量重新分配底层数据,大批量添加数据时,建议一次性分配足够大的空间,减少内存分配和数据复制开销。及时释放不再使用的slice
对象,避免持有过期数组,造成GC无法回收。
2.2. copy
函数copy在两个slice间复制数据,复制长度以len
小的为准。
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := data[8:]
s2 := data[:5]
fmt.Println(s2, s) // [0 1 2 3 4] [8 9]
copy(s2, s) // dst:s2, src:s
fmt.Println(s2) // [8 9 2 3 4]
fmt.Println(data) // [8 9 2 3 4 5 6 7 8 9]
3. Map
- 引用类型,哈希;
- 键必须是支持相等运算符的类型,比如
number
、string
、pointer
、array
、struct
以及对应的interface
。值可以是任何类型,没有限制; - 可以事先申请一大块内存,避免后续操作时频繁扩张
m := make(map[int]string, 1000)
; - 常见操作;
m := map[int]string{1: "Monday"} // 初始化
if v, ok := m[1]; ok { // 判断k是否存在
println(v)
}
println(m[5]) // 对于不存在的key,直接返回\0,不会出错
m[2] = "Tuesday" // 新增或修改
delete(m, 0) //删除,如果key不存在,不会出错
从map
中取回的是value
的临时复制品,对其成员的修改没有任何意义,正确做法是完整替换value
或使用指针。
m := map[string]user{"user1": {"Jim", 10}}
m["user1"].age = 15 // error: cannot assign to struct field m["user1"].age in map
mp := map[string]*user{"user1": &user{"Jim", 10}}
mp["user1"].age = 15
fmt.Println(mp["user1"])
迭代时可以安全的删除键值,不能新增键值。
4. Struct
- 值类型,赋值和传参都会复制全部内容;
- 可用
_
定义补位字段,支持指向自身类型的指针成员; - 顺序初始化必须包含全部字段,否则会出错;
- 支持匿名结构
type File struct {
name string
size int
attr struct {
perm int
owner int
}
}
f := File{
name: "test.txt",
size: 1025,
// attr: {0755, 1}, // Error: missing type in composite literal
}
f.attr.owner = 1
f.attr.perm = 0755
var attr = struct {
perm int
owner int
}{2, 0755}
f.attr = attr
- 可定义字段标签,用反射读取。标签是类型的组成部分,无法比较;
var u1 struct {
name string "username"
}
var u2 struct{ name string }
u2 = u1 // Error: cannot use u1 (type struct { name string "username" }) as
// type struct { name string } in assignment
- 空结构比较节省内存,可以用来实现
set
数据结构,或者实现没有状态只有方法的“静态类”;
set := map[string]struct{}{}
set["a"] = struct{}{}
4.1. 匿名字段
匿名字段是一种语法糖,从根本上说,就是一个与成员类型同名的字段;被匿名嵌入的可以是任何类型,当然也包括指针。可以向普通字段那样访问匿名字段成员,编译器从外向内逐级查找所有层次的匿名字段,直到发现目标或者出错。
type User struct{ name string }
type Role struct {
User
role string
}
func main() {
r := Role{User: User{"Jim"}, role: "Administrator"} // 匿名字段的显式字段名和类型名相同
fmt.Println(r.name)
}
外层同名字段会遮蔽嵌入字段,相同层次的同名字段编译器报错;不能包含某一类型和其指针类型,因为名字相同;
type Resource struct {
id int
name string
}
type Classify struct {
id int
}
type User struct {
Resource // Resource.id 与 Classify.id 处于同⼀层次。
Classify
name string // 遮蔽 Resource.name。
}
func main() {
u := User{Resource{1, "people"},
Classify{100},
"Jack",
}
println(u.name) // User.name: Jack
println(u.Resource.name) // people
// println(u.id) // Error: ambiguous selector u.id
println(u.Classify.id) // 100
}
4.1. 面向对象
面向对象的三大特征里,go
只支持封装,尽管匿名字段的内存布局和行为类似继承。