Gotchas

Assumptions

  1. 以下go-src代表go语言源码文件夹
  2. 以下代码均在test.go中测试
  3. 一切测试从简, 保证代码量尽可能小, 尽可能容易理解
  4. 不推荐用的或奇技淫巧只给例子

Builtin Identifiers

See go-src/builtin/builtin.go

a. byte是uint8的别名

package main

func main() {
    var u uint8 = '1'
    var b byte = u
    println(b) // prints: 49
}

b. rune是int32的别名

package main

func main() {
    var is = []int32{'你', '好'}
    var rs []rune = is
    println(rs[0], rs[1]) // prints: 20320 22909
}

c. int是多少位看系统

有资料称:

自 Go1.1 后,intuint 的尺寸统一是 64bits,即使是在 32bits 平台下。
package main

import (
    "unsafe"
)

func main() {
    var (
        a int32
        b int64
        c int
    )

    println(unsafe.Sizeof(a)) // prints: 4
    println(unsafe.Sizeof(b)) // prints: 8
    println(unsafe.Sizeof(c)) // prints: ?(见下面测试)
}
测试1: darwin amd64
prints: 8

测试2: windows 386
prints: 4

d. 不要cap map

package main

func main() {
    var m = make(map[int]int, 10)
    println(cap(m))
}

编译错误:

# command-line-arguments
./test.go:5: invalid argument m (type map[int]int) for cap

e. for range的使用

Valid usage:

slice:

for range slice {}
for i := range slice { _ = slice[i] }
for i, v := range slice { _ = slice[i] == v }
for _, v := range slice { _ = v }

map:

for range map {}
for k := range map { _ = map[k] }
for k, v := range map { _ = map[k] == v }
for _, v := range map { _ = v }

chan:

for range chan {}
for v := range chan { _ = v } // break after close(chan) and no more elem in chan

f. ‘_’的使用

Valid usage:

// 用于注册驱动等,运行init函数
import _ "fmt"

// 建议只用在测试
_ = v

// 用于获取值时
for _, v := range slice/map {}

// 不建议下面写法
for _ = range slice/map/chan {}
// 可替换为
for range slice/map/chan {}

// 字段名可以使用, 但仅限于为了内存对齐
struct { _ int }

// 建议只有当兼容接口并且参数无用时用 _
// 同样只有当兼容接口并且需要吞掉返回错误值时用 _
func(_ int) (_ error)

另外

// 不建议写成下面形式:
func (_ T) funcName() {}

// 下划线在这没用,可以省略掉:
func (T) funcName() {}

// 因为参数T无用,更建议不要T:
func funcName() {}

g. :=的使用

a. 不能用于声明全局变量

package main

a := 10

func main() {}

编译错误:

# command-line-arguments
./test.go:3: syntax error: non-declaration statement outside function body

b. 不能用于结构体字段值

package main

type foo struct {
    bar int
}

func main() {
    var f foo
    f.bar, tmp := 1, 2
}

编译错误:

# command-line-arguments
./test.go:9: non-name f.bar on left side of :=

c. 隐藏变量作用域

package main

func main() {
    var a  = 5
    if a, ok := 6, true; ok {
        println("a in if:", a) // prints: 6
    }
    println("a outer:", a) // prints: 5
}

String

a. 注意特殊字符

一个字符可能占用多个runes.

package main

import "unicode/utf8"

func main() {  
    data := "é"
    println(len(data))                    // prints: 3
    println(len([]rune(data)))            // prints: 2
    println(utf8.RuneCountInString(data)) // prints: 2
}

b. range string尝试解析为rune

package main

import "fmt"

func main() {
    data := "A\xfe\x02\xff\x04"
    for _,v := range data {
        fmt.Printf("%#x ",v)
    }
    fmt.Println()
    // prints: 0x41 0xfffd 0x2 0xfffd 0x4

    for _,v := range []byte(data) {
        fmt.Printf("%#x ",v)
    }
    fmt.Println()
    // prints: 0x41 0xfe 0x2 0xff 0x4
}
package main

import "fmt"

func main() {
    for i, r := range "你好" {
        fmt.Printf("%d: %c\n", i, r)
    }
}
// Output: 注意下标
// 0: 你
// 3: 好

c. []byte可以append string

package main

import "fmt"

func main() {
    fmt.Printf("%s\n", append([]byte(nil), "Hello world"...))
    // prints: Hello world
}

d. []rune不可以append string

package main

import "fmt"

func main() {
    fmt.Printf("%s\n", append([]rune(nil), "Hello world"...))
}

编译错误:

# command-line-arguments
./test.go:6: cannot use "Hello world" (type string) as type []rune in append

Slice

a. 下标初始化方式

package main

// 有...为数组, 无...为切片
var ls = [...]int{
    1: 3,
    3: 6,
    6: 9,
}

func main() {
    println(len(ls)) // prints: 7
}

b. 什么时候会重新分配

package main

// 机器不同, 地址可能不同
func main() {
    var slice []int
    println(slice) // prints: [0/0]0x0, 初始地址必然相同

    slice = make([]int, 0, 1) // make时重新分配内存
    println(slice) // prints: [0/1]0xc42003bf28

    slice = append(slice, 1) // cap足够时不重新分配
    println(slice) // prints: [1/1]0xc42003bf28

    slice = append(slice, 2) // cap不够时重新分配
    println(slice) // prints: [2/2]0xc42000a120
}

注意: 切片修改, 涉及len和cap的变动, 要么用指针, 要么返回新切片(建议返回切片)

package main

import "fmt"

func usePointer(ls *[]int) {
    *ls = append(*ls, 1)
}

func retSlice(ls []int) []int {
    return append(ls, 2)
}

func main() {
    var ls []int
    usePointer(&ls)
    fmt.Println(ls) // prints: [1]

    fmt.Println(retSlice(nil)) // prints: [2]
}

c. 什么时候会改变值

Slice in struct

package main

import "fmt"

type T struct {
    ls []int
}

func foo(t T) { // t为传值
    t.ls[0] = 999 // 但字段ls却相当于变相传址
}

func main() {
    var t = T{ls: []int{1, 2, 3}}
    fmt.Println(t) // prints: {[1 2 3]}

    foo(t)
    fmt.Println(t) // prints: {[999 2 3]}
}

Slice insert

package main

import "fmt"

func main() {
    var ls = []int{0, 1, 3, 4}
    ls = append(append(ls[:2], 2), ls[2:]...)
    fmt.Println(ls)
    // prints: [0 1 2 2 4] but we want [0 1 2 3 4]
}

正确写法: 用切片的完整写法 slice[start:len:cap]

package main

import "fmt"

func main() {
    var ls = []int{0, 1, 3, 4}
    ls = append(append(ls[:2:2], 2), ls[2:]...)
    fmt.Println(ls)
    // prints: [0 1 2 3 4]
}

d. 调整len和cap值

本作法为奇技淫巧, 不推荐

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    var ls = []int{0, 1, 2, 3, 4}[:0]
    println(ls) // prints: [0/5]0xc4200740c0
    fmt.Println(ls) // prints: [] // 切片过后不知道原数组里的值

    var header = (*reflect.SliceHeader)(unsafe.Pointer(&ls))
    header.Len = header.Cap

    println(ls) // prints: [5/5]0xc4200740c0
    fmt.Println(ls) // prints: [0 1 2 3 4]
}

Map

a. 什么时候不能省略ok

主要用于判断存在关系

1. 当value是struct时,根据ok判断struct是不是空值

2. 判断value值是否存在时,最好用ok;如果value可能存在默认值时,只能用ok

package main

func main() {
    var m = map[int]string{1: "asd", 2: "qwe"}
    if m[1] != "" { // 没有默认值存在时可以与默认值比较, 但最好还是用ok
        println("1 exist")
    }

    m[0] = "" // map中存在默认值
    if _, ok := m[0]; ok { // 这里只能用ok
        println("0 exist")
    }
}

b. 什么时候最好省略ok

主要用于使用取出来的值时

1. 当value是指针、map、chan、func时,据永远不要省略判空操作,取出来的指针是要判空的,所以无谓判ok

2. 当value是数字类型、字符串、切片、数组时,因为都可以直接用默认值,所以不用判ok

package main

func main() {
    var m = make(map[rune]int)

    for _, r := range "Hello wrold" {
        m[r]++
    }

    println("'l' appears", m['l'], "times.")
}

c. 当Set(集合)用

这里,我们不关心value的值,只关心key的值时,即可以把map当set用,有两种形式:
1. map[key]bool,用这种形式时,多是用于判断键值的存在与否,方便写if表达式

2. map[key]struct{}用这种形式时,多是只关心集合内有什么,多用于排重

上述两种写法较之随手写的map[key]int之类有两个好处:

  1. 看到这两种写法时,让阅读代码者知道,去关心key,而不是value及key value间的对应关系。随手写的int或其他类型有一定误导作用;(主要好处,写代码的人要多敲几个字符,但看代码的人会减少心智负担)
  2. 节约内存。(不是主要好处,因为量少的时候也节约不了多少)
package main

func main() {
    var m = make(map[rune]struct{})

    for _, r := range "Hello wrold" {
        m[r] = struct{}{}
    }

    println("Total", len(m), "diffent characters.")
}

d. 非内存安全类型

  1. 在读map之前可以不分配内存,nil map是可读的;
  2. 在写map之前一定记得分配内存,nil map would panic;
  3. 多协程并发读map时可以不加锁,但并读发写时一定要记得加锁,否则非常有可能panic
package main

func main() {
    var m map[string]int
    println(m["abc"]) // prints: 0
}
package main

func main() {
    var m = make(map[int]int)

    go func() {
        for i := 10; i != 0; i++ {
            m[i] = i
        }
    }()

    for i := 1; i != 0; i++ {
        m[i] = i * 10
    }
}

panic:

fatal error: concurrent map writes

goroutine 17 [running]:
runtime.throw(0x679bb, 0x15)
    /usr/local/go/src/runtime/panic.go:566 +0x95 fp=0xc420020688 sp=0xc420020668
runtime.mapassign1(0x59220, 0xc42006a000, 0xc4200207a0, 0xc420020798)
    /usr/local/go/src/runtime/hashmap.go:458 +0x8ef fp=0xc420020770 sp=0xc420020688
main.main.func1(0xc42006a000)
    /Users/sg/test/test.go:8 +0x66 fp=0xc4200207b8 sp=0xc420020770
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4200207c0 sp=0xc4200207b8
created by main.main
    /Users/sg/test/test.go:10 +0x73

goroutine 1 [runnable]:
main.main()
    /Users/sg/test/test.go:13 +0xc3
exit status 2

e. 字面量简化写法

可以省略类型声明(Go1.5添加)

package main

type Pos struct {
    x, y int
}

var m = map[Pos]map[string][]Pos{
    {0, 1}: {"hell": {{0, 1}, {1, 2}}},
    {3, 4}: {"heaven": {{3, 4}, {4, 5}}},
}

func main() {}

Chan

a. 永远不要省略ok

package main

func main() {
    var ch = make(chan int, 1)
    ch <- 1
    close(ch)

    for {
        v := <-ch
        println(v) // prints: 1, 0, 0, 0, 0............
    }
}

正确写法1:

for v := range ch {
    println(v)
}

正确写法2:

for {
    v, ok := <-ch
    if !ok {
        break
    }
    println(v)
}

b. closed chan

如上所述,可以不停地从closed chan中读取数据,所以closed chan一般用作协程同步

package main

import "sync"

func main() {
    var ch = make(chan struct{})
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            <-ch // 等待close(ch),所有协程同时开始
            println(n)
            wg.Done()
        }(i)
    }

    close(ch)
    wg.Wait()
}
// prints: 1 7 2 8 6 5 3 0 9 4 (随机的)

或者如:

package main

func main() {
    var ch = make(chan struct{})

    go func() {
        println("Do something")
        close(ch)
    }()

    <-ch
    println("Done")
}

closed chan不可以再次被close

package main

func main() {
    var ch = make(chan struct{})
    close(ch)
    close(ch)
}

panic:

panic: close of closed channel

goroutine 1 [running]:
panic(0x59640, 0xc42000a130)
    /usr/local/go/src/runtime/panic.go:500 +0x1a1
main.main()
    /Users/sg/test/test.go:6 +0x57
exit status 2

c. nil chan

nil chan的读和写都会阻塞,所以一般用来在select中“禅让”:

package main

func main() {
    var ch = [2]chan int{make(chan int, 5), make(chan int, 5)}
    for i := 0; i < 10; i++ {
        ch[i%len(ch)] <- i
    }

    var tmp chan int
    tmp, ch[1] = ch[1], tmp

    for i := 0; i < 10; i++ {
        select {
        case v := <-ch[0]:
            println("0:", v)
            ch[0], ch[1], tmp = ch[1], tmp, ch[0]
        case v := <-ch[1]:
            println("1:", v)
            ch[0], ch[1], tmp = tmp, ch[0], ch[1]
        }
    }
}

Output:

0: 0
1: 1
0: 2
1: 3
0: 4
1: 5
0: 6
1: 7
0: 8
1: 9

nil chan不可以被close

package main

func main() {
    var ch chan int
    close(ch)
}

panic:

panic: close of nil channel

goroutine 1 [running]:
panic(0x59540, 0xc42000a130)
    /usr/local/go/src/runtime/panic.go:500 +0x1a1
main.main()
    /Users/sg/test/test.go:5 +0x2a
exit status 2

d. 与time.After共用

伤心的time.After, case 1:

package main

import "time"

func main() {
    var ch = make(chan int)

    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
            time.Sleep(800 * time.Millisecond)
        }
        close(ch)
    }()

    for {
        select {
        case v, ok := <-ch:
            if !ok { return }
            println("recv from chan:", v)
        case now := <-time.After(time.Second):
            println("recv from time:", now.Nanosecond())
        }
    }
}

// Output:
// recv from chan: 0
// recv from chan: 1
// recv from chan: 2
// recv from chan: 3
// recv from chan: 4

伤心的time.After, case 2:

package main

import "time"

func main() {
    var ch = make(chan int, 5)
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)

    for {
        select {
        case v, ok := <-ch:
            if !ok { return }
            println("recv from chan:", v)
        case now := <-time.After(0):
            println("recv from time:", now.Nanosecond())
        }
    }
}

// Output:
// recv from chan: 0
// recv from chan: 1
// recv from chan: 2
// recv from chan: 3
// recv from chan: 4

如何才能让time.After上位?

package main

import "time"

func main() {
    var ch = make(chan int, 5)
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)

    for {
        after := time.After(0) // time.Millisecond也可以
        time.Sleep(time.Millisecond)

        select {
        case v, ok := <-ch:
            if !ok {
                return
            }
            println("recv from chan:", v)
        case now := <-after:
            println("recv from time:", now.Nanosecond())
        }
    }
}

// Output:
// recv from time: 796825292
// recv from chan: 0
// recv from time: 799508258
// recv from time: 800867970
// recv from chan: 1
// recv from time: 803666609
// recv from chan: 2
// recv from chan: 3
// recv from time: 807868034
// recv from time: 809129591
// recv from time: 810508449
// recv from chan: 4
// recv from time: 813287164

为何如此伤心? 因为每次一个新的time.After()都是从新计算时间的,而且还要调用runtime中的函数,会有一定的时延,造成time.After(0)也不能上位。

如果是定时任务,建议用time.Ticker

package main

import "time"

func main() {
    var ch = make(chan int, 10)

    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
            time.Sleep(time.Millisecond)
        }
        close(ch)
    }()

    var ticker = time.NewTicker(2 * time.Millisecond)
    defer ticker.Stop() // 一定要记得Stop()

    for {
        select {
        case v, ok := <-ch:
            if !ok {
                return
            }
            println("recv from chan:", v)
        case now := <-ticker.C:
            println("recv from time:", now.Nanosecond())
        }
    }
}

// Output:
// recv from chan: 0
// recv from chan: 1
// recv from time: 864627593
// recv from chan: 2
// recv from chan: 3
// recv from time: 866541281
// recv from chan: 4
// recv from chan: 5
// recv from time: 868498022
// recv from chan: 6
// recv from time: 870687259
// recv from chan: 7
// recv from chan: 8
// recv from time: 872546370
// recv from chan: 9
// recv from time: 874715515

Break

a. break switch

break只能跳出switch,却跳不出for,程序永远不会结束

package main

func main() {
    for {
        switch {
        default:
            break
        }
    }
}

b. break select

break只能跳出select,却跳不出for,程序永远不会结束

package main

func main() {
    for {
        select {
        default:
            break
        }
    }
}

c. 最好单独成函数

当然,一种解决办法就是break label

package main

func main() {
LOOP:
    for {
        switch {
        default:
            break LOOP
        }
    }

    println("Done") // prints: Done
}

比较推荐的办法是,将switch或select抽成一个函数,不要和for搞在一起

package main

func breakfor(i int) bool {
    switch {
    case i > 5:
        return true
    }
    return false
}

func main() {
    for i := 0; i < 10; i++ {
        if breakfor(i) {
            println("break for on i is", i)
            break
        }
    }
}
// prints: break for on i is 6

d. goto不能跨函数跳

package main

func main() {
    func() {
        goto END
    }()
END:
}
# command-line-arguments
./test.go:5: label END not defined
./test.go:7: label END defined and not used

Pointer

a. 永远不要省略判空操作

在项目中,建议永远不要省略对指针的判空操作,因为指不定有哪些人会怎么调这个函数

package main

type T struct {
    someSegment interface{}
}

func process(t *T) {
    if t == nil {
        // do some log or others
        return
    }

    // do something with t...
}

func main() {
    process(nil)
}

如果有必要的话,结构体的方法接收者也要判空

package main

import "fmt"

type T struct {
    someSegment interface{}
}

func (t *T) String() string {
    if t == nil {
        return "<nil>"
    }

    return fmt.Sprint(t.someSegment)
}

func main() {
    fmt.Println((*T)(nil)) // prints: <nil>
}

同理的还有map, chan, func,总之一切有可能出错的地方,尽量加上判空操作

b. 不可寻址的情况

map[key]struct不可寻到struct的物理地址

package main

type T struct {
    n int
}

func main() {
    var m = make(map[int]T)

    m[0].n = 1
}

编译错误:

# command-line-arguments
./test.go:10: cannot assign to struct field m[0].n in map

返回struct不可寻到struct的物理地址

package main

type T struct {
    n int
}

func getT() T {
    return T{}
}

func main() {
    getT().n = 1
}

编译错误:

# command-line-arguments
./test.go:12: cannot assign to getT().n

不可寻址的结构不能调用结构体指针函数

package main

type T struct {
    n int
}

func (t *T) Set(n int) {
    t.n = n
}

func getT() T {
    return T{}
}

func main() {
    getT().Set(1)
}

编译错误:

# command-line-arguments
./test.go:16: cannot call pointer method on getT()
./test.go:16: cannot take the address of getT()

解决办法: 用指针,或用一临时变量

package main

type T struct {
    n int
}

func (t *T) Set(n int) {
    t.n = n
}

func getT() T {
    return T{}
}

func main() {
    var m = map[int]*T{1: &T{}}
    m[1].n = 1 // 一定要先判断存在与否,否则会nil pointer dereference

    var t = getT()
    t.Set(2)
}

Func

a. 只能与nil比较

valid:

package main

func main() {
    var fn = func() {}

    if fn != nil {
        println("fn is not nil")
    }
}

invalid:

package main

func main() {
    var fn1 = func() {}
    var fn2 = func() {}

    if fn1 != fn2 {
        println("fn1 not equal fn2")
    }
}

编译错误:

# command-line-arguments
./test.go:7: invalid operation: fn1 != fn2 (func can only be compared to nil)

b. 闭包使用

1). 最常见的格式: go func(),注意公共变量

错误格式(1):

package main

import "time"

func main() {
    for i := 0; i < 5; i++ {
        go func() {
            println(i) // prints: 5 5 5 5 5
        }()
    }

    time.Sleep(100 * time.Millisecond)
}

错误格式(2):

package main

import "time"

func main() {
    for i := 0; i < 5; i++ {
        go func(n *int) {
            println(*n) // prints: 5 5 5 5 5
        }(&i)
    }

    time.Sleep(100 * time.Millisecond)
}

正确格式(1):

package main

import "time"

func main() {
    for i := 0; i < 5; i++ {
        i := i
        go func() {
            println(i) // prints: 0 2 3 4 1
        }()
    }

    time.Sleep(100 * time.Millisecond)
}

正确格式(2):

package main

import "time"

func main() {
    for i := 0; i < 5; i++ {
        go func(n int) {
            println(n) // prints: 2 4 1 0 3
        }(i)
    }

    time.Sleep(100 * time.Millisecond)
}

range的情况相同,这里不做赘述

2). 递归

package main

func main() {
    var fn = func(n int) {
        if n < 0 {
            return
        }
        println(n)
        fn(n - 1)
    }

    fn(5)
}

编译错误:

# command-line-arguments
./test.go:9: undefined: fn

正确写法:

package main

func main() {
    var fn func(int)
    fn = func(n int) {
        if n < 0 {
            return
        }
        println(n)
        fn(n - 1)
    }

    fn(5)
}
// prints: 5 4 3 2 1 0

3). for循环defer

package main

import "sync"

func main() {
    var mtx sync.Mutex
    var m = make(map[int]int)

    for i := 0; i < 5; i++ {
        mtx.Lock()
        defer mtx.Unlock()
        m[i] = i << 8 // 或是一些其他什么操作
    }
}

本想是离开scope时做的事情,结果出现意外的结果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42000a0ac)
    /usr/local/go/src/runtime/sema.go:47 +0x30
sync.(*Mutex).Lock(0xc42000a0a8)
    /usr/local/go/src/sync/mutex.go:85 +0xd0
main.main()
    /Users/sg/test/test.go:10 +0xf3
exit status 2

正确写法:

package main

import "sync"

func main() {
    var mtx sync.Mutex
    var m = make(map[int]int)

    for i := 0; i < 5; i++ {
        func() {
            mtx.Lock()
            defer mtx.Unlock()
            m[i] = i << 8
        }()
    }
}

c. 结构体的函数

结构体函数的三种调用方式:

package main

type T struct {
    s string
}

func (t *T) greet() {
    println(t.s)
}

func main() {
    var t = &T{"Hello"}
    t.greet()

    var greet = (*T).greet
    greet(&T{"你好"})

    var greeting = (&T{"こんにちは"}).greet
    greeting()
}

d. 搭配go、defer时代码的执行顺序

go、defer关键字新开协程,只针对最终要调用的函数,参数中如果有函数,不是在新协程里或原函数退出时执行的

package main

import "time"

func Defer() (_ string) {
    println("defer")
    return
}

func Go() (_ string) {
    println("go")
    return
}

func main() {
    defer print(Defer())
    go print(Go())
    println("println")
    time.Sleep(10 * time.Millisecond)
}
// Output:
// defer
// go
// println

如果想要类似println, go, defer这种顺序的结果,应该用func(){}把要执行的语句块包起来:

defer func() {
    print(Defer())
}()

go func() {
    print(Go())
}()

e. 再续for range组合

期望输出1, 2, 3的各种组合吗?Not in your life!

package main

import "time"

type T struct {
    n int
}

func (t *T) print() {
    println(t.n)
}

func main() {
    var ts = []T{{1}, {2}, {3}}
    for _, t := range ts {
        go t.print()
    }
    time.Sleep(10 * time.Millisecond)
}

// prints: 3 3 3

它和下面代码等价:

func main() {
    var ts = []T{{1}, {2}, {3}}
    var printT = (*T).print
    var t T
    for i := range ts {
        t = ts[i]
        go printT(&t)
    }
    time.Sleep(10 * time.Millisecond)
}

注意,参考结构体函数的第二种写法,t.print()是要取t的地址传过去,上面的写法最明显的漏洞便在于t的地址没变,而t的值却连变3次

解决办法也很简单:

办法1:

var ts = []*T{{1}, {2}, {3}} // 变成the slice of pointers

办法2:

var ts = []T{{1}, {2}, {3}}
for i := range ts {
    go ts[i].print()
}

其它办法参见闭包使用

map的情况相同,这里不做赘述


Interface

a. nil interface

只有当直接给interface{}赋值为nil时,interface{}才等于nil:

package main

func main() {
    var i interface{} = nil
    println(i == nil) // prints: true

    i = (*int)(nil)
    println(i == nil) // prints: false
}

interface{}为nil的条件:

1. interface{}由两部分组成: type和value,只有其中有一不为nil,则interface{}不为nil

2. 不可能出现type为nil而value不为nil的情况

由上可得出判别interface{}是否为nil的简便办法: %T

package main

import "fmt"

func main() {
    var i interface{} = nil
    fmt.Printf("i == nil: %v, type: %T\n", i == nil, i)
    // prints: i == nil: true, type: <nil>

    i = (*int)(nil)
    fmt.Printf("i == nil: %v, type: %T\n", i == nil, i)
    // prints: i == nil: false, type: *int
}
package main

func isNil(i interface{}) bool {
    switch i.(type) {
    case nil:
        return true
    }
    return false
}

func main() {
    println(isNil(nil)) // prints: true

    var t *int
    var i interface{} = t
    println(isNil(t)) // prints: false

    println(i == (*int)(nil)) // prints: true
}

b. 类型断言

建议使用类型断言的时候永远不要省略ok,否则如果类型不一致会panic

package main

func main() {
    var i interface{} = 5
    n, ok := i.(float64)
    if !ok {
        println("i is not float64")
        return
    }
    println(n)
}
// prints: i is not float64
package main

func main() {
    var i interface{} = 5
    println(i.(float64))
}

panic:

panic: interface conversion: interface is int, not float64

goroutine 1 [running]:
panic(0x59960, 0xc42006a000)
    /usr/local/go/src/runtime/panic.go:500 +0x1a1
main.main()
    /Users/sg/test/test.go:5 +0x8e
exit status 2

c. 实现接口的要求

由于在interface{}内部不可以对变量进行寻址,所以下面程序是错误的:

package main

import "io"

type NopReader struct{}

func (*NopReader) Read(b []byte) (int, error) {
    return len(b), nil
}

func main() {
    var _ io.Reader = NopReader{}
}

编译错误:

# command-line-arguments
./test.go:12: cannot use NopReader literal (type NopReader) as type io.Reader in assignment:
    NopReader does not implement io.Reader (Read method has pointer receiver)

而在interface{}内是可以进行解指针引用操作的,所以下面程序是合法的:

package main

import "io"

type NopReader struct{}

func (NopReader) Read(b []byte) (int, error) {
    return len(b), nil
}

func main() {
    var _ io.Reader = &NopReader{}
}

注意: 在Go1.4后不再在**T进行解引用,也就意味着下面程序是错的

package main

import "io"

type NopReader struct{}

// 无论此处是不是指针
func (*NopReader) Read(b []byte) (int, error) {
    return len(b), nil
}

func main() {
    var nop = &NopReader{}
    var _ io.Reader = &nop
}

编译错误:

# command-line-arguments
./test.go:13: cannot use &nop (type **NopReader) as type io.Reader in assignment:
    **NopReader does not implement io.Reader (missing Read method)

Comment

a. build tag

    ./
    |- mypkg/
    |    +- win.go
    +- test.go

win.go

// +build windows

package mypkg

func init() {
    println("This file will only be compiled on windows.")
}

test.go

package main

import _ "./mypkg"

func main() {}

编译错误:

test.go:3:8: no buildable Go source files in /Users/sg/test/mypkg

+build windows 改成 +build !windows, 输出:

This file will only be compiled on windows.

编译规则:

1). 编译标签由空格分隔的编译选项(options)以”或”的逻辑关系组成

2). 每个编译选项由逗号分隔的条件项以逻辑”与”的关系组成

3). 每个条件项的名字用字母+数字表示,在前面加!表示否定的意思

4). 多个编译标签之间是逻辑”与”的关系

b. package tag

    ./
    |- src/
    |   +- mypkg/
    |        +- my.go
    +- test.go

    export GOPATH=`pwd`

my.go

package mypkg // import "third/mypkg"

func init() {
    println(`This pkg must import with "third/mypkg"`)
}

test.go

package main

import _ "mypkg"

func main() {}

编译错误:

test.go:3:8: code in directory /Users/sg/test/src/mypkg expects import "third/mypkg"

不建议使用pkg tag来表明包的导入方式,否则当包的路径变化后,会出现如上,导入失败的情况


Vendor

a. vendor中的包和外面的包是两个不同的包

以下目录:

  ./
  |- test.go
  +- src/
      |- mypkg/
      |    |- my.go
      +- vender/
           |- vd.go
           +- vendor/
                +- mypkg/
                     +- my.go
// test.go
package main

import (
  _ "mypkg"
  _ "vender"
)

func main() {
}
// mypkg/my.go 
package mypkg

func init() {
  println("init mypkg")
}
// vender/vd.go
package vender

import _ "mypkg"
// vender/vendor/mypkg/my.go 
package mypkg

func init() {
  println("init mypkg in vendor")
}

运行test.go:

init mypkg
init mypkg in vendor

有关vendor的详细说明,可以参考GopherCon 2016: Wisdom Omuya - Go Vendoring(感谢丹丹哥Daniel提供)

当用官方包database/sql时须注意: 每个驱动只能注册一次,否则会panic,如果在导入的包中嵌有vendor,记得检查会不会重复注册驱动!!!


Bit operation

a. 优先级与其他语言不同

先看例子:

#include <stdio.h>

int main() {
    printf("%d\n", 1 << 1 + 1); // prints: 4
    printf("%d\n", 6 & 4 * 3);  // prints: 4
    return 0;
}
package main

func main() {
    println(1 << 1 + 1) // prints 3, not 4
    println(6 & 4 * 3)  // prints 12, not 4
}

golang的运算符优先级

优先级运算符
7^ !
6
  • / % << >> & &^
5
    • | ^
4== != < <= >= >
3<-
2&&
1||

注意,如果不是特别清楚,最好加上()以保证运算优先级

b. 取反操作符与其他语言不同

取反操作,在c语言及其他很多语言中是~,在go语言中是^

// go
package main

func main() {
    println(^9) // prints: -10
}
# python
print ~9 # prints: -10
// c
#include <stdio.h>

int main() {
    printf("%d\n", ~9); // prints: -10
    return 0;
}

c. 不是写算法尽量少用位运算

注意: 位运算本身不容易理解, 不是写算法要求效率时, 尽可能少用位运算

举例

package main

import "fmt"

// 来源: C语言
func setZero() {
    var t = 5
    println(t ^ t)  // prints: 0
    println(t &^ t) // prints: 0
}

// 来源: C语言
func abs() {
    var x int32 = -9
    var y int32 = x >> 31
    println((x ^ y) - y) // prints: 9
}

// 来源: C语言
func swap() {
    var x, y = 5, 6
    x ^= y
    y ^= x
    x ^= y
    println(x, y) // prints: 6 5
}

// 来源: C语言, 防溢出
func average() {
    var x, y = 5, 7
    println((x & y) + ((x ^ y) >> 1)) // prints: 6
}

// 来源: 《汇编语言(第2版)》 --王爽
func transLetter() {
    fmt.Printf("%c\n", 'A'|32)        // prints: a
    fmt.Printf("%c\n", 'a'&^byte(32)) // prints: A

    fmt.Printf("%c\n", 'A'^32) // prints: a
    fmt.Printf("%c\n", 'a'^32) // prints: A
}

// 来源: 数状数组.lowbit()
// 《编程之美——微软技术面试心得》2.1 求二进制数中1的个数
func lastBit1() {
    var t = 12
    println(t & -t)            // prints: 4
    println(t & (t ^ (t - 1))) // prints: 4
    println(t ^ (t & (t - 1))) // prints: 4
    println(t - (t & (t - 1))) // prints: 4
}

func main() {
    setZero()
    abs()
    swap()
    average()
    transLetter()
    lastBit1()
}

另外还有很多常用位运算如<<、>>等,还是那句老话,不写算法,少用位运算,不好理解。


pkg bytes - strings

a. EqualFold

忽略大小写比较(不止如此)

package main

import "strings"

func main() {
    println(strings.EqualFold("AB-CD_EF", "ab-cd_ef"))
    // prints: true
}

b. Fields

根据空格分割,连续空格算为一个空格,多用于处理类似于ls -lh这样的输出

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Printf("%q\n", strings.Fields("  a b \t c \n \t \v \f d"))
    // prints: ["a" "b" "c" "d"]
}
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    fmt.Printf("%q\n", strings.FieldsFunc("  a b \t c \n \t \v \f d", func(r rune) bool { return unicode.IsSpace(r) }))
    // prints: ["a" "b" "c" "d"]
}

c. Split

根据seperator分割,多用于处理标准如csv文件的格式

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Printf("%q\n", strings.Split("  a b \t c \n \t \v \f d", " "))
    // prints: ["" "" "a" "b" "\t" "c" "\n" "\t" "\v" "\f" "d"]
}

如果string为空时,分割后数组长度为1

package main

import "strings"

func main() {
    println(len(strings.Split("", " ")))
    // prints: 1
}

d. Trim

  1. Trim, TrimFunc, TrimLeft, TrimRight, TrimSpace为处理“尽可能多”
  2. TrimPrifix, TrimSuffix为处理“尽可能少”(如果有,最多处理1个)
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    fmt.Printf("%q\n", strings.Trim("   a b c   ", " ")) // prints: "a b c"
    fmt.Printf("%q\n", strings.TrimFunc("   a b c   ", func(r rune) bool {
        return unicode.IsSpace(r)
    })) // prints: "a b c"
    fmt.Printf("%q\n", strings.TrimLeft("   a b c   ", " ")) // prints: "a b c   "
    fmt.Printf("%q\n", strings.TrimSpace("   a b c   "))     // prints: "a b c"

    fmt.Printf("%q\n", strings.TrimPrefix("   a b c   ", " ")) // prints: "  a b c   "
}

e. Map

单个字符Replace的升级版

package main

import (
    "fmt"
    "strings"
)

func main() {
    var m = []rune{
        'x': 'a',
        'y': 'b',
        'z': 'c',
    }
    fmt.Printf("%q\n", strings.Map(func(r rune) rune {
        if int(r) < len(m) && m[r] != 0 {
            return m[r]
        }
        return r
    }, "uvwxyz")) // prints: "uvwabc"
}

pkg encoding/json

a. SetEscapeHTML(感谢Jason Qu)

Golang在序列化字符串json时,会将’<’、’>’、’&’转换成对应的unicode值:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    js, _ := json.Marshal("<>&")
    fmt.Printf("%s\n", js)
    // prints: "\u003c\u003e\u0026"
}

Go1.7后可以通过设置SetEscapeHTML(false)使json编码不对上述三个字符做序列化处理:

package main

import (
    "encoding/json"
    "os"
)

func main() {
    encoder := json.NewEncoder(os.Stdout)
    encoder.SetEscapeHTML(false)
    encoder.Encode("<>&")
    // prints: "<>&"
}

b. Marshaler

json包中有两个接口: MarshalerUnmarshaler,实现接口,可以自由定制最终的json序列化格式

package main

import (
    "encoding/json"
    "fmt"
)

type JsonBool bool

func (jb JsonBool) MarshalJSON() ([]byte, error) {
    if jb {
        return []byte(`1`), nil
    }
    return []byte(`0`), nil
}

func (jb *JsonBool) UnmarshalJSON(js []byte) error {
    switch string(js) {
    case `1`, `"1"`, `true`, `"true"`, `True`, `"True"`:
        *jb = true
    default:
        *jb = false
    }
    return nil
}

func main() {
    var b JsonBool = true
    js, _ := json.Marshal(&b)
    fmt.Printf("%s\n", js) // prints: 1

    json.Unmarshal([]byte("0"), &b)
    fmt.Println(b) // prints: false

    json.Unmarshal([]byte(`"True"`), &b)
    fmt.Println(b) // prints: true
}

c. map[int]…

自Go1.7开始,json序列化支持map[int]...类型(int, uint, int32…)的序列化与反序列化:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    js, _ := json.Marshal(map[int]string{1: "a"})
    fmt.Printf("%s\n", js) // prints: {"1":"a"}

    var m map[int]string
    json.Unmarshal(js, &m)
    fmt.Println(m) // prints: map[1:a]
}

d. MarshalIndent

格式化json字符串:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    js, _ := json.MarshalIndent(map[int]string{1: "a", 2: "b"}, "", "  ")
    fmt.Printf("%s\n", js)
}

Output:

{
  "1": "a",
  "2": "b"
}

e. Buffered

json.Decoder在反序列化过程中会buffer一部分数据,如果读到的数据不全是json,在后续的反序列化的过程中会出错

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
)

func main() {
    buffer := bytes.NewBuffer([]byte(`{"1":"a"}0123456789`))
    decoder := json.NewDecoder(buffer)

    var m map[int]string
    decoder.Decode(&m)
    fmt.Println(m)
    // prints: map[1:a]

    fmt.Printf("remain: %q\n", buffer.Bytes())
    // prints: remain: ""

    bs, _ := ioutil.ReadAll(decoder.Buffered())
    fmt.Printf("buffered: %q\n", bs)
    // prints: buffered: "0123456789"
}

pkg Container

a. List.Remove(感谢Seashore)

package main

import (
    "container/list"
)

func main() {
    ls := list.New()
    ls.PushBack(1)
    ls.PushBack(2)
    for e := ls.Front(); e != nil; e = e.Next() {
        ls.Remove(e)
    }
    println(ls.Len()) // prints: 1
}

See: go-src/container/list/list.go +108

// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
    e.prev.next = e.next
    e.next.prev = e.prev
    e.next = nil // avoid memory leaks
    e.prev = nil // avoid memory leaks
    e.list = nil 
    l.len--
    return e
}

解决办法:

    var next *list.Element
    for e := ls.Front(); e != nil; e = next {
        next = e.Next()
        ls.Remove(e)
    }

b. Heap

注意,用heap的时候,都是调用heap.XXX,而不是如下中的IntHeap.XXX:

package main

import (
    "container/heap"
    "fmt"
)

type IntHeap []int

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() (x interface{}) {
    x, *h = (*h)[len(*h)-1], (*h)[:len(*h)-1]
    return
}

func main() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h) // 对于初始有序的数组不用Init
    for h.Len() > 0 {
        fmt.Println(heap.Pop(h)) // prints: 1 2 5
    }
}

pkg unsafe (不推荐使用该包)

a. 判断机器大小端

简单写法:

package main

import "unsafe"

func main() {
    var a uint32 = 1
    if *(*uint8)(unsafe.Pointer(&a)) == 1 {
        println("Little Endian")
    } else {
        println("Big Endian")
    }
}

看起来正规一些的写法:

package main

import (
    "encoding/binary"
    "reflect"
    "unsafe"
)

func main() {
    var v uint32 = 1
    var b = *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
        Data: uintptr(unsafe.Pointer(&v)),
        Len:  binary.Size(v),
        Cap:  binary.Size(v),
    }))

    if binary.LittleEndian.Uint32(b) == v {
        println("Little Endian")
    } else {
        println("Big Endian")
    }
}

b. 可变字符串

字符串是只读变量,这里用unsafe实现可写字符串

原理见: Slice 调整len和cap值

package main

import (
    "fmt"
    "unsafe"
)

type ChangableString struct {
    b []byte
    s *string
}

func NewChangableString(s string) *ChangableString {
    var c = new(ChangableString)
    c.b = []byte(s)
    c.s = (*string)(unsafe.Pointer(&c.b))
    return c
}

func (c *ChangableString) Bytes() []byte {
    bs := make([]byte, len(c.b))
    copy(bs, c.b)
    return bs
}

func (c *ChangableString) String() string {
    return *c.s
}

func (c *ChangableString) At(i int) byte {
    if i < 0 || i > len(c.b) {
        return 0
    }

    return c.b[i]
}

func (c *ChangableString) Set(i int, b byte) {
    if i < 0 || i > len(c.b) {
        return
    }

    c.b[i] = b
}

func (c *ChangableString) Append(bs []byte) {
    c.b = append(c.b, bs...)
}

func (c *ChangableString) AppendStr(s string) {
    c.b = append(c.b, s...)
}

func (c *ChangableString) Cut(start, end int) {
    if start < 0 {
        return
    }
    if end > len(c.b) {
        return
    }
    if start > end {
        return
    }

    c.b = c.b[start:end]
}

func (c *ChangableString) Len() int {
    return len(c.b)
}

func main() {
    var s = NewChangableString(`123456789`)
    fmt.Println(s) // prints: 123456789

    s.Set(0, 'a')
    fmt.Println(s) // prints: a23456789

    s.AppendStr(`abcdefg`)
    fmt.Println(s) // prints: a23456789abcdefg

    s.Cut(9, s.Len())
    fmt.Println(s) // prints: abcdefg

    s.Cut(0, 3)
    fmt.Println(s) // prints: abc
}

《Verilog and SystemVerilog Gotchas》是一本关于Verilog和SystemVerilog语言的电子书。它旨在帮助读者避免在编写Verilog和SystemVerilog代码时常见的陷阱和错误。 这本电子书对于那些对Verilog和SystemVerilog语言有一定了解的人来说尤为有用。它研究了许多常见的问题和错误,并提供了简明扼要的解释和示例代码。 《Verilog and SystemVerilog Gotchas》首先介绍了Verilog语言的基础知识,包括模块化设计概念、数据类型、运算符和控制结构等。然后,它讨论了一些常见但容易被忽视的问题,例如在条件语句中的敏感列表、并发语句中的竞争条件和时序不确定性。 接下来,这本电子书转向SystemVerilog语言,介绍了其扩展功能,如对象导向编程、断言和宏定义。它还强调了一些SystemVerilog中常见的问题,如虚拟接口的使用和任务和函数的调用方式。 此外,《Verilog and SystemVerilog Gotchas》也涉及了Simulator和Synthesizer之间的一些差异和注意事项。它帮助读者理解不同的仿真和综合工具之间的差异,并提供了一些建议,以避免由于这些差异而引起的问题。 总的来说,这本电子书是一种很好的资源,能够帮助工程师和学生更好地理解和使用Verilog和SystemVerilog语言。它提供了许多实用的建议和示例,可以帮助读者避免在编写代码时常见的陷阱和错误。无论是初学者还是有经验的用户,都可以从这本书中获益,提高他们在编写Verilog和SystemVerilog代码时的效率和准确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值