C++ --- 内联函数,函数的默认参数,函数的占位参数,函数重载
一. 内联函数(inline function)
1. 内联函数的引出
c++从c中继承的一个重要特征就是效率。假如c++的效率明显低于c的效率,那么就会有很大的一批程序员不去使用c++了。
在c中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。
但是在c++出现之后,使用预处理宏会出现两个问题:
- 第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。
- 第二个问题是c++特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类类的成员函数。
为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,c++引入了内联函数(inline function).
内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。
2. 预处理宏的缺陷
#include <iostream>
using namespace std;
#define fun(a,b) a+b
#define min(a,b) a>b?b:a
int main()
{
int a = 10;
int b = 20;
//问题1:
int c = fun(a, b) * 2;
cout << "对于fun(a,b)我们期望的是10+20等于30然后乘以2等于60" << endl;
cout << "结果为: " << c << endl;
//问题2:
cout << "对于min(++a,b)期望的值: 11" << endl;
cout << "结果为: " << (min(++a, b)) << endl;
system("pause");
return 0;
}
运行结果如下:
- 问题3:
预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围。
3. 内联函数
- 内联函数的基本概念
在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++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。
二. 数的默认参数
c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
#include <iostream>
using namespace std;
void TestFunc01(int a = 10, int b = 20) {
cout << "a + b = " << a + b << endl;
}
int main() {
//1.如果没有传参数,那么使用默认参数
TestFunc01();
//2. 如果传一个参数,那么第二个参数使用默认参数
TestFunc01(100);
//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01(100, 200);
return EXIT_SUCCESS;
}
运行结果为:
- 注意:
1. 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
2.如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。
三. 函数的占位参数
c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。
#include <iostream>
using namespace std;
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 0;
}
用处:后面用后置++时会用到。
四. 函数重载
1. 函数重载概述
- 能使名字方便使用,是任何程序设计语言的一个重要特征!
我们现实生活中经常会碰到一些字在不同的场景下具有不同的意思,比如汉语中的多音字“重”。
当我们说: “他好重啊,我都背不动!”我们根据上下文意思,知道“重”在此时此地表示重量的意思。
如果我们说“你怎么写了那么多重复的代码? 维护性太差了!”这个地方我们知道,“重”表示重复的意思。
同样一个字在不同的场景下具有不同的含义。那么在c++中也有一种类似的现象出现,同一个函数名在不同场景下可以具有不同的含义。
在传统c语言中,函数名必须是唯一的,程序中不允许出现同名的函数。在c++中是允许出现同名的函数,这种现象称为函数重载。
函数重载的目的就是为了方便的使用函数名。
2. 函数重载
- 实现函数重载的条件:
- 同一个作用域
- 参数个数不同
- 参数类型不同
- 参数顺序不同
例:
#include <iostream>
using namespace std;
void MyFunc()
{
cout << "MyFunc()" << endl;
}
void MyFunc(int a)
{
cout << "MyFunc(int a)" << endl;
}
void MyFunc(string a)
{
cout << "MyFunc(string a)" << endl;
}
void MyFunc(int a, string b)
{
cout << "MyFunc(int a,string b)" << endl;
}
void MyFunc(string a, int b)
{
cout << "MyFunc(string a,int b)" << endl;
}
//返回值不能作为函数重载依据
//int MyFunc(int a){ cout << "MyFunc(int a)" << endl;}
int main()
{
MyFunc();
MyFunc(1);
MyFunc("私忆一秒钟");
MyFunc(1, "私忆一秒钟");
MyFunc("私忆一秒钟", 1);
return 0;
}
运行结果如下:
注意: 函数重载和默认参数一起使用,需要额外注意二义性问题的产生。
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类型
3. extern "C"浅析
以下在Linux下测试:
c函数: void MyFunc(){} ,被编译成函数: MyFunc
c++函数: void MyFunc(){},被编译成函数: _Z6Myfuncv
通过这个测试,由于c++中需要支持函数重载,所以c和c++中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题,如果在c++中调用一个使用c语言编写模块中的某个函数,那么c++是根据c++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,c++中调用MyFunc函数,在链接阶段会去找Z6Myfuncv,结果是没有找到的,因为这个MyFunc函数是c语言编写的,生成的符号是MyFunc。
那么如果我想在c++调用c的函数怎么办?
extern "C"的主要作用就是为了实现c++代码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而不是按c++的方式。
- 如果只有一个可以直接用extern “C” 函数声明
MyModule.h:
#pragma once
#include <stdio.h>
void show()
MyModule.c:
#include "MyModule.h"
void show()
{
printf("hello\n");
}
cpp:
#include <iostream>
//#include "MyMoudule.h"
using namespace std;
//C++中想调用C语言的方法
extern "C" void show();
int main()
{
show();
return 0;
}
运行结果如下:
- 如果是多个函数,一个一个的写太过麻烦,所以用另一种方法
MyModule.h:
#pragma once
#include <stdio.h>
//注意是两个下划线
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void show();
void func();
#ifdef __cplusplus
}
#endif // __cplusplus
MyModule.c:
#include "MyModule.h"
void show()
{
printf("hello\n");
}
void func()
{
printf("world\n");
}
cpp:
#include <iostream>
#include "MyModule.h"
using namespace std;
//C++中想调用C语言的方法
int main()
{
show();
func();
system("pause");
return 0;
}
运行结果如下: