
系列文章目录深度剖析数据深度剖析递归深度剖析结构体深度剖析动态内存管理深度剖析文件操作深度剖析预处理文章目录系列文章目录前言一、预定义符号二、预定义1. #define 定义标识符1.语法结构2.调用方式3.注意事项2. #define 定义宏1.语法结构2.调用示例3.注意事项3. #define 使用规则1.替换规则2.命名约定3.注意事项三、宏和函数对比1.宏的优势2.宏的劣势3.详细对比表四、命令行定义五、条件编译常见的条件编译指令1.单分支2.多分支3.判断是否被定义4.嵌套指令总结前言C预处理器在程序执行之前查看程序故称之为预处理器。本文将围绕C语言的预处理器深度剖析预处理指令。一、预定义符号符号作用FILE进行编译的源文件LINE文件当前的行号DATE文件被编译的日期TIME文件被编译的时间STDC如果编译器遵循ANSI C其值为1否则未定义这些预定义符号都是语言内置的printf(file:%s line:%d\n,__FILE__,__LINE__);二、预定义1. #define 定义标识符1.语法结构#definenamestuff2.调用方式调用作用#define MAX 1000定义一个全局常量MAX大小为1000#define reg register为 register这个关键字创建一个简短的名字#define do_forever for( ; ; )用更形象的符号来替换一种实现#define CASE break; case在写case语句的时候自动把 break写上3.注意事项在define定义标识符的时候不建议加上;#defineMAX100;#defineMIN10;intretMAX*MIN;由于#define定义的内容是在预处理时将其替换到代码中此时此刻就会出问题。因此强烈不建议在定义标识符时加上;2. #define 定义宏1.语法结构#definename(parament-list)stuff#define 机制包括了一个规定允许把参数替换到文本中这种实现通常称为宏macro或定义宏define macro2.调用示例#defineSQUARE(x)x*x传参替换结果SQUARE( 5 );5 * 525SQUARE( 5 1 )5 1 * 5 111SQUARE( 5 2 1 )5 2 1 * 5 2 115#defineSQUARE(x)(x)*(x)传参替换结果SQUARE( 5 );( 5 ) * ( 5 )25SQUARE( 5 1 )( 5 1 ) * ( 5 1 )36SQUARE( 5 2 1 )( 5 2 1 ) * ( 5 2 1 )64#defineSQUARE(x)(x)(x)传参替换结果2 * SQUARE( 5 );2 * ( 5 ) ( 5 )152 * SQUARE( 5 1 )2 * ( 5 1 ) ( 5 1 )182 * SQUARE( 5 2 1 )2 * ( 5 2 1 ) ( 5 2 1 )24#defineSQUARE(x)((x)(x))传参替换结果2 * SQUARE( 5 );2 * ( ( 5 ) ( 5 ) )202 * SQUARE( 5 1 )2 * ( ( 5 1 ) ( 5 1 ) )242 * SQUARE( 5 2 1 )2 * ( ( 5 2 1 ) ( 5 2 1 ) )323.注意事项所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或 邻近操作符之间不可预料的相互作用3. #define 使用规则1.替换规则在程序中扩展 #define 定义符号和宏时需要涉及几个步骤在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值替换。最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。2.命名约定一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是把宏名全部大写 函数名不要全部大写3.注意事项在程序中使用 #define 定义符号和宏时需要注意宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。三、宏和函数对比1.宏的优势宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。#defineMAX(a,b)((a)(b)?(a):(b))宏相比函数优势的地方用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。2.宏的劣势宏相比函数劣势的地方每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。宏无法进行调试的。宏由于类型无关也就不够严谨。宏可能会带来运算符优先级的问题导致程容易出现错。宏有时候可以做函数做不到的事情。3.详细对比表属 性#define定义宏函数代码长度每次使用时宏代码都会被插入到程序中。除了非常小的宏 之外程序的长度会大幅度增长函数代码只出现于一个地方每次使 用这个函数时都调用那个地方的同 一份代码执行速度更快存在函数的调用和返回的额外开销 所以相对慢一些操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加 上括号否则邻近操作符的优先级可能会产生不可预料的后 果所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一 次它的结果值传递给函数。表达式 的求值结果更容易预测。带有副作用的参数参数可能被替换到宏体中的多个位置所以带有副作用的参 数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次 结果更容易控制。参数类型宏的参数与类型无关只要对参数的操作是合法的它就可 以使用于任何参数类型。函数的参数是与类型有关的如果参 数的类型不同就需要不同的函数 即使他们执行的任务是不同的。调试宏是不方便调试的函数是可以逐语句调试的递归宏是不能递归的函数是可以递归的四、命令行定义许多C 的编译器提供了一种能力允许在命令行中定义符号。用于启动编译过程。 例如当我们根据同一个源文件要 编译出不同的一个程序的不同版本的时候这个特性有点用处。假定某个程序中声明了一个某个长度的数组如果 机器内存有限我们需要一个很小的数组但是另外一个机器内存大写我们需要一个数组能够大写。#includestdio.hintmain(){intarray[ARRAY_SIZE];inti0;for(i0;iARRAY_SIZE;i){array[i]i;}for(i0;iARRAY_SIZE;i){printf(%d ,array[i]);}printf(\n);return0;}编译指令gcc-D ARRAY_SIZE10programe.c五、条件编译在编译一个程序的时候我们如果要将一条语句一组语句编译或者放弃是很方便的。因为我们有条件编译指令。调试性的代码删除可惜保留又碍事所以我们可以选择性的编译。#includestdio.h#define__DEBUG__intmain(){inti0;intarr[10]{0};for(i0;i10;i){arr[i]i;#ifdef__DEBUG__printf(%d\n,arr[i]);//为了观察数组是否赋值成功。#endif//__DEBUG__ }return0;}}常见的条件编译指令1.单分支#if常量表达式//...#endif//常量表达式由预处理器求值。2.多分支#if常量表达式//...#elif常量表达式//...#else//...#endif3.判断是否被定义#ifdefined(symbol)#ifdefsymbol#if!defined(symbol)#ifndefsymbol4.嵌套指令#ifdefined(OS_UNIX)#ifdefOPTION1unix_version_option1();#endif#ifdefOPTION2unix_version_option2();#endif#elifdefined(OS_MSDOS)#ifdefOPTION2msdos_version_option2();#endif#endif总结C语言的重要附件之一就是C预处理器。C预处理器遵循预处理器指令在编译源代码之前调整源代码。它提供了更多更便捷的方式去编写程序是C语言中非常重要的一环。