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

检查 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{}

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

两种规则
一 , 以方法接收者是值还是指针的角度看。

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

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

è¿éåå¾çæè¿°
 上面的表格可以解读为:类型的值只能实现值接收者的接口;指向类型的指针,既可以实现值接收者的接口,也可以实现指针接收者的接口。

golang与c++的几个区别

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

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

funcName redeclared in this book, previous declaration at lineno

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

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

猜你喜欢

转载自blog.csdn.net/wade3015/article/details/88248161
今日推荐