|
前文回顾
上文说到,函数重载是这样定义的:
|
C语言不支持同名函数,但是C++支持
比如:
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
但是下面两个函数构成重载吗?
short Add(short left, short right)
{
return left + right;
}
int Add(short left, short right)
{
return left + right;
}
不构成,虽然它们在同一作用域中,且函数名相同,但是参数是相同的,注意:不关返回值类型的事!
函数重载这部分内容很重要,在面试的时候经常考察,比如下面几道面试题:
1.下面两个函数能形成重载吗?有什么问题或者在什么情况下会出现问题?
void TestFunc(int a = 10)
{
cout << "void TestFunc(int)" << endl;
}
void TestFunc(int a)
{
cout << "void TestFunc(int)" << endl;
}
2.C语言中为什么不能支持函数重载?
3.C++中函数重载底层是怎么处理的?
4.C++中能否将一个函数按照C语言的风格来编译?
OK,别着急,请听俺慢慢道来。
编译&链接
在谈函数重载之前呢,先说一下编译器编译链接的过程,请看下面3个文件:
//f.h
#include<iostream>
using std::cout;
using std::endl;
void f(int a, double d);
void f(double d, int a);
//f.cpp
#include"f.h"
void f(int a, double d)
{
cout << a << " " << d << endl;
}
void f(double d, int a)
{
cout << d << " " << a << endl;
}
//test.cpp
#include"f.h"
int main()
{
cout << f(1, 2.22) << endl;
cout << f(2.22, 1) << endl;
return 0;
}
程序的环境
我们知道,在ANSI C的任何一种实现中,存在两个不同的环境。
- 翻译环境:在这个环境中源代码被转换为可执行的机器代码;
- 执行环境:用于实际执行代码。
大致过程是这样的:源代码经过翻译环境的编译和链接变成可执行程序(又叫做二进制指令/机器指令),可执行程序经过执行环境显示结果。上面是程序的大致执行过程,如果要深刻理解函数重载的底层,那么还需对翻译环境进行详细讲解。
翻译环境
上面说到:
- 组成一个程序的每个源文件经过编译过程分别转换成目标代码.obj
- 每个目标文件由链接器捆绑在一起,形成可执行程序
但其实翻译本身也分为几个阶段,而且这部分内容很重要,不信你看:
翻译环境还分为编译和链接,而编译呢,又具体分为预编译、编译和汇编。
- 预编译:头文件展开、删除注释、宏替换以及条件编译;
- 编译:语法分析、词法分析、语义分析以及符号汇总,把C++代码转换为汇编代码;
- 汇编:形成符号表,把汇编代码转换成二进制指令。
这些都是编译所要执行的操作,结束之后,需要链接。
- 链接:合并段表以及符号表的合并和重定义。
具体内容请见上图。
细谈函数重载
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
深剖存在的问题:
实际我们的项目通常是由多个头文件和多个源文件构成,而通过我们C语言阶段学习的编译链接,我们
可以知道,当前test.cpp中调用了f.cpp中定义的 f 函数时,编译后链接前,test.o的目标文件中没有
f 的函数地址,因为 f 函数是在f.cpp中定义的,所以 f 函数的地址在f.o中。那么怎么办呢?链接阶段就是专门处理这种问题,链接器看到test.o调用 f 函数,但是没有 f 函数的地址,就会到 f.o的符
号表中找 f 函数的地址,然后链接到一起。那么链接时,面对 f 函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了Linux演示。
注意:在Linux下,可认为gcc调用的是C语言编译器,g++调用的是C++编译器。
下面一段代码:
int Add(int a, int b)
{
return a + b;
}
void func(int a, double b, int* p)
{
Add(1, 2);
func(1, 2, 0);
return 0;
}
【注意看汇编代码里的函数的命名】
- 采用C语言编译器编译:
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
- 采用C++编译器编译:
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,可以看到,编译器将函数名的长度以及函数参数类型信息添加到修改后的名字中。
讲到这里大家可能就明白为什么C语言不支持函数重载,而C++支持了。
面试题:为什么C++支持函数重载,而C语言不支持函数重载呢?
因为C语言对同名函数没有办法进行区分,而C++通过函数修饰规则进行区分,只要参数不同,修饰出来的函数名字也就不一样,这样也就支持重载了。另外我们也就能理解为什么函数重载要求的是函数参数不同,跟返回值没有关系。
Windows下的修饰规则就不详细说明咯,原理是差不多的,好,那咱们继续看下面的内容。
缺省参数关函数重载什么事?
掌握了上面的知识,那么这道面试题就小case了。
面试题:下面两个函数能形成重载吗?有什么问题或者在什么情况下会出现问题?
void TestFunc(int a = 10)
{
cout << "void TestFunc(int)" << endl;
}
void TestFunc(int a)
{
cout << "void TestFunc(int)" << endl;
}
解答:上面两个函数不构成重载,因为C++编译器将函数名的长度以及函数参数类型信息添加到修改后的名字中,跟函数参数是否有缺省值无关(与返回值无关也是这个道理)。
C++中能否将一个函数按照C语言的风格来编译?
OK,讲完上面的C++对于函数重载底层的处理,那么可能有铁汁会问,C++中能否将一个函数按照C语言的风格来编译或者C语言中能否将一个函数按照C++的风格来编译?
别着急,不是很难,下文再谈,哈哈拜拜咯铁子们。
|