一、形参带默认值的函数
形参带默认值的函数:
1.定义时给默认值的时候,从右向左给。
2.调用效率:若两个函数都传入变量,会提高效率,形参带默认值的函数会少一次mov指令;若传入数值,则效率不变。
3.定义时可以给形参默认值,函数声明时也可以给形参默认值。
4.形参给默认值时,不管是定义出给还是声明处给,形参默认值只能出现一次。
定义时给默认值:
什么是形参带默认值的函数呢?我们先来看这样一段代码:
#include <iostream>
using namespace std;
int sum(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int ret = sum(a, b);
cout<<"ret:"<<ret<<endl;
return 0;
}
很简单,输出为30;但是我们将代码稍微修改一下:
第一种,我们a没有传入实参,给默认值b=20,最后执行成功;
第二种,我们a,b都没有传入实参,但是都给定默认值,最后也能成功执行;
第三种,b没有传入实参,给定默认值a=10,执行失败;证明形参给默认值的时候,只能从右向左给。平常使用时,从左向右输入参数,但底层实现压栈时是从右向左压栈的。因此这样不符合语法。我们OOP中经常会给构造函数参数给一些默认值来适配各种各样对象产生。
三种成功的调用:汇编指令对比
调用形参带默认值的函数其效率与我们普通函数调用对比。若两个函数都传入变量,会提高效率,形参带默认值的函数会少一次mov指令;若传入数值,则效率不变。
声明时给默认值:
int sum(int a=10, int b=20);
int main()
{
int ret = sum();
cout<<"ret:"<<ret<<endl;
return 0;
}
int sum(int a, int b)
{
return a + b;
}
此时,我们执行成功。
我们知道,函数可以声明无数次,是可以的;但是如果在声明时给函数默认值并且声明多次,编译器会报错。
int sum(int a=10, int b=20);
int sum(int a=10, int b=20);
但是如果是这种情况:是可以的
int sum(int a, int b=20);
int sum(int a=10, int b);
二、内联函数
inline函数:在编译过程中,函数调用没有开销,在函数调用点直接把函数的代码进行展开处理了。内联函数不在产生相应的函数符号。 inline函数只是建议编译器把这个函数处理成内联函数,如果函数代码过多,或者递归,并不一定能够成为内联函数。
注意:平常我们使用的debug版本,inline是不起作用的。inline只有在release版本下才能出现。
我们来看如下代码:
int sum(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 10;
int ret = sum(a, b);
return 0;
}
sum函数调用开销:从右向左压实参,压弯实参后进行call指令先会把call指令下一行指令的地址入栈;完成后进入被调用函数中,sum函数左括号会push ebp将main函数栈底地址入栈,然后再给sum函数开辟栈帧,再给sum函数栈帧初始化(Linux下之开辟栈帧,不初始化)。
x+y实际用到的简单指令: mov add mov;但是函数调用过程中,栈帧开辟,实参压栈,栈帧回退的开销远远大于这三个指令,如果多次执行,函数调用开销太大。如果是复杂的函数,函数指令复杂,相栈帧开辟回退相比之下十分少,则无所谓。但如果像这种简单函数,栈帧调用开销远远大于函数本身指令执行时间,因此我们可以使用内联函数解决这个问题。
内联函数在调用点将代码直接展开,符号表中也不用生成内联函数的符号了。如图:
int ret = a + b;
但是并不是所有的inline都会被编译器处理成内联函数:例如递归。过于发杂的代码。
递归执行多少次,需要运行时候根据递归条件才能判断,但是inline是编译器处理的,编译器并不执行指令,因此并不确定递归能否被编译器处理成内联函数。
内联函数与普通函数的区别:普通函数调用需要标准开销,但是内联函数不需要开销;如果这个函数在短时间内大量调用并且这个函数十分简单,我们尽量将其设置为内联函数;内联函数如果内联成功,不会在符号表生成符号。内联函数并不一定能够内联,最终由编译器决定。
三、函数重载
函数重载: 一组函数,其中函数名相同,参数列表的个数或者类型不同,那么这一组函数就称作函数重载。函数重载属于静多态。
我们先来看几个例子:我们这里有三组函数名相同的函数,但是参数列表不同
1.函数名相同,会报错吗?
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)
{
cout << "compare_char*_char*" <<endl;
return strcmp(a,b);
}
int main()
{
compare(10,20);
compare(10.0,20.0);
compare("aaa","bbb");
return 0;
}
并没有报错,还打印出了结果。说明在C++中,可以定义函数名相同的函数。这里我们参数不同,匹配的结果也不同,这就是一个函数重载的调用。
1.为什么C++支持函数重载,C语言不支持函数重载?
其最主要原因是编译器产生代码的规则不同。C++下我们可以编译通过它,但是C下面却不行。主要原因是C++代码在产生函数符号时候,由函数名 + 参数列表类型组成的,参数列表不同,编译时产生不同的符号;C代码在产生函数符号时,符号只由函数名决定,链接时会产生链接错误,找到多个符号定义。
2.函数重载需要注意什么?
注意1:一组函数要称得上重载,一定先是处在同一个作用域当中。
C和C++函数中在一个函数中再定义一个函数是不允许的,但是如果在函数中再声明一个函数是没有问题的。如果我们在main函数中声明一个函数。
int main()
{
bool compare(int a, int b);
compare(10,20);
compare(10.0,20.0);
compare("aaa","bbb");
return 0;
}

此时发送错误,发现其不能进行自动匹配了相应的函数了,而都去调用int 类型的函数。我们知道如果我们定义了一个全局作用域,再函数中再定义一个名字相同的局部作用域,使用时优先在全局最近的作用域使用。道理是相同的,大家都去调用这个int类型的函数,所有会报错。因此,我们得出一个结论,函数重载满足函数名相同,参数列表不同时首先需要处在同一个作用域。
注意2:当给参数加上const或者volatile时,是如何影响形参类型的。
void func(int a){}
void func(const int a){}
这里有2行代码,首先它们是处于同一个作用域中的,函数名相同,参数列表是相同还是不相同呢?
编译一下:报错
我们使用typeid打印一下它们的类型:
说明对于编译器来说,它们是同一个函数,相当于函数重定义了。
注意3:一组函数,函数名相同,参数列表也相同,仅仅是返回值不同,不是函数重载。
生成的符号还是相同的,返回值与生成的符号没有联系。
3.C++和C语言之间如何互相调用?
C++调用C代码:把C函数声明括在extern "C"里;
我们分别在两个文件创建代码

出现错误,即该符号在main中被引用,但是找不到。主要原因是.c文件中是按照C规则生成的,按照C++方式匹配不到该符号,因此发生错误。我们来仔细瞧一瞧:
C++无法直接调用C文代码,那如何让它能调用呢?
我们使用extern,告诉C++编译器,int sum应该按照C语言方式生成,链接时就可以找到符号定义的地方,最后可以执行调用成功。
extern "C"
{
int sum(int a, int b);
}

C调用C++代码:同理,C编译器不认识extern “C”,因此还是在C++文件中使用extern “C”。
同理,在C++代码中使用extern “C” ,C中也可以成功执行了。
extern "C"
{
int sum(int a, int b)
{
return a + b;
}
}
如图:
最终成功执行:
补充:我们来瞧一瞧这段代码什么意思
#ifdef _cplusplus
extern "C"
{
#endif
int sum(int a, int b)
{
return a + b;
}
#ifdef _cplusplus
}
#endif
只要是C++编译器,都内置了_cplusplus这个宏名,上面代码:若用C+=编译器编译这段代码,sum函数的生成是按照C规则生成的;若用C规则编译这段代码,直接编译中间的函数,生成的也是C符号。其作用是:中间的代码不管是在C++下还是C下写的,都可以直接在其他C项目下调用。
来源:CSDN
作者:硕~
链接:https://blog.csdn.net/qq_42441693/article/details/104685361