go 语言容易犯错的记录

(mismatched types string and nil)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main
func main() {
    var x string = nil //error
    if x == nil { //error
        x = "default"
    }
}

./hello.go:4: cannot use nil as type string in assignment
./hello.go:6: invalid operation: x == nil (mismatched types string and nil)

正确的为

1
2
3
4
5
6
func main() {
    var x string //defaults to "" (zero value)
    if x == "" {
        x = "default"
    }
}
1
invalid operation: nil == nil (operator == not defined on nil)

报错

1
time.Second * connectTimeout (mismatched types time.Duration and int)

产生原因

int and time.Duration are different types. You need to convert the int to a time.Duration

Golang 的和时间相关的可以直接用数字,但不能是 float 浮点型,也不能直接是数值型变量.

1
2
3
4
5
6
time.Sleep(10* time.Second)  //可以
time.Sleep(time.Duration(yourTime) * time.Second) //可以


time.Sleep(1.1*time.Second) //不可以
time.Sleep(paramsTime*time.Second) //不可以

解决方法

1
2
connectTimeout := 10
time.Sleep(time.Duration(connectTimeout) * time.Second)
1
2
3
4
5
loc, err := time.LoadLocation(locationName)
 if err != nil {
  return err
 }
time.Now().In(loc)

在 windows 没用安装 go 开发环境下,是无法工作的

1
2
3
// CST 需要增加 8 个 offerset
loc := time.FixedZone("CST", 8*3600)
time.Now().In(loc)
  • slice
1
2
make([]int, 2) //创建一个初始元素个数为2的整型数组切片,元素初始值为0
make([]int, 4, 8) //创建一个初始元素个数为4的整型数组切片,元素初始值为0,并预留8个元素的存储空间

在 uber 的代码指南里面

  • 对应 map 的 make 有要求,一定要限制大小

https://github.com/uber-go/guide/blob/master/style.md#prefer-specifying-map-capacity-hints

  • 让 nil 作为有效 slice ,节省空间, 不得用 make

https://github.com/uber-go/guide/blob/master/style.md#nil-is-a-valid-slice

  • 在 Channel make 的时候,注意,这个值控制了缓冲策略

https://github.com/uber-go/guide/blob/master/style.md#channel-size-is-one-or-none

1
2
3
4
5
6
package main

func main() {
  var m map[string]string
  m["name"] = "hello"
}

这段代码将导致一个 panic

1
panic: assignment to entry in nil map

这是因为代码中只是声明了 map 的类型,却没有为 map 创建底层数组,此时的 map 实际上在内存中还不存在,即 nil map nil map 不能直接赋值

1
2
3
4
5
func main() {
  var m map[string]string
  m := make(map[string]string)
  m["name"] = "hello"
}

同样的,直接对 nil slice 添加数据也是不允许的,因为 slice 的底层也是数组,没有经过 make 函数初始化时,只是声明了 slice 类型,而底层数组是不存在的

1
2
3
4
func main() {
 var s []int
 s[0] = 1
}

产生一个 panic

1
runtime error:index out of range

正确做法应该是使用 make 函数或者字面量

1
2
3
4
func main() {
 s := make([]int, 1)
 s[0] = 1
}

nil slice使用append函数而不经过make也是有效的

1
2
3
4
5
6
7
import "fmt"

func main() {
 var s []int
 s = append(s, 1)
 fmt.Println(s) // s => [1]
}

那是因为 slice 本身其实类似一个 struct,它有一个 len 属性,是当前长度,还有个 cap 属性,是底层数组的长度

  • append 函数会判断传入的 slice 的 len 和 cap,如果 len 即将大于 cap
  • 会调用 make 函数生成一个更大的新数组并将原底层数组的数据复制过来

这段代码输出是?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
 "errors"
 "fmt"
)

func main() {
 i := 2
 if i > 1 {
  i, err := doDivision(i, 2)
  if err != nil {
   panic(err)
  }
  fmt.Println(i)
 }
 fmt.Println(i)
}

func doDivision(x, y int) (int, error) {
 if y == 0 {
  return 0, errors.New("input is invalid")
 }
 return x / y, nil
}

输出结果不是设想的

1
2
1
1

真实输出是

1
2
1
2

这是因为 golang 中变量的作用域范围小到每个词法块,都是一个单独的作用域 作用域的内部声明会屏蔽外部同名的声明,而每个 if 语句都是一个词法块 也就是会出现

  • 如果在某个 if 语句中,不小心用 := 而不是 = 对某个 if 语句外的变量进行赋值
  • 那么将产生一个新的局部变量,并仅在 if 语句中的这个赋值语句后有效
  • 同名的外部变量会被屏蔽,并且不会因为这个赋值语句之后的逻辑产生任何变化

实际工作中如果误用,那么产生的 bug 会很隐秘

在 golang 中, arraystruct 都是值类型slice map chan引用类型

写代码的时候,基本都是:

  • 引用类型slice 代替 array
  • 对于 struct 则尽量使用指针

这样避免传递变量时复制数据的时间和空间消耗,也避免了无法修改原数据的情况,建议养成这个好习惯

错误的演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := person{name: "dj", age: 99}
 p3 := person{name: "px", age: 20}
 people := []person{p1, p2, p3}
 whoIsDead(people)
 for _, p := range people {
  if p.isDead {
   fmt.Println("who is dead?", p.name)
  }
 }
}

func whoIsDead(people []person) {
 for _, p := range people {
  if p.age < 50 {
   p.isDead = true
  }
 }
}

错误如下

  • struct 是值类型,所以在赋值给 p 的过程中,实际上需要重新生成一份 person 数据,便于 for range 内部使用
  • 所以 p.isDead = true 这个操作实际上更改的是新生成的 p 数据,而非 people 中原本的 person

需要修改数据时,则最好传递 struct 指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := &person{name: "zzy", age: 100}
 p2 := &person{name: "dj", age: 99}
 p3 := &person{name: "px", age: 20}
 people := []*person{p1, p2, p3}
 whoIsDead(people)
 for _, p := range people {
  if p.isDead {
   fmt.Println("who is dead?", p.name)
  }
 }
}

func whoIsDead(people []*person) {
 for _, p := range people {
  if p.age < 50 {
   p.isDead = true
  }
 }
}

当然,还有另外的方法,使用索引访问 people 中的 person ,改动一下 whoIsDead 函数

1
2
3
4
5
6
7
func whoIsDead(people []person) {
 for i := 0; i < len(people); i++ {
  if people[i].age < 50 {
   people[i].isDead = true
  }
 }
}

将之前的 people []person 改成了 map 结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := person{name: "aaa", age: 100}
 p2 := person{name: "bbb", age: 99}
 p3 := person{name: "ccc", age: 20}
 people := map[string]person{
  p1.name: p1,
  p2.name: p2,
  p3.name: p3,
 }
 whoIsDead(people)
 if p3.isDead {
  fmt.Println("who is dead?", p3.name)
 }
}

func whoIsDead(people map[string]person) {
 for name, _ := range people {
  if people[name].age < 50 {
   people[name].isDead = true
  }
 }
}

运行则会报错

1
cannot assign to struct field people[name].isDead in map
  • map 底层使用了 array 存储数据,并且没有容量限制
  • 随着 map 元素的增多,需要创建更大的 array 来存储数据
  • 那么之前的地址就无效了,因为数据被复制到了新的更大的 array 中
  • 所以 map 中元素是不可取址的,也是不可修改的

报错的意思其实就是不允许修改 map 中的元素

怎么改才能正确呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := &person{name: "aaa", age: 100}
 p2 := &person{name: "bbb", age: 99}
 p3 := &person{name: "ccc", age: 20}
 people := map[string]*person{
  p1.name: p1,
  p2.name: p2,
  p3.name: p3,
 }
 whoIsDead(people)
 if p3.isDead {
  fmt.Println("who is dead?", p3.name)
 }
}

func whoIsDead(people map[string]*person) {
 for name, _ := range people {
  if people[name].age < 50 {
   people[name].isDead = true
  }
 }
}

老套路,依然是使用指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p := person{name: "zzy", age: 100}
 isDead(p)
}

func isDead(p interface{}) {
 if p.(person).age < 101 {
  p.(person).isDead = true
 }
}

报一个编译错误

1
cannot assign to p.(person).isDead

即使编译通过,代码也是错误的 ,始终要记住 struct 是值类型的数据,请使用指针去操作它

正确做法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p := &person{name: "zzy", age: 100}
 isDead(p)
 fmt.Println(p)
}

func isDead(p interface{}) {
 if p.(*person).age < 101 {
  p.(*person).isDead = true
 }
}

go 为了正则表达式效率,不不符合 RCPE 规则 Perl Compatible Regular Expressions

使用RE2 规则 wiki https://en.wikipedia.org/wiki/RE2_(software)

规则详情见 https://code.google.com/p/re2/wiki/Syntax 官方文档 http://golang.org/pkg/regexp/

某些表达式无法生效,比如

  • ^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)[\w]{6,20}$

遇到这个按位转换 uint16 即可,例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
        var context = ""
        var additional = ""
        if len(v) < 1 {
            continue
        }
        if len(v) > 4 {
            rs := []rune(v)
            v = string(rs[:4])
            additional = string(rs[4:])
        }
        temp, err := strconv.ParseInt(v, 16, 32)
        if err != nil {
            context += v
        }
        context += fmt.Sprintf("%c", temp)
        context += additional