目录
前言:
1.什么是预处理:
C语言源文件要经过预处理、编译、链接才能生成可执行程序。
预处理就是再编译前,对源文件进行简单的加工处理。
预处理主要是以 # 开头的命令,例如 #include<stdio.h> #define 等。
预处理命令要放在所有函数之外,而且一般都放在源文件的前面。
2.#define是什么:
#define 属于预处理器指令,可以将一些常量或代码模块定义为宏,在预处理是进行文本替换,这个行为被称为宏替换或宏展开。
一.关于#define:
1).#define 的标识符定义:
定义标识符是 #define 最常见的用法,也可以说是没有参数的宏定义。
流程为:
#define -----> 标识符(也叫宏名、宏) ------>替换列表。
示例:
#include<stdio.h>
#define num 20
#define print printf
int main()
{
print("%d", num);
return 0;
}
运行结果如下图:
这里是一个简单的宏定义,我们使用预处理指令 #define 定义了一个名为 print 和 num,“printf” 和“num" 在预处理阶段会被替换成函数 printf 和 20。
2).#define 的函数定义:
简单来说,就是 #define 的宏定义可以像函数一样接受参数。
示例:
#include<stdio.h>
#define add(x, y) ((x)+(y))
#define max(x, y) ((x) > (y) ?x:y)
int main()
{
printf("%d\n", add(2, 3));
printf("%d\n", max(2, 3));
return 0;
}
运行结果如下图所示:
这里我们定义了两个名为 add ,max 的宏,进行文本替换后分别求出参数之和和参数中最大值。
3)#define 的续行操作:
顾名思义就是将 #define 的替换列表放在二行及更多行上。
示例:
#include<stdio.h>
#define for(x) for(int i = 0; i < x; i++)\
{\
printf("%2d",i);\
}
int main()
{
for (5);
return 0;
}
运行结果如下图:
由上,我们可以知道,#define 的多行定义关键是要在每一个换行的时候加上一个"/"。
4)#define 的常见作用:
a.方便维护:
示例:
#define size 100;
int a[size];
这里创建了一个数组,当以后数组觉得数组太大或是太小,我们可以通过改变宏定义的替换列表来一键改变数组 a 的大小。
b.方便程序员检测:
当看别人的代码时,或者回头观看以前的代码,通常会忘记一个代码块代表的含义,当使用如 add 等宏定义的函数时,可以帮助我们快速理解。
c.简化写法:
示例:
#define usi unsigned short int
usi a;
当我们需要经常使用 unsigned short int 时,由于该类型较长,经常会 觉得麻烦,这时候就可以通过将其定义为 usi ,并可以通过 usi 创建变量。
d.进行条件编译:
示例:
#include<stdio.h>
#define add 0
int main()
{
#ifdef add
printf("1");
#endif
return 0;
}
运行结果如下:
如上图,当 add 被定义时,预处理指令 #ifdef 和 #endif 中的语句执行,当讲 add 的宏定义注释掉时,预处理指令中的语句不执行。
二.关于字符串常量化运算符 # :
1).常见用法:
示例:
#include<stdio.h>
#define print(a,b) printf(#a" and %d",b)
int main()
{
print(abc,3);
return 0;
}
运行结果如下图所示:
由此,我们可以知道,在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。
2).对空格处理:
示例:
#include<stdio.h>
#define print(a,b) printf(#a" and %d",b)
int main()
{
print( a bc ,3);
return 0;
}
运行结果如下图:
由此,我们可以知道,编译器会将子字符或者字子符串使用空格连接,并丢弃字符或字符串两边的空格。
三.符号连接操作符符 ##:
示例:
#include<stdio.h>
#define num1 100
#define num(a) num##a
int main()
{
printf("%d", num(1));
return 0;
}
运行结果如下图:
这里我们通过符号链接操作符 ## 将 num 和 1 连接成 num1 。
注:1).宏展开时, ## 两边的空格会自动去除。
2).连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。
3).如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。
四.单字符化操作符 #@ :
用法与##类似。
示例:
#include<stdio.h>
#define fun(x) #@x
int main()
{
printf("%c", fun(a));
return 0;
}
运行结果如下所示:
这里通过 #@ 操作符,将 a 转换成字符。
注:单字符化操作符#@ 最多有四个参数,并只返回最后一个字符,当参数超过四个时,系统将报错。
注意事项:
1.define是宏定义,程序在预处理阶段将用define定义的内容进行了 替换 。因此在程序运行时,常量表中并没有用define定义的常量,系统不为它分配内存。
2.define没有类型检查的功能,因为它只进行文本替换。它只是简单地将宏调用替换为宏定义中指定的文本,没有对文本进行语法分析和类型检查。这种情况下,类型错误很容易发生,特别是对于复杂的宏定义。
3.define定义表达式时要注意“边缘效应”。
示例:
#include<stdio.h>
#define fun(a,b) a+b
int main()
{
printf("%d", fun(1, 2) * fun(3, 4));
return 0;
}
运行结果如下所示:
对于这个代码,我们想得到的值为21, 但是实际的值为11 。这是为什么?我们将宏展开看看:
#include<stdio.h>
int main()
{
printf("%d", 1+2*3+4);
return 0;
}
这里,本应该先和 1 相加的 2 却先与 3 相乘,所以结果错误。
我们可以通过多加括号,来改变表达式运算顺序来预防“边缘效应”。
4.define 宏定义后,直到遇到 #undef 之前可以一直使用;在 #undef 后,先前的宏需要再次定义才能够使用。
5.带副作用的宏参数:
示例:
#include<stdio.h>
#define max(x,y) ((x)>(y)?(x):(y))
int main()
{
int a = 1, b = 2;
int c = max(++a, ++b);
printf("%d", c);
return 0;
}
这里宏展开后得到表达式 c = ((++a)>(++b)?(++a):(++b)),这个式子的值与编译器有关,就大大降低了代码的可移植性。