Go学习笔记

语法

  • for是Go中的while;
  • if可以在判断之前执行一条语句;
  • switch自动break,case无需为整数;
  • 切片的cap就是切片能扩展的最大长度;
  • 指针不能运算;
  • 类型和函数不用先声明就能用;
  • Go里面没有隐式类型提升;
  • 类型定义和类型方法必须在同一个包内;
  • .(type)类型选择必须和switch一起结合使用(泛型编程?);
  • 无论是指针还是值,都可以直接通过 . 访问成员,Go自动(*p).x
  • 类型方法也是,无论接受指针还是值,指针和值都可以直接调用,Go自动(*p).foo()或(&a).foo(),这时的指针接受者可以理解为C++里的引用
  • 每定义一个常量,iota就递增1,iota从0开始;

接口

  • 接口指针永远无法满足接口
  • 接口里存的永远是变量的具体类型,绝不可能是另一个接口类型,因此可以把接口类型和其他类型看做两种类型,接口是用来存其他类型的
  • 实现接口时如果是指针类型,那么就只有类型指针满足接口,毕竟指针接口会更改原值
  • 但实现接口时如果是值类型,那么类型和类型指针都满足接口,这时只需要传一个拷贝,那么是不是指针就无所谓了
  • 空接口interface{}可保存任意值;
  • 接口类型可以内嵌其他接口,相当于把其他接口方法写在这里;

内存

  • Go中函数形参都是传值的;
  • slice、map、chan是引用类型;
  • nil指针是可以调用方法的,不会导致空指针异常,毕竟没有解引用(C++里是因为要先解引用才能调用成员函数,换句话说就是this指针不能为空);
  • 但nil指针是不能访问成员的,这会导致解引用空指针SIGSEGV;

工具

  • Go是静态编译的;
  • go install和go build的区别:从go help来看,go build会编译main包并将bin文件写到当前目录,如果编译的文件不是main包或是多个包,bulid只检查这些文件是否可编译,并不生成文件,并且build会忽略*_test.go文件,但在只编译一个包时可以指定-o强制build生成文件;go install会把二进制文件放在bin文件夹里,*.a文件放在pkg文件夹里;

信道

  • 信道上的发送操作总在对应的接收操作完成前发生
  • 从无缓冲信道进行的接收,要发生在对该信道进行的发送完成之前
  • 再解释一下上面两点,如果读一个ch,只有另一个go程对ch写了,读才能返回;如果写一个无缓冲ch,只有另一个go程对ch读了,写才能返回
  • 就算r1监测到了w1,也不意味着r1之后的r2能检测到w0(应该是因为编译器调换了w之间的位置?)
  • nil chan永远不会准备好通信,这可以用在select里;
  • chan通过放置<-的位置决定chan是只读还是只写还是双向;
  • Go程没有返回值,惯例是传入一个chan,通过这个chan发送返回值;

map

  • map不是线程安全的,不过Go1.9里新加了sync.Map;
  • 容量为0的map和nil map是两个东西,nil map是不能添加元素的;

  • 闭包和Go程一起使用时,当心不要让Go程共享一个变量,请传一个拷贝进去;
  • string类型是不可变的,因此取s[i]的地址也是非法的;
  • 接口内嵌和type alias时注意不要递归循环引用;
  • defer关闭句柄请按参数传入,函数体内直接用的话是引用,可能会在defer函数执行前被更改;

其他

  • 函数的最后一个参数如果是…T,这相当于[]T,说明可以用T类型的零个(nil)或多个参数调用函数;
  • make([]int, 50, 100)和new([100]int)[0:50]是等价的
  • type alias(Go 1.9):使用type关键字时,如果是type A = int,那么A就是int的别名,这和type B int,定义了一个新类型B不一样;
  • 假如有这样一个函数 func (t T) Foo(a int) int {},那么T.Foo就是 func(t T, a int) int 这样一个函数,类似的如果是指针的话,就要(T).Foo这样写,产生一个 func(t T, a int) int 函数;

defer相关

  • defer实现原理剖析,写的相当不错
  • defer调用的函数的参数在defer语句时就已经确定;
  • defer后进先出;
  • 如果一个函数有命名返回值的话,那么在defer函数里是可以更改这个返回值的
  • For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes.

Effective Go

  • 只包含一个方法的接口以方法名加er命名;
  • 可以返回局部变量的地址;
  • 每当获取一个复合字面的地址时,都将为一个新的实例分配内存;
  • new和make:new分配一块内存并将内存置零,返回其指针;make只用于创建三种引用类型——slice、map、channel——并对其进行初始化;
  • 实现Stringer接口当心递归;
  • func init()在main前设置一些初始状态,也可以检查一些东西;
  • 如果一个类型只实现了一个接口,没有其他导出的方法,那么应该只导出这个接口,这能让代码更通用;
  • 通过通信共享内存;
  • 使用Context时,把它放在函数的第一个参数;
  • 从其他包拷贝结构体时当心拷贝其内部的引用(切片等);
  • var t []string 与 t:=[]string{},前者是nil切片,后者是non-nil空切片,注意区分;
  • 使用crypto/rand生成密钥;
  • error string应该是小写的一个短语,因为它通常用在别的句子里;
  • 尽可能控制Go程生命周期;
  • 分组import;
  • . import通常只在test中有用;
  • 函数返回错误应该是最后一个返回值;
  • 处理错误流请这样写:

    1
    2
    3
    4
    5
    x, err := foo()
    if err != nil {
    // error handling
    }
    // use x, normal code
  • 使用后再定义接口,在接口的使用处定义,而不是接口的实现处;

  • 方法接受者的名字应该是一个简短的缩写,不应该是this, self这种,并且接受者名字请在所有方法中保持一致;
  • 传值或指针的场景:
    • 值:小类型、不需要更改的值、map、func、chan、不变的slice;
    • 指针:大类型、需要更改的值、包含同步的值、选值或指针都差不多时就选指针;
  • 尽可能使用同步函数;
  • 变量名尽可能短一点,声明距离使用越远就越应该有描述性;