目录
- 宏的优缺点分析
- 概念回顾
- 宏的缺点
- 宏的优点
- 内联函数(inline)
- inline函数的定义和声明
- 总结
宏的优缺点分析
概念回顾
下面是宏的申明方式:
#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
例如:
#define SQUARE( x ) x * x
- 这个宏接收⼀个参数 x .如果在上述声明之后,你把 SQUARE( 5 ); 置于程序中,预处理器就会⽤
下⾯这个表达式替换上⾯的表达式: 5 * 5
注:① 参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
相信很多人都忘了C语言阶段的宏。这时候定义一个宏形式就千奇百怪
//1.
#define Add(int x, int y) return x + y;
//2.
#define ADD(x, y) x + y
//3.
#define ADD(x, y) (x + y)
//4.
#define ADD(x, y) ((x) + (y));
//5.
#define ADD(x, y) ((x) + (y))
- 第一种肯定是不行的,这分明是函数的定义方式
后面四个的主要区别就在于后面的stuff,我们一一来分析一下
那第二个对了吗?
看看这个:
int ret = ADD(1, 2) * 3;
- 可以观察到此时在预处理阶段也是直接进行了一个替换,不过仔细观察就可以发现,由于*的优先级来得高,所以2会和后面的3先进行一个运算,这也就造成了最后结果的不同
- 所以我们要在外层加上一个大括号防止出现优先级问题
#define ADD(x, y) (x + y)
- 那这样就行了吗,如果我们向ADD函数这样传参呢?
int a = 10;
int b = 20;
int ret = ADD(a | b, a & b);
- 编译器还是一样会去做傻傻的替换,但是这个时候我们又得注意优先级的问题了,对于+号来说,它的优先级高于&按位与和|按位或的,
- 所以中间的【b】和【a】会先进行结合,然后再去算&和|
- 如果要防止这种表达式传参出现优先级的问题,那么我们就应该为参数加上括号
#define ADD(x, y) ((x) + (y));
- 最后这一种才是最正确的写法
#define ADD(x, y) ((x) + (y)) //✔
宏的缺点
- 看了以上的代码,就知道宏有一个明显的缺点,定义太麻烦了,哪里都要加括号
- 而且宏是不能像函数一样调试的,这样我们在写错宏的时候就很难排查哪里出了问题
- 宏也没有类型的检查,无论我们传入那种类型的参数,都不会出问题,那么就会造成算术混乱
宏的优点
- 宏提高了可复用性和可维护性,比如我们一般用到宏最多的地方就是把一个数字定义成一个宏,如一个数组我们刚开始想要是10个空间,但是后面不够,就可以直接更改宏来替换,不用一句句去改变
#define n 500
- 宏不用创建函数栈帧,这就不会造成内存的消耗
内联函数(inline)
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
inline int Add(int x, int y)
{
int z = x + y;
return z;
}
- 但是他真的不会建立函数栈帧吗?
这里有call指令(call就是调用函数),调用了函数就会创建函数栈帧。难道是骗我们的?
-
其实我们还需要去做一些配置
-
然后我们再去观察一下汇编就可以发现不存在call指令了,编译器直接将内敛函数Add做了展开
-
所以C++中我们是不推荐用宏的,因为有内联函数这个特性,即保留了宏的优点,无需调用函数建立栈帧,而且还修复了宏的缺陷,不再需要将内容写得那么复杂,写成日常的函数形式即可,只需要在前面加上一个inline关键字,就可以起到这种效果。非但如此,它还可以调试
-
vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试
注意:vs的release版本是默认展开的
- C++标准没有规定默认展开inline函数,是否展开这个函数取决于编译器本身,编程器通常只展开短小函数的内联函数(10行代码以内),不会对递归函数或者迭代展开
- 这其实就是内联函数在替代宏之后很优秀的一个特性,假设说现在你这个设置的内联函数有1000多行代码,在一个大项目中又有1000个地方调用了这个内联函数。
- 如果采用内联将其展开的话消耗的就是1000 * 1000条指令
inline函数的定义和声明
- inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址
址,链接时会出现报错。
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
//--------------------------
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
//--------------------------
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
- 解决这个问题就直接把定义和声明写到同一个头文件中
这样内联函数在展开的时候就可以找到地址
#nullptr
- C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种
定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的
f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL);
调⽤会报错。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
所以我们推荐在C++中使用nullptr代表空指针
总结
- 引入内联函数的目的实际上就是为了补充C语言中宏的缺陷,如优先级问题,不能调试问题
- 但是内联函数用的时候也要注意C++标准没有规定默认使用内联函数,取决于编译器。这种空间换时间的思想只适用于小型的函数,对于大型的函数不建议定义成【内联函数】,会造成程序的过多臃肿