预处理详解
- 1.预定义符号
- 2.#define定义常量
- 3. #define定义宏
- 4. 带有副作用的宏参数
- 5. 宏替换的规则
- 6. 宏函数的对比
- 7. #和##
- 8. 宏命名约定
- 9. #undef
- 10.条件编译
1.预定义符号
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 这个代码在vs上面是不可以使用的
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
//printf("%s\n", __STDC__);//vs不支持
return 0;
}
以下是运行结果:
2.#define定义常量
基本使用方法
#define name stuff
举例子
#define MAX 1000
#define reg register //为 register这个关键字,创建⼀个简短的名字
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
#define CASE break;case //在写case语句的时候⾃动把 break写上。
// 如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n",\
__FILE__,__LINE__,\
__DATE__,__TIME__)
如果在define定义的表示符后面加上分号会发生什么?用一下来解释
#define MAX 1000;
#define MAX 1000
再比如:
if(condition)
max = MAX;
else
max = 0;
3. #define定义宏
下面是宏定义的声明方式:
#define name( parament-list ) stuff
其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
这里需要注意:
举例
例1
#define SQUARE( x ) x * x
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
我们可以使用以下方式来定义宏
#define SQUARE(X) (X)*(X)
就是每个变量都加括号,所以基本相同的代码,加上括号后运行结果就是不一样的
例2
看以下代码
#define DOUBLE(x) (x) + (x)
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
这里运行结果是
但是我们实际想要的答案是100,所以以下代码是改进方式
#define DOUBLE(x) ((x)+(x))
int main()
{
int a = 5;
printf("%d\n", 10*DOUBLE(a));
//10*((a)+(a))
return 0;
}
运行结果
这里就达到了我们预期的结果
提示
:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
4. 带有副作用的宏参数
例如:
x+1;//不带副作⽤
x++;//带有副作⽤
MAX宏可以证明具有副作⽤的参数所引起的问题。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
预处理之后的代码就是
z = ( (x++) > (y++) ? (x++):(y++));
所以输出的结果是:
5. 宏替换的规则
注意:
:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
6. 宏函数的对比
宏通常被应用于执行简单的运算。 比如在两个数中找出较大的一个时,写成下面的宏,更有优势一些。
#define MAX(a, b) ((a)>(b)?(a):(b))
这里不使用函数的原因是有以下两点:
和函数相比宏的劣势:
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type)\
(type )malloc(num sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int )malloc(10 sizeof(int));
宏和函数的一个对比
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方,每次使用这个函数时都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果。所以建议宏在书写的时候。多些括号。 | 函数参数只在函数调用的时候组织一次。它的结果值传递给函数表达式的求值,结果更容易预测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置。如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何参数类型 | 函数的参数是与类型有关的。如果参数的类型不同,就需要不同的函数,即使它们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
7. #和##
7.1 #运算符
#define PRINT(n) printf("the value of "#n " is %d", n);
当我们按照下⾯的⽅式调⽤的时候:
printf("the value of ""a" " is %d", a);
7.2 ## 运算符
例如
int int_max(int x, int y)
{
return x>y?x:y;
}
float float_max(float x, float y)
{
return x>yx:y;
}
我们可以使用以下代码
//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
使用宏,定义不同函数
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
//调⽤函数
int m = int_max(2, 3);
printf("%d\n", m);
float fm = float_max(3.5f, 4.5f);
printf("%f\n", fm);
return 0;
}
8. 宏命名约定
9. #undef
例如以下代码:
#define ma 10
int main()
{
printf("从定义之前ma是%d\n", ma);
#undef ma
#define ma 100
printf("从定义之后ma是%d", ma);
return 0;
}
10.条件编译
调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译。
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}
结果是
常见的条件编译指令
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif