《C++初阶之路》函数重载

一、本章重点

  1. 什么是函数重载?
  2. 函数重载的条件
  3. 为什么有函数重载?
  4. 为什么C不支持函数重载,C++确能支持函数重载?
  5. extern “C”

二、函数重载

2.1函数重载的概念

简单来说,C++允许同一作用域中出现函数名相同,参数不同,功能相似的函数,而这些函数就构成函数重载。

void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void Swap(double* a, double* b)
{
	double temp = *a;
	*a = *b;
	*b = temp;
}
//这两个函数就构成函数重载

2.2函数重载的条件

第一要满足:函数名相同

第二要满足:参数不同,具体表现在参数类型的顺序、参数的个数、参数的类型。

参数的类型不同的函数重载

void Pt(int a)
{
	cout << a << endl;
}

void Pt(double a)
{
	cout << a << endl;
}
//类型不同

参数的个数不同的函数重载

int Add(int a, int b, int c)
{
	return a + b + c;
}

int Add(int a, int b)
{
	return a + b;
}
//个数不同

参数类型的顺序不同

void Func(int a, double b)
{
	cout << a << b << endl;
}

void Func(double a, int  b)
{
	cout << a << b << endl;
}
//顺序不同

2.3为什么有函数重载?

就是问函数重载到底能干嘛?c语言不支持同一作用域存在同名函数,那么我们修改一下函数名不就行了吗?函数重载真正能体现价值的地方到底在哪呢?

第一:书写函数名方便。

比如,我们要写两个交换函数,第一个是交换两个整形,第二个是交换两个浮点型,那么你只需要都取Swap即可,不需要写成Swapi、Swapd,如果参数复杂呢?你又要写成什么呢?

到不如直接写成Swap,然后传不同的参数,编译器会自动匹配最符合的函数。

第二:类中构造函数的实现也依靠函数重载

构造函数是同名的成员函数,它一般被划分为:有参构造、无参构造、拷贝构造,它们构成函数重载。

第三:模板的底层实现也依靠函数重载

模板的其实就是让编译器就为你创建重载函数,比如Swap(const T& a,const T&b)

当你传两个整形给a和b时,编译器会自动生成Swap(int* a,int* b),当你传两个浮点型double时,,编译器会自动生成Swap(double* a,double* b)。

以上等等C++中好用的机制都依靠于函数重载,函数重载在C++中的地位不言而喻。

2.4为什么C不支持函数重载,C++确能支持函数重载?

首先,我们需要了解函数名修饰规则

函数名修饰(Decorated Name)就是编译器在编译期间创建的一个字符串。用来指明函数的定义或原型。

在编译期间,C++会对函数名做一些处理,这种处理与函数参数有关,而C语言并不会,或者说C语言的函数名修饰规则与函数中参数无关。

我们都知道生成一个可执行文件,分以下步骤:预处理、编译、汇编、链接。

预处理:头文件展开、去注释、条件编译、宏替换(在linux平台下,去注释先于宏替换)

编译:先进行语法检查,然后将高级语言写的代码转化为汇编指令。

汇编:汇编来源于汇编语言中专业词汇,指将汇编指令转为二进制代码。

链接:并不是简单的将多文件合并,主要进行的是文件之间的交互。

以Swap.h、Swap.c、Test.c为例。

Swap.h主要在Swap.c和Test.c中展开。

Swap.c在编译之后,链接的时候,会生成一个符号表,符号表的主要作用是映射函数名和函数地址,简单来说就是存储函数名和对应的函数地址,然后Test.c就以调用的函数名去取符号表中的函数名对应的地址,完成Swap.c和Test.c的交互。

图解:

 这下我们可以开始解释为什么C不支持函数重载,C++确能支持函数重载?

因为C语言的函数名修饰规则与函数参数无关,或者说没有修饰规则。

而C++在编译期间,会将函数名修饰一下,即将函数名与参数联系在一起。

图解:

这里我们用math.h、math.c、test.c来举例

 Test.cpp调用函数Swap(int a,int b)编译之后,它的函数名会变成_ZSwapii。

同理调用函数Swap(int a,double b) 编译之后,它的函数名会变成_ZSwapid。

(以上的函数名变化参考至linux平台,具体函数名修饰规则不同平台不一样)

然后在链接的时候去符号表找函数的地址,再将函数的地址填在Test.cpp中调用的地方。

总之就是,我们不想改的函数名,编译器帮我们改了,这就是函数重载的底层原理。

这里同样可以解释为什么返回值不构成函数重载,因为c++的函数名修饰规则不考虑返回值,只将函数名与参数结合起来组成新的函数名。

而C语言就很老实,你写什么函数名,我是不会改的,如果在同一作用域存在同名函数,在编译期间会发生错误,因为语法不允许。

以下这个.c文件很好说明这一点

 2.5 extern “C”

C++为了调用C语言写的库,增加了extern “C”这样的语法,以前C语言中extern的作用一般是声明外部符号,而这里extern的作用也差不多,都是其声明告知的作用。

extern “C”一般的用法有两种:

第一:添加在函数声明的前面

第二:extern “C”再用花括号将函数声明括起来。

其作用是告知编译器该函数用C语言去编译。

经过上面的函数名修饰规则,我们知道C语言编译的库是没有改函数名的,而C++调用时会修改函数名(把函数参数与函数名结合),当用C++直接C库,那个在链接时,一个修改过的函数名,去符号表里找没被修改过的函数,显然是找不到的,这时会产生链接错误。

举例:C语言编译含有int Add(int a,int b)的库时,C++调用这一库,如果没有加上extern “C”那么在链接的时候,C++会以_ZAddii去符号表找_ZAddii的地址,但是符号表只有Add,找不到,导致连接错误。

如果加上extern “C”那么C++会以C语言的方式去编译Add(),链接的时候,C++会以Add去符号表找Add的函数地址,此时自然能找到。

包含extern “C”自然就能够使C++调用C语言的库。

现在我们知道了:c能够调用c的库,c++能够调用c++的库,c++也能调用c的库(加上extern “C”),那么c语言能调用C++库吗?

答案是确定的,不过c语言调用c++的库不是那么容易,我们需要在C++的库中运用extern “C”,即用extern “C”将C++中的头文件中的函数声明括起来,然后生成c++的库,c语言再去调用这个生成的库,但c语言包含的c++的头文件中有extern “C”,c语言编译器识别不了,因此需要用到条件编译,将c语言包含c++头文件中的extern “C”去除。

具体模拟:

c++调用c静态库(VS2019)

第一步建库:

 

 vs2019默认生成可执行程序,这里更改一下项目属性

 

 将配置类型改为静态库

之后点击生成解决方案

打开所在文件上层目录的Debug可以看到成功生成Add_C.lib

 再使用C++调用这个生成的静态库

新建测试的项目

 包含Add.h,这里用的是相对路径。

之后再配置一下

 

 接下来运行一下

链接失败,因为c代码的函数名编译时不会与参数结合,而c++代码编译时会将函数名与参数结合,导致链接的时候找不到。

用extern “C”包含Add.h即可,意思是告诉C++编译器,编译时用C的方式解析改函数,即使用C的函数修饰规则。

 

c调用c++静态库(VS2019)

这里演示就快点了

把.c改为.cpp

由于c项目中要包含Add.h

其中的extern “C”编译器是不认识的

因此要保证Add.h头文件中extern “C”在c++的静态库存在, 在c项目包含该头文件时不存在。

可以用条件编译(__cplusplus是c++特定的标识符,c没有)

因此改为:

然后C去调用这个C++的静态库

 

 总结:

c++调用c库较容易,只需要在c++中使用extren “C”即可

而c调用c++库,确需要改c++库,如果有现成的c++库,确需要找到头文件加上extren “C”,改动源码,改变之后C++还能再调用这个C++库吗?显然不行了。

  • 37
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李逢溪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值