Tony Bai · Go 语言第一课
Tony Bai
资深架构师,tonybai.com 博主
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 59 讲
开篇词 (1讲)
结束语 (1讲)
Tony Bai · Go 语言第一课
15
15
1.0x
00:00/00:00
登录|注册

41 | 驯服泛型:明确使用时机

示例:doSomethingCMT any
示例:泛型 Stack[T any]
使用类型参数
使用 interface{} 实现通用结构
为每种元素类型实现栈结构
字典区分GC Shape相同的不同类型
以类型的GC Shape为单元生成函数代码
可能拖慢执行性能
一套函数逻辑,多出一个包含类型信息的dict参数
编译器负担重,可能拖慢编译器
为每个类型实参生成单独实现
封装泛型函数简化调用
示例:commonMethod[T any] 实现 MyInterface
自定义接口类型作为参数
示例:maxGenerics[T ordered](sl []T)
函数参数为切片、map或channel
泛型方案
非泛型方案
为sort.Interface接口提供泛型实现
执行性能仍有提升空间
Go泛型实现方案避免了对编译性能的大影响
泛型提升Go语法复杂性,需慎用
Go 1.20版本性能优化预期
示例:add[T plusable](a, b T) 与 addInt(a, b int)
GC Shape + Dictionaries混合方案影响执行效率
GC Shape Stenciling方案(Go采用)
Dictionaries方案
Stenciling方案
场景三:不同类型实现相同逻辑方法时
场景二:函数操作Go原生容器类型时
场景一:编写通用数据结构时
思考题
小结
泛型对执行效率的影响
Go泛型实现原理简介
何时适合使用泛型?
Go 泛型使用时机与实现原理

该思维导图由 AI 生成,仅供参考

你好,我是 Tony Bai。
在前面关于 Go 泛型的两讲中,我们学习了 Go 泛型的基本语法类型参数,掌握了使用 Go 内置约束和自定义约束的方法,并对 Go 泛型新引入的类型集合概念做了全面说明。有了上面的知识铺垫后,我相信你已经具备了应用泛型语法编写泛型函数、定义泛型类型和方法的能力了。
不过,Go 对泛型的支持,在提升了 Go 语言表达力的同时,也带来了不小的复杂性。也就是说,使用了泛型语法编写的代码在可读性、可理解性以及可维护性方面,相比于非泛型代码都有一定程度的下降。Go 当初没有及时引入泛型的一个原因就是泛型与 Go 语言“简单”的设计哲学有悖,现在加入了泛型,Go 核心团队以及 Go 社区却又开始担心“泛型被滥用”
不过作为 Go 语言开发人员,我们每个人都有义务去正确、适当的使用泛型,而不是滥用或利用泛型炫技,因此在泛型篇的这最后一讲中,我就来说说什么时机适合使用泛型,供你参考。

何时适合使用泛型?

Go 泛型语法体现在类型参数上,所以说,类型参数适合的场景就是适合应用泛型编程的时机。我们先来看看类型参数适合的第一种场景。

场景一:编写通用数据结构时

在 Go 尚不支持泛型的时候,如果要实现一个通用的数据结构,比如一个先入后出的 stack 数据结构,我们通常有两个方案。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言的泛型实现方案备受关注,本文介绍了三种不同的实现方案:Stenciling、Dictionaries和GC Shape Stenciling。其中,GC Shape Stenciling方案是Go 1.18泛型最终采用的实现方案。文章通过具体的示意图和示例,清晰地阐述了每种方案的特点和优缺点。此外,文章还提到了泛型对执行性能的影响,指出在一些性能敏感的系统中,了解泛型对执行性能的影响尤为重要。在Go 1.20版本中,由于将使用Unified IR替换现有的IR表示,Go泛型函数的执行性能将得到进一步优化。总的来说,本文通过深入浅出的方式,为读者提供了对Go泛型实现方案的全面了解,为读者提供了实用的指导建议。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Tony Bai · Go 语言第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(6)

  • 最新
  • 精选
  • Geek14
    // 定义一个支持比较的接口,用于类型参数约束 type ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string } // 定义支持排序的泛型切片 type SortableSlice[T ordered] []T // 让泛型切片实现sort.Interface func (s SortableSlice[T]) Len() int { return len(s) } func (s SortableSlice[T]) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s SortableSlice[T]) Less(i, j int) bool { return s[i] < s[j] } // 定义一个泛型排序函数 func SortGeneric[T ordered](s SortableSlice[T]) { sort.Sort(s) }

    作者回复: 👍

    2023-01-06归属地:辽宁
    3
  • Geek14
    请教老师两个问题: 1、在讲解泛型实现原理时,文中提到“C++ 语言路径:就像 C++ 的泛型实现方案那样,通过增加编译器负担为每个类型实参生成一份单独的泛型函数的实现,这种方案产生了大量的代码,其中大部分是多余的,……”
为啥“其中大部分是多余的”,每个类型实参一个单独的实现,这不是刚刚好吗,为啥会有多余的实现? 2、Dictionaries 方案没看明白。模板方案比较好理解。编译阶段为每个类型实参创建一个泛型函数的单独实现,单独实现后函数内使用的泛型类型都会是具体的类型。那Dictionaries 方案种泛型函数中的泛型类型是具体类型实参类型吗?如果是具体的实参的类型,是怎么做到? 希望老师有时间帮忙解答下疑惑。

    作者回复: 都是好问题。 问题1:这句话来自于Russ Cox 的“泛型窘境”的文章。不知道你对c++的编译过程了解怎样。像c/c++这样的源码的编译分为两个阶段:编译和链接。其中编译阶段是以.c/.cpp为编译单元,将源码编译为一个个.o文件,每个编译单元的编译都是独立的。因此如果一个泛型函数在多个编译单元都会被调用(比如实参是int),那么每个编译单元编译时都会为int生成一份独立的泛型函数代码,这样就拖慢了编译器的编译时间。之后在链接阶段,链接器才会将位于各个.o中的这些冗余的重复代码进行清除,只保留一份。 问题2:调用泛型函数时传入的实参肯定是实参类型啊。这块编译器会将其转换为特定的函数调用,比如:f(dict.float64, 3.14)。至于具体实现,https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries.md 这个proposal design给出了一个伪码的例子,可以看看那个。

    2023-01-06归属地:辽宁
    2
    3
  • return
    老师讲的好呀,感谢老师。 期待老师新作品。

    作者回复: 👍

    2022-12-07归属地:广东
    1
  • Calvin
    思考题: // sort.Interface -> IntSlice / StringSlice 泛型版 type xsl interface { ~int | ~string } type xSlice[T xsl] []T func (x xSlice[T]) Len() int { return len(x) } func (x xSlice[T]) Less(i, j int) bool { return x[i] < x[j] } func (x xSlice[T]) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func sortX[T xsl](data xSlice[T]) { sort.Sort(data) } func TestXSlice(t *testing.T) { x1 := make(xSlice[int], 0, 5) x1 = append(x1, 3) x1 = append(x1, 10) x1 = append(x1, 2) x1 = append(x1, 0) x1 = append(x1, 9) sortX(x1) t.Logf("[]~int x = %#v", x1) type mystr string x2 := []mystr{"ab", "ca", "fc", "ce", "bf"} sortX(x2) t.Logf("[]~string x = %#v", x2) }

    作者回复: 👍。

    2022-11-10归属地:北京
    1
  • lesserror
    tony bai 老师,文中的:“并且被保存在 ELF 的只读数据区段(.data)中”,这里的ELF是什么的缩写呢?

    作者回复: ELF 是 Executable and Linkable Format的缩写。是linux上一种标准的可执行文件的格式。

    2023-08-07归属地:广东
  • 罗杰
    看一遍肯定是不够的,🉐️好好吸收

    作者回复: 👍

    2022-11-09归属地:北京
收起评论
显示
设置
留言
6
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部