常见的预定义符号有:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANST C,其值为1,否则未定义
//这些预定义符号都是语言内置的,例如:
printf("file:%s line:%d date:%s",__FILE__,__LINE__,__DATE__);
#define
- #define定义标识符
语法:
#define name stuff
#define的运用:
#define max 100
#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定义标识符的时候,最好不要在最后加上;因为这样可能导致语法错误。比如下面的场景:
if(condition
max = MAX;
else
max = 0;
//如果#define MAX 1000;
//则替换成max = 1000;;,这两个分号就是两条语句,
//所以if条件语句就要将这两条语句用花括号括起来,否则就会出现语法错误而报错。
#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏。
宏的申明方式:
#define name(parament-list) stuff
//其中parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:参数列表的左括号必须与name紧邻,如果两者之间出现空白,参数列表就会被解释为stuff的一部分。如:
#define SQUARE(x) (x)*(x)
注意:由于乘除运算的优先级大于宏定义的加法,所以在宏定义表达式两边务必加上一对括号避免在使用宏时由于参数列表中的操作符或临近操作符之间不可预料的相互作用。
- #define替换在程序中扩展#define定义符号和宏时,需要涉及几个步骤
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果有,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值替换
3.最后,再次对结果进行扫描,看看它是否包含任何由#define定义的符号,如果有,重复上述处理过程。
#的作用
我们知道字符串有自动连接的特点,如以下代码:
char* p = "hello ""world\n";
printf("hello"" world\n");
printf("%s",p);
//这里两个输出的都是 hello world ,从而证明了字符串有自动连接的特点
但是只有当字符串作为宏参数的时候才可以把字符串放在字符串中,此时可以使用#,把一个宏参数变成对应的字符串,比如:
int i = 10;
#define PRINT(FORMAT,VALUE)\
printf("the value of " #VALUE " is " FORMAT "\n",VALUE);
PRINT("%d",i+3);
//#VALUE会被预处理器处理为 "VALUE",最终的输出结果是:
//the value of i+3 is 13
##的作用
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
宏和函数的对比
属性 | #define定义的宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中,除了非常小的宏之外,程序的长度会大幅度增长 | 函数的代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有范围表达式的上下文环境里,除非加上括号,否则临近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多加括号 | 函数参数只在函数调用的时候求值一次,它的结果值传给函数,表达式的求值结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一次,结果更容易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作合法,它就可以使用任何参数类型 | 函数的参数类型与类型有关,如果参数的类型不同,就需要不同的函数,即使它们的执行的任务是相同的 |
调试 | 宏不方便调试,因为在预编译时就完成了替换 | 函数可以逐语句调试 |
递归 | 宏不能递归 | 函数是可以递归的 |