内联函数
为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,c++引入了内联函数(inline function).
内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。
预处理宏的缺陷
//问题一:
#define ADD(x,y) x+y
inline int Add(int x,int y){
return x + y;
}
void test(){
int ret1 = ADD(10, 20) * 10; //希望的结果是300
int ret2 = Add(10, 20) * 10; //希望结果也是300
cout << "ret1:" << ret1 << endl; //210
cout << "ret2:" << ret2 << endl; //300
}
//问题二:
#define COMPARE(x,y) ((x) < (y) ? (x) : (y))
int Compare(int x,int y){
return x < y ? x : y;
}
void test02(){
int a = 1;
int b = 3;
//cout << "COMPARE(++a, b):" << COMPARE(++a, b) << endl; // 3
cout << "Compare(int x,int y):" << Compare(++a, b) << endl; //2
}
//问题三:
/*预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围。
一直到程序结束或者是#udefine
*/
内联函数定义
在c++中,预定义宏的概念是用内联函数来实现的,而****内联函数本身也是一个真正的函数****。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。
内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以****空间换时间****。
//注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。
inline void func(int a);
//以上写法没有任何效果,仅仅是声明函数,应该如下方式来做:
inline int func(int a)
{
return ++;
}
注意: 编译器将会检查函数参数列表使用是否正确,并返回值(进行必要的转换)。这些是预处理器无法完成的。
类内部的内联函数
为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。****任何在类内部定义的函数自动成为内联函数。****
class Person{
public:
Person(){ cout << "构造函数!" << endl; }
void PrintPerson(){ cout << "输出Person!" << endl; }
}
//构造函数Person,成员函数PrintPerson在类的内部定义,自动成为内联函数。
内联函数与编译器
内联函数并不是何时何地都有效,为了理解内联函数何时有效,应该要知道编译器碰到内联函数会怎么处理?
对于任何类型的函数,编译器会将函数类型(包括函数名字,参数类型,返回值类型)放入到符号表中。同样,当编译器看到内联函数,并且对内联函数体进行分析没有发现错误时,也会将内联函数放入符号表。
当调用一个内联函数的时候,编译器首先确保传入参数类型是正确匹配的,或者如果类型不正完全匹配,但是可以将其转换为正确类型,并且返回值在目标表达式里匹配正确类型,或者可以转换为目标类型,内联函数就会直接替换函数调用,这就消除了函数调用的开销。假如内联函数是成员函数,对象this指针也会被放入合适位置。
类型检查和类型转换、包括在合适位置放入对象this指针这些都是预处理器不能完成的。
以下情况编译器可能考虑不会将函数进行内联编译:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
*内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。*
函数的默认参数
c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
void TestFunc01(int a = 10, int b = 20){
cout << "a + b = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}
int main(){
//1.如果没有传参数,那么使用默认参数
TestFunc01();
//2. 如果传一个参数,那么第二个参数使用默认参数
TestFunc01(100);
//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01(100, 200);
return EXIT_SUCCESS;
}
占位参数
c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。
void TestFunc01(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
int main(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc01(10,20);
//正确调用
TestFunc01(10,20,30);
//正确调用
TestFunc02(10,20);
//正确调用
TestFunc02(10, 20, 30);
return EXIT_SUCCESS;
}
函数重载(同名函数)
*实现函数重载的条件:*
- 同一个作用域
- 参数个数不同
- 参数类型不同
- 参数顺序不同
//1. 函数重载条件
namespace A{
void MyFunc(){ cout << "无参数!" << endl; }
void MyFunc(int a){ cout << "a: " << a << endl; }
void MyFunc(string b){ cout << "b: " << b << endl; }
void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}
//2.返回值不作为函数重载依据
namespace B{
void MyFunc(string b, int a){}
//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数
}
函数重载和默认参数一起使用,需要额外注意二义性问题的产生。
void MyFunc(string b){
cout << "b: " << b << endl;
}
//函数重载碰上默认参数
void MyFunc(string b, int a = 10){
cout << "a: " << a << " b:" << b << endl;
}
int main(){
MyFunc("hello"); //这时,两个函数都能匹配调用,产生二义性
return 0;
}
函数返回值不作为重载条件:
当编译器能从上下文中确定唯一的函数的时,如int ret = func(),这个当然是没有问题的。然而,我们在编写程序过程中可以忽略他的返回值。那么这个时候,一个函数为
void func(int x);另一个为int func(int x); 当我们直接调用func(10),这个时候编译器就不确定调用那个函数。所以在c++中禁止使用返回值作为重载的条件。
函数重载原理
编译器用不同的参数类型来修饰不同的函数名,比如void func(); 编译器可能会将函数名修饰成_func,当编译器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char我这里使用”可能”这个字眼是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。
void func****(){}*void func*(*int x*){}*void func*(*int x*,*char y*){}****
以上三个函数在linux下生成的编译之后的函数名为:
_Z4funcv //v 代表void,无参数_Z4funci //i 代表参数为int类型_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型