C++基础学习笔记深入理解C++函数重载(非常重要!!!)

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++基础学习笔记深入理解C++函数重载(非常重要!!!)相关的知识,希望对你有一定的参考价值。

【高频面试题】
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的风格来编译 ?
这些问题,相信大家看完博客后心中就会有答案。



函数重载

有趣的小故事:自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如 : 以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”(谁也 / 赢不了),后者是"谁也赢不了!”(谁/也赢不了)(中华文化真是博大精深,这里断句也不一定对,小伙伴们可以自己试试)

函数重载的概念

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)必须不同,常用来处理实现功能类似数据类型不同的问题

举例:

#include<iostream>
using namespace std;
int Add(int x, int y)
{
	return x + y;
}
double Add(double x, double y)
{
	return x + y;
}
long Add(long x, long y)
{
	return x + y;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;
	cout << Add(10L, 20L) << endl;
	return 0;
}


思考,以下两个函数构成函数重载吗?

int Add(short x, short y)
{
	return x + y;
}
short Add(short x, short y)
{
	return x + y;
}

回答:不构成函数重载,不满足函数重载的条件,函数重载不考虑返回值类型的不同,编译器在编译的时候会报错。

理解:
重载,也就是一词多义
函数重载:同一个函数名可以表示多个不同的函数

要求:函数名相同,参数不同(参数不同体现在以下三个方面:类型不同、个数不同、顺序不同,满足其中一种即可),但是对返回值无要求(如果只是返回值不同,那么不能构成重载)
C语言中规定函数名不能相同,但是在C++中函数名可以相同,因为C++支持函数重载
函数重载的应用,函数调用会自动识别参数类型


名字修饰(name Mangling)

为什么C++支持函数重载,而C语言不支持函数重载呢 ?
为了方便演示,在linux下创建相应的文件和代码:

//list.h
#include<stdio.h>
void list_push_back(int x);
void add(int i, int j);
int add(double i, double* j);
//list.c
#include"list.h"
void list_push_back(int x)
{
	printf("%d\\n", x);
}
void add(int i, int j)
{}
int add(double i, double* j)
{
	return 0;
}
//test.c
#include"list.h"
int main()
{
	list_push_back(1);
	return 0;
}

如果我们直接用gcc编译器(c语言编译器)来编译以上代码,肯定是编译不通过的,因为这里有函数重载

如果将函数重载的部分先注释掉

再进行编译,应该是没有问题的,同时我们用c++的编译器g++编译一下,得到:

利用xshell同时开多个窗口,可以快速进行修改

在C / C++中,一个程序要运行起来,需要经历以下几个阶段:
预处理、编译、汇编、链接。
c语言中源文件:test.c(以.c结尾,英文:source) c++源文件:test.cpp(以.cpp结尾,英文:c - plus - plus)
预处理->头文件展开、宏替换、条件编译、去掉注释 生成 list.i test.i
编译->检查语法,生成汇编代码 生成 list.s test.s
汇编->将汇编代码转换成二进制的机器码 生成 list.o test.o
链接->将两个目标文件链接到一起 生成可执行文件test.exe

上面的汇编和链接两个阶段就是我们要理解函数重载的关键,尤其是链接阶段

如果找到了,就正常链接,生成可执行程序.exe文件,如果找不到,就会出现链接时错误。
什么时候会出现找不到的情况呢?
当我们声明了一个函数,然后对其进行正常调用,但是没有定义,那么这个函数的地址就找不到。

在C语言中,汇编生成的符号表中,可以发现对应的函数名字修饰没有发生改变,如果出现两个名称相同的函数,那么在链接的时候调用函数时应该找到哪一个函数的地址进行使用呢?是不是无法区分出来,这也是为什么C语言不支持函数重载的原因(汇编后函数名字的修饰未变化)


那么C++呢?又是什么情况?为什么会支持函数重载?难道是函数的修饰名称发生了变化?

将之前生成的cpp先去掉,然后将之前注释的代码段去除注释,重新生成cpp文件

使用objdump - S 命令来查看符号表相关信息

找到list_push_back, add, add三个函数的符号表

可以看到加了一些修饰内容,比如前面的Z14 / _Z3,后面的ii,dPd),因此即便函数名相同,但是函数参数不同,生成的符号表也会不同,在调用的时候,根据生成的符号表,依然可以找到对应的函数地址,不会发生冲突,可以进行区分,所以支持重载!

<_Z3Addii> _Z3是前缀 Add是函数名,ii(i是类型的首字母)指两个int类型的参数

C语言格式

总结:C++与C函数名修饰规则不一样,C++的函数名修饰规则中会引入参数相关的信息,所以在函数名相同,参数不同的情况下,经过函数名修饰规则修饰后生成的名称也不同,可以做到相同函数名称之间的区分!而C语言的函数名修饰规则是直接使用函数名,跟函数参数无关,如果出现名称相同的函数,那么在链接的时候去调用函数,应该调用哪一个呢?两个名称完全相同,就无法确定该调用哪一个了!

1.实际我们的项目通常是由多个头文件和多个源文件构成,而通过我们C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢 ?
⒉所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
3.那么链接时,面对Add函数,连接器会使用哪个名字去找呢 ? 这里每个编译器都有自己的函数名修饰规则。
4.由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂, 下面我们使用了gcc演示 了这个修饰后的名字。
5.通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z + 函数长度 + 函数名 + 类型首字母】。
6.通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
7.另外我们也理解了,为什么函数重载要求参数不同!而跟返回值没关系。


extern “C”

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如: ttmlloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern“C"来解决。
静态库和动态库

为了让C和C++程序都能用到这个C++的静态库 / 动态库,可以用extern “C”

C++兼容c语言的规则,用了extern “C”后就不支持函数重载了,因为此时符号表是按照C语言的规则去生成的


再来回顾一下开头所所提到的四个面试题,看完我的秘密笔记后,相信你应该对此有所了解~

【高频面试题】
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的风格来编译 ?

以上是关于C++基础学习笔记深入理解C++函数重载(非常重要!!!)的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C++类和对象(中)

C++笔记--面向对象(OOP)编程基础--操作符重载及友元

C++基础--函数重载

刚接触 c++ 和重载运算符,不确定如何使用该函数

c++学习笔记:多态

C++内存管理机制学习笔记:重载operate new/::operator new..../new()