· CSDN的uu们,大家好。这里是C++入门的第六讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题
目录
1. 知识引入
2. 知识点讲解
2.1 内联函数的使用
2.2 内联函数的特性
2.2 内联函数的注意点
3. 面试题
1. 知识引入
在学C语言的宏时,我们了解了宏函数的概念与用法。我们也知道宏函数有一些易错点,稍不注意就可能出错!你若过不相信的话,就别看下面的代码自己写一个 a + b 的宏函数。
#define ADD(a, b) ((a) + (b))
int main()
{
cout << ADD(10, 20) << endl;
return 0;
}
不知道UU们写对了吗?为什么说宏函数容易写错呢?我们知道红的特性就是替换,这种特性会使我们遇到一些难以避免的错误,我们来看看这些常见的错误:
不加外面的括号替换之后由于运算符的优先级问题,导致结果出错!
#define ADD(a, b) (a) + (b)
int main()
{
cout << ADD(10, 20) * 2 << endl; //使用宏函数
cout << (10) + (20) * 2 << endl; //宏函数替换之后
return 0;
}
下面的代码同样也存由于运算符的优先级产生的问题:加法的优先级更高,导致结果出错。
#define ADD(a, b) (a + b)
int main()
{
int a = 1, b = 2;
cout << ADD(a | b, a & b) << endl; //使用宏函数
cout << (a | b + a & b) << endl; //宏函数替换之后
return 0;
}
那你可能会说了:那我每次都想正确的写法那样,多加几个括号不就能避免这种错误啦!很遗憾因为宏的替换特性,有些错误难以避免,观察如下代码:
我们尝试利用一个宏函数求解 b 的平方,但是我们用 ++b 来使用该函数,替换之后 ++b 会被执行两次,显然最终的结果并不是我们想要的。
#define Square(a) ((a) * (a))
int main()
{
int b = 2;
cout << Square(++b) << endl; //使用宏函数
cout << ((++b) * (++b)) << endl; //宏函数替换之后
return 0;
}
但是呢宏还是有优点的,下面我们就来看看宏的优缺点:
优点:宏函数不需要建立函数栈帧,可以提高调用的效率。
缺点:不方便进行调试。(预处理阶段会进行宏的替换)
代码可读性差,可维护性差,容易误用。
没有类型安全的检查。(宏仅仅是替换嘛)。
C++ 是既想要宏的优点,又不想要宏的缺点。因此C++引入了内联函数。
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
2. 知识点讲解
2.1 内联函数的使用
用法很简单,我们只要在一个函数返回值的前面加上 inline 关键字即可。像这样:
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
cout << Add(2, 3) << endl;
return 0;
}
2.2 内联函数的特性
再来看看内联函数的概念吧:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
这里的在调用内联函数的地方展开,指的是:内联函数经过编译之后,会直接在调用处转化成汇编代码,而不是通过 call 指令,跳转到函数的实际地址处进行函数汇编代码的执行。
在VS2019的debug模式下,默认是不会对程序进行优化的,我们看不到内联函数的展开,但是在release模式下又不支持调试,因此我们需要进行相关的设置才能看到内联函数的展开。
在项目---->属性中更改下面的设置即可
我们通过调试下面的代码,然后查看汇编代码之后发现 Add 函数的调用并不是 call 函数地址而是直接在函数调用的地方展开,变成了汇编代码。
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
2.2 内联函数的注意点
通过上面的调试过程我们直到内联函数是直接在调用处展开的,因此内联函数并没有函数地址。也就是说,内联函数不会出现在符号表中,也就意味着内联函数不可使用函数定义与函数声明分开的写法。
为什么会报错呢?UU们不妨结合编译,链接的相关知识想一想。
Add函数是内联函数,在调用Add函数的地方,编译器会在调用的地方展开。但是在调用Add函数的源文件中,只有Add函数的声明。 编译器无法做到在调用的地方展开,于是编译器会尝试通过 call 指令去调用函数,但是内联函数不会在符号表中出现,就会找不到函数的地址。因此会出现链接错误。
因此我们得出一个重要的结论:内联函数的声明与定义不可分离。
那应该怎么解决这个问题呢?很简单,直接在头文件里面写内联函数的定义即可。
3. 面试题
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数