频道栏目
首页 > 程序开发 > 软件开发 > 其他 > 正文
go语言:Gotchas
2016-11-08 09:42:00         来源:暮雨潇湘  
收藏   我要投稿
Assumptions
以下go-src代表go语言源码文件夹 以下代码均在test.go中测试 一切测试从简, 保证代码量尽可能小, 尽可能容易理解。

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是64bits

自 Go1.1 后,int,uint 的尺寸统一是 64bits,即使是在 32bits 平台下。
// 注: 未经测试, 暂时没有32位机器

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 when close(chan)

f. ‘_’的使用

Valid usage:

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

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

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

// 结构体名称不建议用, 没意义
// 字段名可以使用, 但仅限于为了内存对齐
type _ 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 := "e?"
    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
}

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

// 机器不同, 地址可能不同
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]
}

b. 什么时候会改变值

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]
}

c. 调整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之类有两个好处:

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

在用map之前一定记得分配内存,nil map would panic; 多协程并发读map时可以不加锁,但并发写时一定要记得加锁,否则非常有可能panic
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 process(n int) {
    switch {
    case n%2 == 0:
        println(n, "is even num")
    default:
        println(n, "is odd num")
    }
}

func main() {
    for i := 0; i < 5; i++ {
        process(i)
    }
}

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 ""
    }

    return fmt.Sprint(t.someSegment)
}

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

同理的还有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}} // 变成the slice of pointers
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: 

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

b. 实现接口的要求

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

package main

import "io"

type NopReader struct{}

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

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

编译错误:

# 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 reader io.Reader = &NopReader{}
    _ = reader
}

注意: 在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 reader io.Reader = &nop
    _ = reader
}

编译错误:

# 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)

Bit operation

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

先看例子:

#include 

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 

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()
}

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

点击复制链接 与好友分享!回本站首页
上一篇:理解HashMap
下一篇:源码分析:PriorityQueue
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站