今天学的是 重载(overload),c中没有c++开始使用的。话不多说,直奔主题!
一.什么是重载?
同一作用域中,函数名相同,参数列表不同的函数,就叫做重载。
从定义中,我们可以发现函数重载的几个条件:
- 同一作用域
- 函数名相同
- 参数列表不同
三个条件缺一不可!!!
我们先来看个例子:求两个数中最大的数
#include <iostream>
using namespace std;
int getmax(int x,int y){
cout<<"getmax(int,int)"<<endl;
return x>y ? x : y;
}
double getmax(int x,double y){
cout<<"getmax(int.double)"<<endl;
return x>y ? x: y;
}
int main()
{
getmax(1,2);
getmax(1,2.3);
return 0;
}
看看输出的结果是什么样的:
由上面的代码我们可以知道,两个getmax函数构成了重载,这在c语言中是不允许的。
重载给我们带来了很大的方便,如果是在c语言中,我们要编写求两个数最大的数的值的话,两个int类型的要区一个函数名,两个double类型的又要取一个函数名。如果类型各种各样,你就得取成百上千的函数名,太麻烦了。
c++就很好的做到了这一点,通过重载适配各种参数类型,给编程带来极大方便。
我们来分析一下上面那段代码:
getmax(int,int)和getmax(int,double)两个函数,都属于全局函数,所以满足条件一:同一作用域
两个函数的名字都相同,都是getmax,满足条件二:函数名相同
第一个getmax的参数列表是int和int,第二个getmax的参数列表是int和double,他们的参数列表第二个参数类型不同,满足第三个条件:参数列表不同
由此可见,重载的三个条件是与关系,缺一不可!
我相信细心的人会发现,这两个getmax函数的返回值类型不一样(一个是int,一个是doubl),这样也算重载吗?
Of course!只要满足上述三个条件,管你什么返回值类型,都属于重载!
二.怎样才算参数列表不同?
有人会说,getmax(int x,int y)和getmax(int a,int b)这两个函数构成了重载,因为一个函数的参数叫x和y,另一个叫a和b。
我们说的参数列表不同,不是说形参名不同。
具体的参数列表不同表现在三个方面:
- 参数类型不一样。
- 参数个数不一样。
- 参数顺序不一样。
同样的我们来用代码说话:
#include <iostream>
using namespace std;
int getmax(int x){
cout<<"getmax(int)"<<endl;
return x;
}
int getmax(int x,int y){
cout<<"getmax(int,int)"<<endl;
return x>y ? x : y;
}
double getmax(int x,double y){
cout<<"getmax(int,double)"<<endl;
return x>y ? x: y;
}
double getmax(double x,int y ){
cout<<"getmax(double,int)"<<endl;
return x>y ? x: y;
}
int main()
{
getmax(1,2);
getmax(1,2.3);
getmax(1);
getmax(1.23,5);
return 0;
}
我们把getmax函数写了四个,每个都不一样,但是都构成 了重载。
第一个getmax只有一个参数(int),而第二个getmax有两个参数(int,int),满足条件二: 参数的个数不同
第二个和第三个参数列表中的第二个参数类型不同,第一个是int y,第二个是double y。满足条件一: 参数的类型不同
第三个和第四个参数虽然都是一个int一个double,但是int 和 double 的位置不一样。第三个函数的int在前,double在后;第四个函数的double在前,int在后。满足条件三: 参数的顺序不同。
由此可见,参数列表不同的三个条件是或关系,满足之一即可!
三.重载的原理是什么?
先给大家看一张图哦:重载的原理其实很简单,两个字: 换名 。
c++的编译器会给每个函数名取不一样的名字,同样是foo函数,编译器分别给他们取名叫做_Z3foov()._Z3fooi,_Z3food。实际上他们就是三个不一样的函数了。
而c编译器可不会这样,你给函数取什么名字就是什么名字,这就是为什么c中没有函数重载的概念!
怎么来验证我说的对不对呢?我们来看源程序翻译成汇编后的汇编代码(一般编译器做不到,谁叫我们学unix 的,上gcc编译器!):
我们先来看c语言编写的函数源代码(overload.c):
gcc overload.c -S,生成汇编文件(overload.s),我们来看汇编文件里面有什么:
没学过汇编的第一眼看上去肯定会说,什么gui!我也没学过,我也看不懂。但我知道第5行,是foo子函数的入口;第22行是main函数的入口;第31行是调用(call)foo函数!
由此可见,在c编写的程序中,函数名是什么就是什么,编译器不会帮你修改的。
那我们现在再来看看c++写的程序会是什么样的。先看overload.cpp:
g++ overload.cpp -S生成汇编文件(overload.s):
这里就有点不一样了。第七行是foo函数的入口(已经被编译器换名为_Z3fooi);第24行是main函数的入口(main函数是不会改名的,所以 main函数没有重载!),33行是调用foo函数(call _Z3fooi).
看了foo函数,同样用c和c++写,他们底层实现是定点区别显而易见。c++编译器,会将foo函数改名为_Z3fooi,而c编译器还是直接使用foo。
之所以能够重载,是因为c++将同一个函数名换名成不同的函数名,实际上调用的是不同的函数。
换名其实是有规律的:前缀"_Z"是固定的,之后的数字代表函数名有几个字符,比如foo为3个字符,所以是_Z3.
之后跟上原来的函数名,最后加上参数的类型缩写(int 就是 i,double就是d,char就是c,void就是v,等等)。
换名后的函数名的规律就是: _Z + 函数名的字符个数 + 原函数名 + 参数列表的缩写。
举几个例子: getmax(int x,double y)换名后就是_Z6getmaxid
getmax(double x,double y)换名后就是_Z6getmaxdd
这样就很容易解释重载成立的三个条件了。如果你函数名不一样,_Zx后面的函数名就不一样;如果你参数列表是一样的,id和id就会一样,最后还是同一个函书名不能构成重载。
四.换名带来的问题
万事都有两面性。换名自然可能出现某些问题,而这些问题一般发生在跨语法,跨编译器上的。什么意思呢?还是举例说明来的实在。
在多文件编程中,我用c语言实现了某一个函数功能,做成一个库文件(以 .o 文件为例)供c++程序来调用这个函数。(不懂的自己去学怎么用vi写代码,怎么用gcc编译器哦)
getmax.c:
#include <stdio.h>
int getmax(int x,int y){
printf("getmax(int,int)\n");
return x>y ? x : y;
}
这个getmax.c文件很简单,只有一个函数(因为只是做库文件,就不需要什么主函数了)。
gcc getmax.c -c 生成 getmax.o文件已备接下来的main.cpp文件调用哦。
main.cpp:
#include <iostream>
using namespace std;
int getmax(int x,int y);
int main()
{
getmax(1,2);
return 0;
}
让我们来向做多文件编程时一样的调用getmax.o文件:g++ main.cpp getmax.o。结果编译器报错了:
注意方框内的提示,getmax未定义!!!为什么呢?我明明在main.cpp的第三行先声明了外部的getmax函数以便接下来的使用,而且编译的时候也链接了getmax.o文件!
如果我们同时使用的是c++或者c编写两个源程序,肯定运行不会出错。
问题出就出在,getmax这个函数我是用c风格写出来的,而我却在c++的代码中调用了他!这就是我们上面说道的换名在跨语言带来的错误。(虽然c++兼容c,但其实这是两种语言)
为什么呢?让我们回顾重载的原理—— 换名!
c编写的getmax,编译器不进行换名,它还叫做getmax。在c++中我们声明了getmax,但是c++编译器将它换名为_Z6getmaxii。你在调用的时候,实际上是在调用_Z6getmaxii,而这里根本没有_Z6getmaxii的定义,只有getmax,所以编译出错了!
那么解决办法是什么呢?
在c++的函数前加上 extern "C" 即可,c++编译器就会像c编译器那样对待函数,不对它进行换名,也就从根本上杜绝了重载!
修改后的main.cpp:
#include <iostream>
using namespace std;
extern "C" int getmax(int x,int y);
int main()
{
getmax(1,2);
return 0;
}
一切都和我们想像的一样美好了!所以,各位一定要注意在跨编译器或者跨语言编写的函数之间相互调用时,换名可能带来的问题哦!
五.用函数指针调用重载函数
学了c/c++的同学们,对函数指针一定不陌生。比如我定义了一个函数:
int max(int x){
return x;
}
那么我们定义函数指针来调用它就是这样的:
int (*pm) (int x) = max;
int a = (*pm)(3);
关于函数指针我就不罗嗦了,反正学了的人都会觉得函数指针是个很好用的东西。
其实重载函数的函数指针也是一样的,虽然大家函数名都一样,但是参数列表很好的区分了他们之间的不同。
看代码:
#include <iostream>
using namespace std;
int getmax(int x){
cout<<"getmax(int)"<<endl;
return x;
}
int getmax(int x,int y){
cout<<"getmax(int,int)"<<endl;
return x>y?x:y;
}
int main()
{
int (*p1) (int x) = getmax;
int (*p2) (int x,int y) = getmax;
(*p1)(1);
(*p2)(1,2);
return 0;
}
函数指针p1和p2都是通过getmax赋值的。但是调用的结果完全不一样,因为这两个函数指针在定义的时候的参数列表就不一样。
So,你应该知道怎么用重载函数的函数指针了吧?
六.重载的解析
其实这一部分我也只是一知半解,还不是完全懂。希望各路大神指点一二!所谓的重载的解析呢,就是说,你在调用各种重载了的函数时,编译器怎么判断去用哪个函数呢?这就是重载解析要去解决的问题啦。
比如:
#include <iostream>
using namespace std;
void foo (char* p, int n) {
cout << "foo(char*,int)" << endl;
}
void foo (const char* p, char c) {
cout << "foo(const char*,char)" << endl;
}
void bar (char c) {
cout << "bar(char)" << endl;
}
void bar (int n) {
cout << "bar(int)" << endl;
}
void bar (long long l) {
cout << "bar(long long)" << endl;
}
void hum (double f, ...) {
cout << "hum(double,...)" << endl;
}
void hum (int n, void* p) {
cout << "hum(int,void*)" << endl;
}
int main (void) {
char* p;
char c;
foo (p, c);
short s;
bar (s);
double f;
void* q;
hum (f, q);
return 0;
}
可能看到结果,你有很多疑问。我们一点一点来分析:
1.foo(p,c)调用的为什么是foo(const char×,char)?
p是char*型 的,明显和foo(char*,int)的第一个参数更加匹配。但是c是char,又明显和foo(const char×,char)的第二个参数匹配?那我到底用哪个?编译器有自己的一套评分细则,它觉得, 常量转换(把const转换成非const比)比 升级转换(把char变为int)更加好!所以,我要用第二个foo。
2.bar(s)调用的为什么是bar(int)而不是 bar(double)呢?
同1,编译器觉得的把short(2字节)转换成int(四字节)明显比转换成double(八字节)更加省事,也就是 升级转换>标准转换。
3.hum(f,q)为什么调用的是hum(int,void*)?
同1,f虽然为double,跟hum(double f, ...)的参数一更加匹配,但是编译器认为 标准转换比 省略号匹配更加好。
综上,我们有一个规则: 完全匹配>常量转换>升级转换>标准转换>自定义转换>省略号匹配
结束语:
好了,今天的笔记就记到这里了。菜鸟写的笔记果然就是这样没有深度啊
如果大家有什么疑问,欢迎留言!
如有不对的地方,望大神不吝赐教!