本篇内容:
errors.Is
方法与==
两种方式进行error
比较
在Go语言中,处理错误(error 类型)时,errors.Is 和直接使用 == 操作符进行错误比较,虽然看起来都用于比较错误,但实际上它们有着根本的不同。这主要体现在对错误值的比较方式和用途上。
1) 使用 == 操作符
直接使用 == 操作符来比较两个错误值,实际上是比较这两个错误变量的内存地址是否相同。这意味着,除非这两个错误变量引用的是同一个错误实例(即指向相同的内存地址),否则 == 将返回 false。这种比较方式对于区分不同的错误实例(即使它们包含相同的错误信息)非常严格,并且不常用于错误处理,因为它通常不是你所需要的比较方式。
2) 使用 errors.Is
errors.Is 函数用于判断一个错误是否是由特定的错误或错误的类型产生的。这意呀着,即使两个错误值不是同一个实例(即它们的内存地址不同),只要它们在类型上或者通过错误包装(wrapping)关系上是相关的,errors.Is 也能返回 true。
errors.Is 函数特别适合与错误包装(%w 格式化指令在 fmt.Errorf 中)一起使用,用于判断一个错误是否是由于某个特定的底层错误引起的。
代码示例 :
var myErr = errors.New("my err")func TestError(t *testing.T) {err := fmt.Errorf("hello %w", myErr)fmt.Printf("myErr:%s , err:%s \n", myErr, err)fmt.Println("使用 == 的结果:", err == myErr)fmt.Println("使用 errors.Is(err, myErr) 的结果:", errors.Is(err, myErr))fmt.Println("使用 errors.Is(myErr, err) 的结果:", errors.Is(myErr, err))
}
输出:
myErr:my err , err:hello my err
使用 == 的结果: false
使用 errors.Is(err, myErr) 的结果: true
使用 errors.Is(myErr, err) 的结果: false
带缓冲channel和无缓冲channel区别
Go语言中的channel是用于在不同的goroutine之间进行通信的一种机制。channel可以是带缓冲的,也可以是无缓冲的,它们之间有一些关键的区别:
无缓冲channel:
无缓冲的channel没有存储空间,发送操作会阻塞直到另一方准备好接收数据,接收操作也会阻塞直到有数据可以接收。
代码示例:
func TestNoBufferChannel(t *testing.T) {ch := make(chan int)go func() {ch <- 10fmt.Println("Send data to channel")}()time.Sleep(time.Second * 3) // 确保goroutine有足够的时间执行data := <-chfmt.Println("Received data:", data)
}
输出:
Received data: 10
Send data to channel
带缓冲channel:
Go语言中的channel是一种用于在不同的goroutine之间进行通信的机制。Channel可以是带缓冲的(buffered)或不带缓冲的(unbuffered),它们之间的主要区别在于数据的发送和接收方式。
带缓冲的channel有固定的存储空间,可以在channel满之前存储数据项。发送操作只会在缓冲区满时阻塞,接收操作会在缓冲区为空时阻塞。可以使用len函数查看当前缓冲区中剩余元素的数量,使用cap函数查看缓冲区的容量。
代码示例:
func TestHasBufferChannel(t *testing.T) {ch := make(chan int, 2) // 创建一个容量为2的带缓冲channelch <- 10ch <- 20go func() {time.Sleep(time.Second * 2)ch <- 30 // 这条语句会阻塞,直到缓冲区有空间}()data := <-chfmt.Println("Received data:", data)data = <-chfmt.Println("Received data:", data)time.Sleep(time.Second * 3) // 等待足够的时间来接收最后一条数据data = <-chfmt.Println("Received data:", data)
}
输出:
Received data: 10
Received data: 20
Received data: 30
在无缓冲channel的例子中,主goroutine和发送goroutine之间的操作是同步的。而在带缓冲channel的例子中,发送goroutine可以继续发送数据,直到缓冲区满为止,而接收goroutine可以从缓冲区接收数据,而不需要等待发送操作。
defer func() 函数返回值
defer 关键字是 Go 语言中的一个非常有用的特性,它用于确保函数调用结束时执行特定的操作,通常用于资源清理、文件关闭、解锁以及记录时间等场景。使用 defer 可以让代码更加简洁,并且避免忘记释放资源或执行必要的清理操作。
当你在函数中使用 defer 关键字调用一个函数时,这个被 defer 调用的函数会在包含它的函数即将返回之前执行。如果有多个 defer 调用,它们会以逆序执行(后进先出),即最后一个 defer 语句会最先执行。
但是有一点需要注意,就是在defer一个函数的时候,恰好这个函数返回值也是一个函数,比如如下代码:
var str stringfunc hello() func() {str = "Hello World"fmt.Println("This is hello() func ...")return func() {fmt.Println("This is return func() ...")}
}func TestDeferReturnFunc(t *testing.T) {defer hello()fmt.Println("This is test func , str = ",str)
}func TestDeferReturnFuncCall(t *testing.T) {defer hello()()fmt.Println("This is test func , str = ",str)
}
两个函数执行的结果分别是:
=== RUN TestDeferReturnFunc
This is test func , str =
This is hello() func ...
--- PASS: TestDeferReturnFunc (0.00s)=== RUN TestDeferReturnFuncCall
This is hello() func ...
This is test func , str = Hello World
This is return func() ...
--- PASS: TestDeferReturnFuncCall (0.00s)
PASS
在TestDeferReturnFunc测试用例中,hello() 函数被 defer 延迟执行。这意味着 hello() 将在 TestDeferReturnFunc 函数即将返回之前被调用。因此,执行顺序如下:
1)打印 "This is test func , str = ",此时 str 还未被 hello() 修改,所以 str 是空字符串。
2)hello() 被调用,设置 str 为 “Hello World”,并打印 “This is hello() func …”。
3)hello() 返回的匿名函数没有被调用,因为它只是被 defer 了,而不是 defer hello()()。
在TestDeferReturnFuncCall测试用例中,hello() 被调用,并且它的返回值(一个匿名函数)也被立即调用,并且这个匿名函数的调用被 defer 延迟执行。执行顺序如下:
1)hello() 被调用,设置 str 为 “Hello World”,并打印 “This is hello() func …”。
2)hello() 返回的匿名函数被 defer,意味着它将在 TestDeferReturnFuncCall 函数即将返回之前被调用。
3)打印 "This is test func , str = ",此时 str 已经被 hello() 修改为 “Hello World”。
4)defer 的匿名函数被调用,打印 “This is return func() …”。
这两个测试用例展示了 defer 关键字如何与返回函数的调用相结合,以及它们对执行顺序和变量状态的影响。
本篇结束~