预定义符号:
在C语言中,有这么一些预定义符号是C语言内置的:
__FILE__ //进行编译的源文件(这些__其实是由两个下划线组成的)
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
举个小例子:
int main()
{
printf("%s", __DATE__);
return 0;
}
#define定义标识符
语法:
#define name stuff
#define MAX 100
首先来看这个举例:
int main()
{
int a = 10;
int ret = a * a;
printf("%d", ret);
return 0;
}一般我们都是这样写的
但是我们可以用#define来定义,请看如下:
#define MAX 10
int main()
{
int ret = MAX * MAX;
printf("%d", ret);
return 0;
}
这样所有MAX的地方都被替换成了10
#define reg register //为 register这个关键字,创建一个简短的名字
可以用这种方法将代码中的register全部替换成reg,不用一个一个单独修改
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
切记:定义#define的时候,最后最好别加;(分号)
举个例子:
int array[MAX];
如果在 #define MAX 10 后面误加了分号 ;,虽然大部分编译器可能不会报错,但这个分号在这里是不必要的,并且不推荐这种做法。
#define定义宏
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
来实战一下吧:
#define NUM(x) x*x
int main()
{
int a = 5;
printf("%d", NUM(a));
return 0;
}
这个宏接收一个参数 x 如果在上述声明之后,你把置于程序中,预处理器就会用下面这个表达式替换上面的表达式,结果为25
那么我们将这段代码稍加修改呢?结果会是怎样的
#define NUM(x) x*x
int main()
{
int a = 5;
printf("%d", NUM(a+1));//我们传一个表达式a+1
return 0;
}
乍一看是不是感觉没问题,按照我们目前的理解应该是36对吧,可是并不是,让我来给大家解释一下吧,上图!
说了这么一大堆,我们应该怎样得到正确的结果呢?
其实特别简单,我们只需要这样定义
#define SQUARE(x) (x) * (x)
加个()来保证a+1是一个整体,这样预处理之后就变成了这样:
printf ("%d\n",(a + 1) * (a + 1) );
那么这里还有一个宏定义,让我们来看一下吧!
#define DOUBLE(x) (x) + (x)
定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a))
看上去,好像打印100,但事实上打印的是55,我们发现替换之后:
printf ("%d\n",10 * (5) + (5));
那这个又该怎么解决了,其实更简单,只需要在两边都加上()就行
#define DOUBLE(x) ((x)+(x))
tip:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define 替换规则
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
特殊操作符:
#
(字符串化操作符): 当你在宏定义中使用 #
时,它会将宏参数转换为一个字符串字面量。
#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE);
PRINT("%d", 10);
代码中的 #VALUE 会预处理器处理为:"VALUE"
最终的输出的结果应该是:
the value of i+3 is 13
##
(连接操作符): 宏定义中的 ##
用于连接两个符号。它通常与宏参数一起使用来创建新的标识符或表达式。
#define ADD_TO_SUM(num, value) \
sum##num += value;
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.
注意:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
带副作用的宏参数:
x+1;//不带副作用
x++;//带有副作用
#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++));
结果为:x=6 y=10 z=9
根本不符合我们的预期,所以我们最好别使用这种带副作用的
宏和函数对比
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以
用于>来比较的类型。
宏是类型无关的。
宏的缺点:当然和函数相比宏也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- .宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写
#undef
这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。