C++修炼之练气期第八层——内联函数

简介: C++修炼之练气期第八层——内联函数

0000000000000.png

目录


一、宏的缺点

引例

改正一

改正二

改正三

宏的缺陷

二、内联函数的概念

三、内联与非内联的区别

四、内联函数的特性


文章导读


大家是否还记得C语言中的宏函数?内联函数与C语言中宏函数作用类似,但是由于宏的缺陷较多,使用体验较差且安全性不高,所以C++中不建议使用宏,而是使用内联函数替代宏。本章我们就一起学习内联函数吧~


正文


一、宏的缺点


引例


在学习宏时,我们曾经实现过ADD的宏函数,作用是求两个数的和。例如:

#define ADD(x , y)  x+y;

如果你的宏学的还不错的话,会发现上面的代码就是个典型的错误示例,说是错误锦集也不为过。我们试着将它修改正确。


改正一


首先,末尾的分号是必须要去掉的,否则编译都不会通过;

//改正一
#define ADD(x , y)  x+y

好了,接下来进行测试;

//测试用例1
int a = 3, b = 5;
printf("%d\n", ADD(a, b));
//测试用例2
printf("%d\n", ADD(a | b, a & b);

87.png

执行结果为:测试用例1通过、测试用例2错误;

原因是 #define 意为替换,测试用例实际上执行的是:

printf("%d\n", 3 | 5 + 3 & 5);

又因为运算符 ' + ' 的优先级高于 ' & ' 和 ' | ' ,所以结果错误。

那么继续改正。


改正二


为了解决优先级问题,需要给每个值都添加括号;

#define ADD(x , y)  (x)+(y)


继续测试;

//测试用例3
int a = 3, b = 5;
printf("%d\n", ADD(a, b)*2);

继续测试;

//测试用例3
int a = 3, b = 5;
printf("%d\n", ADD(a, b)*2);

55.png

OK,测试未通过,原因很简单:乘优先于加;编译后的代码其实是这样的:

printf("%d\n", (3)+(5)*2);

继续改正


改正三


依然是优先级的问题,这次需要为整体添加括号;

#define ADD(x , y)  ((x)+(y))

终于,我们的ADD宏函数最终被修改正确。


宏的缺陷


显而易见,一个功能如此简单的ADD宏,都有这么多错误的版本,要是面对复杂的工程项目那么宏的安全性就让它的使用变得谨慎万分。


此外,宏的缺点还有:


1. 宏不能调试;


由于宏在预处理阶段就会被替换,所以不能调试。


2. 宏没有类型检查;


宏的参数不需要定义类型,导致宏容易出现类型相关的错误。


3. 有些场景下非常复杂,容易出错,不容易掌握。


二、内联函数的概念


inline 修饰的函数叫做 内联函数,类似于宏,编译阶段内联函数在调用的地方进行展开,不会建立函数栈帧。没有了建立函数栈帧的开销,意味着程序的效率会因此提高。

//定义一个内联函数
inline int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 3, b = 5;
  int ret = Add(a, b);
  cout << ret << endl;
  return 0;
}


三、内联与非内联的区别


非内联函数在调用时,会建立函数栈帧,内联函数则不会;下面我们就在调用两种不同的函数时,查看各自的汇编代码。

//非内联函数
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 3, b = 5;
  int ret = Add(a, b);
  cout << ret << endl;
  return 0;
}

456.png

如上图所示,该指令就是调用函数的指令,调用函数必会建立函数栈帧。再来看看内联函数;

//内联函数
inline int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 3, b = 5;
  int ret = Add(a, b);
  cout << ret << endl;
  return 0;
}

433.png

如图,此处并没有调用函数的过程,而是直接展开。


四、内联函数的特性


内联函数并不总是最好的选择,它也是有利有弊。


1. inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。


2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用 inline 修饰,否则编译器会忽略 inline 特性。


例如,我们将上述内联函数Add稍作修改,使它看起来规模较大较为繁琐,此时内联函数特性被忽略。

inline int Add(int x, int y)
{
  int z = x + y;
  z = x + y;
  z += x + y;
  z = x + y;
  z = x + y;
  z = x * y;
  z = x + y;
  z += x + y;
  z -= x + y;
  z += x + y;
  z += x * y;
  z -= x / y;
  z += x + y;
  z += x + y;
  return z;
}

567.png

3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。

目录
相关文章
|
20小时前
|
编译器 C语言 C++
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr(下)
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr
6 0
|
6天前
|
存储 C++ 容器
【C++从练气到飞升】09---string语法指南(二)
【C++从练气到飞升】09---string语法指南(二)
|
6天前
|
存储 Linux C语言
【C++从练气到飞升】09---string语法指南(一)
【C++从练气到飞升】09---string语法指南(一)
|
6天前
|
编译器 C++
【C++从练气到飞升】08---模板
【C++从练气到飞升】08---模板
|
6天前
|
存储 Linux C语言
【C++从练气到飞升】07---内存管理
【C++从练气到飞升】07---内存管理
|
6天前
|
Java C++ Python
【C++从练气到飞升】06---重识类和对象(二)
【C++从练气到飞升】06---重识类和对象(二)
|
6天前
|
编译器 C++
【C++从练气到飞升】06---重识类和对象(一)
【C++从练气到飞升】06---重识类和对象(一)
|
6天前
|
编译器 C语言 C++
【C++从练气到飞升】05---运算符重载(二)
【C++从练气到飞升】05---运算符重载(二)
|
6天前
|
编译器 C++
【C++从练气到飞升】05---运算符重载(一)
【C++从练气到飞升】05---运算符重载(一)
|
6天前
|
存储 编译器 C++
【C++从练气到飞升】04---拷贝构造函数
【C++从练气到飞升】04---拷贝构造函数