美文网首页golang进阶之路Golang 入门资料+笔记Go
Golang中一些容易出错的知识点总结

Golang中一些容易出错的知识点总结

作者: 不屈真实 | 来源:发表于2020-06-12 11:38 被阅读0次

    检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可

    // 错误的 key 检测方式
     
    func main() {
     
        x := map[string]string{"one": "2", "two": "", "three": "3"}
     
        if v := x["two"]; v == "" {
     
            fmt.Println("key two is no entry")    // 键 two 存不存在都会返回的空字符串
     
        }
     
    }
     
     
    // 正确示例
     
    func main() {
     
        x := map[string]string{"one": "2", "two": "", "three": "3"}
     
        if _, ok := x["two"]; !ok {
     
            fmt.Println("key two is no entry")
     
        }
     
    }
    

    string 类型的值是常量,不可更改

    
    // 修改字符串的错误示例
     
    func main() {
     
        x := "text"
     
        x[0] = "T"        // error: cannot assign to x[0]
     
        fmt.Println(x)
     
    }
     
    // 修改示例
     
    func main() {
     
        x := "text"
     
        xBytes := []byte(x)
     
        xBytes[0] = 'T'    // 注意此时的 T 是 rune 类型
     
        x = string(xBytes)
     
        fmt.Println(x)    // Text
     
    }
    

    注意: 上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的。

    更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符

    func main() {
        x := "text"
        xRunes := []rune(x)
        xRunes[0] = '我'
        x = string(xRunes)
        fmt.Println(x)    // 我ext
    }
    在多行 array、slice、map 语句中缺少 , 号
    
    func main() {
        x := []int {
            1,
            2    // 错误syntax error: unexpected newline, expecting comma or }
    }
     
    //修改
    X :=[]int{
        1,
        2,
    }
        y := []int{1,2,}    可以这么写
        z := []int{1,2}   
        // ...
    }
    

    在类型断言语句中,断言失败则会返回目标类型的“零值”,断言变量与原来变量混用可能出现异常情况:

    // 错误示例
     
    func main() {
        var data interface{} = "great"
        // data 混用
        if data, ok := data.(int); ok {
            fmt.Println("[is an int], data: ", data)
        } else {
            fmt.Println("[not an int], data: ", data)    // [isn't a int], data:  0
        }
    }
     
    // 正确示例
    func main() {
        var data interface{} = "great"
        if res, ok := data.(int); ok {
            fmt.Println("[is an int], data: ", res)
        } else {
            fmt.Println("[not an int], data: ", data)    // [not an int], data:  great
        }
    }
    

    golang在多个goroutine中进行map或者slice操作应该注意的事项

    因为golang的map和列表切片都是引用类型,且非线程安全的,所以在多个go routine中进行读写操作的时候,会产生“map read and map write“的panic错误。某一些类型的对象,会有这种类似的set方法来写数据,或者get方法来返回一个map:

    func (this *object) Set(name, val) {
        this.Lock()
        defer this.Unlock()
        this.m[name] = val
    }
     
    func (this *object) Get() map[string]string {
      this.Lock()
      defer this.Unlock()
     
      return this.m
    }
    

    如果会在多个go routine中通过该对象的Get()方法获取到的map进行读操作,并且在其他go routine中用Set()方法来写操作,那么有可能会导致“map read and map write“的panic错误。

    原因是Get方法获取的map和Set方法操作的map是同一个map,如果读写线程在同一时刻操作这2个map,就会产生错误。

    所以Get方法最好用这种方式返回map:

    func (this *object) Get() map[string]string {
        this.Lock()
        defer this.Unlock()
     
        newm := make(map[string]string)
        for k, v := range this.m {
            newm[k] = v
        }
     
        return newm
    }
    

    这样每次Get获取的map,其实是一个新的map,就可以不用考虑同时读写的问题了。

    fatal error:concurrent map read and map write

    如果map由多goroutine同时进行读写操作,就会出现fatal error:concurrent map read and map write错误。

    因为map并不像chan,对于goroutine的同步访问是安全的,map为引用类型,即使是函数调用,也不会产生多个副本,因此对于多个goroutine的访问,实际上是对同一块内存进行访问。基于我们对临界资源的认识,如果不加任何限制的对map进行访问,map共享资源就会遭到破坏并报错,这种错误也不是固定的,而是随机的,因为并不是每次对map的操作都会引起这种错误。

    针对上述错误,一般有如下两种解决方案。

    1、加锁

    go语言sync包中实现了两种锁Mutex(互斥锁)和RWMutex(读写锁),其中RWMutex是基于Mutex实现的。

    (1)互斥锁
    func (m *Mutex) Lock()

    func (m *Mutex) Unlock()

    其中Lock()加锁,Unlock解锁,成对进行使用,否则会panic。使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock解锁。对于使用读写锁的资源,每次只能有一个goroutine对其进行访问,适用于读写操作没有明显区别的场景。

    对map使用互斥锁举例

    type Demo struct {
        Data map[string]string
        Lock sync.Mutex
    }
     
    func (d Demo) Get(k string) string{
        d.Lock.Lock()
        defer d.Lock.Unlock()
        return d.Data[k]
    }
     
    func (d Demo) Set(k,v string) {
        d.Lock.Lock()
        defer d.Lock.Unlock()
        d.Data[k]=v
    }
    

    2、读写锁

    type RWMutex
        func (rw *RWMutex) Lock()
        func (rw *RWMutex) RLock()
        func (rw *RWMutex) RLocker() Locker
        func (rw *RWMutex) RUnlock()
        func (rw *RWMutex) Unlock()
    

    RWMutex为读写锁,该锁可以对某个资源加多个读锁或者一个写锁,适用于读次数远大于写次数的场景。

    注(1)其中写锁RLock()的优先级要高于读锁Lock(),当有写锁请求时,读请求就阻塞,直到没有写锁或者没有锁时,才会加载读锁。

    (2)读写锁都是成对使用的,并且加锁要在解锁前使用,否则会panic或者fatal error。

    对map使用读写锁举例。

    type Demo struct {
        Data map[string]string
        Lock sync.RWMutex
    }
     
    func (d Demo) Get(k string) string{
        d.Lock.RLock()
        defer d.Lock.RUnlock()
        return d.Data[k]
    }
     
    func (d Demo) Set(k,v string) {
        d.Lock.Lock()
        defer d.Lock.Unlock()
        d.Data[k]=v
    }
    

    2、利用channel串行化处理

    能使用chan的场景,推荐使用chan进行goroutine的交互。chan自身的机制,保证数据访问的高效性和正确性。

    接口使用注意细节(go经典面试题)

    以下代码能编译通过么?

    package main
    import "fmt"
     
    type People interface {
        Speak(string) string
    }
     
    type Student struct {}
     
    func (stu *Student)Speak(think string)(talk string)  {
        if think == "Excellent"{
            talk = "Nice"
        } else {
            talk = "Hi"
        }
        return
    }
     
    func main()  {
        var p People  = Student{}
        think := "Excellent"
        fmt.Println(p.Speak(think))
    }
    

    答案是不能通过的,为什么? 看编译信息:

    go:20:6: cannot use Student literal (type Student) as type People in assignment:
    Student does not implement People (Speak method has pointer receiver)

    简而言之,Student没有实现People接口.

    那么正确的打开方式是怎样的呢?

    要想正常编译,可以这么做:

    两种方案:
    一: func (stu *Student)Speak(think string)(talk string){}
    main函数中 : var p People = &Student{}

    二:func (stu Student)Speak(think string)(talk string)
    main函数中var p People = Student{} 或 var p People = &Student{}

    小结
    我们都知道,如果要实现一个接口,必须实现这个接口提供的所有方法,但是实现方法的时候,我们可以使用指针接收者实现,也可以使用值接收者实现,这两者是有区别的,我们就好好理解下这两者的区别。

    两种规则
    一 , 以方法接收者是值还是指针的角度看。
    [图片上传中...(image.png-ab1c95-1591933052284-0)]

    上面的表格可以解读为:如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有类型的指针能够实现对应的接口。

    二 , 以实体类型是值还是指针的角度看。

    上面的表格可以解读为:类型的值只能实现值接收者的接口;指向类型的指针,既可以实现值接收者的接口,也可以实现指针接收者的接口。

    defer中的坑儿

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        fmt.Println(f())
        fmt.Println(f1())
        fmt.Println(f2())
    }
     
    func f() (result int) {
        defer func() {
            result++
        }()
        return 0
    }
     
    func f1() (r int) {
        t := 5
        defer func() {
            t = t + 5
        }()
        return t
    }
     
    func f2() (r int) {
        defer func(r int) {
            r = r + 5
        }(r)
        return 1
    }
    

    输出:
    1
    5
    1

    要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令!
    函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。
    defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。

    golang与c++的几个区别

    Go 语言里面的指针和 C++ 指针一样,都是指向某块内存的地址值,可以解引用,不同只是在于 C++ 里可以直接对指针做算术运算(+、-、++、--)而 Go 里面不行。

    函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参与/或者不同的返回
    值,在 Go 里面函数重载是不被允许的。这将导致一个编译错误:

    funcName redeclared in this book, previous declaration at lineno

    函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与 C、C++、Java 和 C#,多值返回是 Go 的一大特性,为判断一个函数是否正常执行提供了方便。

    如有不对欢迎指正,相互学习,共同进步。

    相关文章

      网友评论

        本文标题:Golang中一些容易出错的知识点总结

        本文链接:https://www.haomeiwen.com/subject/fzjgtktx.html