我孩子昨天放寒假,这两天都在露天玩耍。 他最大的爱好就是玩电脑,而他妈妈一般都不让他碰电脑。 现在已经放寒假了,就让他“疯狂玩”几天吧。
他也不玩游戏,只是写程序(可能是受我的影响)。 中午下班回家,他在饭桌上问了我一些关于C++中“类模板”的问题。 我惊呆了。
你孩子什么时候学“班级模板”的? 我语气不好的说道。 他知道我不支持他学习C++。 我想,对于一个编程爱好者来说,如果不先脚踏实地的了解C语言,然后去开发其他语言,最终肯定会遭遇基础薄弱的苦果。
他说:C语言没有太多方便的功能,C++多么强大。
我不想和他陷入语言争论,因为争论太多了。 而且他认为我的想法是错误的。 他只是想学C++,但是如果他不学C语言,那就没有我说的那么严重了。
我问他:你有什么函数式C语言? 你总不能告诉我它没有面向对象的特性吧?
我不讲面向对象了,老爸,我问你,函数重载应该是经常使用的函数吧? 这非常重要。 哪种编程语言不支持它? 甚至还支持C++、C#、Java等,还有很多其他语言我就不一一列举了。 但C语言不支持。 只要传入的数据类型不同,功能相同的函数就一定有相似的名字。 它既不简洁也不易于维护。 有时候真的让人抓狂。 。 。 。 。 。
说完,他挑衅地看着我,认为我现在很尴尬,因为他知道C语言不支持这个功能。
我笑了笑,心想你还是太年轻了。
其他的我就不和你争论了,就说正事吧。 谁说C语言不支持函数重载?
他拿着盘子,盘子惊得掉了下来。 。 。 。 。 。 他的嘴张成O形。
他了解我的性格,从不说任何不确定的话。
老爸,别为了证明C语言的强大而无中生有。 。 。
我问,你觉得我是这样的人吗?
但。 。 。 。 。 。 。
好吧,我们先吃饭吧。 吃完晚饭,我给你写一个用C语言实现的函数重载的demo,让你看看。
话说回来,C语言真的支持函数重载吗?
函数重载是泛型编程思想的重要体现,而编程语言则是编程思想在语法层面的具体实现。 说到编程思想,这个话题太大了。
从如何解决问题的角度来看,目前最常用的有面向对象编程、泛型编程、函数式编程、基于过程的编程、基于对象的编程等。当然严格来说,基于对象的编程应该属于面向对象编程的范畴。 这些解决问题的思维方式实际上属于“命令式编程”,即程序执行的结果是我们自己通过指令一步步推导出来的。
举个例子,你立刻就明白了。 比如我要求两个整数的和,如果我使用“命令式编程”,我就得一步步编译一些“解题步骤”:
int sum(int x,int y){
x + y;
如果是x – y,而不是x+y,那么程序就没有问题。 编译器不会给出任何警告,仍然会按照你的“指令”正常编译执行。 如果你把它命名为和呢? ? 而这种混乱会急剧增加出现 bug 的风险。 如果编译器能给出提示:sum函数如何进行xy运算? (sum是sum的缩写,常用来表示求和)这有多聪明? 而这个时候,你可能还在想:所有的程序不都是这样写的吗? 还怎么编程呢?
“命令式编程”只是目前最常用的编程方法。 如果我们在编程时写的每一行代码只是直接询问问题的结果,而不考虑结果是如何得到的,我们称这种编程方式为“基于结果的编程”。 那么有没有一种编程语言支持这种编程思想呢?
当然有,不仅存在,而且几乎和“命令式编程”编程语言一样长! 我们每天都在使用它,但我们从来没有从编程的角度思考过它。
比如有一个需求:请统计18岁以上的男生有多少个。
如果是“命令式编程”,就必须自己给出实现方法,一步步推导。
但如果使用“结果式编程”,则只需要直接提出需求就可以得到结果。 例如指令如下:
请统计18岁以上男孩的人数。
只不过目前计算机和人类之间的界面语言是英语,所以我们必须将其翻译成英语,而且必须按照“结果式编程”的语法规范来翻译。 译文如下:
count(*) from where = 'boy' 且年龄 > 18;
如果你有一些英语基础知识,你的下巴会惊掉的! 这段代码非常接近人类的自然语言! (当然你也可以用性来代替)
如果你聪明的话,你可能已经意识到了。 这不就是我们日常使用的SQL语法吗? !
目前支持“基于结果的编程”的最成熟的编程语言是SQL语法。 但从目前的情况来看(截至2023年底),以为首的生成式大模型将是未来“基于结果的编程”的最佳工具。 只是它刚刚诞生,还比较不成熟。 假以时日,一定会给我们的编程习惯带来翻天覆地的变化,“命令式编程”可能不再占据主流。
所以之前我和儿子讨论的“函数重载”只是编程语言层面泛型编程思维方式的一个具体函数。 所谓“函数重载”是指“在同一作用域内,允许使用多个同名的函数,它们仅通过参数类型、参数个数、参数顺序的不同来区分。它们具有类似的功能”。功能。” 这不正是“泛型”的思想吗? 所谓“通用”,就是让我们的代码在不同的业务场景下能够尽可能的复用,适应更多的情况。
例如,以下代码:
int sum(int x,int y){
x+y;
浮点总和(浮点x,浮点y){
x+y;
C++编译器可以正常编译,但是在C编译器中肯定会因为函数名重复而编译失败。
事实上,在C99和C1x之间的漫长时期里,我们就已经开始准备解决函数重载的需求了。 最终,C11通过“宏”实现了这个功能。 C11已经过去十几年了,我们应该已经享受到了C语言标准带来的最新特性(其实我们应该羞于说“最新”,因为它已经出来十几年了,而我们的国内大学不是还有几个还在用C99,甚至用C89/90来教学吗?)
话不多说,我们来修改刚才编译失败的函数:
#
# SUM(a,b) ((a,b),int:(a+b),float:(a+b),:(a+b))
int main(){
int a0 = 2,b0 = 3;
浮点a1 = 2.5f,b1 = 3.5f;
a2=2.5,b2=3.5;
(“%d\n”,SUM(a0,b0)); //5
("%f\n",SUM(a1,b1));//6.0
("%f\n",SUM(a2,b2));//6.0
0;
这是一个完整的C语言代码,任何支持C11的编译器都可以直接编译运行。
首先,预定义一个 SUM 宏。 这个 SUM 是我们要在 main 中使用的重载函数。 SUM中有两个参数a和b,它们的类型反映在.
通过实现宏替换,第一个参数是SUM对应的参数列表表达式。 例如SUM的参数列表为(a,b),则第一个参数列表为(a,b)。 从第二个参数开始,就是需要重载的类型。
例如第二个参数:int:(a+b),int对应SUM中的参数类型和返回类型。 括号中的表达式是函数体。 当然,还有其他的写法,比如不使用表达式而是使用函数名,稍后会演示。
第三个参数与第二个参数类似。 float表示SUM中参数的类型和返回值的类型。 括号中的表达式是函数体。
以后可以继续写。 这是一个可变参数列表。
最后一个参数可以写成形式,也可以省略。 和case in的final函数类似,只要前面的参数不是任何类型,就用于异常处理。
编译器在编译时确切地知道如何调用相应的函数。 这就是所谓的函数重载。 相同的SUM函数可以匹配不同的参数。
其实从名字上或许也能看出来,英文中的意思是普遍的、广泛的、普遍的。 所以宏也被称为“通用宏”。
让我再举一个例子。 这次我们不直接使用函数体,而是使用函数名。 示例代码如下:
#
# SUM(a,b) ((a,b),int:sum1,float:sum2,:sum3)(a,b)
int sum1(int a,int b){
a + b;
浮点 sum2(浮点 a,浮点 b){
a+b;
无效 sum3(char a,char c){
(“异常。”);
int main() {
int a0 = 2,b0 = 3;
浮点a1 = 2.5,b1 = 2.5;
字符 a2 = 'a',b2 = 'b';
("%d\n", SUM(a0,b0));
("%f\n", SUM(a1,b1));
总和(a2,b2);
0;
在这个例子中,参数类型不是对应于直接函数体,而是对应于函数名。 编译器在编译时会自动识别并绑定相应的函数实现。
当然,也可以直接在main函数中使用,如:
整数a=1;
b=1.0;
字符c = 'c';
int val = (a,int:a,:(int)b,char:(int)c);
("%d\n",val);
在这种用法中,除了宏体没有被宏函数替换之外,本质上没有任何变化。
第一个参数是参数表达式,可以放入对应的业务逻辑中,然后对业务逻辑表达式进行求值,判断结果的类型,然后调用对应的表达式继续求值。
需要注意的是,第一个参数无论怎么操作都不会改变传递的变量的值。 本节涉及到一些关于左值和右值的知识点,今天不讨论。
最后希望大家可以看一下C11增加了哪些“新功能”(确实不“新”)、C14以及未来的新标准。 诸如此类的信息在互联网上随处可见。 这也让大家重新认识这门已经流行了60多年的编程语言,它早已与时俱进了!
如果需要交流可以直接在评论区给我留言或者私信我。 我每天晚上都会登录后台回复。
段誉,2024 年 1 月写于合肥。