上一篇 Go圣经-学习笔记之复合类型golang
map是一种无序的key/value对的集合,在Go语言中,一个map就是一个hash表的引用。map中的key数据类型必须是能够比较的,否则key的相等性没法校验,也就没法hash到哪一个链表了。编程
map的删除key能够用过builtin内置delete方法:数组
func delete(s map[Type]Type1 key Type)
map要注意的一点,我在写RBAC构建多叉树的时候遇到过,map中的value元素并非一个变量,咱们不能对value元素作取地址操做,这样作的话,编译直接报错。禁止对map的value元素去地址操做缘由在于:安全
相似于slice动态数组,底层数组可能随时会变化。map的hash链表增加也是动态的,因此随着map中元素的增长,链表上存放的value元素存储的空间也会发生动态的变化,致使以前的value元素存放地址是无效的数据结构
因此,map也是不能用来直接比较的。并发
总结,slice动态数组和map哈希表即取即用,可是在大并发时,map的读写须要手动互斥的,不过再Go1.9版本中引入了sync.map标准库对map的访问时并发安全的。app
map的迭代顺序是不肯定的,由于开始就说起了map是一种无序的key/value对的集合。咱们若是要保证输出的时候要是有序的,能够这样作:函数
var smap map[Type]Value var keys = make([]Type, 0, len(smap)) for key := range smap { keys = append(keys, key) } sort.Sort(keys) for _, key := range keys { fmt.Println(smap[key]) }
首先收集map中的全部key元素,而后对这些key进行排序,而后再遍历key,并获取map中key对应的value元素。保证其输出有序。学习
这里提到了一个sort标准库:
type Interface interface{ Len() int // 数据长度 Less(i, j int) bool // 大小比较 Swap(i, j int) // 元素交换 }
数据的排序经过获取数据的长度、数据的比较和数据的交换, 就能够实现集合排序。因此咱们能够对要排序的数据类型添加这三种行为,就可使用sort.Sort排序了。这里要注意的地方是,咱们能够不直接在Type类型上添加这三个方法。能够定义其余类型,而后把Type数据类型转成定义的类型,就ok。这里举例说明一下:
var keys []Type // 对keys进行排序 type TypeSlice []Type func (p TypeSlice) Len() int { xxx } func (p TypeSlice) Less(i, j int) bool { xxx } func (p TypeSlice) Swap(i, j int) { xxx } sort.Sort(TypeSlice(keys)) // 这里的sort原型不是func Sort(v []T) []T的缘由:v不会动态增加,因此是安全的
明白会用就ok了。
Go语言中没有set集合数据类型,它可使用map来实现:var s map[Type]bool
, 保证集合的数据不重复性。
由于map中的key类型必须是能够比较的,若是想要的key是slice类型,则须要这样绕道走两步:
这里有一个关键点:x=y时, K(x)==K(y)为true;当x!=y时,K(x)==K(y)为false。用序列化为byte流再加密取前N个字符,做为string类型的数据就能够了,N个字符大小由通常比较熟悉的算法决定。
结构体 是一种聚合的数据类型,是由零个或者多个任意类型的值聚合成的实体。
由函数返回结构体参数引出的讨论:
func EmployeeById(id int) *Employee { ... } func print() { fmt.Println(EmployeeById(1234).Position) // "管理员" EmployeeById(1234).Position = "销售员" // 这个是合理的。虽然是给返回的参数赋值,它确实能够对原实体有修改的影响 }
上面的DEMO,若是把函数原型改成func EmployeeById(id int) Employee
,则编译没法经过。错误在最后一个语句。若是EmployeeById返回参数是一个拷贝的变量,则返回后没有变量名,也就没有任何意义,它会做为垃圾回收处理,咱们在《Go圣经-学习笔记之程序结构》有说过,当一个内存地址再也不被任何别名引用时,它会被垃圾回收掉。这里你也没法引用它。
由此知道,当返回的参数没有引用其余变量时,则这个新的内存地址没有被任何变量引用,则会被垃圾回收掉, 作赋值操做也就没有任何意义, 因此编译会直接报错。而指针值类型引用了变量,则暂时不会被垃圾回收,修改内存值是由意义的。
咱们首先看一个圆和一个轮子的结构体定义:
type Point struct { X, Y int } type Circle struct { // 圆的定义 Center Point Radius int // 半径 } type Wheel struct { // 轮子的定义 Circle Circle // 这里成员变量名称也能够和类型名相同 Spokes int // 辐条的数量 } var w Wheel w.Circle.Center.X = 8 w.Circle.Center.Y = 8 w.Circle.Radius = 5 w.Spokes = 20
上面Wheel变量赋值是很麻烦的。为了简便赋值操做,Go语言提供了一个特性:声明一个成员对应的数据类型而不指明成员的名字,这类成员就叫匿名成员。匿名成员的数据类型能够是命名的数据类型,也能够是命名的数据类型指针。下面改造下:
type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } var w Wheel w.X = 8 w.Y = 8 w.Radius = 4 w.Spokes = 20
这样赋值方便简单,代码不冗余, 当咱们用字面声明时,须要这样作:
var w = Wheel{ Circle: Circle{ Point: Point{X: 8, Y: 8}, Radius: 5, }, Spokes: 20, // NOTE: trailing comma necessary here (and at Radius) }
要注意的一点是,若是在结构体采用匿名成员形式,其中不能有相同的两个匿名成员类型,由于它会有隐式的成员名字,会出现名字冲突。那么为何咱们要在结构体中采用匿名成员呢?
Go语言面向编程思想的核心有一个是组合特性,它经过简单的把匿名成员放在一块儿,就能够吸纳匿名成员类型全部的方法集,这样就能够实现想要的复杂对象行为,同时也能够修改和管理对象的属性。