数据

1. Array

  • 数组是值类型,赋值和传参都会拷贝整个数组;
  • 数组长度必须是常量,且是类型的组成部分,[2]int[3]int是不同类型;
  • 支持==!=操作符;
  • 指针数组[10]*int,数组指针*[10]int;
  • lencap都返回数组长度;
  • 可用复合语句初始化;
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,那么lencap都为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

  • 引用类型,哈希;
  • 键必须是支持相等运算符的类型,比如numberstringpointerarraystruct以及对应的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只支持封装,尽管匿名字段的内存布局和行为类似继承。

你可能感兴趣的:(数据)