Go圣经-学习笔记之复合类型(二)

上一篇 Go圣经-学习笔记之复合类型golang

下一篇 Go圣经-学习笔记之复合数据结构(三)算法

map介绍和简单使用

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了。

Set集合

Go语言中没有set集合数据类型,它可使用map来实现:var s map[Type]bool, 保证集合的数据不重复性。

map中key类型是slice,请绕道走

由于map中的key类型必须是能够比较的,若是想要的key是slice类型,则须要这样绕道走两步:

  • 定义一个辅助函数K,将slice转化为map中对应string类型的key,确保x和y相等时,K(x)==K(y)。
  • 当每次对map操做时,先用辅助函数K将slice转化为string类型的数据,再操做。

这里有一个关键点: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语言面向编程思想的核心有一个是组合特性,它经过简单的把匿名成员放在一块儿,就能够吸纳匿名成员类型全部的方法集,这样就能够实现想要的复杂对象行为,同时也能够修改和管理对象的属性。

相关文章
相关标签/搜索