1

1、不允许左大括号单独一行

2、不允许出现未使用的变量

3、不允许出现未使用的import(使用 _ 包名 引入)

4、短的变量声明(Short Variable Declarations)只能在函数内部使用

// myvar := 1   // error
var myvar = 1   // ok

5、不能使用短变量声明(Short Variable Declarations)重复声明

6、不能使用短变量声明(Short Variable Declarations)这种方式来设置字段值

data.result, err := work() //error

7、意外的变量幽灵(Accidental Variable Shadowing)
代码块中同名短变量声明从声明开始到代码块结束,对变量的修改将不会影响到外部变量!

8、不能使用nil初始化一个未指定类型的变量

9、不能直接使用nil值的Slice和Map

10、map使用make分配内存时可指定capicity,但是不能对map使用cap函数

在golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量。

12、数组用于函数传参时是值复制

注意:方法或函数调用时,传入参数都是值复制(跟赋值一致),除非是map、slice、channel、指针类型这些特殊类型是引用传递。

13、range关键字返回是键值对,而不是值

14、Slice和Array是一维的

15、从不存在key的map中取值时,返回的总是”0值”

16、字符串是不可变的

17、字符串与[]byte之间的转换是复制(有内存损耗),可以用map[string] []byte建立字符串与[]byte之间映射,也可range来避免内存分配来提高性能

//[]byte: 
for i,v := range []byte(str) {
}

18、string的索引操作返回的是byte(或uint8),如想获取字符可使用for range,也可使用unicode/utf8包和golang.org/x/exp/utf8string包的At()方法。

19、字符串并不总是UTF8的文本

20、len(str)返回的是字符串的字节数

str := "我"
fmt.Println(len(str)) //3

21、在Slice、Array、Map的多行书写最后的逗号不可省略,单行书写,最后一个元素的逗号可省略

22、内置数据结构的操作并不同步,但可把Go提供了并发的特性使用起来:goroutines和channels。

23、使用for range迭代String,是以rune来迭代的。
一个字符,也可以有多个rune组成。需要处理字符,尽量使用golang.org/x/text/unicode/norm包。

for range总是尝试将字符串解析成utf8的文本,对于它无法解析的字节,它会返回oxfffd的rune字符。
因此,任何包含非utf8的文本,一定要先将其转换成字符切片([]byte)。

24、使用for range迭代map时每次迭代的顺序可能不一样,因为map的迭代是随机的。

25、switch的case默认匹配规则不同于其它语言的是,匹配case条件后默认退出,除非使用fallthrough继续匹配;而其它语言是默认继续匹配,除非使用break退出匹配。

26、只有后置自增(a++)、后置自减,不存在前置自增(++a)、前置自减

27、位运算的非操作是^(跟异或位运算一样),有别于其它语言的~。

28、位运算(与、或、异或、取反)优先级高于四则运算(加、减、乘、除、取余),有别于C语言。

29、结构体在序列化时非导出字段(以小写字母开头的字段名)不会被encode,因此在decode时这些非导出字段的值为”0值”

30、程序不等所有goroutine结束就会退出。可通过channel实现主协程(main goroutine)等待所有goroutine完成。

31、对于无缓存区的channel,写入channel的goroutine会阻塞直到被读取,读取channel的goroutine会阻塞直到有数据写入。

32、从一个closed状态的channel读取数据是安全的,可通过返回状态(第二个返回参数)判断是否关闭;而向一个closed状态的channel写数据会导致panic。

33、向一个nil值(未用make分配空间)的channel发送或读取数据,会导致永远阻塞。

34、方法接收者是类型(T),接收者只是原对象的值复制,在方法中修改接收者不会修改原始对象的值;如果方法接收者是指针类型(*T),是对原对象的引用,方法中对其修改当然是原对象修改。

35、log包中的log.Fatal和log.Panic不仅仅记录日志,还会中止程序。它不同于Logging库。

36、使用defer语句关闭资源时要注意nil值,在defer语句之前要进行nil值判断处理(否则会引发空引用的panic)

37、关闭HTTP连接,可使用

  1. req.Close=true,表示在http请求完成时关闭连接
  2. 添加Connection: close的连接请求头。http服务端也会发送Connection: close的响应头,http库处理响应时会关闭连接。
  3. 全局关闭http连接重用。

    package main
    
    import (  
     "fmt"
     "net/http"
     "io/ioutil"
    )
    
    func main() {  
     //全局关闭http连接重用
     //tr := &http.Transport{DisableKeepAlives: true}
     //client := &http.Client{Transport: tr}
    
     req, err := http.NewRequest("GET","http://golang.org",nil)
     if err != nil {
         fmt.Println(err)
         return
     }
    
     req.Close = true
     //or do this:
     //req.Header.Add("Connection", "close")
    
     resp, err := http.DefaultClient.Do(req)
     if resp != nil {
         defer resp.Body.Close()
     }
    
     if err != nil {
         fmt.Println(err)
         return
     }
    
     body, err := ioutil.ReadAll(resp.Body)
     if err != nil {
         fmt.Println(err)
         return
     }
    
     fmt.Println(len(string(body)))
    }

37、Json反序列化数字到interface{}类型的值中,默认解析为float64类型

38.Struct、Array、Slice、Map的比较
如果struct结构体的所有字段都能够使用==操作比较,那么结构体变量也能够使用==比较。
但是,如果struct字段不能使用==比较,那么结构体变量使用==比较会导致编译错误。

同样,array只有在它的每个元素能够使用==比较时,array变量才能够比较。

Go提供了一些用于比较不能直接使用==比较的函数,其中最常用的是reflect.DeepEqual()函数。

DeepEqual()函数对于nil值的slice与空元素的slice是不相等的,这点不同于bytes.Equal()函数。

var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false

var b3 []byte = nil
b4 := []byte{}
fmt.Println("b3 == b4:",bytes.Equal(b3, b4)) //prints: b3 == b4: true

如果要忽略大小写来比较包含文字数据的字节切片(byte slice),
不建议使用bytes包和strings包里的ToUpper()、ToLower()这些函数转换后再用==、byte.Equal()、bytes.Compare()等比较,ToUpper()、ToLower()只能处理英文文字,对其它语言无效。因此建议使用strings.EqualFold()和bytes.EqualFold()

如果要比较用于验证用户数据密钥信息的字节切片时,使用reflact.DeepEqual()、bytes.Equal()、
bytes.Compare()会使应用程序遭受计时攻击(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏时间信息。

39、recover()函数能够捕获或拦截panic,但必须在defer函数或语句中直接调用,否则无效。

40、在slice、array、map的for range获取的数据项是从集合元素的复制过来的,并非引用原始数据,但使用索引能访问原始数据。

data := []int{1,2,3}
for _,v := range data {
    v *= 10          // original item is not changed
}

data2 := []int{1,2,3}
for i,v := range data2 {
    data2[i] *= 10       // change original item
}

// 元素是指针类型就不一样了
data3 := []*struct{num int} {{1}, {2}, {3}}
for _,v := range data {
    v.num *= 10
}

fmt.Println("data:", data)              //prints data: [1 2 3]
fmt.Println("data:", data2)             //prints data: [10 20 30]
fmt.Println(data3[0],data3[1],data3[2])    //prints &{10} &{20} &{30}

41、从一个slice上再生成一个切片slice,新的slice将直接引用原始slice的那个数组,两个slice对同一数组的操作,会相互影响。

可通过copy()为新切片slice重新分配空间,从slice中copy部分的数据来避免相互之间的影响。

42.从已存在的切片slice中继续切片时,新切片的capicity等于原capicity减去新切片之前部分的数量,新切片与原切片都指向同一数组空间。

新生成切片之间capicity区域是重叠的,因此在添加数据时易造成数据覆盖问题。

slice使用append添加的内容时超出capicity时,会重新分配空间。
利用这一点,将要修改的切片指定capicity为切片当前length,可避免切片之间的超范围覆盖影响。

    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    // 解决方法
    // dir1 := path[:sepIndex:sepIndex] //full slice expression
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)

    fmt.Println("new path =>",string(path))

43、slice在添加元素前,与其它切片共享同一数据区域,修改会相互影响;但添加元素导致内存重新分配之后,不再指向原来的数据区域,修改元素,不再影响其它切片。

    s1 := []int{1,2,3}
    fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]

    for i := range s2 { s2[i] += 20 }

    //still referencing the same array
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [22 23]

    s2 = append(s2,4)

    for i := range s2 { s2[i] += 10 }

    //s1 is now "stale"
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [32 33 14]

44、类型重定义与方法继承

从一个已存在的(non-interface)非接口类型重新定义一个新类型时,不会继承原类型的任何方法。
可以通过定义一个组合匿名变量的类型,来实现对此匿名变量类型的继承。

但是从一个已存在接口重新定义一个新接口时,新接口会继承原接口所有方法。

45、从”for switch”和”for select”代码块中跳出。

无label的break只会跳出最内层的switch/select代码块。
如需要从switch/select代码块中跳出外层的for循环,可以在for循环外部定义label,供break跳出。

return当然也是可以的,如果在这里可以用的话。

46、在for语句的闭包中使用迭代变量会有问题

在for迭代过程中,迭代变量会一直保留,只是每次迭代值不一样。
因此在for循环中在闭包里直接引用迭代变量,在执行时直接取迭代变量的值,而不是闭包所在迭代的变量值。

如果闭包要取所在迭代变量的值,就需要for中定义一个变量来保存所在迭代的值,或者通过闭包函数传参。

47、defer函数调用参数

defer后面必须是函数或方法的调用语句。defer后面不论是函数还是方法,输入参数的值是在defer声明时已计算好,
而不是调用开始计算。

要特别注意的是,defer后面是方法调用语句时,方法的接受者是在defer语句执行时传递的,而不是defer声明时传入的。

48、defer语句调用是在当前函数结束之后调用,而不是变量的作用范围。

49、失败的类型断言:var.(T)类型断言失败时会返回T类型的“0值”,而不是变量原始值。

func main() {  
      var data interface{} = "great"
      res, ok := data.(int); 
    fmt.Println("res =>",res, ",ok =>",ok)//res => 0 ,ok => false
}

50、阻塞的goroutine与资源泄漏

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    // 解决1:使用缓冲的channel: c := make(chan Result,len(replicas))
    searchReplica := func(i int) { c <- replicas[i](query) }
    // 解决2:使用select-default,防止阻塞
    // searchReplica := func(i int) { 
    //     select {
    //     case c <- replicas[i](query):
    //     default:
    //     }
    // }
    // 解决3:使用特殊的channel来中断原有工作
    // done := make(chan struct{})
    // defer close(done)
    // searchReplica := func(i int) { 
    //     select {
    //     case c <- replicas[i](query):
    //     case <- done:
    //     }
    // }

    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

51、用值实例上调用接收者为指针的方法

对于可寻址(addressable)的值变量(而不是指针),可以直接调用接受对象为指针类型的方法。
换句话说,就不需要为可寻址值变量定义以接受对象为值类型的方法了。

但是,并不是所有变量都是可寻址的,像Map的元素就是不可寻址的。

package main

import "fmt"

type data struct {  
    name string
}

func (p *data) print() {  
    fmt.Println("name:",p.name)
}

type printer interface {  
    print()
}

func main() {  
    d1 := data{"one"}
    d1.print() //ok

    // var in printer = data{"two"} //error
    var in printer = &data{"two"}
    in.print()

    m := map[string]data {"x":data{"three"}}
    //m["x"].print() //error
    d2 = m["x"]
    d2.print()      // ok
}

52、更新map值的字段

如果map的值类型是结构体类型,那么不能更新从map中取出的结构体的字段值。
但是对于结构体类型的slice却是可以的。

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    //m["x"].name = "two" //error
    r := m["x"]
    r.name = "two"
    m["x"] = r
    fmt.Println(s)       // prints: map[x:{two}]

    mp := map[string]*data {"x": {"one"}}
    mp["x"].name = "two" // ok

    s := []data{{"one"}}
    s[0].name = "two"    // ok
    fmt.Println(s)       // prints: [{two}]
}

53、nil值的interface{}不等于nil
在golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量。

interface{}表示任意类型,可以接收任意类型的值。interface{}变量在底是由类型和值两部分组成,表示为(T,V),interface{}变量比较特殊,判断它是nil时,要求它的类型和值都是nil,即(nil, nil)。
其它类型变量,只要值是nil,那么此变量就是nil(为什么?变量类型不是nil,那当然只能用值来判断了)

声明变量interface{},它默认就是nil,底层类型与值表示是(nil, nil)。
当任何类型T的变量值V给interface{}变量赋值时,interface{}变量的底层表示是(T, V)。只要T非nil,即使V是nil,interface{}变量也不是nil。

    var data *byte
    var in interface{}

    fmt.Println(data,data == nil) //prints: <nil> true
    fmt.Println(in,in == nil)     //prints: <nil> true

    in = data
    fmt.Println(in,in == nil)     //prints: <nil> false
    //'data' is 'nil', but 'in' is not 'nil'

    doit := func(arg int) interface{} {
        var result *struct{} = nil
        if(arg > 0) {
            result = &struct{}{}
        }
        return result
    }
    if res := doit(-1); res != nil {
        fmt.Println("good result:",res) //prints: good result: <nil>
        //'res' is not 'nil', but its value is 'nil'
    }

    doit = func(arg int) interface{} {
        var result *struct{} = nil
        if(arg > 0) {
            result = &struct{}{}
        } else {
            return nil //return an explicit 'nil'
        }
        return result
    }

    if res := doit(-1); res != nil {
        fmt.Println("good result:",res)
    } else {
        fmt.Println("bad result (res is nil)") //here as expected
    }

54、变量内存的分配

在C++中使用new操作符总是在heap上分配变量。Go编译器使用new()和make()分配内存的位置到底是stack还是heap,
取决于变量的大小(size)和逃逸分析的结果(result of “escape analysis”)。这意味着Go语言中,返回本地变量的引用也不会有问题。

要想知道变量内存分配的位置,可以在go build、go run命令指定-gcflags -m即可:
go run -gcflags -m app.go

55、GOMAXPROCS、Concurrency和Parallelism

Go 1.4及以下版本每个操作系统线程只使用一个执行上下文execution context)。这意味着每个时间片,只有一个goroutine执行。
从Go 1.5开始可以设置执行上下文的数量为CUP内核数量runtime.NumCPU(),也可以通过GOMAXPROCS环境变量来设置,
还可调用runtime.GOMAXPROCS()函数来设置。

注意,GOMAXPROCS并不代表Go运行时能够使用的CPU数量,它是一个小256的数值,可以设置比实际的CPU数量更大的数字。

56、读写操作排序

Go可能会对一些操作排序,但它保证在goroutine的所有行为保持不变。
但是,它无法保证在跨多个goroutine时的执行顺序。

package main

import (  
    "runtime"
    "time"
)

var _ = runtime.GOMAXPROCS(3)

var a, b int

func u1() {  
    a = 1
    b = 2
}

func u2() {  
    a = 3
    b = 4
}

func p() {  
    println(a)
    println(b)
}

func main() {  
    go u1()
    go u2()
    go p()
    time.Sleep(1 * time.Second)
    // 多次执行可显示以下以几种打印结果
    // 1   2
    // 3   4
    // 0   2 (奇怪吗?)
    // 0   0    
    // 1   4 (奇怪吗?)
}

57、优先调度

有一些比较流氓的goroutine会阻止其它goroutine的执行。
例如for循环可能就不允许调度器(scheduler)执行。

scheduler会在GC、go语句、阻塞channel的操作、阻塞系统调用、lock操作等语句执行之后立即执行。
也可以显示地执行runtime.Gosched()(让出时间片)使scheduler执行调度工作。

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    done := false
    go func(){
        done = true
    }()

    for !done {
        // ... 
        //runtime.Gosched() // 让scheduler执行调度,让出执行时间片
    }
    fmt.Println("done!")
}

参考资料:https://blog.csdn.net/gezhong...


IT小马
1.2k 声望166 粉丝

Php - Go - Vue - 云原生


下一篇 »
K8S安装Mysql8.0