函数重载实现原理

函数重载

方法重载 (overload) C++允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading),借助重载,一个函数名可以有多种用途。

永远记住一点:函数重载只与参数列表有关,与返回值类型无关

 1int fun(int a,int b){
 2    return 0;
 3}
 4
 5int fun(int a,char b){
 6    return 0;
 7}
 8
 9void fun(){   
10}
11
12int fun(int a, int b, int c){
13    return 0;
14}

使用 objdump -S a.out 命令便可以通过a.out查看编译器编译出的汇编代码:

 10000000000400674 <_Z3funii>:
 2  400674:	55                   	push   %rbp
 3  400675:	48 89 e5             	mov    %rsp,%rbp
 4  400678:	89 7d fc             	mov    %edi,-0x4(%rbp)
 5  40067b:	89 75 f8             	mov    %esi,-0x8(%rbp)
 6  40067e:	b8 00 00 00 00       	mov    $0x0,%eax
 7  400683:	c9                   	leaveq 
 8  400684:	c3                   	retq   
 9
100000000000400685 <_Z3funic>:
11  400685:	55                   	push   %rbp
12  400686:	48 89 e5             	mov    %rsp,%rbp
13  400689:	89 7d fc             	mov    %edi,-0x4(%rbp)
14  40068c:	89 f0                	mov    %esi,%eax
15  40068e:	88 45 f8             	mov    %al,-0x8(%rbp)
16  400691:	b8 00 00 00 00       	mov    $0x0,%eax
17  400696:	c9                   	leaveq 
18  400697:	c3                   	retq   
19
200000000000400698 <_Z3funv>:
21  400698:	55                   	push   %rbp
22  400699:	48 89 e5             	mov    %rsp,%rbp
23  40069c:	c9                   	leaveq 
24  40069d:	c3                   	retq   
25
26000000000040069e <_Z3funiii>:
27  40069e:	55                   	push   %rbp
28  40069f:	48 89 e5             	mov    %rsp,%rbp
29  4006a2:	89 7d fc             	mov    %edi,-0x4(%rbp)
30  4006a5:	89 75 f8             	mov    %esi,-0x8(%rbp)
31  4006a8:	89 55 f4             	mov    %edx,-0xc(%rbp)
32  4006ab:	b8 00 00 00 00       	mov    $0x0,%eax
33  4006b0:	c9                   	leaveq 
34  4006b1:	c3                   	retq   
35
3600000000004006b2 <main>:
37  4006b2:	55                   	push   %rbp
38  4006b3:	48 89 e5             	mov    %rsp,%rbp
39  4006b6:	b8 00 00 00 00       	mov    $0x0,%eax
40  4006bb:	c9                   	leaveq 
41  4006bc:	c3                   	retq

由此可见g++编译器在编译cpp源文件的时候,对于同名称的函数做了区分,添加了相应的函数修饰,修饰之后的函数名称不同,所以是可以区分的! 同样的道理我们可以看看C语言是否支持函数重载:

1#include<stdio.h>
2
3void fun(int a, int b){
4
5}
6
7int main(){
8    return 0;
9}

对应的汇编代码如下

 10000000000400474 <fun>:
 2  400474:	55                   	push   %rbp
 3  400475:	48 89 e5             	mov    %rsp,%rbp
 4  400478:	89 7d fc             	mov    %edi,-0x4(%rbp)
 5  40047b:	89 75 f8             	mov    %esi,-0x8(%rbp)
 6  40047e:	c9                   	leaveq 
 7  40047f:	c3                   	retq   
 8
 90000000000400480 <main>:
10  400480:	55                   	push   %rbp
11  400481:	48 89 e5             	mov    %rsp,%rbp
12  400484:	b8 00 00 00 00       	mov    $0x0,%eax
13  400489:	c9                   	leaveq 
14  40048a:	c3                   	retq   
15  40048b:	90                   	nop
16  40048c:	90                   	nop
17  40048d:	90                   	nop
18  40048e:	90                   	nop
19  40048f:	90                   	nop

由此可见对于C语言是没有函数修饰的,所以C是不支持函数重载的!

extern “C”

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译:

1extern "C" int add(int a, char b) {
2	return 0;
3}

如果是头文件与实现分离的情况下,只要头文件写了extern "C",源文件可写可不写,但是只要头文件没写extern "C" ,源文件中就不应该写extern "C",否则会报链接错误

函数重载与函数重写(覆写)的区别 方法重写(覆盖)override:发生在有继承关系的类中,子类定义了与父类完全相同的方法(方法名、返回值、参数列表全都一样),被重写的方法必须比父类中的方法权限更高!

缺省参数

缺省参数顾名思义,就是说参数列表中定义了默认的参数:

 1#include<iostream>
 2
 3int fun(int a, int b = 10){  //定义缺省参数
 4    return a+b;
 5}
 6
 7int main(){
 8    std::cout << fun(20) << std::endl; //30
 9    std::cout << fun(20, 20) << std::endl; //40
10    return 0;
11}

缺省参数的使用注意事项: 1、缺省参数必须定义在参数列表的最右边,这个比较容易理解,如果你把缺省参数定义在中间的话,编译器也就不知道到底哪些参数是缺省的 error: default argument missing for parameter 2 of ‘int fun(int, int)’

2、缺省参数不能同时在函数声明和定义中出现,只能二者留其一,这个也是比较好理解的,站在编译器的角度看问题,要想让编译器明白,首先我们自己的明白到底该用哪一个值作为默认值

3、缺省值必须是常量或者全局变量

4、C语言并不支持缺省参数

指针与引用

 1int main(){
 2    
 3    const int a = 10;
 4    const int& b = a;
 5
 6    int c = 20;
 7    const int& d = c; 
 8    
 9    const int x = 10;
10    //int& y = x; error 非const引用不能引用const变量
11    
12    int e = 10;
13    //double& f = e; error
14    const double& f = e; //之所以可以这样定义,后面解释
15
16    return 0;
17}

相同点

  1. 都是地址的概念
  2. 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名

区别

  1. 指针是一个实体,而引用仅是个别名;

  2. 引用只能在定义时被初始化一次,之后不可变;指针可变;可以理解为引用从定义那一刻就已经不能被改变

  3. const的引用只能去引用const的变量,使用const引用非const变量是错误的,但是有一中情况除外:

    1int e = 10;
    2const double& f = e;
    

这种情况是这样的,在计算机想把 int 转为 double 的时候会创建一个临时的double变量,而就在这个时引用便引用的是这块临时存储区域,所以没有报错,但是这样的一块临时区域本来是不应该被引用的

  1. 引用不能为空,指针可以为NULL
  2. sizeof (引用) 得到的是所指向的变量(对象)的大小,而 sizeof 指针 得到的是指针本身(所指向的变量或对象的地址)的大小
  3. 指针和引用的自增(++)运算意义不一样,指针++是加上数据类型的大小,而引用++ 就是原变量加1,因为引用就是给原来的变量起别名!

使用规则

  • 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
  • 不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
  • 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)
  • 如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引 用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)