欢迎阅读这篇文章 目录1、预定义符号2、#define定义常量3、#define定义宏4、带有副作用的宏参数5、宏替换规则6、宏和函数对比7、#和##7.1#运算符7.2##运算符8、命名约定9、#undef10、命令行定义11、条件编译12、头文件包含12.1本地文件包含12.2库文件包含12.3嵌套文件包含1、预定义符号C语言设置了一些预定义符号可以直接使用预定义符号在预处理期间处理。__FILE__//进行编译的源文件__LINE__//文件当前行号__DATE__//文件被编译的日期__TIME__//文件被编译的时间__STDC__//如果编译器遵循ANSI C其值为1否则未定义例如#includestdio.hintmain(){printf(%s\n,__FILE__);//打印当前文件路径printf(%d\n,__LINE__);//打印当前代码的行号printf(%s\n,__DATE__);//打印该文件被编译的日期printf(%s\n,__TIME__);//打印该文件被编译的时间printf(%d\n,__STDC__);//部分编译器打印1部分编译器报错return0;}2、#define定义常量基本语法#definenamestuff举例#defineMAX1000#defineregregister//为register这个关键字创建一个简短的名字#definedo_foreverfor(;;)//无限循环#defineCASEbreak;case//在写case语句的时候可以自动加break//如果定义的stuff过长可以分多行书写除了最后一行外要加上续行符\#defineDEBUG_PRINTprintf(file:%s\\tline:%d\\ \ date:%s\\time:%s,\__FILE__,__LINE__,\__DATE__,__TIME__)思考在define定义标识符的时候要不要在最后加上;建议不要加上;,这样容易导致问题。3、#define定义宏#define机制包含了一个机制允许把参数替换到文本中这种实现通常称为宏或定义宏。宏的声明方式#defineNAME(list)stuff其中的list是一个由逗号隔开的符号表他们可能出现在stuff中。注意参数列表的左括号必须与name紧邻如果两者之间有任何空白参数列表就会被当作是stuff的部分。举例定义一个求一个数的平方的宏#includestdio.h#definesquart(x)x*xintmain(void){inta10;intcsquart(a);printf(%d,c);return0;}这个宏接受一个参数x。在主程序里预处理的时候会将SQUART(a)替换为a*a。但是仔细考虑会发现这个宏存在一个问题假如这样使用inta10;intcSQUART(a1);这时会将SQUART(a1)替换为a1*a1计算的时候就先算1*a再算其他部分就会出现优先级的问题。为了解决上述问题我们应该在宏定义的时候为x加上括号。#definesquart(x)(x)*(x)再看一个宏定义#defineSQUART(x)(x)(x)intc10*SQUART(a1);这时会将10*SQUART(a1)替换为10*(a1)(a1)计算的时候就会出现先算10*(a1)又出现了优先级的问题。为了解决上方的问题我们可以在宏定义的时候在表达式两边加上括号#defineSQUART(x)((x)(x))所以⽤于对数值表达式进⾏求值的宏定义都应该⽤这种⽅式加上括号避免在使⽤宏时由于参数中的操作符和邻近操作符之间不可预料的相互作⽤。4、带有副作用的宏参数当宏参数在宏的定义中出现超过⼀次的时候如果参数带有副作⽤那么你在使⽤这个宏的时候就可能出现危险导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果。例如x1;//不带副作⽤x;//带有副作⽤定义一个求两个数的最大值的宏#includestdio.h#defineMAX(a,b)((ab)?(a):(b))intmain(){intx5;inty8;intzMAX(x,y);printf(%d %d %d,x,y,z);return0;}上方代码经过预处理后MAX部分会变成((xy)?(x):(y))。先把x5y8传给MAX后x和y自增变成x6y9。58所以执行y部分先把y赋值给zz9。y再自增后y变成10。最终x 6y 10z 9。5、宏替换规则在程序中扩展#define定义符号和宏时需要涉及几个步骤在调用宏时首先对参数进行检查看是否包含看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏参数名被他们的值所替换。最后再次对结果⽂件进⾏扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。注意宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。6、宏和函数对比宏通常被用于执行简单的计算。⽐如在两个数中找出较⼤的⼀个时写成下⾯的宏更有优势⼀些。#defineMAX(a,b)((a)(b)?(a):(b))那为什么不⽤函数来完成这个任务⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之宏可以适⽤于整形、⻓整型、浮点型等可以⽤ 来⽐较的类型。宏的参数与类型⽆关。和函数相⽐宏的劣势每次使⽤宏的时候⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短否则可能⼤幅度增加程序的⻓度。宏是没法调试的。宏由于类型⽆关也就不够严谨。宏可能会带来运算符优先级的问题导致程容易出现错。宏有时候可以做函数做不到的事情。⽐如宏的参数可以出现类型但是函数做不到。例如//利用宏定义一个malloc函数#includestdlib.h#defineMALLOC(num,type)((type*)malloc((num)*(sizeof(type))))intmain(){int*ptrMALLOC(10,int);return0;}MALLOC(10,int)预处理后会变成((int*)malloc((10)*(sizeof(int))))。利用函数无法做到上述代码。宏和函数对比表 宏和函数对比表宏和函数对比表属性#define定义宏函数代码长度每次使用时宏代码都会被插入到程序中。除了非常小的宏之外程序的长度会大幅增加函数代码只出现在一个地方每次使用函数的时候都调用那一个地方的代码执行速度更快存在函数的调用和返回额外开销相对会慢一些操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近的操作符优先级会产生不可预料的后果函数参数只在函数调用的时候求值后把结果传给函数表达式求值结果容易预测带有副作用的参数参数可能被替换到宏体中的多个位置若宏的参数被多次计算带有副作用的参数求值会产生不可预料的后果函数参数只在传参的时候求值一次结果容易控制参数类型宏的参数与类型无关只要对参数的操作合法他就可以适用于任何操作类型函数的参数与类型有关如果参数类型不同就需要不同的函数即使他们执行的任务相同调试宏不方便调试函数可以逐语句调试递归宏不可以递归函数可以递归7、#和##7.1#运算符#运算符可以将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。#运算符的操作可以理解为“字符串化”例假如想完成下列代码的任务#includestdio.hintmain(){inta10;printf(the value of a is %d\n,a);floatb3.5f;printf(the value of b is %.2f\n,b);return0;}我们想定义一个宏来完成对printf函数的调用并且能够根据参数名修改打印的信息。如果这样定义宏#definePRINT(x,format)printf(the value of x is format,x)intmain(){inta10;PRINT(a,%d);floatb3.5f;PRINT(b,%.2f);return0;}打开预处理后生成的.i文件观察并没有进行任何的更改因为当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。将PRINT里的a和%d传给宏后x接收aformat接收%d发现宏体里x和format都在字符串常量中不会被搜索。只有参数x会被替换成a。所以我们要做的是把原本宏体里字符串常量的x和format变成参数而且最后还可以再拼接成字符串。这时就需要用到#运算符了。我们把字符串里的x抠出来把x变成#x这样就是先把x当作宏参数用然后转化成字符串方便后续拼接。format需要接收的是一个占位符字符串所以把format抠出来让他变成一个宏参数能够接收传来的字符串后面再拼接即可。#definePRINT(x,format)printf(the value of #x is format\n,x)intmain(){inta10;PRINT(a,%d);floatb3.5f;PRINT(b,%.2f);return0;}预处理后会被替换为C 语言会自动把它们粘成一个完整字符串7.2##运算符##可以把位于它两边的符号合成⼀个符号它允许宏定义从分离的⽂本⽚段创建标识符。##被称为记号粘合。这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。我们想想写⼀个函数求2个数的较⼤值的时候不同的数据类型就得写不同的函数。//定义不同类型数据的比大小函数intint_max(intx,inty){returnxy?x:y;}floatfloat_max(floatx,floaty){returnxy?x:y;}charchar_max(charx,chary){returnxy?x:y;}intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}但是这样写起来太繁琐了现在我们这样写代码试试#definecompare(type)type type##_max(type x,type y){returnxy?x:y;}compare(int);compare(float);compare(char);intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}经过预处理后就会变成intint_max(intx,inty){returnxy?x:y;};floatfloat_max(floatx,floaty){returnxy?x:y;};charchar_max(charx,chary){returnxy?x:y;};intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}和上面的代码一样的效果这里就用到了##运算符来根据不同的类型拼接不同的函数名。8、命名约定⼀般来讲函数和宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。那我们平时的⼀个习惯是宏名全部⼤写函数名不要全部⼤写9、#undef这条指令⽤于移除⼀个宏定义。#undefNAME//如果现存的⼀个名字需要被重新定义那么它的旧名字⾸先要被移除。10、命令行定义许多C 的编译器提供了⼀种能⼒允许在命令⾏中定义符号。⽤于启动编译过程。例如当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候这个特性有点⽤处。假定某个程序中声明了⼀个某个⻓度的数组如果机器内存有限我们需要⼀个很⼩的数组但是另外⼀个机器内存⼤些我们需要⼀个数组能够⼤些。例如//命令行定义演示#includestdio.hintmain(){intarray[SZ];inti0;for(i0;iSZ;i){array[i]i1;}for(i0;iSZ;i){printf(%d ,array[i]);}printf(\n);return0;}可以通过以下指令设置SZ的值并生成可执行文件gcc test.c-DSZ10-o test运行./test11、条件编译在编译⼀个程序的时候我们可以决定是要将⼀条语句⼀组语句编译还是放弃。我们可以使用条件编译指令常⻅的条件编译指令1.#if常量表达式//.....#endif//常量表达式由预处理器求值。例如#define__DEBUG__1intmain(){#if__DEBUG__printf(%d,10);#endif}源文件与预处理后的.i文件对比2.//多个分支的条件编译#if常量表达式//.......#elif常量表达式//......#else//......#endif//......例如// 例如#defineM50intmain(){#ifM10printf(%d,10);#elifM45printf(%d,15);#elseprintf(%d,20);#endif}源文件与预处理后的.i文件对比3.//判断是否被定义#ifdefined(symbol)或#ifdefsymbol#if!defined(symbol)或#ifndefsymbol例如// 例如#defineM50#defineN30intmain(){#ifdefined(M)printf(%d,10);#endif#ifdefMprintf(%d,15);#endif#if!defined(N)printf(%d,20);#endif#ifndefNprintf(%d,25);#endif}源文件与预处理后的.i文件对比4.//嵌套指令#ifdefined(M)#ifdefN//.....#endif#ifdefO//.....#endif#elifdefined(Q)#ifdefN1//.....#endif#endif12、头文件包含12.1本地文件包含#includefilename查找策略先在源⽂件所在⽬录下查找如果该头⽂件未找到编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件。如果找不到就提⽰编译错误。Linux环境的标准头⽂件的路径/usr/include12.2库文件包含#includefilename.h查找头⽂件直接去标准路径下去查找如果找不到就提⽰编译错误。这样是不是可以说对于库⽂件也可以使⽤ “” 的形式包含答案是肯定的可以但是这样做查找的效率就低些当然这样也不容易区分是库⽂件还是本地⽂件了。12.3嵌套文件包含我们已经知道#include指令可以使另外⼀个⽂件被编译。就像它实际出现于#include指令的地⽅⼀样。这种替换的⽅式很简单预处理器先删除这条指令并⽤包含⽂件的内容替换。⼀个头⽂件被包含10次那就实际被编译10次如果重复包含对编译的压⼒就⽐较⼤。如果⼯程⽐较⼤有公共使⽤的头⽂件被⼤家都能使⽤⼜不做任何的处理那么后果将不堪设想。我们可以使用条件编译解决头文件被重复引用的问题每个头文件都这样写#ifndef__TEST__#define__TEST__//头文件内容.........#endif这样在预处理的时候先判断是否定义了__TEST__这个常量第一次包含头文件的时候一定没有定义那么就定义第二次再包含前面已经定义了__TEST__这个常量这次就会直接跳出不会再词拷贝头文件里的内容。或者#pragmaonce
二十五、预处理详解
发布时间:2026/6/6 20:18:26
欢迎阅读这篇文章 目录1、预定义符号2、#define定义常量3、#define定义宏4、带有副作用的宏参数5、宏替换规则6、宏和函数对比7、#和##7.1#运算符7.2##运算符8、命名约定9、#undef10、命令行定义11、条件编译12、头文件包含12.1本地文件包含12.2库文件包含12.3嵌套文件包含1、预定义符号C语言设置了一些预定义符号可以直接使用预定义符号在预处理期间处理。__FILE__//进行编译的源文件__LINE__//文件当前行号__DATE__//文件被编译的日期__TIME__//文件被编译的时间__STDC__//如果编译器遵循ANSI C其值为1否则未定义例如#includestdio.hintmain(){printf(%s\n,__FILE__);//打印当前文件路径printf(%d\n,__LINE__);//打印当前代码的行号printf(%s\n,__DATE__);//打印该文件被编译的日期printf(%s\n,__TIME__);//打印该文件被编译的时间printf(%d\n,__STDC__);//部分编译器打印1部分编译器报错return0;}2、#define定义常量基本语法#definenamestuff举例#defineMAX1000#defineregregister//为register这个关键字创建一个简短的名字#definedo_foreverfor(;;)//无限循环#defineCASEbreak;case//在写case语句的时候可以自动加break//如果定义的stuff过长可以分多行书写除了最后一行外要加上续行符\#defineDEBUG_PRINTprintf(file:%s\\tline:%d\\ \ date:%s\\time:%s,\__FILE__,__LINE__,\__DATE__,__TIME__)思考在define定义标识符的时候要不要在最后加上;建议不要加上;,这样容易导致问题。3、#define定义宏#define机制包含了一个机制允许把参数替换到文本中这种实现通常称为宏或定义宏。宏的声明方式#defineNAME(list)stuff其中的list是一个由逗号隔开的符号表他们可能出现在stuff中。注意参数列表的左括号必须与name紧邻如果两者之间有任何空白参数列表就会被当作是stuff的部分。举例定义一个求一个数的平方的宏#includestdio.h#definesquart(x)x*xintmain(void){inta10;intcsquart(a);printf(%d,c);return0;}这个宏接受一个参数x。在主程序里预处理的时候会将SQUART(a)替换为a*a。但是仔细考虑会发现这个宏存在一个问题假如这样使用inta10;intcSQUART(a1);这时会将SQUART(a1)替换为a1*a1计算的时候就先算1*a再算其他部分就会出现优先级的问题。为了解决上述问题我们应该在宏定义的时候为x加上括号。#definesquart(x)(x)*(x)再看一个宏定义#defineSQUART(x)(x)(x)intc10*SQUART(a1);这时会将10*SQUART(a1)替换为10*(a1)(a1)计算的时候就会出现先算10*(a1)又出现了优先级的问题。为了解决上方的问题我们可以在宏定义的时候在表达式两边加上括号#defineSQUART(x)((x)(x))所以⽤于对数值表达式进⾏求值的宏定义都应该⽤这种⽅式加上括号避免在使⽤宏时由于参数中的操作符和邻近操作符之间不可预料的相互作⽤。4、带有副作用的宏参数当宏参数在宏的定义中出现超过⼀次的时候如果参数带有副作⽤那么你在使⽤这个宏的时候就可能出现危险导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果。例如x1;//不带副作⽤x;//带有副作⽤定义一个求两个数的最大值的宏#includestdio.h#defineMAX(a,b)((ab)?(a):(b))intmain(){intx5;inty8;intzMAX(x,y);printf(%d %d %d,x,y,z);return0;}上方代码经过预处理后MAX部分会变成((xy)?(x):(y))。先把x5y8传给MAX后x和y自增变成x6y9。58所以执行y部分先把y赋值给zz9。y再自增后y变成10。最终x 6y 10z 9。5、宏替换规则在程序中扩展#define定义符号和宏时需要涉及几个步骤在调用宏时首先对参数进行检查看是否包含看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏参数名被他们的值所替换。最后再次对结果⽂件进⾏扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。注意宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。6、宏和函数对比宏通常被用于执行简单的计算。⽐如在两个数中找出较⼤的⼀个时写成下⾯的宏更有优势⼀些。#defineMAX(a,b)((a)(b)?(a):(b))那为什么不⽤函数来完成这个任务⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之宏可以适⽤于整形、⻓整型、浮点型等可以⽤ 来⽐较的类型。宏的参数与类型⽆关。和函数相⽐宏的劣势每次使⽤宏的时候⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短否则可能⼤幅度增加程序的⻓度。宏是没法调试的。宏由于类型⽆关也就不够严谨。宏可能会带来运算符优先级的问题导致程容易出现错。宏有时候可以做函数做不到的事情。⽐如宏的参数可以出现类型但是函数做不到。例如//利用宏定义一个malloc函数#includestdlib.h#defineMALLOC(num,type)((type*)malloc((num)*(sizeof(type))))intmain(){int*ptrMALLOC(10,int);return0;}MALLOC(10,int)预处理后会变成((int*)malloc((10)*(sizeof(int))))。利用函数无法做到上述代码。宏和函数对比表 宏和函数对比表宏和函数对比表属性#define定义宏函数代码长度每次使用时宏代码都会被插入到程序中。除了非常小的宏之外程序的长度会大幅增加函数代码只出现在一个地方每次使用函数的时候都调用那一个地方的代码执行速度更快存在函数的调用和返回额外开销相对会慢一些操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近的操作符优先级会产生不可预料的后果函数参数只在函数调用的时候求值后把结果传给函数表达式求值结果容易预测带有副作用的参数参数可能被替换到宏体中的多个位置若宏的参数被多次计算带有副作用的参数求值会产生不可预料的后果函数参数只在传参的时候求值一次结果容易控制参数类型宏的参数与类型无关只要对参数的操作合法他就可以适用于任何操作类型函数的参数与类型有关如果参数类型不同就需要不同的函数即使他们执行的任务相同调试宏不方便调试函数可以逐语句调试递归宏不可以递归函数可以递归7、#和##7.1#运算符#运算符可以将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。#运算符的操作可以理解为“字符串化”例假如想完成下列代码的任务#includestdio.hintmain(){inta10;printf(the value of a is %d\n,a);floatb3.5f;printf(the value of b is %.2f\n,b);return0;}我们想定义一个宏来完成对printf函数的调用并且能够根据参数名修改打印的信息。如果这样定义宏#definePRINT(x,format)printf(the value of x is format,x)intmain(){inta10;PRINT(a,%d);floatb3.5f;PRINT(b,%.2f);return0;}打开预处理后生成的.i文件观察并没有进行任何的更改因为当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。将PRINT里的a和%d传给宏后x接收aformat接收%d发现宏体里x和format都在字符串常量中不会被搜索。只有参数x会被替换成a。所以我们要做的是把原本宏体里字符串常量的x和format变成参数而且最后还可以再拼接成字符串。这时就需要用到#运算符了。我们把字符串里的x抠出来把x变成#x这样就是先把x当作宏参数用然后转化成字符串方便后续拼接。format需要接收的是一个占位符字符串所以把format抠出来让他变成一个宏参数能够接收传来的字符串后面再拼接即可。#definePRINT(x,format)printf(the value of #x is format\n,x)intmain(){inta10;PRINT(a,%d);floatb3.5f;PRINT(b,%.2f);return0;}预处理后会被替换为C 语言会自动把它们粘成一个完整字符串7.2##运算符##可以把位于它两边的符号合成⼀个符号它允许宏定义从分离的⽂本⽚段创建标识符。##被称为记号粘合。这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。我们想想写⼀个函数求2个数的较⼤值的时候不同的数据类型就得写不同的函数。//定义不同类型数据的比大小函数intint_max(intx,inty){returnxy?x:y;}floatfloat_max(floatx,floaty){returnxy?x:y;}charchar_max(charx,chary){returnxy?x:y;}intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}但是这样写起来太繁琐了现在我们这样写代码试试#definecompare(type)type type##_max(type x,type y){returnxy?x:y;}compare(int);compare(float);compare(char);intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}经过预处理后就会变成intint_max(intx,inty){returnxy?x:y;};floatfloat_max(floatx,floaty){returnxy?x:y;};charchar_max(charx,chary){returnxy?x:y;};intmain(){intm10;intn20;intm1int_max(m,n);floatm2float_max(3.5f,4.6f);charm3char_max(a,d);return0;}和上面的代码一样的效果这里就用到了##运算符来根据不同的类型拼接不同的函数名。8、命名约定⼀般来讲函数和宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。那我们平时的⼀个习惯是宏名全部⼤写函数名不要全部⼤写9、#undef这条指令⽤于移除⼀个宏定义。#undefNAME//如果现存的⼀个名字需要被重新定义那么它的旧名字⾸先要被移除。10、命令行定义许多C 的编译器提供了⼀种能⼒允许在命令⾏中定义符号。⽤于启动编译过程。例如当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候这个特性有点⽤处。假定某个程序中声明了⼀个某个⻓度的数组如果机器内存有限我们需要⼀个很⼩的数组但是另外⼀个机器内存⼤些我们需要⼀个数组能够⼤些。例如//命令行定义演示#includestdio.hintmain(){intarray[SZ];inti0;for(i0;iSZ;i){array[i]i1;}for(i0;iSZ;i){printf(%d ,array[i]);}printf(\n);return0;}可以通过以下指令设置SZ的值并生成可执行文件gcc test.c-DSZ10-o test运行./test11、条件编译在编译⼀个程序的时候我们可以决定是要将⼀条语句⼀组语句编译还是放弃。我们可以使用条件编译指令常⻅的条件编译指令1.#if常量表达式//.....#endif//常量表达式由预处理器求值。例如#define__DEBUG__1intmain(){#if__DEBUG__printf(%d,10);#endif}源文件与预处理后的.i文件对比2.//多个分支的条件编译#if常量表达式//.......#elif常量表达式//......#else//......#endif//......例如// 例如#defineM50intmain(){#ifM10printf(%d,10);#elifM45printf(%d,15);#elseprintf(%d,20);#endif}源文件与预处理后的.i文件对比3.//判断是否被定义#ifdefined(symbol)或#ifdefsymbol#if!defined(symbol)或#ifndefsymbol例如// 例如#defineM50#defineN30intmain(){#ifdefined(M)printf(%d,10);#endif#ifdefMprintf(%d,15);#endif#if!defined(N)printf(%d,20);#endif#ifndefNprintf(%d,25);#endif}源文件与预处理后的.i文件对比4.//嵌套指令#ifdefined(M)#ifdefN//.....#endif#ifdefO//.....#endif#elifdefined(Q)#ifdefN1//.....#endif#endif12、头文件包含12.1本地文件包含#includefilename查找策略先在源⽂件所在⽬录下查找如果该头⽂件未找到编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件。如果找不到就提⽰编译错误。Linux环境的标准头⽂件的路径/usr/include12.2库文件包含#includefilename.h查找头⽂件直接去标准路径下去查找如果找不到就提⽰编译错误。这样是不是可以说对于库⽂件也可以使⽤ “” 的形式包含答案是肯定的可以但是这样做查找的效率就低些当然这样也不容易区分是库⽂件还是本地⽂件了。12.3嵌套文件包含我们已经知道#include指令可以使另外⼀个⽂件被编译。就像它实际出现于#include指令的地⽅⼀样。这种替换的⽅式很简单预处理器先删除这条指令并⽤包含⽂件的内容替换。⼀个头⽂件被包含10次那就实际被编译10次如果重复包含对编译的压⼒就⽐较⼤。如果⼯程⽐较⼤有公共使⽤的头⽂件被⼤家都能使⽤⼜不做任何的处理那么后果将不堪设想。我们可以使用条件编译解决头文件被重复引用的问题每个头文件都这样写#ifndef__TEST__#define__TEST__//头文件内容.........#endif这样在预处理的时候先判断是否定义了__TEST__这个常量第一次包含头文件的时候一定没有定义那么就定义第二次再包含前面已经定义了__TEST__这个常量这次就会直接跳出不会再词拷贝头文件里的内容。或者#pragmaonce