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{}
小结
我们都知道,如果要实现一个接口,必须实现这个接口提供的所有方法,但是实现方法的时候,我们可以使用指针接收者实现,也可以使用值接收者实现,这两者是有区别的,我们就好好理解下这两者的区别。
两种规则
一 , 以方法接收者是值还是指针的角度看。
[图片上传中...(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 的一大特性,为判断一个函数是否正常执行提供了方便。
如有不对欢迎指正,相互学习,共同进步。