15分钟快速入门C++函数重载、extern “C“和引用

本文介绍了C++中的函数重载原理,解释了C语言不支持重载的原因,并详细讲解了"函数名修饰规则"。接着讨论了extern "C"在C++与C语言交互中的作用,确保C程序可以调用C++编写的库。最后,探讨了C++中的引用概念,包括引用的初始化、权限和应用场景,如作为函数参数和返回值。

函数重载

函数名相同,参数不同就叫做函数重载。这里的参数不同,指的是参数类型不同或者参数个数不同。然而C语言是不支持重载的,C++就可以。下面就分析一下原因。

函数名修饰

首先回顾下编译链接过程。有这3个文件,分别是f.h、f.cpp和main.cpp。在预处理阶段,会发生头文件展开,宏替换、去掉注释和条件编译。最终会生成f.i和main.i。编译阶段会进行语法检查,生成汇编代码,得到f.s和main.s。紧接着就把汇编代码转换成机器码,生成f.o和main.o。最终阶段是链接.o文件,生成可执行文件。

//f.h
int add(int a, int b);
double add(double a, double b);
//f.cpp
int add(int a, int b)
{
	return a + b;
}

double add(double a, double b)
{
	return a + b;
}
//main.cpp
int main()
{
	add(1, 2);
	add(1.1, 2.2);

	return 0;
}

这里面涉及到函数重载的几个关键阶段分别是:在代码编译成汇编文件后,此时调用add函数会被转换成一条汇编指令call add(add函数地址),但是此时add函数地址是未知的,因此汇编指令为call add(?),地址用?代替。

当经过汇编处理后,会生成符号表。在f.o中,会保存add的地址,因此在链接时,之前的函数地址(?)就会被符号表中的具体函数地址代替,从而调用对应的函数。因为在C语言中,符号表是通过函数名保存的函数地址,因此若出现两个相同的函数名,在调用的时候就不知道该调用哪个。

C++则是通过"函数名修饰规则",实现了函数重载。在main.cpp中调用的两个add函数,转换成汇编文件时就会变成call _Z3addii(?)和call _Z3adddd(?),ii和dd对应的是函数参数(int, int)和(double, double),因此只要参数不同就能构成函数重载。同样地,此时函数地址是未知的。等到链接时,就会调用不同的修饰过的函数名对应的函数。

extern “C”

假设有这么一个中间件程序,如果想让它给外部程序提供接口调用。那么我们通常会把它编译成动态库或静态库。若这个程序使用C++编写的,那么用C++编写的外部程序自然可以去调用它,若是C语言编写的外部程序想要调用,那么在链接时,C程序就会使用函数名去找函数定义的,但是由于这个中间件是用C++编写的,符号表中保存的是经过函数名修饰过的函数名,因此会报LinkError。在中间件程序中加入extern “C”+调用的函数声明,C程序就可以调用了。比如经过下面的声明,C++编写的程序就可以被C程序调用。

extern "C" int add(int a, int b);//要调用的函数声明

因为已经经过上述声明,所以在符号表中存放的是函数名add,而不是修饰过的函数名(比如_Z3addii)。若之后用C++程序调用,那么就需要在C++调用程序中加入相同的函数声明。

引用

引用就是取别名。

int a = 0;
int& b = a;
int& c = b;

上面这段代码就是给a取别名,也就是说,在内存空间开辟出一块空间存放0,这块空间可以用a,b,c访问,即一个变量可以有多个引用

同时,引用必须先初始化,才能去访问所引用的变量。引用一旦是某块空间的别名,就不能再是其它空间的别名

引用一个变量,引用的权限必须小于或等于原来变量的权限。如下所示。

const int a = 0
int& ra = a;

变量a是只读的,而ra是可读可写的,因此这种引用是不行的。同样的道理,下面的代码定义了一个可读可写的变量b,对b取别名,其中crb只能对b进行读,rb对b可读可写,两个别名对变量的权限没有进行放大,因此下面的代码是可行的。

int b = 10;
const int& crb = b;
int& rb = b;
隐式类型转换

当"="两边类型不同时,会产生临时变量,临时变量具有常性。

int c = 0;
double& rc = c;
const double& rc = c;

上面第二行代码在执行时,会生成临时变量,因为就临时变量具有常性,所以在引用时,必须加const。需要注意的是,rc引用的并不是c,而是临时变量。

引用的场景
做参数

当函数的参数是引用类型时,对行参的修改就是对实参的修改,这个作用类似于传出参数。

做返回值

先说一下传值返回,如下代码所示。在return c的时候并不是直接把c返回,而是先把c赋值给一个临时变量,然后再把这个临时变量返回。换句话说,返回的是c的拷贝。

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

下面这个代码可以说明函数返回的不是c,而是临时变量。

int& ret = add(1, 2);

如果返回的c,那么这么引用就没有问题;如果返回的是临时变量,那么改用const int& ret就可以。

下面说明一下传引用返回。传引用返回的是c的引用,首先将c赋值给int&类型的临时变量,然后返回这个引用。

int& add(int a, int b)
{
	int c = a + b;
	return c;
}

需要注意的是引用越界问题,如下代码所示。

int& ret = add(1, 2);
add(2, 3);
cout<< ret << endl;

当执行完第一行代码,变量c被销毁,但是ret仍然是变量c所在内存空间的别名。当执行完第二行代码时,又在栈空间相同位置(第一次调用add)开辟空间,产生一块空间存储c,值为5。当打印ret时,若此时空间没有被清零,那么ret打印的值就为5。因此,这个add函数其实是不可以用引用作为返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Schuyler Hu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值