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

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

下一篇 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的访问时并发安全的。

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

转载于:https://my.oschina.net/u/3287304/blog/1555084

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值