C++深入浅出讲解函数重载

📅 2026/7/26 6:42:27 👁️ 阅读次数 📝 编程学习
C++深入浅出讲解函数重载

前言

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。

比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

函数重载

1.1 函数重载的概念

函数重载:

  1. 它是函数的一种特殊情况,C++允许在同一作用域中同一作用域中声明几个功能类似的同名函数
  2. 函数重载的关键是函数的参数列表,也称为“函数特征标”
  3. 这些同名函数的形参列表(参数个数、类型和顺序(不同类型的顺序))必须不同,常用来处理实现功能类似数据类型不同的问题
  4. 函数重载也是多态的一种,多态指的是“有多种形式”

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//C语言不支持重载,C++支持重载

intAdd(intleft,intright)

{

returnleft+right;

}

doubleAdd(doubleleft,doubleright)

{

returnleft+right;

}

intAdd(intleft,doubleright)

{

returnleft+right;

}

intAdd(doubleleft,intright)

{

returnleft+right;

}

intmain()

{

Add(10, 20);

Add(10.0, 20.0);

Add(10, 20.0);

Add(10.0, 20.0)

return0;

}

下面两个函数属于函数重载吗?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

shortAdd(shortleft,shortright)

{

returnleft+right;

}

intAdd(shortleft,shortright)

{

returnleft+right;

}

intmain()

{

Add(10, 20);

Add(10, 20);

return0;

}

代码解析:

  1. 上述代码中的两个函数不属于函数重载
  2. 因为重载的形参列表(参数个数、类型和顺序)必须不同
  3. 函数重载与函数返回值的类型无关,并且在函数调用时,也是无法识别它的

1.2 函数重载的意义

意义:

在C语言中,想要定义多个不同类型交换数据的子函数,需要不同的函数名来命名,比如SweapA、SweapB…等等

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

voidSweapA(int*pa,int*pb)

{

inttemp = *pa;

*pa = *pb;

*pb = temp;

}

voidSweapB(double*pa,double*pb)

{

doubletemp = *pa;

*pa = *pb;

*pb = temp;

}

intmain()

{

inta = 10, b = 20;

doublec = 10.0, d = 20.0;

SweapA(&a, &b);

SweapB(&c), &d);

return0;

}

  1. 但是,在C++中,通过函数重载,只需要命名一次就可以了
  2. 虽然跟C语言一样要重复定义函数,但是后面会学到函数模板后,可以很好的解决这个重复定义问题

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

voidSweap(int*pa,int*pb)

{

inttemp = *pa;

*pa = *pb;

*pb = temp;

}

voidSweap(double*pa,double*pb)

{

doubletemp = *pa;

*pa = *pb;

*pb = temp;

}

intmain()

{

inta = 10, b = 20;

doublec = 10.0, d = 20.0;

Sweap(&a, &b);

Sweap(&c), &d);

return0;

}

1.3 名字修饰(name Mangling)

名字修饰(name Mangling):

  • C++为了跟踪每一个重载函数,它都会给这些函数指定一个私密身份
  • 使用C++编译器编写函数重载程序时,C++编译器将执行一些奇特的操作 — — ---名称修饰 或 名称矫正
  • 它根据函数原型中指定的形参对每个函数名进行加密
  • 对参数数目和类型进行编码,添加的一组符号符合随函数形参列表而异,修饰时使用的约定(函数名)随编译器而异

为什么C++支持重载,而C语言不支持呢?

  • 在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接
  • 预处理(.i):文件展开、宏替换、条件编译、去注释
  • 编译(.s):检查语法是否正确,生成汇编代码
  • 汇编(.o):将汇编代码转化成二进制的机器码
  • 链接(a.out):生成符号表,找调用函数的地址,链接匹配,合并到一起

  • 实际我们的项目通常是由多个头文件和多个源文件构成,当前a.cpp中调用了b.cpp中定义的Add函数
  • 在编译后链接前的处理阶段,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
  • 链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起
  • 链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则

在Linux下使用gcc和g++编译器演示函数名被修饰后的名字

采用C语言编译器编译后结果(反汇编)

结论:在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变

采用C++编译器编译后结果(反汇编)

结论:在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中

总结

gcc的函数修饰后名字不变。而g++的函数修饰后变成(_Z+函数长度+函数名+类型首字母)

C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载

Windows下名字修饰规则

结论:对比Linux会发现,windows下C++编译器对函数名字修饰非常奇怪,但道理都是一样的

扩展学习:C/C++函数调用约定和名字修饰规则

C++函数重载

C/C++的调用约定

接下来,再演示一个例子

f.h
#include <stdio.h>

void f(int a, double b);
void f(double b, int a);

f.cpp
#include "f.h"

void f(int a, double b);
{
printf("%d %lf\n", a, b)
}

void f(double b, int a);
{
printf("%lf %d\n", b, a)
}
Test.cpp
#include "f.h"

int main()
{
f(1, 2.222);
f(2.222, 1);
return 0;
}

编译后,生成汇编指令;链接时,生成符号表

Linux下g++(C++)编译器的命名:

Linux下gcc(C)编译器的命名:

1.4 extern "C"

  • 有时候在C++工程中可能需要将某些函数按照C的风格来编译
  • 但是,大多数情况下是C工程需要将某些函数按照C++的风格来编译
  • C可以调用CPP的静态/动态库,而CPP也可以调用C的静态/动态库
  • extern “C”是告诉编译器,它所声明的函数,是C的库,要用C的链接方式去调用静态库或动态库

那么CPP是怎么调用C中的静态/动态库呢?(vs2022演示)

首先,我们用C来生成一个静态库或动态库

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

Test.h

#include <stdio.h>

voidPrintArray(int* p,intn);//显示数组内容

voidInsertSort(int* p,intn);//插入排序

Test.C

#include "Test.h"

voidInsertSort(int* p,intn)

{

for(inti = 0; i < n - 1; ++i)

{

intend = i;

inttmp = p[end + 1];

while(end >= 0)

{

if(tmp < p[end])

{

p[end + 1] = p[end];

--end;

}

else

{

break;

}

}

p[end + 1] = tmp;

}

}

配置类型改成静态库后,生成解决方案,就得到后缀.lib文件了

在CPP项目中添加新的库目录(这个库是你生成的静态库的路径)

增加新的依赖项(依赖项为生成静态库的文件名+后缀"Test.lib")

做完这些准备后,我们来进行编译程序

  • 编译后我们发现链接阶段时出现了错误
  • 原因是:C++调用C时,它们之间的函数命名规则(名称修饰)不同
  • 我们需要C++中的extern "C"来解决
  • extern “C”是告诉编译器,它所声明的函数,是C的库,要用C的链接方式去调用静态库或动态库

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

extern"C"

{

//"../"是在当前目录的上一个目录中找文件

#include "../../Test/Test/Test.h"

}

#include <iostream>

usingnamespacestd;

voidTestInsertSort()

{

intArray[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };

InsertSort(Array,sizeof(Array) /sizeof(Array[0]));

for(inti = 0; i < 10; ++i)

cout << Array[i] <<" ";

cout <<" "<< endl;

}

intmain()

{

TestInsertSort();

return0;

}

如果C想调用CPP的静态或动态库呢?

  • C调用CPP库时,也会遇到名称修饰的问题
  • 这里需要对CPP的名称修饰规则改成C的规则
  • 这里我们需要用到"条件编译"来解决问题

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

Test.h

#include <stdio.h>

#ifdef __cplusplus

#define EXTERN_C extern "C"

#else

#define EXTERN_C

#endif

EXTERN_CvoidPrintArray(int* p,intn);

EXTERN_CvoidInsertSort(int* p,intn);

Test.cpp

#include "Test.h"

voidPrintArray(int* p,intn)

{

for(inti = 0; i < n; ++i)

{

printf("%d ", p[i]);

}

printf("\n");

}

voidInsertSort(int* p,intn)

{

for(inti = 0; i < n - 1; ++i)

{

intend = i;

inttmp = p[end + 1];

while(end >= 0)

{

if(tmp < p[end])

{

p[end + 1] = p[end];

--end;

}

else

{

break;

}

}

p[end + 1] = tmp;

}

}

感谢大家支持!!!到此这篇关于C++深入浅出讲解函数重载的文章就介绍到这了