天天看点

【c++】——形参带默认值的函数、内联函数、函数重载

文章目录

    • 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的宏函数的解决方案。但是具体他们的区别如下:

  1. 处理时机不同

    :内联函数是在编译阶段将代码嵌入到调用点,而define是在预编译阶段进行简单的文本替换。
  2. 安全性

    :内联函数具有类型安全、语法检查而宏函数没有此功能
  3. 访问权限

    :内联函数可以是成员函数,访问类的成员变量,但是宏函数无法访问类的私有或者保护成员
  4. 调试

    :宏是没有办法进行调试的,内联函数在debug版本下和普通函数一样,出了问题很方便进行断点调试,定位问题。

3、内联函数与static修饰的函数区别

  1. 开销方面

    :因为内联函数是直接代码展开,所以没有开栈和清栈的开销。static修饰的函数和普通函数一样,有开栈和清栈的开销
  2. 相同点——本文件下可见

    :内联函数因为不生成符号所以只在本文件中可见,static函数是生成local符号也是在当前文件下可见。

4、内联函数和普通函数的区别

普通函数的调用在汇编上有标准的push压实参指令,然后call指令调用函数,给函数开辟栈帧,函数运行完成,有函数退出栈帧的过程。

内联函数在编译阶段,在函数的调用点将函数的代码展开,省略了函数栈帧开辟回退的调用开销,效率比较高。

5、内联函数的限制

  • 内联函数的使用建议

    :当函数执行的开销小于函数清栈开栈的开销时,反映出函数体非常小,就建议使用内联。因为如果以代码膨胀为代价就会使得目标文件非常庞大。
  • 内联函数只在release版本生效

    :即在debug版本下,内联函数和普通函数一样,也有栈帧的开辟和回退
  • inline只是建议编译器把这个函数处理成内联函数

    真正是否是内联函数还是编译器决定的,所以,递归、循环、分支switch是一定不会处理成内联的
  • 一般将内联函数写在.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++】——形参带默认值的函数、内联函数、函数重载

细细观察上面几个函数,我们可以发现,他们的函数名相同,只是参数列表类型不同而已,但是同样的代码放在.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]
           

具体的规则如下:

  • _cdecl调用约定

    :"?"+函数名+参数表的开始标识"@@YA"+函数返回类型代号+参数类型代号+结束标识"@Z"或"Z”(无参数)
  • _stdcall调用约定

    :?”+函数名+参数表的开始标识“@@YG”+函数返回类型代号+参数类型代号 +结束标识“@Z”或“Z”(无参数)。
  • _fastcal调用约定

    :“?”+函数名+参数表的开始标识 “@@YI”+ 函数返回类型代号+参数类型代号 +结束标识“@Z”或“Z”(无参数)。

下表示参数类型代号表

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的函数了,因为他们在本地的局部作用域已经看到了这个声明。出现了类型不匹配的问题。

c++

继续阅读