Golang 的一些陷阱(一)

前端之家收集整理的这篇文章主要介绍了Golang 的一些陷阱(一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

Golang 的一些陷阱(一)

这里是 50 Shades of Go: Traps,Gotchas,and Common Mistakes for New Golang Devs 的一些总结, 忽略了一些原文提到编译器能检查到的陷阱, 我想这些陷阱写代码是多遇到几次,就会非常熟悉了o(∩_∩)o 。

关于空白符 "_"

原文中提到了 用"_"去保证一些未被引用的变量,导入包 能到顺利编译。空白符在实际应用中的作用主要是

  • 当我们不需要显式的调用某个包中的功能,仅需要它正确初始化时:
    import (
        _ "xxx/abc"    // xxx/abc包中的 init函数将会被调用
    )
  • 忽略某些不需要的值
    nums := []int{2,3,4}
    sum := 0
    for _,num := range nums {  // 忽略数组index
        sum += num
    }
  • 开发阶段标记或调试
    import (
        _ "xxx/abc"    // 仅仅是提示后面开发会用到这个包
    )

关于操作符

  • 异或(XOR) 和 按位求反 是同一个操作符 "^"

    package main
    
    import "fmt"
    
    func main() {  
        var a uint8 = 0x82
        var b uint8 = 0x02
        fmt.Printf("%08b [A]\n",a)
        fmt.Printf("%08b [B]\n",b)
    
        fmt.Printf("%08b (NOT B)\n",^b)
        fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n",b,0xff,b ^ 0xff)
    
        fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n",a,a ^ b)
        fmt.Printf("%08b & %08b = %08b [A AND B]\n",a & b)
        fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a &^ b)
        fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a & (^b))
    }
  • 操作符及其优先级

    +    sum                    integers,floats,complex values,strings
    -    difference             integers,complex values
    *    product                integers,complex values
    /    quotient               integers,complex values
    %    remainder              integers
    
    &    bitwise AND            integers
    |    bitwise OR             integers
    ^    bitwise XOR            integers
    &^   bit clear (AND NOT)    integers
    
    <<   left shift             integer << unsigned integer
    >>   right shift            integer >> unsigned integer
    
    Precedence    Operator
        5             *  /  %  <<  >>  &  &^
        4             +  -  |  ^
        3             ==  !=  <  <=  >  >=
        2             &&
        1             ||

    ++和--的优先级由外层操作符确定, 比如*p++和(*p)++是一样的

意外的变量屏蔽

如下代码: ``` package main

import "fmt"

func main() {  
    x := 1
    fmt.Println(x)     // prints 1
    {
        fmt.Println(x) // prints 1
        x := 2         // 重新定义和赋值x,这里的x相当于一个新的变量,但作用域只在当前花括号内
        fmt.Println(x) // prints 2
    }
    fmt.Println(x)     // prints 1 (这里你需要2的话, 将会是个错误) 
}
```

我们可以用 go tool vet -shadow xxx.go 来检测这样的陷阱。

关于"nil"

  • 不要用nil去初始化类型不明确的变量,如:
    var x = nil // it is error
    var x interface{} = nil // it is ok
  • 为nil的切片可以直接使用, 但使用为nil的map,将会panic。
    // it is ok for slices
    var s []int
    s = append(s,1)
    
    // here will panic
    var m map[string]int
    m["one"] = 1 //error
  • string 不能初始化为nil,string的初始化默认值是""。
    var x string = nil //error
    var x string   // ok

关于数组 与 切片

  • 数组是值类型的,所以它在相互赋值和作为函数参数时会发生数据拷贝, 通常情况下应避免这样情况出现。

    package main
    
    import "fmt"
    
    func main() {  
        x := [3]int{1,2,3}
    
        func(arr [3]int) {
            arr[0] = 7
            fmt.Println(arr) //prints [7 2 3]
        }(x) //调用函数时,会发送数组拷贝
    
        fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
    }
  • "var a [3]int" 与 "var b [5]byte" 中, a 和 b 是不同的类型。 不能相互直接赋值。

  • Golang只支持一维数组和一维切片, 可以通过一维的数组或切片构建多维数组和切片, 必须自己负责索引,边界检测 和 初始化及内存分配。

    import "fmt"
    
    func main() {  
        h,w := 2,4
    
        raw := make([]int,h*w)
        for i := range raw {
            raw[i] = i
        }
        fmt.Println(raw,&raw[4])
        //prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>
    
        table := make([][]int,h)
        for i:= range table {
            table[i] = raw[i*w:i*w + w]
        }
    
        fmt.Println(table,&table[1][0])
        //prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
    }

关于 map

  • 访问不存在的 map key
    x := map[string]string{"one":"a","two":"","three":"c"}
    if v := x["two"]; v == "" { //incorrect,当key不存在时 v == "" 也为true
        fmt.Println("no entry")
    }
    // ................................................
    if _,ok := x["two"]; !ok {     //正确的方法
        fmt.Println("no entry")
    }
  • map中的数据是无序的,当通过for range 去遍历map时,每次得到的数据顺序都可能是不一样的。

关于switch

  • switch中 匹配当前case后,将会从switch 退出,若想继续执行下一个case,可以使用 fallthrough 关键字。
    package main
    
    import "fmt"
    
    func main() {  
        isSpace := func(ch byte) bool {
            switch(ch) {
            case ' ':
                fallthrough
            case '\t':
                return true
            }
            return false
        }
    
        fmt.Println(isSpace('\t')) //prints true (ok)
        fmt.Println(isSpace(' '))  //prints also true (ok)
    }

关于 string

  • string是不可以变的,如果要修改string的值, 可以将其转换为byte切片, 如下:
    package main
    
    import "fmt"
    
    func main() {  
        x := "text"
        xbytes := []byte(x)   
        xbytes[0] = 'T'
    
        fmt.Println(string(xbytes)) //prints Text
    }
  • string和[]byte相互转换会发送数据拷贝, 目前来说两种情况除外:
    1. key为[]byte,当它作为map[string]的key去访问map[string]时: m[string(key)]
    2. 在 for range中转换, 如下
      for i,v := range []byte(str) {...}
  • string做索引操作时返回的是byte而不是character:
    x := "text"
    fmt.Println(x[0]) //print 116
    fmt.Printf("%T",x[0]) //prints uint8
    若想访问字符串中的字符应用 for range 或转化成runes切片
  • string中并不总是UTF8 字符,它可以包含任何字符
    package main
    
    import (  
        "fmt"
        "unicode/utf8"
    )
    
    func main() {  
        data1 := "ABC"
        fmt.Println(utf8.ValidString(data1)) //prints: true
    
        data2 := "A\xfeC"
        fmt.Println(utf8.ValidString(data2)) //prints: false
    }
  • 在golang中 string是一个rune序列, 一个UTF8字符由一个或多个rune组成。 rune对应的是字符编码中的Code point概念。
    package main
    
    import (
    	"bytes"
    	"fmt"
    	"strings"
    	"unicode/utf8"
    )
    
    func main() {
    	data := "é中文"     //é由两个rune组成,中和文各由一个rune组成
    	fmt.Println(len(data))                          //prints: 9
    	fmt.Println(utf8.RuneCountInString(data))       //prints: 4
    	fmt.Println(strings.Count(data,"") - 1)        //prints: 4
    	fmt.Println(bytes.Count([]byte(data),nil) - 1) //prints: 4
    }
  • 通过for range可以获取string中的rune,但当string含有非UTF8字符时,如果不能被for range理解的话将会将原有数据用0xfffd代替。此时如果要正确获取到string中的数据,可以将string转化成[]byte
    package main
    
    import "fmt"
    
    func main() {  
        data := "A\xfe\x02\xff\x04"
        for _,v := range data {
            fmt.Printf("%#x ",v)
        }
        //prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
    
        fmt.Println()
        for _,v := range []byte(data) {
            fmt.Printf("%#x ",v)
        }
        //prints: 0x41 0xfe 0x2 0xff 0x4 (good)
    }

关于struct

  • 未导出的结构体字段,不能被json,xml,gob之类的编码和解码。
    package main
    
    import (  
        "fmt"
        "encoding/json"
    )
    
    type MyData struct {  
        One int
        two string
    }
    
    func main() {  
        in := MyData{1,"two"}
        fmt.Printf("%#v\n",in) //prints main.MyData{One:1,two:"two"}
    
        encoded,_ := json.Marshal(in)
        fmt.Println(string(encoded)) //prints {"One":1}
    
        var out MyData
        json.Unmarshal(encoded,&out)
    
        fmt.Printf("%#v\n",out) //prints main.MyData{One:1,two:""}
    }
  • 值类型Receiver的方法不能改变原有变量的中的数据, 指针类型的则可以。
    package main
    
    import "fmt"
    
    type data struct {  
        num int
        key *string
        items map[string]bool
    }
    
    func (this *data) pmethod() {    //this 将改变原有变量的值
        this.num = 7
    }
    
    func (this data) vmethod() {    //这里this 将会是原有变量的拷贝,所以对this的任何修改都不会影响原有变量。
        this.num = 8
        *this.key = "v.key"
        this.items["vmethod"] = true
    }
    
    func main() {  
        key := "key.1"
        d := data{1,&key,make(map[string]bool)}
    
        fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
        //prints num=1 key=key.1 items=map[]
    
        d.pmethod()
        fmt.Printf("num=%v key=%v items=%v\n",d.items) 
        //prints num=7 key=key.1 items=map[]
    
        d.vmethod()
        fmt.Printf("num=%v key=%v items=%v\n",d.items)
        //prints num=7 key=v.key items=map[vmethod:true]
    }

log库

  • log库中 log.Fatal*() 和 log.Panic*() 会导致程序退出

关于并发

  • 在并发操作中, 内置的数据结构是不安全的, 需要自己去保证数据安全。
  • 程序不会等待所有goroutine完成,当程序退出后, 所有goroutine也会被终止。可以采用channel,sync.WaitGroup去等待goroutine完成。
  • 为nil的channel将会永久block程序。
  • 关闭的channel发送数据将会panic。
原文链接:https://www.f2er.com/go/189664.html

猜你在找的Go相关文章