预定义符号
预定义符号 | 解析 |
---|---|
__FILE__ | 进行编译的源文件 |
__LINE__ | 文件当前的行号 |
__DATE__ | 文件被编译的日期 |
__TIME__ | 文件被编译的时间 |
__STDC__ | 如果编译器遵循ANSI C ,其值为1,否则未定义 |
这些预定义符号都是语言内置的
例如:
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
return 0;
}
运行后:
但是__STDC__
预定义符号无法再VS
编译器上执行
在
VS
编译器上不完全遵循ANSI C
#define
#define 定义标识符
语法:#define name stuff
在define定义标识符的时候,不要在最后加上
;
- 防止发生语法错误,例如:
#define MAX 1000;
表示MAX
=1000;
- 出现
max=MAX;
等价于max=MAX;;
发生语法错误
举例:
#define M 20
#define STR "abcd"
#define FOR for(;;)
int main()
{
printf("%d\n", M);
printf("%s\n", STR);
FOR
{
printf("hehe");
};
return 0;
}
#define M 20
表示M=20
#define STR "abcd"
表示STR="abcd"
#define FOR for(;;)
表示FOR
是一个for(;;)
循环(此循环为死循环)
运行后:
#define 定义宏
#define name( parament-list ) stuff
其中
parament-list
是一个由逗号隔开的符号表,它们可能出现在stuff
中
- 参数列表的左括号必须与name紧邻
- 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
例题分析:
#define SQUARE(x) ((x)*(x))
int main()
{
int a = SQUARE(5);
printf("%d\n", a);
return 0;
}
运行结果为:25
#define SQUARE(x) ((x)*(x))
- 需要将后面的每个参数都用括号括起来
#define SQUARE(x) x*x
SQUARE(5 + 5);
预计结果为10*10=100
实际结果
算式在机器中表示为5+5*5+5
=35
- 将整体的参数也括起来
#define SQUARE(x) (x)+(x)
10 * SQUARE(5);
预计结果为100
实际结果
算式在机器中表示10*(5)+(5)
=55
用于对数值表达式进行求值的宏定义,都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
#define 替换规则
在程序中扩展#define定义符号和宏时的步骤
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
先进行
M
替换为(3+5)
再将(3+5)
替换为SQUARE(x)
,计算式为((3+5)*(3+5))
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#和##
首先请先看代码1:
int main()
{
int a = 100;
printf("the value of a is %d\n", a);
int b = 25;
printf("the value of b is %d\n", b);
float f = 3.14f;
printf("the value of f is %f\n", f);
return 0;
}
在代码1中,重复打印很多,但是又不得不每个都进行打印一遍
但是通过代码2
可以发现字符串是有自动连接的特点的
那么上面的代码1就可以使用#
,把一个宏参数变成对应的字符串
#define PRINT(n,format) printf("the value of "#n" is "format"\n",n)
int main()
{
int a = 100;
PRINT(a, "%d");
int b = 25;
PRINT(b, "%d");
float f = 3.14f;
PRINT(f, "%f");
return 0;
}
运行后,与代码1打印的结果相同
##可以把位于它两边的符号合成一个符号
允许宏定义从分离的文本片段创建标识符
例如代码:
#define CAT(x,y) x##y
int main()
{
int Mylove = 520;
printf("%d\n", CAT(My, love));
printf("%d\n", Mylove);
return 0;
}
运行后,打印结果相同
注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候
- 如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果
- 副作用就是表达式求值的时候出现的永久性效果
x+1
-----不带副作用,x
不变
x++
-----带副作用,x
有变化
例如:
#define MAX(x, y) ( (x) > (y) ? (x) : (y) )
int main()
{
int a = 5;
int b = 6;
int c = MAX(a++, b++);
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
运行后
int c = MAX(a++, b++);
等价于int c = ((a++)>(b++)?(a++):(b++))
(a++)>(b++)
先进行比较在进行后置++
,5>6
为假,所以运行b++
- 此时的
a=6,b=7
,这时原式子等价于int c=b++;
- 所以先将
b
的值赋给c
,在进行b++
,此时c=7,b=8
宏和函数对比
宏通常被应用于执行简单的运算
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多
所以宏比函数在程序的规模和速度方面更胜一筹 - 更为重要的是函数的参数必须声明为特定的类型
所以函数只能在类型合适的表达式上使用
反之宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型
宏是类型无关的
宏的缺点:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中;除非宏比较短,否则可能大幅度增加程序的长度
- 宏是没法调试的
- 宏由于类型无关,也就不够严谨
- 宏可能会带来运算符优先级的问题,导致程容易出现错
- 宏不可以递归
宏的参数可以出现类型,但是函数做不到
例如:
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
int* p = (int*)malloc(126 * sizeof(int));
int* p = MALLOC(126, int);
return 0;
}
命名约定
平时的一个习惯是:
- 把宏名全部大写
- 函数名不要全部大写
#undef
用于移除一个宏定义
#undef NAME
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
int c = MAX(3, 5);
printf("%d\n", c);
#undef MAX
c = MAX(3, 5);
printf("%d\n", c);
return 0;
}
#undef MAX
表示移除MAX
的宏定义,在此指令之后,表示MAX
已经没有作用了
这段代码运行时会报错