前言:
编译一个程序涉及很多的步骤
第一个就是预处理阶段
预处理器就是在源码编译之前进行一些文本性质的操作
主要任务比如: 删除注释,插入被include 包含的头文件的内容,替换由define定义的符号,以及确认根据
条件编译进行编译
预定义的符号
这些都是被占用的预定义的
符号 | 样例值 | 含义 |
---|---|---|
__ FILE __ | XXX.CPP | 进行编译的源文件的名字 |
__ LINE __ | 30 | 文件当前的行号 |
__ DATE __ | Jan 32 1997 | 文件被编译的日期 |
__ TIME __ | 12:00:00 | 文件被编译的时间 |
还有一些预定义宏
宏
#define 机制包括一个允许把参数替换到文本中 ,这种规定成为 宏
#deine name(parameter-list) stuff
注意: 这个parameter-list 参数列表,是由逗号分隔的列表 它的左括号必须与name 紧挨,否则被认为是
stuff 的一部分
我们定义一个宏 实现开方
#define SQUARE(x) x*x
SQUARE(5)
这时预处理器就会把这个替换为 5*5
看下面的代码
坑(一)
int a = 5;
SQUARE(a+1)
等于 6*6 = 36 ??
错误
仔细看 被转换为
(5+1 * 5+1)
结果为:11
是不是有坑
我们改为
#define SQUARE(x) (x)*(x)
这样就ok了
坑(二)
定义一个宏
#define DOUBLE(x) (x)+(x)
int a = 10 * DOUBLE(5);
会出现什么结果? 20吗?
错误
展开宏
10 * (5) + (5)
结果为: 55
所以我们还要在表达式两边加个括号
#define DOUBLE(X) ((X) + (X))
这样就ok 了
宏与函数的对比
宏非常频繁的用于执行简单的计算比如 比大小
#define MAX(a,b) ((a) > (b) ? (a) : (b))
为什么不用函数呢?
第一 函数的代码写起来要比宏的代码量要大
第二 用宏在执行小型的代码量时速度要比函数快
第三 如果不用模板写法 函数参数必须要指定参数类型,而 宏 double int float 都可以传入
这些优点都要建立在 宏定义非常短的前提下
带副作用的宏参数
当宏参数在宏定义中出现超过一次,这个参数就具有副作用,可能就会出现危险,导致不可预料的结果
来看下面的代码 (大坑)
#define MAX(a,b) ((a) > (b) ? (a) : (b))
int x = 5;
int y = 8;
int z = MAX(x++,y++);
z 是多少?
13 ? 15 ?
x,y 是多少?
6,9?
这样写有坑啊 大坑
宏展开
((x++) > (y++) ? (x++) : (y++))
((5++) > (8++) ? (5++) : (8++))
这个问号表达式由前部分和后部分组成的
前半部分都执行 而 后半部分 只执行问号确的那个
所以前面变为 6 > 9 ?
后面 执行 y++
那么 z = 9
y 是 10
x 是 6
卧槽 这样的代码写出来 搞毛啊
所以为了避免这样的问题出现,我们要把宏参数存储到临时变量中
int x = 5;
int y = 8;
x++;
y++;
int z = MAX(x,y);
这样就可以了
命名约定
value = max(a,b);
这是一个宏还是一个参数 ?
这样看根本区分不出来
为了不查看定义部分 直观一点
命名约定 宏 要大写字母
宏和函数的不同之处
属性 | #define 宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会插入到程序中,所以宏的定义要非常剪短的才有优势 | 函数的代码只会出现在一个地方,每次使用时都会调用同一份代码 |
执行速度 | 更快 | 多于宏的 函数调用和返回时的额外的开销 |
操作符优先级 | 宏是根据周围表达式的优先级,一定要加括号否则会出现上文中的问题 | 中规中矩,结果比较好预测 |
参数类型 | 宏与类型无关,只要参数的操作是合法的就可以 | 除非使用模板,否则必须要指定参数的类型 |