C++_2_ inline内联函数 宏函数(2/3)

C++推出了inline关键字,其目的是为了替代C语言中的宏函数。

我们先来回顾宏函数:

宏函数

现有个需求:要求你写一个Add(x,y)的宏函数。

正确的写法有一种,错误的写法倒是五花八门,我们先来"见不贤而自省也。"

cpp 复制代码
// 实现⼀个ADD宏函数的常⻅问题
#define ADD(int a, int b) return a + b;
#define ADD(a, b) a + b;
#define ADD(a, b) (a + b)

分析:

第一种写法:宏函数不是函数,它是宏,在预处理阶段就替换成了目标内容。

第二种写法:还是错的,少括号,较第一种好一点------ 在于没加分号

第三种写法:还是不对,不过好多了,错在少括号。

正确写法:

cpp 复制代码
// 正确的宏实现
#define ADD(a, b) ((a) + (b))

这个写对了,不过"知其然,亦要知其所以然"。
// 为什么不能加分号 ?
// 为什么要加外⾯的括号 ?
// 为什么要加⾥⾯的括号 ?

cpp 复制代码
//为什么不能添加分号 ;
#define Add(a,b) ((a) + (b));
int main()
{
    int ret1 = Add(1,2);//意在实现1 + 2并将结果赋值给ret1
    cout << ret1 << endl;//意在打印ret1
	return 0;
}

运行截图:

好戏还在后头:

cpp 复制代码
cout << Add(1,3) << endl;

我们在前面曾说到:cout 能够自动识别变量类型,这里报错是因为函数的结果是它识别不了了嘛。 非也非也:

宏在预处理阶段,会将Add(1,3)替换成后面的表达式:这里若多加了分号,这行代码就在分号处中断了,而作为二元操作符的 << 前面无操作对象 后面又有个endl;自然因为缺少参数(表达式)报错。

cpp 复制代码
cout << ((1) + (3)); << endl;//预处理阶段被替换成了如此模样

所以,宏函数里分号是多余的:

cpp 复制代码
#define Add(a,b)  ((a) + (b))
int main()
{
    cout << Add(1,3) << endl;//这才能输出4
	return 0;
}

运行截图:

cpp 复制代码
// 为什么要加外⾯的括号?
#define Add(a,b)  (a) + (b)
int main()
{
    cout << Add(1, 3) * 2 << endl;//意图输出 8 (4*2)
	return 0;
}
cpp 复制代码
//替换后
//cout << Add(1, 3) * 2 << endl;
cout << (1) + (3) * 2 << endl;//然而据分析,输出7 (1+6)

运行截图:

可见,外面括号是为了不改变运算表达式的结合的优先级,我们写个宏函数Add本意就是为了先算加法。

cpp 复制代码
// 为什么要加⾥⾯的括号?
#define Add(a,b)  (a + b)
int main()
{
  int x = 1, y = 2;
  Add(x & y, x | y);//不加内括号,替换成 (x&y + x|y)
  return 0;
}
//但是忽略了加法作为算数运算符的优先级,其实结果会先算 y+x,故而与目标结果背道而驰

得出结论:宏函数不能加分号,为了避免保证运算中合理的优先级,应该在外面和里面加上括号。


C++表示:这宏函数是在预处理直接展开成了表达式,不像普通函数还要建立栈帧。但是也忒麻烦了点,稍有不慎,结果就不正确了。

于是,C++引入了关键字inline 就是替代C语言中的宏函数。

inline

inline既然要替代,首先它要延续宏函数的优点:调用函数时不用创建栈帧,直接展开,还要优于宏函数:方便书写------ inline 直接在函数定义的 返回类型前加上即可。

cpp 复制代码
inline int Add(int x, int y)//现在写个函数+inline就方便很多了
{
	return x + y;
}
int main()
{
	int b = Add(1,2);
	return 0;
}

inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展
开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁
调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
对于过长的函数作为内联函数,若也被频繁调用,此时已经不适合展开:比如下面这个场景:一个函数初次编译100条指令,编译完后成为一条指令。(在编译后的代码中,直接调用一个函数(尤其是在同一编译单元或模块内)通常涉及很小的开销,可能只是一条指令(比如一个跳转)。 )在函数在被复用时,就会执行被调用次的指令条数。

而如果编译器不阻拦这种展开,直接执行10000 * 100的指令,效率慢得可想而知。

所以,inline适合短小、频繁调用的函数。

inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

比如:

cpp 复制代码
//F.h 声明
#pragma once
inline void f(int a);
//F.cpp 函数定义
#include "F.h"
#include <iostream>
using namespace std;
void f(int a)
{
    cout << a << endl;
}
//执行文件
#include "F.h"
int main()
{
   f(10);
   return 0;
}

如上述代码所示:我们把声明和定义放在两个文件

报错:

内联函数编译器默认认为是不需要地址的,因为已经在调用地方展开了。在测试代码中,头文件展开,但是只有函数F的声明,找不到它的实现,导致调用地方展不开函数。

为什么找不到它的实现(展不开函数),因为在F.cpp中,虽然也包含了F.h,但是前面加了inline,默认是内联函数。这时候就不会把函数的实现地址放进符号表,导致测试代码中,无法链接到所调用函数的定义。

等到后面有更多的知识补充,我们会更好理解链接错误这一概念

总结: inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。

相关推荐
工业甲酰苯胺5 分钟前
聊一聊 C#线程池 的线程动态注入
java·开发语言·c#
zfenggo7 分钟前
c/c++ 无法跳转定义
c语言·开发语言·c++
图灵猿10 分钟前
【Lua之·Lua与C/C++交互·Lua CAPI访问栈操作】
c语言·c++·lua
向宇it17 分钟前
【从零开始入门unity游戏开发之——C#篇30】C#常用泛型数据结构类——list<T>列表、`List<T>` 和数组 (`T[]`) 的选择
java·开发语言·数据结构·unity·c#·游戏引擎·list
hakesashou22 分钟前
python怎么看矩阵维数
开发语言·python
daopuyun30 分钟前
GB/T34944-2017 《Java语言源代码漏洞测试规范》解读——安全功能
java·开发语言·安全
A懿轩A38 分钟前
C/C++ 数据结构与算法【树和二叉树】 树和二叉树,二叉树先中后序遍历详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·二叉树·
qh0526wy42 分钟前
pyqt5冻结+分页表
开发语言·python·qt
hjxxlsx1 小时前
探索 C++ 自定义函数的深度与广度
开发语言·c++
罗政1 小时前
PDF书籍《手写调用链监控APM系统-Java版》第12章 结束
java·开发语言·pdf