文章目录
-
- 1、形参带默认值的函数
- 2、inline内联函数
- 3、函数重载
1、形参带默认值的函数
说到形参带默认值的函数我们还是引用我们最常写的函数,并给出语句指令执行方式如下:
#include<iostream>
#include<string.h>
using namespace std;
int sum(int a, int b)
{
int temp = 0;
temp = a + b;
return temp;
}
int main()
{
int a = 10;
int b = 20;
int ret = sum(a, b);
cout << "ret:" << ret << endl;
return 0;
}
那么形参如何给实参传参数呢?以下是给出的几种传递方式,以及传参时应该注意的规则。
规则一:给默认值的时候,从右向左给
如下的方式是正确的
int sum(int a, int b=20)
{
int temp = 0;
temp = a + b;
return temp;
}
但是以下的方式就是错误的
int sum1(int a=10, int b )
{
int temp = 0;
temp = a + b;
return temp;
}
函数的调用过程中形参的入栈顺序是自右向左的,针对函数的实参和形参的匹配顺序是自左向右的。
规则二:相比于不传参数,传入参数的效率更高,因为少了move指令。所以默认值一般都设置在声明点
我们来看看,具体的语句指令
int ret = sum(a, b);的指令如下:
mov eax,dword ptr[ebp-8]
push eax
mov ecx,dword ptr[ebp-4]
push ecx
call sum
ret = sum(a);的指令调用如下:
push 14H
mov ecx,dword ptr[ebp-4]
push ecx
call sum
ret = sum2();的指令调用如下:它的效率等同于sum2(20,50)
push 14H
push 0AH
call sum
规则三:定义时可以给形参默认值,声明也可以给形参默认值。
申明的时候给默认值,函数体放在主函数后面
int sum3(int a = 10, int b = 20);
int main()
{
......
}
int sum3(int a, int b = 20)
{
int temp = 0;
temp = a + b;
return temp;
}
规则四:形参给默认值的时,不管是定义处给还是声明处给,形参默认值只能出现一次
如下的传递方式则是错误的,形参的默认值出现了两次
int sum3(int a = 10, int b = 20);
int sum3(int a , int b = 20);
但是如下的方式则是正确的,虽然第一个的形参赋值不是从右向左的但是第二个等式弥补了错误
int sum3(int a = 10, int b );
int sum3(int a, int b = 20);
2、inline内联函数
还是使用文章最开始的时候我们给出的代码进行分析。
1、什么是inline内联函数?
例如下面一段语句
int ret = sum(a+b);
此处有有函数调用的开销。包括参数压栈,函数栈帧的开辟和回退过程。x+y 有两个指令 mov add mov 如果有10000次的x+y操作,那么将会有大量的时间用在了函数调用的开销上。
此时我们就可以使用内联函数。内联函数就是在编译过程中,就没有函数的调用开销,在函数的调用点直接把函数的代码进行展开处理(在符号表中也不用生成函数符号了)
因此
内联函数的处理过程
就相当于在执行到int ret = sum(a, b);时,直接把sum函数展开,把a,b直接替换成了x,y变成了int ret = a+b;
2、内联函数和宏的区别
从上面的概念里面我们可以了解到内联函数可以看做是C++专门用来替代c的宏函数的解决方案。但是具体他们的区别如下:
-
:内联函数是在编译阶段将代码嵌入到调用点,而define是在预编译阶段进行简单的文本替换。处理时机不同
-
:内联函数具有类型安全、语法检查而宏函数没有此功能安全性
-
:内联函数可以是成员函数,访问类的成员变量,但是宏函数无法访问类的私有或者保护成员访问权限
-
:宏是没有办法进行调试的,内联函数在debug版本下和普通函数一样,出了问题很方便进行断点调试,定位问题。调试
3、内联函数与static修饰的函数区别
-
:因为内联函数是直接代码展开,所以没有开栈和清栈的开销。static修饰的函数和普通函数一样,有开栈和清栈的开销开销方面
-
:内联函数因为不生成符号所以只在本文件中可见,static函数是生成local符号也是在当前文件下可见。相同点——本文件下可见
4、内联函数和普通函数的区别
普通函数的调用在汇编上有标准的push压实参指令,然后call指令调用函数,给函数开辟栈帧,函数运行完成,有函数退出栈帧的过程。
内联函数在编译阶段,在函数的调用点将函数的代码展开,省略了函数栈帧开辟回退的调用开销,效率比较高。
5、内联函数的限制
-
:当函数执行的开销小于函数清栈开栈的开销时,反映出函数体非常小,就建议使用内联。因为如果以代码膨胀为代价就会使得目标文件非常庞大。内联函数的使用建议
-
:即在debug版本下,内联函数和普通函数一样,也有栈帧的开辟和回退内联函数只在release版本生效
-
真正是否是内联函数还是编译器决定的,所以,递归、循环、分支switch是一定不会处理成内联的inline只是建议编译器把这个函数处理成内联函数
-
,只有把内联的整个实现写在头文件中,到时候头文件被其他源文件包含过后,预编译的第一步处理,直接把头文件中的所有东西进行展开过后,当前源文件下就会出现内联的定义,由调用点进行调用。一般将内联函数写在.h文件当中
3、函数重载
说到函数重载,我们首先给出下列代码
#include<iostream>
#include<typeinfo>
using namespace std;
bool compare(int a, int b)
{
cout << "compare_int_int" << endl;
return a > b;
}
bool compare(double a, double b)
{
cout << "compare_double_double" << endl;
return a > b;
}
bool compare(const char* a, const char* b)//compare_char*_char*
{
cout << "compare_char*_char*" << endl;
return strcmp(a ,b)>0;
}
int main()
{
compare(10, 20);
compare(10.0, 20.0);
compare("aaa","bbb");
return 0;
}
代码执行结果为:
细细观察上面几个函数,我们可以发现,他们的函数名相同,只是参数列表类型不同而已,但是同样的代码放在.c文件中就不可以了,这是为什么呢?
1、c++为什么支持函数重载,但是c语言不支持这其实与函数符号的生成有关系。
1、c语言里面的函数符号生成规则
-
c标准调用约定
C语言源代码文件中所有函数经过编译以后,相对应的符号名前加上下划线“_”,这其实就只和函数名相关。
就拿上面的代码例子来说,最终生成的函数符号都是
所以没办法区分开这三个函数,就没办法支持函数重载_compare
-
:函数名前加下划线,函数名后加“@”符号和其参数字节_stdcall调用约定
-
:函数名前加“@”符号,函数名后加“@”字符和其参数字节。_fastcall调用约定
2、c++里面的函数符号生成规则
主要和函数原型有关。函数原型主要包括了函数的返回值、函数名称和形参列表。
【举个栗子】
int Sum(int,int);//[email protected]@[email protected]
double Sum(double,double);//[email protected]@[email protected]
char Sum(char,char);//[email protected]@[email protected]
具体的规则如下:
-
:"?"+函数名+参数表的开始标识"@@YA"+函数返回类型代号+参数类型代号+结束标识"@Z"或"Z”(无参数)_cdecl调用约定
-
:?”+函数名+参数表的开始标识“@@YG”+函数返回类型代号+参数类型代号 +结束标识“@Z”或“Z”(无参数)。_stdcall调用约定
-
:“?”+函数名+参数表的开始标识 “@@YI”+ 函数返回类型代号+参数类型代号 +结束标识“@Z”或“Z”(无参数)。_fastcal调用约定
下表示参数类型代号表
X | D | E | F | H | I | J | K | M | N | _N |
---|---|---|---|---|---|---|---|---|---|---|
void | char | unsigned char | short | int | unsigned int | long | unsigned long | float | double | bool |
由此引出我们来探讨一下哪些元素是可以影响函数重载的。
2、影响函数重载的元素探讨
返回值——无影响
返回值其实并没有什么实质的影响,因为在调用点只看得见函数名和形参列表。
函数名——无影响
因为函数重载本来就是主要针对同名的函数,所以函数名不同也不能影响重载
形参列表——有影响
当形参类型不同、形参个数不同还有形参顺序不同的时候都会构成函数重载
3、函数重载三要素
-
:一组函数,函数名相同同名
-
:一组函数,参数列表的个数、顺序或者类型不同不同参
-
:一组函数要称得上重载,一定是先处在同一个作用域当中同一个作用域
为了解释相同作用域是什么意思,我们举一个反例,在上面代码的基础上,我们把main函数改成这样:
int main()
{
bool compare(int a, int b);
compare(10, 20);
compare(10.0, 20.0);
compare("aaa","bbb");
return 0;
}
编译的时候我们会发现,这个程序是错误的。
因为当有这个函数声明的时候,大家都去一股脑的调用参数类型为int的函数了,因为他们在本地的局部作用域已经看到了这个声明。出现了类型不匹配的问题。