golang拾遗:为什么我们需要泛型
从golang诞生起是否应该添加泛型支持就是一个热度未曾消减的议题 。 泛型的支持者们认为没有泛型的语言是不完整的 , 而泛型的反对者们则认为接口足以取代泛型 , 增加泛型只会徒增语言的复杂度 。 双方各执己见 , 争执不下 , 直到官方最终确定泛型是go2的发展路线中的重中之重 。
【golang拾遗:为什么我们需要泛型】今天我们就来看看为什么我们需要泛型 , 没有泛型时我们在做什么 , 泛型会带来哪些影响 , 泛型能拯救我们吗?
本文索引没有泛型的世界暴力穷举依靠通用引用类型动态类型语言的特例动静结合使用interface模拟泛型interface会进行严格的类型检查内置类型何去何从性能陷阱复合类型的迷思最后也是最重要的泛型带来的影响 , 以及拯救彻底从没有泛型的泥沼中解放泛型的代价
没有泛型的世界泛型最常见也是最简单的需求就是创建一组操作相同或类似的算法 , 这些算法应该是和数据类型无关的 , 不管什么数据类型只要符合要求就可以操作 。
看起来很简单 , 我们只需要专注于算法自身的实现 , 而不用操心其他细枝末节 。 然而现实是骨感的 , 想要实现类型无关算法在没有泛型的世界里却是困难的 , 需要在许多条件中利弊取舍 。
下面我们就来看看在没有泛型的参与下我们是如何处理数据的 。
暴力穷举这是最简单也是最容易想到的方法 。
既然算法部分的代码是几乎相同的 , 那么就copy几遍 , 然后把数据类型的地方做个修改替换 , 这样的工作甚至可以用文本编辑器的代码片段+查找替换来快速实现 。 比如下面的c代码:
float a = logf(2.0f);double b = log(2.0);typedef struct {int *data;unsigned int max_size;} IntQueue;typedef struct {double *data;unsigned int max_size;} DoubleQueue;IntQueue* NewIntQueue(unsigned int size){IntQueue* q = (IntQueue*)malloc(sizeof(IntQueue));if (q == NULL) {return NULL;}q->max_size = size;q->data = http://kandian.youth.cn/index/(int*)malloc(size * sizeof(int));return q;}DoubleQueue* NewDoubleQueue(unsigned int size){DoubleQueue* q = (DoubleQueue*)malloc(sizeof(DoubleQueue));if (q == NULL) {return NULL;}q->max_size = size;q->data = http://kandian.youth.cn/index/(double*)malloc(size * sizeof(double));return q;}
问题看上去解决了 , 除了修改和复查比较麻烦之外 。 做程序员的谁还没有cv过呢 , 然而这种方法缺点很明显:
- 严重违反DRY(don't repeat yourself) , 数据结构的修改和扩展极其困难
- 复制粘贴修改中可能会出现低级的人力错误 , 并且耗费精力
- 最关键的一点 , 我们不可能针对所有类型去写出特定的算法 , 因为这些类型的数量少则5,6种 , 多则上不封顶 。
- 保证了类型安全 , 任何类型问题都能在编译期暴露
- 更灵活 , 对于某些特定类型我们还可以做出非常细致的优化工作(比如对于bool类型我们可以使用unsigned int这个一般来说4字节大小的类型存放32个bool值 , 而不是用32个bool变量消耗32字节内存)
依靠通用引用类型其实方案1还可以依靠宏来实现 , linux内核就是这么做的 , 不过宏这个机制不是每个语言都有的 , 因此参考价值不是很高 。
既然明确指出数据的类型不可行 , 那我们还有其他的办法 。 比如马上要介绍的使用通用类型引用数据 。
通用的引用类型 , 表示它可以引用其他不同类型的数据而自身的数据类型不会改变 , 比如c中的void *:
void *ptr = NULL;ptr = (void*)"hello";int a = 100;ptr = (void*)
c语言允许非函数指针的数据类型指针转换为void * , 因此我们可以用它来囊括几乎所有的数据(函数除外) 。于是Queue的代码就会变成如下的画风:
typedef struct {void *data;unsigned int max_size;} Queue;Queue* NewQueue(unsigned int size){Queue* q = (Queue*)malloc(sizeof(Queue));if (q == NULL) {return NULL;}q->max_size = size;q->data = http://kandian.youth.cn// 这里填什么呢?}
代码写了一半发现写不下去了?放心 , 这不是你的问题 。 在c语言里我们不能创建void类型的变量 , 所以我们不可能给data预先分配内存 。那么退一步考虑 , 如果引入一个java那样的类似void*的Object类型 , 是否就能解决内存分配呢?答案是否定的 , 假设Object大小是8字节 , 如果我们放一个通常只有一字节大小的bool进去就会有7字节的浪费 , 如果我们放一个32字节的自定义类型 , 那么很显然一个Object的空间是远远不够的 。 在c这样的语言中我们想要使用数据就需要知道该数据的类型 , 想要确定类型就要先确定它的内存布局 , 而要能确定内存布局第一步就是要知道类型需要的内存空间大小 。
- 看不上|为什么还有用户看不上华为Mate40系列来看看内行人怎么说
- 制药领域|为什么AI制药这么火,为什么是现在?
- 手机壳里头|为什么要在手机壳里面夹钱?10个有9个不懂,我才知道大有讲究
- 短视频|全球最火APP?抖音爆火背后离不开这几剂“猛药”为什么抖音能够这么火?
- 电商快递|包邮不香吗,为什么还有人加49元让小哥穿西装专车送快递?
- 团队|为什么项目管理非常重要?
- 猫腻|为什么拼多多上商品价格那么便宜还包邮?有什么猫腻?看完明白了
- 刷机|前几年满大街的“刷机”服务去哪里了,为什么大家都不爱刷机了?
- 手机|便宜没好货!为什么二手iPhone很便宜,这些手机都来自哪儿?
- 中国|相对论Vol.48丨一个“歪果仁”,为什么要在海外电商平台直播带中国货