细节总结
循环遍历
在使用 for 循环某个集合的时候,会复用过程中承载值的变量,所以在涉及指针的时候要提起注意。
a := make([]*int, 4)
for i := 0; i < len(a); i++ {
a[i] = &i // i 始终是复用自最初变量,所以结果是a中所有值都是相同的i地址
}
func main() {
a := make([]*int, 4)
b := [...]int{1, 2, 3, 4}
for k, v := range b {
a[k] = &v // v 同样是复用自一个变量,所以结果也是a中所有值都是相同的v地址
}
}
方法赋予
在 GO 中自定义了一个类型之后,可以给该类型赋予方法。被赋予方法的类型,就叫做接受者,接受者可以是值接受者,也可以是指针接受者。
type T struct {
x int
}
func (t T) Func1() { } // 值接受者
func (t *T) Func2() { } // 指针接受者
这里就会出现一个疑惑,上述的方法最终是赋予给了该类型还是该类型的指针?先给出结论:在调用方法的时候,值类型和指针类型都可以调用指针接受者的方法和值接受者的方法,因为调用时的细节转换已经由编译器完成。仍然是上边的例子,可以如此调用:
func main() {
t1 := T{x: 0} // 获取一个T类型值
t1.Func1()
t1.Func2() // 编译器转换成 (&t1).Func2()
t2 := &T{x: 10} // 获取一个T类型指针
t2.Func1() // 编译器转换成 (&t2).Func1()
t2.Func2()
}
这两者的区别在于是否会真正改动接受者的字段。
类型 | 定义 | 行为 |
---|---|---|
值接受者 | func (t T) Func() {} | 获取T类型的值拷贝,之后在函数中的操作都是对该副本进行,不会影响原值。 |
指针接受者 | func (t *T) Func() {} | 本质上也是获取一个值拷贝,但这个值是 *T 类型,也就是地址。所以之后的操作都会访问该地址,就会影响到原值。 |
容器
heap
结构分析
小根堆,使用的时候需要实现 heap.Interface 接口,该接口定义了构造堆所必要的方法。
// heap.Interface
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool // 排序默认是升序,可看作返回true则i在j之前
Swap(i, j int)
}
通过源码的分析可以知道:
- heap 插入元素的过程是先调用用户的 Push 方法,接着通过将最后一个叶子上拉来维护堆性质,所以在实现 Push 方法时只需要在集合的最后添加元素;
- 而 heap 弹出元素弹出的是堆顶元素,过程是先将堆顶与最后一个叶子互换位置,然后将新堆顶下拉来维护堆的性质,最后调用用户的 Pop 方法,所以在实现 Pop 方法的时候是删除集合的最后一个元素。
总而言之,在实现 Interface 接口时,Pop 和 Push 都针对集合的最后一个元素就好。
常用 API
// h均为一个集合的 引用
func Fix(h Interface, i int)
func Init(h Interface)
func Pop(h Interface) interface{}
func Push(h Interface, x interface{})
func Remove(h Interface, i int) interface{} // 返回被删除的元素
type Interface
list
结构分析
节点可以是任意类型,使用示例
双向链表,其结构稍微与常见的双向链表不同,非常巧妙。链表的总体类型为 List,每一个元素类型为 Element
type Element struct {
next, prev *Element
list *List // 当前节点元素所属的链表
Value interface{} // 节点值可以是任意类型
}
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
List 可以看作是整个链表的抽象代表,包含一个很巧妙的节点元素 root,root 既是双向链表的头部又是尾部:root.next 指向第一个元素,root.prev 指向最后一个元素。所以总体来看,链表是一个环形结构。
从 Element 的结构可以发现,其多了一个 list 字段指向所属链表。这个字段的作用是在删除某个节点时,快速判断节点是否属于链表,省去了不必要的遍历时间,提高删除的效率。
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
l.remove(e)
}
return e.Value
}
常用 API
func New() *List
func (l *List) Back() *Element
func (l *List) Front() *Element
func (l *List) Init() *List
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
func (l *List) InsertBefore(v interface{}, mark *Element) *Element
func (l *List) Len() int
func (l *List) MoveAfter(e, mark *Element)
func (l *List) MoveBefore(e, mark *Element)
func (l *List) MoveToBack(e *Element)
func (l *List) MoveToFront(e *Element)
func (l *List) PushBack(v interface{}) *Element
func (l *List) PushBackList(other *List)
func (l *List) PushFront(v interface{}) *Element
func (l *List) PushFrontList(other *List)
func (l *List) Remove(e *Element) interface{} // 返回节点的Value