预处理

来源:互联网 发布:宏基笔记本怎么样 知乎 编辑:程序博客网 时间:2024/05/30 19:34

基础

        预处理以#开头,到其后第一个换行符为止。也就是说,预处理的指令仅限一行。但可以使用反斜杠(\)和换行符将多个物理行,合成一个逻辑行。如:

#define TEST "tetafa\fdafa"
那么在实际使用中TEST的值是:tetafafdafa。

        预处理指令可出现在源文件的任何地方,指令定义的作用域从定义出现的位置开始一直到文件的结尾。

#define

基础

        每一个#define行(逻辑行)由三部分组成。第一部分#define自身,第二部分为定义的缩略语,这些缩略语被称为宏,宏名中不允许有空格;第三部分为主体,即宏要替换的部分。从宏变成最终的主体,称为宏展开。

        宏可以表示任何字符串,也可以表示整个表达式。但预处理器不进行计算,只是按照指令进行文字替换。如下:

#define TEST 2+2int main(int argc, const char * argv[]) {    printf("%d",TEST * 4);}

        使用宏替换后的计算式为2+2*4=10,而不是(2+2)*4=16。记住:宏只负责进行替换,不会在替换过程中进行任何计算

宏函数

        通过使用参数,可以创建外形和作用都与函数相似的函数宏。宏的参数也使用括号括起来,可以用圆括号括起一个或多个参数,随后这些参数出现在宏的主体部分。

#define TEST(x,y) x+y#define PRD(x) printf("x = %d\n",x)int main(int argc, const char * argv[]) {    int x = 5;    PRD(TEST(x, 6));//x = 11}

        会将PRD,TEST进行替换,也就相当于printf("x = %d\n",x+6);。而且在宏中不要使用自增或自减运算符。

        在上面中,PRD(x)并没有将字符串中的x进行替换。如果想在字符串在使用宏参数,可以使用#符号。例如x是一个宏参数,那么#x可以把参数名转化为相应的字符串。如下:

#define PRD(x) printf(#x" = %d\n",x)int main(int argc, const char * argv[]) {    int x = 5;    int y = 3;    PRD(x+y);//x+y = 8}
        首先将参数名转成为字符串,即将x+y转换成"x+y",那么printf(#x" = %d\n",x)将被转换为printf("x+y"" = %d\n",x+y),最后就成了printf("x+y = %d\n",x+y),所以输出为x+y = 8。

##

        将两个符号组合成一个符号。例如:

#define PRD(n) printf("x"#n"=%d\n",x##n)int main(int argc, const char * argv[]) {    int x1 = 3;    PRD(1);//x1=3}
        首先因为参数为1,所以printf()的第一个参数将会变成x1 = %d\n。而##会将x与n组合成一个,这里的n是1,所以结合之后就是x1。故PRD(1)就相当于printf("x1=%d\n",x1);。

可变宏

        有些函数的参数个数是可变的,也可将宏函数的参数个数设置为可变的。方法为:宏定义中的参数列表的最后一个参数为省略号,主体部分用__VA_ARGS__代替。如下:

#define PRD(n,...) printf("%s,%d\n",__VA_ARGS__,n);int main(int argc, const char * argv[]) {    PRD(1,"main");//main,1}

        定义宏参数列表时使用了...,在替换部分用的是__VA_ARGS__。

#include与头文件

        引用头文件,并将头文件中的内容替换源代码中的#include指令。有两种指令形式:<>告诉预处理器在一个或多个系统目录中寻找文件;""告诉预处理器先在当前目录中寻找文件,然后在标准系统目录中寻找。

        #include中可以指定路径:如include "/user/biff/bi.h"就是搜索/user/biff目录。

        头文件内容最常见的形式包括:

        1,常量。

        2,宏函数。

        3,函数声明。将函数声明写在头文件中,函数实现放在.c文件中。

        4,结构体的定义。

        5,类型定义。使用typedef将一些复杂类型进行定义别名。

        6,将各个文件共享的外部变量声明在头文件中,省去别的文件写extern。例如有A,B两个文件,都共享一个变量intC,并且一方修改对另一方也可见。可以将C定义在A或B中,然后在一头文件中定义extern int C,并在B或A中引入该头文件即可。如果还有别的文件需要使用变量C,可直接引用头文件。

        7,将多个文件具有,但被const修饰,内部链接和文本作用域定义在头文件中。比如BASE_URL是多个文件需要的变量,且不可修改。那么可以在一个头文件中将它定义成static const的变量,使用该变量的各个文件直接include这个头文件即可。因为使用了static修饰,每个包含该头文件的文件都获得了一份该常量的副本

#undef

        用于取消一个由#define定义的宏。如#define LIMIT此时LIMIT被定义了,不能再使用LIMIT指代别的。#undef LIMIT会取消LIMIT的定义,此时就可以使用LIMIT指代别的。

        #undef只能取消由#define定义的宏名,不能取消变量。如下:

#define CSIZE 4  //首先对CSIZE进行定义#undef CSIZE     //取消对CSIZE的定义#define CSIZE "HAHAHAH" //将CSIZE定义成别的值void test() {printf("%s\n", CSIZE);//输出的就是:HAHAHAH}
        从这里也可以看出:#define的作用域从定义行开始,到#undef或文件结尾结束(哪个先出现以哪个为准)。

#indef,#ifndef,#else和#endif

        这些指令告诉编译器,根据编译时的条件接受或忽略某些代码块。

        #ifdef:如果预处理器已经定义了后面的标识符(即使用了#define),那么就会编译执行#ifdef与#else或#endif(两者谁先出现为谁为准)之间的代码。

        #else:#ifdef不满足时会编译执行#else与#endif之间的代码。但它不是必须的。

        #endif:结束整个条件编译,它是必须存在的。

        这三个预指令类似于if else语句,但由于预处理器不能识别标识代码块的{},所以必须使用#endif和#esle来标识代码块。如:

#define CSIZE "#defind了"//#undef CSIZEvoid test() {int a = 1;//#ifdef a  此时a是没有定义的,因为#ifdef只识别#define定义的标识符#ifdef CSIZEputs("defined");#elseputs("undef");#endif}
        上述代码中,会输出defined。如果前#undef CSIZE的注释给除掉,就会输出undef。如果将#ifdef CSIZE换成#ifdef a,也会输出undef。

        因为#ifdef只接受#define定义的标识符,并不能识别变量

        #ifndef:与#ifdef类似,但它用来判断后面的标识符是否为未定义的。同样可以与#else,#endif连用。使用该预处理指令可以防止对同一个宏进行重复定义。例如:

#ifndef CSIZE#define CSIZE "no def"#endif
        首先判断CSIZE是否未定义,如果未定义就将它定义成no def。

#if与#elif

        #if更类似于常规的if else语句(#ifdef #else #endif只是执行过程类似),它可以将常量表达式做为判断条件,不单单是判断这个标识符是否定义过,还可以判断这个标签符定义的值

        #elif:就是if else中的else。而且,它们也需要与#endif连用。如:

#define A 1//代码二void test() {#if A == 1   //代码一puts("a 1");#elif A == 2puts("a 2");#elseputs("a default");#endif}
        因为A的值是1,所以在代码一处判断是成立的,就会输出a 1。如果将A的值改为2,那么输出的就会是a 2。如果a的值没有定义,或者值不是1、2则会输出a default。

        由上面可以看出#if与#ifdef的区别:#ifdef只判断标识符是否定义过,不管定义的值是多少。而#if可以根据标识符的值选择不同的操作

        由于它们都是预处理器进行处理的,所以只能识别由#define定义的宏,而不能识别变量

预定义宏

        __LINE__:当前语句在文件中的行号。

        __FILE__:当前文件的全路径。

        __TIME__:当前的时间。格式为13:23:12,即hh:mm:ss。

        __DATE__:当前的日期。格式为:Apr 20 2016

        __func_:当前的函数名。

#line

        重置由__LINE__和__FILE__指定的行号和文件名。其中指定行号时,是指定下一行的行号,而不是本行的行号。如:

#line 200 "xxx.txt"#line 300printf("%d,%s",__LINE__,__FILE__);//300,xxx.txt
        第二行中将下一行的行号置为了300,所以__LINE__代表的值是300;第一行将名置为xxx.txt,所以输出__FILE__时值为xxx.txt。



0 0
原创粉丝点击