目录

Go容器

new 与 make

new操作用于基本类型和struct自定义类型,返回的是指针:

var sum *int
sum = new(int) // 分配空间
*sum = 98
fmt.Println(*sum)

type Student struct {
    name string
    age int8
}
var s *Student
s = new(Student)
s.name = "codekissyoung"
fmt.Printf("type : %T value: %v", s, s)

make专门用于 slice map chan 容器,分配及初始化它们的结构和数据。

数组

相同类型[3]int的数组ab可以进行a=b赋值操作,数组元素个数也是类型的一部分。

// 比较数组是否相等
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"

pow := []int {1, 2, 4, 8, 16, 32, 64, 128}
// 用于遍历 Array Slice Map
for i, v := range pow {                 // 依次将下标 和 值 复制 给 i 和 v
    fmt.Printf("pow[%d] = %d\n", i, v)
}
for _, v := range pow { ... }           // 不复制 下标
for i, _ := range pow { ... }           // 不复制 值
for i := range pow { ... }              // 不复制 值,简写版

切片

切片的底层结构包括三部分:起始地址 + 大小 + 容量

primes := [6]int{2, 3, 5, 7, 11, 13}        // 方式1 数组
abc := []int{2, 3, 5, 7, 11, 13}                             // 方式2 
edf := make([]int, 5, 5)                                                 // 方式3 make([]Type, len, cap) [0, 0, 0, 0, 0]

fmt.Println(primes[1:4], len(primes[1:4]), cap(primes[1:4]) ) // [3 5 7] 3 5
fmt.Println(primes[:4], len(primes[:4]), cap(primes[:4]) )    // [2 3 5 7] 4 6 省略 low ,默认取 0
fmt.Println(primes[1:], len(primes[1:]), cap(primes[1:]) )    // [3 5 7 11 13] 5 5 省略 high,默认取 len(a)
fmt.Println(primes[1:4], (primes[1:4])[1:2])          // [3 5 7] [5] 切了后 再切 ^_^

往切片追加值,当切片的底层数组大小不足,容不下追加的值时,Go底层会分配一个更大的数组,然后将数据复制一份到新数组上。最后,返回的是新数组上的切片 ^_^

// 尾部追加
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

// 开头插入
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

// 拷贝
src := []int{1, 2, 3, 4, 5}
dst := []int{5, 4, 3}
copy(dst, src) // 复制 src 到 dst,长度限制,只复制了 3 个元素
copy(src, dst) // 复制 dst 到 src 的前3个位置

// 删除
a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素

a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素

a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素

a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

Map

type Vertex struct {
    Lat, Long float64
}
m := map[string]Vertex{}            // 声明
n := make( map[string]Vertex )      // make 式声明,等价上句,作用是一样的

m["Bell Labs"] = Vertex{ 40.68433, -74.39967 }

m[key] = elem           // 新增 or 修改
a = m[key]              // 获取
b, ok = m[key]          // 如果 key 在 m 中,则 ok 为 true;否则 ok 为 false, b 为零值
key is: 3 - value is: 3.000000
2
key is: 1 - value is: 1.000000
3
key is: 4 - value is: 4.000000
4
key is: 2 - value is: 2.000000
fmt.Println( map[string]bool{"192.168.0.101":true} )  // 字面量 map[192.168.0.1:true]
var ipSwitches = map[string]bool{}
ipSwitchs["192.168.0.1"] = true
delete(ipSwitchs, "192.168.0.1")        // 删除元素

// 一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?
// 用切片作为 map 的值
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

// 删除
scene["route"] = 66
scene["brazil"] = 4
delete(scene, "brazil")

// 清空 map 中的所有元素
// 清空 map 的唯一办法就是重新 make 一个新的 map
// 不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多

Map的多键索引

// 人员档案
type Profile struct {
    Name    string   // 名字
    Age     int      // 年龄
    Married bool     // 已婚
}

// 查询键
type classicQueryKey struct {
    Name string // 要查询的名字
    Age  int    // 要查询的年龄
}

func simpleHash(str string) (ret int) {
    for i := 0; i < len(str); i++ {
        c := str[i]
        ret += int(c)
    }
    return
}

// 计算查询键的hash值
func (c *classicQueryKey) hash() int {
    // 将名字的hash和年龄hash合并
    return simpleHash(c.Name) + c.Age*1000000
}

// 创建hash值到数据的索引关系
var mapper = make(map[int][]*Profile)

// 构建数据索引
func buildIndex(list []*Profile) {
    for _, profile := range list {
        key := classicQueryKey{profile.Name, profile.Age}
        existValue := mapper[key.hash()]         // 计算数据的hash值,取出已经存在的记录
        existValue = append(existValue, profile)         // 将当前数据添加到已经存在的记录切片中
        mapper[key.hash()] = existValue         // 将切片重新设置到映射中
    }
}

func queryData(name string, age int) {
    keyToQuery := classicQueryKey{name, age}     // 根据给定查询条件构建查询键
    resultList := mapper[keyToQuery.hash()]     // 计算查询键的哈希值,并查询,获得同哈希值的所有结果集合
    for _, result := range resultList {
        if result.Name == name && result.Age == age {        // 与查询结果比对,确认找到打印结果

            fmt.Println(result)
            return
        }
    }
    fmt.Println("no found")    // 没有查询到时,打印结果
}

func main() {
    list := []*Profile{
        {Name: "张三", Age: 30, Married: true},
        {Name: "李四", Age: 21},
        {Name: "王麻子", Age: 20},
    }
    buildIndex(list)
    queryData("张三", 30)
    queryData("link",89)
}

Go语言的底层会为 map 的键自动构建哈希值。能够构建哈希值的类型必须是非动态类型、非指针、非函数、非闭包。利用这个特性,可以使用如下实现:

type Profile struct {
    Name    string
    Age     int
    Married bool
}

// 查询键
type queryKey struct {
    Name string
    Age  int
}

// 构建查询索引
func buildIndex(list []*Profile) {
    for _, profile := range list {
        key := queryKey{
            Name: profile.Name,
            Age:  profile.Age,
        }
        mapper[key] = profile         // 保存查询键
    }
}

// 根据条件查询数据
func queryData(name string, age int) {
    key := queryKey{name, age}     // 根据查询条件构建查询键
    result, ok := mapper[key]     // 根据键值查询数据
    if ok {
        fmt.Println(result)     // 找到数据打印出来
    } else {
        fmt.Println("no found")
    }
}
var mapper = make(map[interface{}]*Profile)
func main() {
    list := []*Profile{
        {Name: "张三", Age: 30, Married: true},
        {Name: "李四", Age: 21},
        {Name: "王麻子", Age: 21},
    }
    buildIndex(list)
    queryData("张三", 30)
    queryData("link", 30)
}

map 也可以用函数作为自己的值,这样就可以用来做分支结构(详见第 5 章):key 用来选择要执行的函数。

key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 string、int、float。所以数组、切片和结构体不能作为 key (译者注:含有数组切片的结构体不能作为 key,只包含内建类型的 struct 是可以作为 key 的),但是指针和接口类型可以。如果要用结构体作为 key 可以提供 Key()Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。

value 可以是任意类型的;通过使用空接口类型(详见第 11.9 节),我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言(详见第 11.3 节)。

func main() {
    mf := map[int]func() int{
        1: func() int { return 10 },
        2: func() int { return 20 },
        5: func() int { return 50 },
    }
    fmt.Println(mf) // map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]
}

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理unix机器上的所有进程,以父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题。

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

Map类型的切片

假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配 切片中每个 map 元素。

// Version A:
items := make([]map[int]int, 5)
for i:= range items {
    items[i] = make(map[int]int, 1)
    items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]

// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
    item = make(map[int]int, 1) // item is only a copy of the slice element.
    item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
// Version B: Value of items: [map[] map[] map[] map[] map[]]

map 默认是无序的

不管是按照 key 还是按照 value 默认都不排序(详见第 8.3 节)。

如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包,详见第 7.6.6 节),然后可以使用切片的 for-range 方法打印出所有的 key 和 value。