4.函数重载
C++是支持函数重载的,
所谓函数重载就是指:两个函数名称相同,但是参数的个数或者类型或者类型顺序不同,即构成函数重载
注意:如果两个函数只有返回值不同的话,并不会构成函数重载
1.函数重载的形式
这里以Add函数为例
int Add(int num1, int num2) { return num1 + num2; } double Add(double num1, double num2) { return num1 + num2; } int main() { cout << Add(1, 2) << endl; cout << Add(1.1, 1.2) << endl; //cout << Add(1, 1.2) << endl;//err return 0; }
大家注意到了这一行了吧
cout << Add(1, 1.2) << endl;//err • 1
为什么会报错呢?
首先,在C语言的学习中,我们知道int和double类型是可以发生隐式类型转换的
也就是说在Add(1,1.2)这个函数调用中,有可能会发生两种情况:
(1)(int类型的1)被隐式转换为double类型,去匹配double Add(double num1, double num2)
(2)(double 类型的1.2)被隐式转换为int类型,去匹配int Add(int num1, int num2);
也就是说这个函数调用存在歧义,所以编译器会报错
也就是说哪怕我们去掉其中的一个Add函数,这个函数调用就不会报错了
下面是函数重载的几种错误形式
1.两个函数仅有返回值不同
2.参数仅有名称不同或者仅有参数名称顺序不同
注意:并不是说两个函数构成重载之后对它们的调用就不会产生歧义
2.函数重载和缺省参数的联系
下面请大家看一下下面两个函数能不能构成函数重载
void f(int num) { cout << "f(int num)" << endl; } void f(int num = 1) { cout << "f(int num = 1)" << endl; }
答案是:不构成重载,因为参数的个数,类型,类型顺序均相同
那么在请大家看一下这两个函数会不会构成重载呢?
void f() { cout << "f()" << endl; } void f(int num = 1) { cout << "f(int num = 1)" << endl; }
答案是:构成重载
但是这意味着这两个函数在调用时就真的没有歧义了吗?
这种情况下是可以的
但是这种情况下就不行了:
因为在调用的时候产生了歧义
这就像是一个经典的问题:
你妈妈和你老婆掉水里,你只能救一个,你救谁?
C++懒得跟你折腾,直接在语法上不允许这样做,直接报错
3.函数重载的底层原理:函数名修饰规则
还有一个问题:函数的参数不同就能构成函数重载,那我返回值不同凭什么就无法构成函数重载呢?
这就要谈一下函数名修饰规则了
在谈这个之前,我们先建立一个Func.h,一个Func.cpp,一个Test.cpp文件
并且复习一下C语言阶段的编译链接的知识
现在我们有Func.h,Func.cpp,Test.cpp这三个文件 其中在Func.cpp文件中定义了两个重载的func函数 我们知道编译分为4个阶段: 1.预处理: 头文件展开 宏替换 去注释 条件编译 Fun.h在Func.cpp和Test.cpp中展开 Func.cpp中同时拥有func函数的声明和定义,Test.cpp中拥有func函数的声明和具体调用 Fun.cpp -> Fun.i Test.cpp -> Test.i 2.编译: 把.i文件进行语法检查(语法分析,词法分析,语义分析)生成汇编代码(.s文件) Fun.i -> Fun.s Test.i -> Test.s 3.汇编阶段: 把汇编代码转换为二进制机器码,生成目标文件(.o文件) Fun.s -> Fun.o Test.s -> Test.o 4.链接阶段: 合并.o目标文件,链接一些没有确定的函数的地址,合并段表,符号表的重定位等等 生成可执行程序: Windows: .exe, Linux: a.out
而这个函数名修饰规则就是在链接阶段进行的 在预处理阶段结束后 Func.cpp中同时拥有func函数的声明和定义,Test.cpp中拥有func函数的声明和具体调用 所谓声明就是一种承诺,是承诺,就要兑现 而链接阶段就是这个承诺兑现的时候 怎么兑现呢?让我的Test.cpp能够找到func函数的定义,就是兑现 怎么找到呢? (通过函数声明去找地址) .o文件中有一个东西叫做符号表,符号表中存储了函数名跟函数地址的一种映射关系 在链接阶段,通过符号表跟一种映射关系找到函数的地址,就能进行函数的调用了 而对于这个符号表来说 在C语言中,符号表中只会建立函数名跟函数地址的映射关系, 也就决定了在C语言中不允许存在同名函数,否则在链接阶段会找到多个函数地址,产生歧义 而对于C++来说 有函数名修饰规则:把函数的参数代入进去对符号表中的映射进行修饰 在Linux下可以尝试看到这个名字 Linux下函数名修饰规则: _Z4func(_Z是前缀+函数名的字符个数+函数名+参数首字母) _Z4funcdi(第一个参数类型:double,第二个:int) _Z4funcid(第一个参数类型:int,第二个:double) int* :Pi 有兴趣,可以自己在Linux下去试试
下面我们在Linux下给大家看一看
我们在Linux下建立了两个文件:
test.c
test.cpp
先看.c:
cpp:
然后我们用gcc把test.c编译出来,编译成testc
然后用这个命令查看testc的内容
这个 < func>前面的就是它的地址,也就是第一个指令的地址,
而且我们发现这个函数名根本就没有被修饰
下面我们来看一下.cpp文件
先用g++把test.cpp编译成了testcpp
还是用这个命令进行查看
然后我们发现C++编译后的函数名的确被修饰了
修饰成了
_Z4funcid • 1
大家也可以试一下其他的类型,指针的话:
例如int* 类型的是:会被修饰为Pi
下面我们就可以回答这个问题了:
为什么C语言不支持函数重载,而C++支持函数重载呢?
因为在链接阶段
C语言文件的函数地址是完全只通过函数名去进行查找的,如果存在同名函数,会导致找到多个函数地址,让函数调用产生歧义
而C++文件的函数名经过了修饰,那么如果两个函数名相同,但是参数不同就可以进行区分了
所以对于构成重载的函数,只会找到那唯一一个,不会产生歧义
Linux下函数名修饰规则:
_Z4func(_Z是前缀+函数名的字符个数+函数名+参数首字母)
注意:不同编译器下的函数名修饰规则是不同的
那么为什么我刚才不在windows下演示呢?
因为windows下的函数名修饰太过复杂
我们把Func.cpp中的代码注释掉,让它发生链接错误
看到蓝色标注的地方,windows下也是一个类型对应一个符号,不过不像Linux下那么直观
以上就是C++入门1的全部内容,希望能对大家有所帮助