在C语言中设置了许多的预定义符号,这些预定义符号是可以直接使用的,预定义符号也是在预处理阶段进行处理的。
常见的预定义符号:
__FILE__ //进⾏编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
【示例】:
#include<stdio.h>
int main()
{
printf("%s\n", __FILE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
printf("%d\n", __LINE__);
return 0;
}
我们在VS上使用 _ _ STDC _ _ 会发现显示未定义,这也就说明VS的编译器是不完全遵循ANSI C的,为了展示效果,我没可以在gcc的环境下查看一下。
在gcc环境下运行可以看到它输出的是1,这表明gcc环境下的编译器是遵循ANSI C的。 预处理之后我们会发现,前面我们就学过,程序在预处理之后会把预定义指令给替换掉,这里结果也确实如此。
#define一般有两种应用场景:
#define定义常量基本语法:
#define name stuff
【示例】:
#include<stdio.h>
#define MAX 100
#define STR "hello world"
int main()
{
int a = MAX;
printf("%d\n", MAX);
printf("%s\n", STR);
return 0;
}
#define定义标识符加不加 ; 的区别:
#include<stdio.h>
#define MAX 100
#define MAX1 100;
int main()
{
int a = MAX;
int b = MAX1;
printf("%d\n", MAX);
printf("%d\n", MAX1);
return 0;
}
可以看到,MAX1加了分号之后, 之后后面使用的MAX1全都加上了分号,这也就导致了在打印MAX1时报错,在预处理之后可以清楚的看到原因(#define把;也替换过来了)。所以一般使用 #define 定义常量时,不要加分号。
#define 机制包括了⼀个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
宏的申明方式:
#define name( parament-list ) stuff
parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。 注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的⼀部分。
【示例】:利用宏求一个数的平方
#include<stdio.h>
#define SQURE(x) x * x
int main()
{
int a = 12;
int b = SQURE(a);
printf("%d\n", b);
return 0;
}
因为参数是允许替换到文本中的,把a传给SQURE(a)就相当于把程序替换成了a * a, 这里预处理后也能看到效果。
但是这个宏也存在着一些问题:
int a = 5;
printf("%d\n", SQURE(a + 1));
按照惯性,我们会觉得这个代码的运行结果会是6 * 6 = 36,但结果真的会是这样吗? 我们来运行试一下:
运行之后可以发现结果等于11,这里就要注意了,宏的参数是不会参与计算的,会直接进行替换,我们进行预处理生成目标文件后可以发现SQURE(a + 1)替换成了a + 1 * a + 1,而 * 的优先级比 + 高,导致会先算 * 再算 + ,a等于5,乘以一还是5,再加上6就等于11。
那怎么让他得到36呢,其实这里加个括号就可以了。
#include<stdio.h>
#define SQURE(x) (x) * (x)
int main()
{
int a = 5;
int b = SQURE(a + 1);
printf("%d\n", b);
return 0;
}
当然,下面这种方法也是一样的。
我们只要确保替换之后运算顺序不发生改变就可以达到目的了。
下面是一个宏定义:
#define DOUBLE(x) (x) + (x)
定义中我们使用了括号,虽然这样可以避免之前的问题,但是这个宏定义可能会出现新的问题:
#include<stdio.h>
#define DOUBLE(x) (x) + (x)
int main()
{
int a = 5;
printf("%d\n", 5 * DOUBLE(a));
return 0;
}
按照惯性思维,我们可能会认为打印50,但结果是否会是50呢?
结果发现打印的是30,预处理之后生成目标文件之后可以发现5会先和a相乘,然后再加a,导致结果与我们的出现误差。 这个问题,的解决办法是在宏定义表达式两边加上⼀对括号就可以了。
#define DOUBLE(x) ((x) + (x))
提示:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
【示例】:
x+1;//不带副作用
x++;//带有副作用
【示例】:MAX宏可以证明具有副作用的参数所引起的问题。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main()
{
int a = 3;
int b = 5;
int m = MAX(a++, b++);
printf("m = %d\n", m);
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
替换之后是一个三目运算符,首先a = 3,b = 5,由于是后置加加,判断之后才会加一,所以判断之后a就等于4,b就等于6,因为表达式为假,后面那个a++不会执行,a还是等于4,后面的b++会执行,但由于也是后置加加,先使用后再加一,即m就等于6,b就等于7。
结论:如果一个带有副作用的参数在宏定义中出现两份,就有可能出现不同的结果,即带有副作用的参数是非常危险的,要尽量避免使用。
宏替换是C语言预处理器的一个重要功能,它在编译之前进行文本替换。宏替换遵循一定的规则,这些规则确保了宏能够在正确的上下文中被替换为定义的文本,需要涉及几个步骤:
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
int a = 3;
int b = 5;
int m = MAX(a, M);
printf("m = %d\n", m);
printf("M = %d\n", M);
printf("b = %d\n", b);
return 0;
}
每个宏名的位置都会用宏定义中的文本替换。
注意:
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
int a = 3;
int b = 5;
int m = MAX(a, MAX(2, 3));
printf("m = %d\n", m);
printf("M = %d\n", M);
printf("b = %d\n", b);
return 0;
}