【C++】函数重载-概念和剖析底层原理以及C/C++互调静态库扩展

【C++】函数重载-概念和剖析底层原理以及C/C++互调静态库扩展

函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,

即该词被重载了。

比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

1.函数重载的概念

函数重载**:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数**,

但是函数重载并不是函数结构都相同,使用函数重载,这些同名函数的形参列表必须有以下限制

  • 形参个数不同
  • 形参类型
  • 形参类型的顺序不同

通过以上的限制,编译器在调用函数的时候,才能根据这些不同来选择函数。

1.1形参个数不同

int Add(int a, int b,int c)
{
	return a+b+c;
}
int Add(int a, int b)
{
	return a+b;
}
int main()
{
 Add(10, 20,30);
 Add(10, 20);
 return 0; 
}

image-20220505204721883

1.2形参类型不同

int Add(int left, int right)
{
	return left + right;
}
double Add(double left, double right)
{
	return left + right;
}
long Add(long left, long right)
{
	return left + right;
}
int main()
{  
	cout << Add(10, 20)<<endl;
	cout<<Add(10.0, 20.0)<<endl;
	cout<<Add(10L, 20L)<<endl;
	return 0;
}

image-20220505205219210

1.3形参类型的顺序不同

int Add(int left, double right)
{
	return left + right;
}
double Add(double left, int right)
{
	return left + right;
}
int main()
{
	cout << Add(10, 20.0) << endl;
	cout << Add(10.0, 20) << endl;
	
	return 0;
}

image-20220505205718447

1.4函数重载误点

注意:我们可以看到以上的函数返回值都是不同的,那如果我们只改变函数的返回值,如:

int Add(int left, int right)
{
	return left + right;
}
double Add(int  left, int right)
{
	return left + right;
}

还能构成函数重载吗?答案是不行的!

  • 只有返回类型不同是不可以的,因为返回类型是对于结果的解释,是没有放进c++汇编代码的,但是函数名,函数长度,函数参数类型等是放进了汇编代码的,所以c++是可以认出他们,同时c语言的汇编中函数的解释是没有这么多东西是,就是原名,所以这也是c语言不支持函数重载的原因。

2.C++实现函数重载原理

在剖析整个原理之前,我们先回顾一下程序运行的四个阶段:

image-20220505222104928

符号表的内容:

符号表里面有什么:

1.目标文件中引用的全局变量和函数

2.目标文件中定义的全局变量和函数

本质上符号表表达的内容:

1.我能提供给其他文件使用的符号

2.我需要其他文件提供给我的符号

(编译器在编译过程中遇到的全局变量和函数名都会写进去他们的相关信息(下面会进行查看)

链接器的作用就是要保证多文件综合时,判断所有目标文件中的符号都有唯一的定义。

2.1在linux系统下分析程序

以上我们了解了整个程序的运行过程,下面我们就来具体解析整个原理。

在linux环境下,我们是用g++ 编译器来运行c++的.cpp文件

image-20220505224413900

image-20220505224427540

image-20220505224437575

以上是用C语言的语法来编写代码,更好地使我们理解。

在进行第一个阶段:预处理的时候,这时候,我们的头文件就会被展开。

image-20220505230037049

然后经过第一个阶段:编译之后,会检查语法并且生成汇编代码,并且把所遇见的定义了的函数的地址记录下来。然后经过第三个阶段:汇编之后生成 .o 文件 ,里面就有符号表(编译时生成的)

这里具体过程是我们的重点,要通过查看汇编来一点点解析。

2.2查看汇编

先查看C++的汇编生成的汇编指令

image-20220505235746649

这里我们很清楚的看到了俩个汇编函数名,我们取其中一个函数名来分析

image-20220506000543101

2.2.1汇编函数名的含义

image-20220508163138375

我们取其中函数名来进行了分析,另一个函数 _Z1fdi也是一样的,只不过函数参数不一样倒过来是(double,int)

从而我们通过查看汇编知道了C++函数名的命名规则,那么C语言里面的汇编代码中,是怎么命名的呢。往下看——》

image-20220508163856322

可以知道C语言汇编代码的函数名就是最初的函数名f,没有函数参数

2.3函数重载原理结论

所以我们对比C++和C的区别,可以知道C语言为什么不支持函数重载,当出现同名函数的时候,

C语言无法区别,因为符号表里面会有两个同名的函数名并且附带他们的地址,当链接的时候编译器就不知道哪个才是他的地址。所以main函数无法调用函数,没有找到函数的地址,就会出现大家熟悉的链接错误。

而在C++里,

C++的汇编代码中,函数名还保存了函数的形参类型

从上面函数重载的概念,我们可以知道,函数重载有三个条件:

  • 形参个数

  • 不同形参类型

  • 形参类型的顺序不同

类型形式一形式二
形参个数不同_Z3funii(int,int)_Z3funiii(int,int,int)
不同形参类型_Z3funii(int,int)_Z3fundd(double,double)
形参类型的顺序不同_Z3funid(int,double)_Z3fundi(double,int)

从而C++编译器在链接的时候,找到汇编之后生成 .o 文件里面的符号表,进行链接,main函数能通过函数传参的不同找到它需要调用的对应函数的地址

image-20220508165840751

3.语法 extern"C"

由上面我们提到,C++汇编处理中对函数名的修饰和C语言不同,所以C++专门有一个语法,专门用来告诉编译器,某某某函数要用C语言的规则来修饰

#include <iostream>
using namespace std;

extern "C" int f(int a,int b);

int main()
{
    int sum=fun(1,2);
    return 0;
}

int f(int a,int b){ 
    return a+b;
}

通过extern"c"修饰函数名后,C++汇编后的函数名也就是C语言汇编后的函数名,是不加函数参数修饰的,就不是C++汇编后函数名如:_Z1fii的结果了。

这样的话,我们也可以用C语言的代码就可以链接这种方式写的C++静态库(前提是这个静态库中没有函数重载和C++的语法)

这和C的静态库的区别就在于,库里有些函数接口,C语言也能支持,这样C语言也能调用。

3.1C++调用C语言静态库

首先创建一个vs项目,把之前栈的实现代码放进去

image-20220508194102392

image-20220508194206422

然后我们进行编译生成了.lib的文件

image-20220509014412568

因为静态库无法单独执行的,需要被调用。

所以我另开了一个cpp项目来进行调用,然后如果要调取这里的静态库,我们要引头文件找到这个静态库项目的stack.h文件。

所以我们要找到文件的路径来找到这个头文件,利用 …/ 这个表达返回上级目录,找到目录,再往下找我们的头文件进行引用。

image-20220509014612817

然后我们进行编译,你会发现是会报错的

image-20220509015309345

接下来,我们需要在这个项目再配置一下,还是右击点击项目,点击属性。

image-20220509015842579

编辑库目录,找到.lib库文件生成的目录。

image-20220509015952780

接着找到下面的输入,再附加依赖项中,最前面加上库文件的名字然后加分号隔开。

image-20220509020334057

然后我们这个项目就配置好了,能够链接到静态库头文件了,但是还差了最后一步,因为

这个库是C语言写的,我们没有使用extern "C"来引用。

image-20220509021542402

image-20220509020834308

这样我们就完成调用啦,可以顺利编译打印出来了。

3.2C语言调用C++静态库

从上面我们可以知道C++调用C库是可以通过,extern”C“的语法来进行调用的,这是属于C++的语法,C语言是没有的,所以C语言也没有 extern“C++” 的语法的。那我们要怎么用C语言调用C++静态库呢?

我们首先还是先建立一个空项目,把栈的实现代码搞进去,并且把文件改为.cpp文件。

image-20220509023050703

之后还是老配方,将属性-配置属性改为静态库,并进行编译,生成.lib文件。

image-20220509023251383

之后我们还是新建一个C语言项目来进行调用,image-20220509023458148

同时我们也对C++静态库的头文件进行引用包含一下

image-20220509023745193

并进行添加库目录和添加依赖项

image-20220509024233468

image-20220509024332488

配置完成,我们进行编译,是无法编译成功的,因为C语言和C++的函数命名方式不同,无法链接,而且C语言是没有extern”C“的语法的。 即使你在C++的静态库里,对函数进行extern”C“的修饰,在静态库里能编译,但在C项目进行调用的时候是无法通过的,因为调用的时候,头文件进行展开,C项目还是不认识extern”C“这个语法的。

image-20220509025438083

image-20220509025511567

这时候,该怎么办,这时候,我们就可以使用条件编译

3.2.1条件编译

#ifdef __cplusplus
     #define EXTERN_C extern"C"
#else
     #define EXTERN_C
#endif

EXTERN_C void StackInit(ST * ps);
EXTERN_C void StackDestory(ST* ps);
EXTERN_C void StackPush(ST* ps,STDataType x);
EXTERN_C void StackPop(ST* ps);
EXTERN_C bool StackEmpty(ST* ps);
EXTERN_C int StackSize(ST* ps);
EXTERN_C STDataType StackTop(ST* ps);

我们来对这个条件编译进行分析,是怎么完成的

首先 __cplusplus 这个是C++独有的标识符,C语言是不认识的,

然后我们将extern”C“进行了宏定义,所以这个条件编译实现的是

如果在C++静态库里面,是认识这个标识符的,然后extern”C“放出来修饰函数名

然后再C项目里面展开头文件的时候,C语言是不认识这个表示符的,跳到下一个宏定义,下一个宏定义是没有extern”C“定义的,是空白的,不会加以修饰,所以C项目展开头文件的时候的函数名还是C语言原来的函数名。

所以最后我们还是能成功调用C++静态库。

注意:修改条件编译后,静态库要先编译一次,再去C项目编译进行调用

image-20220509031422011

我们也可以用另一种条件编译的表达方式

 #ifdef __cplusplus
extern"C"
  {
#endif
	void StackInit(ST* ps);
	void StackDestory(ST* ps);
	void StackPush(ST* ps, STDataType x);
	void StackPop(ST* ps);
	bool StackEmpty(ST* ps);
	int StackSize(ST* ps);
	STDataType StackTop(ST* ps);
#ifdef __cplusplus
   }
#endif

进行批量定义,也是可以成功编译的。

image-20220509032101827

3.3总结

  • C++调用C静态库,可以使用C++的extern”C“语法进行调用

  • C调用C++静态库,则可以用条件编译去解决C和C++函数名修饰不同的问题,从而进行调用

注意:extern”C"只修饰头文件是因为定义里面也会包含头文件,头文件展开后,编译器是先看见 extern ”C“ 再看见定义的。

4.结语

以上就是C++函数重载的介绍,包括了它的概念和底层实现的原理。

还有C/C++互调静态库的过程,也是对函数重载知识的一些扩展实际操作。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Poolblue7

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

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

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

打赏作者

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

抵扣说明:

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

余额充值