go

Go 语言学习笔记

Posted on July 30, 2016

类型

类型基础

  • 未初始化的变量自动初始化为二进制的零值

    • int: 0
    • string: “”
    • slice: nil, 零值slice可append
    • map: nil, 零值的map能读(返回value零值)不能写
    • channel: nil
  • 变量声明(函数体内, 体外): var x int32

  • 变量声明+赋值(函数体内, 体外): var x int32 = 5 var x = 5

  • 变量声明+赋值(只能函数体内): x := 5

  • 批量声明 var x, y int

    批量声明赋值 var a, b = 0, "abc" x, z := 1, 5

    批量赋值 x, z = 1, x+2 先计算右值, 再赋值

  • :=

    限制: 声明变量同时显示初始化; 不能有数据类型; 只能在函数体内

    批量操作时, 可以部分退化为赋值, 就是赋值+声明赋值

  • 局部变量未使用将报错

    特殊变量_ 作为左值, 接收不用的返回值, 无法读取

  • 常量

    字面常量: 无类型, 赋值时常量自动转换

    编译期可确定的字符, 字符串 数字, 布尔值

    const x,y=123,"sds"

    cosnt a,b int=1,2

    常量作为指令数据, 无法取地址

  • 预定义常量

    • true
    • false
    • iota: 在每个const关键字出现时被置为0, 之后每次出现iota, 其值加一
  • 比较

    两个不同类型的整型数不能直接比较

    浮点数直接比较不精确, 使用math.Fdim代替

  • 自定义类型 type

    type 新的类型名称 基础类型

    新的类型名称 和 基础类型 的实例可以使用()相互强制类型转换

    即使基础类型相同, 也只表明它们有相同的底层数据结构, 两者不存在任何关系, 属于不同类型

    自定义类型只会继承基础类型的操作符, 其他如方法不会继承(但是基础类型中因为组合而来的方法, 新类型是可用的, 这其实和哪个类型无关, 是组合而来的)

    但是对于接口别名, 结果不一样: type 新的接口 基础接口 新的接口方法集和基础接口一致

    不能视为别名, 不能隐式转换, 不能直接用于比较表达式

    别名: byte/uint8 rune/int32

  • 未命名类型

    具有相同声明的未命名类型视为同一类型, 参见(Go语言学笔记)page 35

    未命名类型的转换规则 参见(Go语言学笔记)page 36

  • 分类:

    按照是否命名:

    • 命名类型: 数字, bool, 字符串, 字符
    • 未命名类型: 类型与具体元素类型或者长度有关: array, slice, map, channel, struct, 也可以通过type定一个命名类型

    按照语义:

    • 值语义:
    • 引用语义: slice, map, channel

    按照基本/复合:

    • 基本类型
    • 复合类型(array, slice, map, struct, pointer? channel? interface?) 初始化时(通常在右值)必须包含类型标签

      //struct
      a := mytype{
        1,
        "fox"}
      
      //slice
      b := []int {
         1,
         2}
      
      //array
      c := [2]int {
         1,
         2}
      
      //map
      d := map[string]int{
           "a": 1,
           "b": 2}
      
  • 字面量中类型推断

    type T struct {
      A int
      B string
    }
    
    type TT struct {
      t T
    }
    
    // map中kv是结构体可以推断
    m := map[string]T{
      "a": {3, "3"},
    }//ok
    
    // 类型数组中结构体可以推断
    t := []T{
      {1, "1"},
      {2, "2"},
    }//ok
    
    // 类型数组中map可以推断
    mm := []map[string]int{
      {"a": 1},
      {"b": 2},
    }//ok
    
    // 结构体中结构体不能推断
    tt := TT{{1, "1"}}//报错
    

具体类型

  1. 整数

  2. 浮点数

  3. 复数

  4. bool

    没有类型转换, 以下都报错:

    var v bool = 1 var v bool = bool(1)

    bool才能作为条件判断, 其他, 包括nil, 都不行

  5. 字符

    • byte (别名 uint8)
    • rune UTF-8字符 (别名 int32)

    别名无需转换, 可以直接赋值

    字符串变量:

    • byte: for i := 0; i < len(str); i++ {
    • rune: for i, ch := range str {
  6. 字符串

    • 不可变
    • 如果要可变, 需要转换为[]byte或者[]rune, 会重新分配内存和复制数据
    • len 返回字节长度; 字符长度可以这样len([]rune("世界"))
    • ``: 反引号支持跨行, 其中字符串不支持转义
    • 可以通过索引访问字节数组, 但是无法取其地址
    • 可通过切片方式访问, 但是返回值类型还是string
  7. 数组

    • 数组是一个值类型( value type)
    • 长度是数组类型的一部分, 数组长度在定义后就不可更改(len 不可变, 不能使用append方法, 但是内部数据可以变)
    • 初始化: a := [5] int{1,2,3} a := [...]int{1,2,3,4}
    • 结构体中的数组属性, 初始化时可以不需要类型
    • 定义类型时居然要带上长度: func modify(array [10] int) {
    • 所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作
      如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本, 如需修改, 可以传递数组指针或者数组的切片
    • 比较: 如果元素支持== != 数组也支持; 如果元素不支持(如?), 则比较会报错
    • 数组的指针和数组第一个元素的指针相同
      数组指针可以用于操作元素: &someArray[1]
    • len 和cap等值
  8. slice

    • 切片并非数组指针, 它有指向底层数组的独立数据结构, 限定读写范围
    • 切片访问起始索引从0开始, 超过index的读写会报错: ‘index out of range’
    • 通过已有数组创建切片:
      • len, cap不能超出原始数组的范围, cap不能小于len
      • someArray[开始索引:结束索引:cap索引], 这种数组必须是addressable的, 比如map中的数组就不是addressable
      • cap索引可以省略, 右开.
      • 开始索引:结束索引 左闭右开
      • len: 可读元素, 结束索引-开始索引
      • 新的切片会受到数组元素修改的影响, 不过如果切片append后超过cap重建后, 这时会复制数组元素为新的slice, 此时二者影响分离(即使slice的cap还在array的范围内, apend如果超过了cap, 但是还没超过array的范围, 也会导致重建)
    • 通过切片创建切片: 受原始切片cap影响
    • 切片修改对所有关联切片可见

    • 初始化变量: var a []int 没初始化的slice零值是nil
    • 数据初始化: make初始化: make([]int, len, cap) len必要, cap可省, 该数据不为nil
    • 赋值: b := []int {}

    • 不支持比较运算
    • slice 可以取地址, 但是地址和第一个元素的地址是不同的, slice的地址不能支持索引操作, 如(&someslice)[0]报错

    • append(someslice, item …Type)
      • 如果是基于底层数组, 不能超过底层数组的len
      • 如果超过本身的cap, 重新分配底层数组, cap将double(不精确), 因此性能有影响
      • 返回新的slice, 新旧slice指针不同, len, cap也是独立的, 但是底层数组相同
    • copy(des, src)

    • 迭代: for index, value range aSlice value 是副本, 整个循环中, value的地址不变, 每次复制. 想要获取slice的元素地址: &aSlice[i]
  9. map

    • 类型表示: map[key类型] value类型
    • 右值初始化: make(map[key类型] value类型)
    • 赋值: someMap[key] = value
    • 读取: value, ok := someMap[key] ok标识存在性, 读取如果不存在, value返回的是类型的零值
    • 删除: delete(someMap, key) key 如果不存在, 什么也不发生, key 如果是nil将抛出异常
    • 遍历: for k, v := range someMap { ...
    • len: 返回键值对数量, 无法使用cap
    • map的value成员是not addressable, 因此无法直接修改, 只能先返回value, 修改后再赋值到key上
  10. struct

    • 初始化时, 最后一个成员后面必须有同行的逗号或者}
    • new(结构体类型) 返回的是该结构体类型实例的指针
    • 字段名, 排列顺序, tag, (类型) 属于类型的组成
    • 不像map的value, struct的成员value是可以寻址的, 可以直接修改
    • 初始化:
      • 按序: 需要所有字段
      • 命名: 可以省略字段 (推荐)
    • 只有所有字段类型全部支持时, 才能做相等操作

    • 匿名字段/匿名组合
      • 字段只有类型, 没有字段名
      • 匿名字段名默认使用类型名, 类型可以是结构体, 接口
      • 匿名字段类型可以是指针, 默认的字段名是指针对应的原始类型, 因此不能同时有一个原始类型和它对应的指针的2个匿名字段
      • 匿名字段名不包括包名
      • 显示字段名(外层)会遮蔽匿名字段中的字段名
      • 该结构体实例可以直接使用匿名字段类型中的字段, 但是如果2个匿名字段中有相同的字段, 则需要指定匿名字段进行调用, 否则会报错
      • 注意采用匿名调用方法, 方法的接收者还是匿名字段实例, 而不是外部的struct
    • 可见性
      • 同包内的struct, 任何字段和方法都可见
      • 不同包内, 大写字母开头的字段和方法才可见
    • tag:
      • 类型的组成部分
      • 设置: 使用双引号或者反引号
      • 获取: 通过反射 reflect.TypeOf(结构体实例).Field(从0开始的属性索引).Tag
  11. 指针

    • 指针和内存地址不能混为一谈, 指针会分配内存空间, 存储地址
    • 指针支持相等运算, 但不能做加减乘除和类型转换
    • 指向同一地址或者都为nil的指针相同
    • 指针没有-> 使用.
    • 零值指针不等于nil
    • 二级指针(TODO) 可以用于实现传出参数

语句

  • 只有三种控制语句: if switch for, 其中for可以表达while

    for i:=0; i<10; i++ {...

    for x<10 {}

    for {}

    for i, v := range someArray {}

  • if

    条件不需要(), 语句必须要{}

    if之后, 条件之前, 可以进行变量初始化使用;间隔, 该变量是块内变量, if外无法访问: if a:=4; a<5 {...

  • for

    条件不需要(), 语句必须要{}

    多个赋值只支持多重赋值, 不支持平行多个赋值

    break 可以指定跳出多重for循环

  • switch

    • 如果有switch后有条件表达式, 则是与各个case后的值对比(相等?)
    • 如果没有条件表达式, case后应该是bool求值, 类似一堆if else
    • 不需要break
    • 只有明确的fallthrough, 才会执行下面一个case
  • 自增自减不是运算符, 只能作为独立语句, 不能用于表达式

    没有前置自增自减

    表达式可以用于语句, 语句不能用于表达式

  • 除位移操作外的二元运算符, 操作数类型必须相同


函数

  • 函数是第一类型

    可以作为返回值和参数

    可以多返回值

    无默认参数

  • 参数传递都是值拷贝: 拷贝目标对象或者拷贝指针

  • 可见性

    • 小写字母开头函数包内可见
    • 大写字母开头函数可以导出
  • 不定参数

    必须是函数最后一个参数

    函数定义: func fname(其他参数, args ...类型)

    args在函数内是一个数组切片

    调用时如果参数是slice, 展开: fname(someSlice…)

    任意类型的不定参数: func fname(args, ...interface{})

  • 多返回值

    多返回值函数的调用, 可以作为参数传递给其他函数; 还可以作为return的值返回

    如果没有返回值, return可省

    形参上的返回值名字可选, 加上名字好处:

    • 自动初始化, 可以通过一个return直接返回
    • 清晰可读, 用于文档
  • 函数只能判断是否是nil, 不能做其他比较操作

    未赋值的函数类型变量等于nil

  • defer

    一个函数可以有多个defer, 按照FILO执行(先入后出)

    延迟注册的是调用, 必须提供所需参数, 参数在注册时被复制缓存(赋值)

    顺序: return (return会修改返回参数), defer, ret

    延迟执行对性能影响大, 应该避免使用

  • 特殊函数main和init

    • Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)
    • 这两个函数在定义时不能有任何的参数和返回值
    • Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数
    • 虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,建议一个package中每个文件最多只写一个init函数, 同包不同源文件中init的执行顺序不做保证
    • 一个包会被多个包同时导入,那么它只会被导入一次

    执行过程:


错误处理

  • 标准错误是一个接口:

    type error interface {
      Error() string
    }
    
  • 创建标准错误; errors.New("error message")

  • 自定义错误:

    type PathError struct {...}
    //任意类型只需要实现了Error方法
    func (e *PathError) Error() string {
      return "......"
    }
    
  • 错误处理实例:

    n, err := Foo(0)
    if err != nil {
      //错误处理 􏰜􏰝􏰞􏰟
    } else {
      //使用n
    }
    
  • 异常抛出: func panic(interface{})

    立即中断函数执行, 开始执行defer

    在defer中调用panic, 不会终止后续defer的执行

  • 异常捕获: func recover() interface{}

    同一个函数中连续调用(第二个应该是在defer中调用)panic, 只有最后执行那个能被recover捕获

    异常捕获后, 可以再次抛出

    recover 必须在defer调用的函数中才有效, 如defer recover()无效

  • 异常捕获实例:

    defer func() {
      if r := recover(); r != nil {
        log.Printf("Runtime error caught: %v", r)
        debug.PrintStack() //打印异常栈, 需要import "runtime/debug"
      }
    }()
    
  • 规范:

    • 错误变量以err开头, 错误消息全小写, 不要结束标点
    • 自定义错误类型通常以Error结尾

方法

  • 定义要求: 对当前包里的除了指针,接口外的任何类型

  • 定义时receiver 如果是指针, 调用时对象不被复制

  • 实例方法可以按照类方法方式调用, 第一个参数是receiver

    type N int
    func (t N) test() {}
    var n N = 5
    N.test(n)
    
  • 方法可以通过类方法形式作为first class 传递

    f := N.test  //func(n N)
    f(n)
    
    f := (*N).test  //func(n *N)
    f(&n)
    

方法集规则

  • struct 上的普通方法, T 和 T 的方法是相互集成的, 且无法定义(T和T)同名方法.

    methodSet(T) = T#methods + *T#methods
    methodSet(*T) = T#methods + *T#methods
    
  • interface IT 上的方法, struct T 进行了实现, 和*T进行实现, 效果不一样:

    methodSet(T) = T#methods(T实现)
    methodSet(*T) = T#methods(T实现) + *T#methods(*T实现)
    
  • 组合方法

    type T struct{
      X
      *Y
    }
    methodSet(T)  = T#methods + *T#methods + X#methods              + Y#methods + *Y#methods
    methodSet(*T) = T#methods + *T#methods + X#methods + *X#methods + Y#methods + *Y#methods
    

    T的实例虽然可以直接调用*T#methods, 但是 T的实例的方法集中却没有包含*T#methods, 难道不是很奇怪吗?


接口

  • 接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量

  • 接口赋值: 小=大 (大变小)
    • 接口=类型实例: 要求类型实例实现了接口的所有方法
    • A接=B接口: A方法集 包含于 B方法集
  • 接口查询: 大=小 (小还原大)

    var varName1 interface1 = obj interface2|typeName  //大变小
    
    // varName1 必须是接口变量, 否则要报错
    if varName2, ok := varName1.(interface2|typeName); ok { //小还原大
      //此时 varName2 的类型为 interface2|typeName(同右边)
    } else {
      //varName1不能转换 interface,或者 varName1 不是 typeName 类型的变量  
    }
    
  • 接口的传递赋值:

    任何接口拿到pair(实际数据的类型和数据地址)都是一样的, 不会改变.

    接口查询(断言)可以查出中间接口类型, 并不需要在中间存在该接口传递赋值.

  • 类型查询:

    查询接口指向的对象的具体类型 只能用于接口类型

    varName.(type) 用于判断原始类型类型,只能用于switch,此处的 type 关键字

    这里的意思是, 小类型中装了大类型, 现在求出大类型用于判断

    所以这里编译时要求, varName 类型兼容所有case中类型

  • 接口组合

    内嵌的接口的方法集必须是互不相交的

    只有接口能被嵌入到接口中

    // ReadWriter 接口结合了 Reader 和 Writer 接口。
    type ReadWriter interface {
      Reader
      Writer
    }
    

面向对象

  • 所有的Go语言类型(指针类型除外)都可以有自己的方法

  • 为类型增加方法:

    • func (本体 类型) 方法名(入参列表) (出参列表) {...本体... 调用者(本体)值传递, 不会被改变
    • func (本体 *类型) 方法名(入参列表) (出参列表) {...*本体... 通过指针传递, 本体可以改变

    以上两种调用方式却是一样的, 都可以用本体调用, 也可以用指针调用

  • 类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法

  • 值语义

    赋值会复制: var a = [3]int{1, 2, 3}; var b = a

    包括

    • 基本类型
    • 复合类型(array, struct, pointer)

    如果需要表达引用, 需要使用指针: var a = [3]int{1, 2, 3}; var b = &a

  • 引用语义

    数组切片, map, channel, 接口 可以认为是引用语义, 仅是因为他们内部都存有指针

    引用类型需要使用make初始化, 如make(map[string]int):

    • 变量初始化: var m map[string]int
    • 数据类型初始化 m = make(map[string]int)
    • 数据初始化 m = map[string]int{ "name": fox}

    如果使用new, 只是返回指针, map对象并没有初始化, 无法工作 new(map[string]int)

    • 数组切片有点像引用语义, 但实际上是因为数组切片含有指向数组的指针, 所以切片赋值仍然是值语义
    • map 可以认为是存储了一个字段指针的结构体
    • channel 本质上也是一个指针

    总结:(数组切片, map, channel)赋值后, 是2个同步的变量, 变量指向的对象地址不同, 但是对象中复制了(数据结构相关的)地址, 因此可以实现引用的语义

more about go oop:

  • Go中没有Objects 和Classes, 通过types和methods来代替
  • Go中没有继承, 通过复合来实现继承

对象构造

  • new

    • 用于任意类型
    • 返回*T, 对结构体, 成员对象初始化为零值
    • 对于引用类型, 结果不可用, 对于其他类型, 结果可用
  • make

    • 只能用于”引用”类型: slice, channel, map
    • 返回类型为 T(而非*T)的一个已初始化 (而非置零)的值
  • 复合: sometype{k1: v1, k2: v2}

    • 可用于 struct, slice, array, map; key 是index或者属性, key可用用变量
    • 返回T, 测试看来结果是可用的 TODO

    • struct, slice, array 构造还可以省略其中的key

并发

  • go 函数调用

  • 函数调用的返回值抛弃

  • 主程序不会等其他goroutine结束, 需要程序自己控制

  • channel

    • 是Go语言在语言级别提供的goroutine间的通信方式
    • 是进程内的通信方式
    • 是类型相关的
    • 是原生类型, 其中也可以传递channel

    • 类型声明: var chanName chan ElementType
    • 初始化:
      • 双向: ch := make(chan int)
      • 单向只写: var ch2 chan<- int = ch 信道共享
      • 单向只读: var ch3 <-chan int = ch 信道共享, 只读通道无法close
      • 带缓冲: c := make(chan int, 1024), 这种读取可以直接使用range遍历for i := range c {
      • 初始化数组: chs := make([]chan int, 10) 其中元素是nil, 需要单独赋值
    • 写入: ch <- value 如果ch缓冲满了, 当前goroutine阻塞
    • 读取: value := <-ch 如果ch无数据, 当前goroutine阻塞
    • 关闭: close(ch), 阻塞的读通道将解除阻塞
    • 重复close channel 将引发panic
    • 向已关闭的channel写数据将引发panic
    • 已经close的channel还可以继续读取数据
    • b, ok := <-c ok 如果为false, 表示channel关闭且没有数据了.
  • select TODO

    • 每个 case语句都必须是一个面向channel的操作, 读出写入都行
    • 果任意一个通讯都可以进一步处理,则从中随机选择一个,执行对应的语句。否则,如果又没有默认分支(default case),select语句则会阻塞,直到其中一个通讯完成
    • 只有最多一个case可以执行成功, 一个case成功后, 其他case的channel操作应该都取消了吧? 是的, 见Go并发编程基础的最后’综合所有示例’
    • select 每次执行都会重新执行 case 条件语句,并重新注册到 select 中 正确使用Go的Timer
    • 监控多个io操作
    • 利用select可以实现io超时控制, 类似nodejs中的Promise#race
  • 出让时间片: runtime.Gosched()


反射

Go对象本身并没有类型指针, 通过自身无法在运行期获得任何类型相关信息

反射所需的全部信息来源于接口变量, 接口变量除了存储自身类型外, 还会保留实际对象的类型数据

  • reflect.TypeOf() 返回类型 *reflect.rtype

    func TypeOf(i interface{}) Type

  • reflect.ValueOf() 返回类型 reflect.Value

  • Type

    Type 的实例再取Typeof, 得到 *reflect.rtype TODO

    实例方法:

    • Kind() Kind 返回该Type的底层类型, Type是返回的静态类型(并不是变量的类型, 是对象初始化时的类型)
      如果是指针类型, 返回的是ptr
    • Name() string 输出Type的字符串形式, 会带上包名
    • PkgPath() string named type’s package path, that is, the import path
    • func ArrayOf(count int, elem Type) Type
  • Value

    实例方法:

    • func (v Value) Type() Type 返回Type
    • func (v Value) Elem() Value Elem returns the value that the interface v contains or that the pointer v points to. It panics if v’s Kind is not Interface or Ptr. It returns the zero Value if v is nil.
  • reflect.Type#Field(结构体属性索引) 返回类型 reflect.StructField
  • reflect.StructField属性Tag 返回结构体属性的tag

工程管理

包管理

  • 每个 go 语言程序的入口点都是 main package 的 main 函数

  • 一个包中的所有文件都必须使用相同的包名

  • 一个包下面可以有子目录, 子目录将是另一个包, 和父目录的包没有任何关系, 项目没有加载关系

  • 链接成单个二进制文件的所有包,其包名无需是唯一的,只有导入路径(它们的完整文件名)才是唯一的

    不同的路径, 里面定义的是同一个包, 只要在同一个文件使用时, 区分开来(用别名)就可以

  • 建议:

    • 包应当以小写的单个单词来命名,且不应使用下划线或驼峰记法
    • 包名应为其源码目录的基本名称
    • 请勿使用 import . 记法,它可以简化必须在被测试包外运行的测试, 除此之外应尽量避免使用
  • vendor

    import时, go 会根据当前包(当前编译的包), 查找vendor中是否存在import目标, 找不到的话就向上查找, 找上层目录中的vendor, 直到找到

    如果找不到应该就去gopath中找

    import路径中不能出现vendor

go tool

  • go get github.com/myteam/exp/crc32 远程import

    -u 同时更新依赖包

  • go build

    • 在不包含文件名时, go工具会使用当前目录来编译.
    • (当前目录)如果是普通包,当你执行go build,它不会产生任何文件。
    • (当前目录)如果是main包,当你执行go build,main包会编译可执行文件到当前目录(不是go文件所在的目录, 而是pwd执行目录) build main 包并不会把依赖的包install 到pkg
  • go install

    • go install 后面的是一个路径, 而不是一个包, 路径最后的目录名和包没有必然关系
    • import 后面也是一个路径, 但是import后默认导入可用的是真实的包名, 而不是之前路径的目录名
    • “无论是安装包还是二进制文件,go 工具都会安装它所依赖的任何东西(到pkg)” 到pkg的是按照路径目录, 最后一个目录生成.a文件, 和真实包名没有必然联系
    • (当前目录)如果是普通包,当你执行go install, 会编译出$GOPATH/pkg/平台/包名.a
    • (当前目录)如果是main包,当你执行go install, 会把main的可执行文件编译到$GOPATH/bin/, 同时把其他非main包编译到$GOPATH/pkg/平台/包名.a
    • 在任意目录, 需要执行go install 包名 可以达到以上同样效果

    • Go的可执行命令是静态链接的;在运行Go程序时,包对象(pkg)无需存在
  • go run

    • 不会生成pkg, 也不会生成bin
  • go get

    • 第一步是下载源码包,第二步是执行go install
    • 若指定的包不在工作空间中,go get 就会将会将它放到 GOPATH 指定的第一个工作空间内。(若该包已存在,go get 就会跳过远程获取, 其行为与 go install 相同)
    • 这个会描某个包的源码,获取能找到的所有依包
  • 目录

    • src/a/.../b b是包名, a…都是命名空间

    • 同一个包里的所有go文件将编译到一起(非main包将编译为一个.a文件)

  • go fmt 文件名 如不带任何参数直接执行go fmt的话,可以直 接格式化当前目􏰁下的所有*.go文件

  • vim 插件:


编译和调试参数

-gcflags

传递给编译器的参数. go tool compile -h

  • -B 禁用边界检查
  • -N 禁用优化
  • -l 禁用函数内联, 内联使可执行的二进制文件更大了,但性能更好.
  • -u 禁用unsafe代码
  • -m 输出优化信息
  • -S 输出汇编代码

常用:

-gcflags "-N -l" 忽略Go内部做的一些优化,聚合变量和函数等优化, 禁止优化和内联可以让运行时(runtime)中的函数变得更容易调试

-ldflags

传递给链接器的参数. go tool link -h

  • -w 禁用DRAWF调试信息,但不包括符号表
  • -s 禁用符号表
  • -X 修改字符串符号值 -X main.VER ‘0.99’ -X main.S ‘abc’
  • -H 链接文件类型,其中包括windowsgui. cmd/ld/doc.go

常用:

  • -ldflags "-s" 去掉调试信息,减小大约一半的大小, 常用于需要对外发布的结果
  • -ldflags "-s -w" 删除调试符号, -s: 去掉符号信息 -w: 去掉DWARF调试信息
  • 在编译时用ldflags设置变量的值: go build -ldflags “-X main.VERSION=1.0.0 -X ‘main.BUILD_TIME=date’ -X ‘main.GO_VERSION=go version’”

参考:


DOC

  • 貌似go doc 输出的不全, 貌似弃用, 使用godoc

  • godoc 包路径 是路径, 和包名没有必然联系

  • 输出:

    • PACKAGE DOCUMENTATION

      包的improt方式, 说明等

      包说明:

      • 放置在包子句(package)前的一个块注释。对于包含多个文件的包, 包注释只需出现在其中的任一文件中即可
      • 注释是不会被解析的纯文本
    • VARIABLES

      (首字母大写)export的会输入

    • FUNCTIONS

      输出的方法签名和方法注释

    • SUBDIRECTORIES

      包含的子目录

  • 建议

    • 每个可导出(首字母大写)的名称都应该有文档注释

2017-9-28 补充

 type duration int

 func (d *duration) pretty() string {
   return fmt.Sprintf("Duration: %d", *d)
 }

 func main() {
   duration(42).pretty()
 }

42 是常量, 是在代码段里的, duration(42) 只是转换了一下类型, 还是常量, 仍然在代码段里, 代码段里的数据, 是不允许取地址的. 取到了地址, 就可以改数据, 代码段是只读, 不能修改.


2018-2-16 补充

fmt.Printf(" %#v", a) 打印出的类型一定不是interface, 会展示对应的数据类型.


TODO

  • unsafe.Pointer
  • fmt
  • struct tag
  • reflect

参考资料

2016-11-23 补充:

2017-09-07 补充:

2017-09-09 补充:

空结构体struct{}解析

空struct不占空间

Go Interface 实现 还没看完 golang: 详解interface和nil