当前位置: 首页 > article >正文

Go小技巧易错点100例(二十一)

本篇内容:

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 <- 10
		fmt.Println("Send data to channel")
	}()

	time.Sleep(time.Second * 3) // 确保goroutine有足够的时间执行
	data := <-ch
	fmt.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的带缓冲channel

	ch <- 10
	ch <- 20

	go func() {
		time.Sleep(time.Second * 2)
		ch <- 30 // 这条语句会阻塞,直到缓冲区有空间
	}()

	data := <-ch
	fmt.Println("Received data:", data)
	data = <-ch
	fmt.Println("Received data:", data)

	time.Sleep(time.Second * 3) // 等待足够的时间来接收最后一条数据
	data = <-ch
	fmt.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 string

func 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 关键字如何与返回函数的调用相结合,以及它们对执行顺序和变量状态的影响。

本篇结束~


http://www.kler.cn/a/469417.html

相关文章:

  • javaEE-网络原理-1初识
  • php.ini配置中有10处设置不当,会使网站存在安全问题哦
  • 【数据可视化】数据可视化看板需求梳理模板(含示例)
  • 安卓NDK视觉开发——手机拍照文档边缘检测实现方法与库封装
  • git理解记录
  • Leetcode 3414. Maximum Score of Non-overlapping Intervals
  • BGP(Border Gateway Protocol)路由收集器
  • 下载word报表
  • reactor中的并发
  • Java(day3)
  • 使用JMeter对Linux生产服务器进行压力测试
  • Golang中的大端序和小端序
  • 五类推理(逻辑推理、概率推理、图推理、基于深度学习的推理)的开源库 (二)
  • 51单片机——蜂鸣器模块
  • SpringCloud源码-nacos
  • 图片验证码
  • 解锁kafka组件安全性解决方案:打造全方位安全防线
  • 解决TortoiseGit 在Windows系统中文件不显示状态图标的问题
  • Elasticsearch操作笔记版
  • HarmonyOS学习大纲
  • 2.5万字 - 用TensorFlow和PyTorch分别实现五种经典模型
  • Go语言的 的接口(Interfaces)核心知识
  • 优雅草采集器系统全面开源-优雅草YYC采集器系统不同版本的合集整体开源yyc-gather-采集器开源-优雅草央千澈
  • centos7安装elasticsearch8.17
  • 智能运维分析决策系统:赋能数字化转型的新引擎
  • 【Stable Diffusion】用AI给老照片上色,岁月不改它模样