全国旗舰校区

不同学习城市 同样授课品质

北京

深圳

上海

广州

郑州

大连

武汉

成都

西安

杭州

青岛

重庆

长沙

哈尔滨

南京

太原

沈阳

合肥

贵阳

济南

下一个校区
就在你家门口
+
当前位置:首页  >  技术干货

使用具名返回值巧妙解决泛型函数返回零值的问题

发布时间:2022-08-04 16:17:00
发布人:qyf

使用具名返回值巧妙解决泛型函数返回零值的问题

  Go语言泛型语法特性在Go 1.18版本落地后,不出所料,在github上看到大量的基础容器类型数据结构被用泛型重写。这种重写我觉得是很正常、很自然的,并且实现良好的通用数据结构改为泛型其实也不难,有些简单的结构可能分分钟就能搞定。

  Go 1.18发布后,我一直没机会写泛型,今天在做DSL语义模型提取时,多处用到Stack结构,于是想到使用泛型简单实现了一个通用的Stack结构。

  在Go中,我们可以用一个切片来定义Stack。泛型Stack类型的定义如下:

  type Stack[T any] []T

  这里的Stack类型就是一个带有类型参数(type parameter)的泛型类型,它的类型参数的约束(constraints)为any,即允许任何类型作为Stack的元素类型。

  Stack是最基础的数据结构,一般来说它具有的操作方法包括:

  Push:压栈;

  Pop:弹栈;

  Top:获取栈顶元素;

  Len:获取栈内元素个数。

  对于以切片为底层存储的Stack而言,压栈Push操作就相当于对切片的追加(append)操作:

  func (s *Stack[T]) Push(v T) {

  (*s) = append((*s), v)

  }

  不过,这里有两点要注意:

  泛型类型的方法原型中,receiver部分的类型要带上类型参数,比如这里的*Stack[T];

  这里务必要用*Stack[T],而不要像下面代码这样用Stack[T],否则append方法改变的仅仅是Stack[T]的拷贝,而不是原Stack[T]类型的实例。

  func (s Stack[T]) Push(v T) {

  s = append(s, v)

  }

  我们再来看看*Stack[T]的弹栈Pop方法:

  func (s *Stack[T]) Pop() T {

  if len(*s) == 0 {

  return nil

  }

  // Get the last element from the stack.

  t := (*s)[len(*s)-1]

  // Remove the last element from the stack.

  *s = (*s)[:len(*s)-1]

  return t

  }

  这样实现的Pop方法会提示return nil一行有错误:cannot use nil as T value in return statement。Go编译器错误信息提示我们:nil不能作为T类型的值返回。

  Stack的类型参数的约束为any,即Stack的元素可以是任意类型,即可以是切片、map等复合类型,亦可以是int、string等值类型。如果将nil作为所有这些类型的零值的确不恰当。

  那么当Stack为空时,应该如何返回呢?多亏Go原生支持类型零值。

  我们可以声明一个类型零值并将其作为返回值返回:

  func (s *Stack[T]) Pop() T {

  if len(*s) == 0 {

  var zero T

  return zero // 模拟类型零值

  }

  // Get the last element from the stack.

  t := (*s)[len(*s)-1]

  // Remove the last element from the stack.

  *s = (*s)[:len(*s)-1]

  return t

  }

  虽然这种方法有效,但你是不是和我有一样的感觉:不够优雅。下面我们就来看一个更为优雅的小技巧:利用函数的具名返回值,看代码:

  func (s *Stack[T]) Pop() (t T) {

  if len(*s) == 0 {

  return

  }

  // Get the last element from the stack.

  t = (*s)[len(*s)-1]

  // Remove the last element from the stack.

  *s = (*s)[:len(*s)-1]

  return

  }

  我们看到:具名返回值(named return value)一出马,一切都变得自然而然了。当然这也要归功于Go的类型零值特性。

  具名返回值日常使用的不多,从使用的频度来看,Go标准库以及多数项目的代码默认选择非具名返回值(unamed return value)。当函数使用defer且在deferred函数中修改外部函数返回值时,应用具名返回值可以让代码显得更清晰一些:

  func Foo() (a int) {

  defer func() {

  a = 5

  }

  a = 6

  }

  其他情况,看项目编码规范一致性要求以及个人喜好了。不过,Go引入泛型后,针对上述的泛型函数返回零值的情况,相信具名返回值将得到更多的“出镜”的机会。

  更多关于“java培训”的问题,欢迎咨询千锋教育在线名师。千锋教育多年办学,课程大纲紧跟企业需求,更科学更严谨,每年培养泛IT人才近2万人。不论你是零基础还是想提升,都可以找到适合的班型,千锋教育随时欢迎你来试听。

相关文章

主机和端系统之间有什么不同?

主机和端系统之间有什么不同?

2023-10-14
进程和线程有什么联系和区别?

进程和线程有什么联系和区别?

2023-10-14
结束任务管理器快捷键是什么?

结束任务管理器快捷键是什么?

2023-10-14
服务器为什么要用Linux?

服务器为什么要用Linux?

2023-10-14

最新文章

常见网络安全面试题:Windows常用的命令有哪些?

常见网络安全面试题:Windows常用的命令有哪些?

2023-10-09
常见网络安全面试题:根据设备告警如何展开排查?

常见网络安全面试题:根据设备告警如何展开排查?

2023-10-09
常见网络安全面试题:mysql加固呢?(数据库加固)

常见网络安全面试题:mysql加固呢?(数据库加固)

2023-10-09
常见网络安全面试题:windows和linux加固?(操作系统加固)

常见网络安全面试题:windows和linux加固?(操作系统加固)

2023-10-09
在线咨询 免费试学 教程领取