Struct

前面提到过,struct是值类型,所以支持 "=="、"!=" 相等操作符

由于是值类型,复制和传参会复制全部内容。可用 "_" 定义补位字段,支持指向自身类型的指针成员

//声明一个结构体
type Node struct {
	//_ int 补位字段
	id int
	data *byte
	next *Node
}

初始化

对于上面提到的结构体的初始化

//普通初始化
n1 := Node{
	id: 1,
	data: nil,
}

//顺序初始化
n2 := Node{1,nil,nil} //顺序初始化必须包含全部字段,否则会出错。

如果结构体包含有补位字段则不能使用顺序初始化

匿名结构体

type File struct {
	name string
	size int
	attr struct { //匿名结构体做结构体的成员
		perm int
		owner int
	}
}

func main(){
    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 {
    porm int
    owner int
}{2,0755}
f.attr = attr

匿名字段

匿名字段不过是一种语法糖,从根本上说,就是一个与成员类型同名 (不含包名) 的字段。被匿名嵌入的可以是任何类型,当然也包括指针。

type User struct {
	name string
}

type Manager struct {
	User //同名字段,类型和变量名称都是User
	title string
}

m := Manager{
	User: User{"Tom"}, // 匿名字段的显式字段名,和类型名相同。
	title: "Administrator",
}

可以像普通字段那样访问匿名字段成员,编译器从外向内逐级查找所有层次的匿名字段,直到发现目标或出错。

type Resource struct {
	id int
}

type User struct {
	Resource
	name string
}

type Manager struct {
	User
	title string
}

var m Manager
m.id = 1
m.name = "Jack"
m.title = "Administrator"

Map

引用类型,哈希表(key-value)。

key:必须支持相等运算符(==、!=),比如 numberstringpointerarraystruct,以及对应的 interface

value:可以是任意类型,没有限制

m := map[int]struct {
	name string
	age int
}{
	1: {"user1", 10}, // 可省略元素类型。
	2: {"user2", 20},
}

println(m[1].name)

预先给 make 函数一个合理元素数量参数,有助于提升性能。因为事先申请一大块内存,可避免后续操作时频繁扩张。

m := make(map[string]int, 1000)

常见操作

对于一个Map

m := map[int]string{1:"hello"}

检查Map中是否包含某元素

//v:如果有值为1的key则返回对应的值,若没有则返回该类型默认值
//ok:是否含有值为1的key,bool类型
if v,ok := m[1];ok {
    ...
}

新增或修改

//如果有值为2的key则添加,否则为修改
m[2] = "data"

删除

//删除m中key为2的键值对,如果不存在也不会出错
delete(m,2)

获取Map长度

//注意,cap对map无效!!
len(m)

遍历/迭代

不能保证迭代返回的次序,通常是随即结果

//k:key
//v:value
for k,v := range m{
    fmt.Println(k,v)
}

从map中取得的value为原数据的副本

map 中取回的是一个 value 临时复制品,对其成员的修改是没有任何意义的。

type user struct{ name string }

m := map[int]user{ // 当 map 因扩张而重新哈希时,各键值项存储位置都会发生改变。 因此,map
	1: {"user1"}, // 被设计成 not addressable。 类似 m[1].name 这种期望透过原 value
} // 指针修改成员的行为自然会被禁止。

m[1].name = "Tom" // Error: cannot assign to m[1].name

正确做法是完整替换value

	m := map[int]user{
		1: {"user1"},
	}

	//完整替换
	m[1] = user{"user2"}

或map的value使用指针

	//使用指针
	m2 := map[int]*user{
		1:{"user1"},
	}
	m2[1].name = "user2" //返回的是指针复制品,头故宫指针修改原对象是允许的
//回顾:直接用 "." 访问目标成员(相较C简化了使用,不再需要将指针转换为目标实体)。 (Let’s Go 3 — 指针与自定义类型)

如果迭代同时进行增删

可以在迭代时安全删除键值。但如果期间有新增操作,那么就不知道会有什么意外了。

package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		m := map[int]string{
			0: "a", 1: "a", 2: "a", 3: "a", 4: "a",
			5: "a", 6: "a", 7: "a", 8: "a", 9: "a",
		}

		for k := range m {
			m[k+k] = "x"
			delete(m, k)
		}

		fmt.Println(m)
	}
}
/*
输出:
map[2:x 6:x 10:x 12:x 14:x 18:x 32:x]
map[2:x 4:x 10:x 12:x 14:x 18:x 32:x]
map[12:x 16:x 18:x 20:x 28:x]
map[8:x 10:x 12:x 16:x 18:x 28:x]
map[2:x 8:x 10:x 12:x 14:x 18:x 32:x]
每次执行都不同
*/

参考

Go 边看边练

Q.E.D.


此 生 无 悔 恋 真 白 ,来 世 愿 入 樱 花 庄 。