您的位置:首页 > 其它

预处理 之 宏定义、文件包含、条件编译

2014-07-30 00:34 706 查看
预处理总共分为三种:宏定义(宏替换)、文件包含、条件编译

参考书籍:《C与指针的预处理篇》

一、宏定义(宏替换):

通用规则:------ 宏定义的符号要大写,用以区分标识符(变量、函数名等);

                     ------ 宏定义的替换文本若是为表达式,则要用( )将表达式括起来;

                     ------ 宏定义的替换文本若是太长,可以分行并且以反斜杠’ \‘作为每行的结尾,作为连接标志;

                     ------
不能在宏定义的末尾加' ',因为如果加了,那么连' '也会一起替换进去,而我们有时候是替换到语句中间的,那么就会变成语句了,而且就算是替换一语句                                    我们实际上再用宏时,也会在末尾加上' ' ,那么替换后就会造成有两个' ' ,这样也是错的,所以不论怎样都是错,我们就规定统一不允许在宏的后加' 

                     ------ 双引号内部的宏名无效,即使出现了宏,也不能替换

系统定义好了的宏:_FILE_         此宏所在的被编译的源文件名

                    _LINE_        此宏所在的当前文件行号

                    _DATE_       此宏所在文件被编译的日期

                    _TIME_        此宏所在文件被编译的时间

宏定义与宏取消:在代码中,#define  XX  yy 用于定义宏,#undef  XX 用于取消宏。

                                  在命令行编译文件时,-DXX=yy 用于定义宏,-UXX 用于取消宏。

                                                                        如:$gcc -DSIZE=100 prog.c

                                                                                $gcc -USIZE prog,c

分为以下几种情况:

       (1) 一般的符号常量的宏定义,用于替换一些如字符、数字、字符串、符号、简单语句等。

       (2)连接字符串、连接成标识符。

       (3)宏函数的定义,尤其要注意宏参数

(1) 一般的符号常量的宏定义,用于替换一些如字符、数字、字符串、符号、简单语句、多条语句等;

          如:#define CH         'c'
字符    

                  #define SIZE      100        数字

                  #define NAME    "Lily"                字符串

                  #define db         double             符号

                  #define PRINTF                printf("line :%d",_LINE_) 简单语句

                  #define DO_FERVER      for( ; ; )

                  #define CASE                   break ; case

若是多条语句则多条语句要用{ }括起来,防止如if 与 外部的else 联用了:

                  #define  PRINTIF(n)           {      if(n)   while(n) {  printf("%d\t",n);    (n) = (n) - 1; }     }

  还可用带有宏参数的宏来替换:

                  #define PRINTSTR(str)           printf("%s\n",str);

(2)连接

连接字符串:

有两种方法:一种是替换文本为带双引号的字符串常量,则直接替换,因为相邻的字符串会自动连接成一个字符串:

                   注意:只能是宏与字符串常量之间进行连接或字符串常量与字符串常量进行连接或宏与宏进行连接,但不能是字符串常量与字符串指针的连接:

                                如:#define NAME “Linda”

                                        printf("this is " NAME);

                                        printf("these are " NAME "'s apple, all are %d\n",number);

                                        #define PRINT(FORMAT,VALUE)  \

                                        printf("The value is "FORMAT "\n",VALUE)

                                     则:PRINT("%d",int_a );  相当printf("The value is%d\n",int_a);

                                             PRINT("%f",float_b );  相当printf("The value is%f\n",float_b);

                                             PRINT("%c",char_c);  相当printf("The value is%c\n",char_c);

                                            //这里的FORMAT就是字符串替换且连接,而VALUE就是简单的替换而已。

                                             

                       一种是替换文本为不带双引号的符号序列,则需要用 #转成字符串后再替换,用法为
 #替换文本  :

                                如:#define PRINT(FORMAT,VALUE)  \

                                       printf("The value of "#VALUE "is" FORMAT “\n",VALUE);

                                        则:PRINT("%d",
x+3);想当于printf("The value of x+3 is %d “\n",x+3);

连接成标识符:(标识符可以是变量名、函数名等)

                用##来连接两边的符号拼成标识符:

                           #define a(n) a##n  //n作为宏参数

                           int a1 = 3 , a2 = 24 ,a3 = 9;

                           for(int i = 1;i<4;i++)

                           printf("%d\t",aN(i));

   则相当于:printf("%d\t",a1);

                           printf("%d\t",a2);

                           printf("%d\t",a3);

                 ##还会阻止外层调用参数的扩展,因为##会将参数连接成标识符,标识符不能够参与宏的替换,因为标识符不能识别宏:

                           如:#define CAT(x,y)   x##y

                           那么CAT(CAT1,2),3)将生成的是CAT(1,2)3,而不是123,

                           因为外层的CAT替换后,就会将里层的CAT(1,2)和3作为标识符连接起来,既然作为标识符了,那么就不能被编译器识别宏,因为只对符号才识别宏,而标识符                              不行

                           但若是#define XCAT(x,y)   CAT(x,y)

                           那么XCAT(XCAT(1,2),3)将生成123

(3)宏函数

                 宏函数不仅是在表达式外部要用()括起来,单个宏参数也要用()括起来:

                 如:#define  ADD(a,b)   ((a) + (b))
                 宏函数不同于函数,没有参数类型限制,它仅仅只是替换,所以可以适用于任何类型,有点类似于C++的函数模板:

                 如:ADD( 3 ,15);

                         ADD(19.62 , 20.03);

                         ADD('a' + 'm');

                  宏函数使用宏参数时要注意宏参数的副作用,因为宏参数每被替换一次都会进行一次参数求值,尤其要注意能改变参数值的宏参数:

                  如:#define  MAX( a , b)    ( (a) > (b) ? (a) : (b) )

                          则此时若是传入的参数在参数求值是本身能改变参数的值,则这样替换后得到的表达式就有副作用:

                          如  m = 3; n = 5;   z = MAX(m++,n++); ,则得到的是  z = ((m++) > (n++) ? (m++) : (n++)); 得到的结果是m = 4, n = 7 ,z = 6 ,因为这样替换后的表达式本身是有副作用的,m被求值一次,可n是被求值了两次的,所以我们要遵守一个表达式的原则,即当表达式有一个参数时,那么就不允许再出想这个参数的自增或自减或
                                  是能改变这个参数本身值的函数。

                  宏函数 VS 函数:

                            优点:宏函数的使用限制:

                                       从执行速度上看:宏函数的使用使用是要看情况的,有时候使用宏函数益处多多,有时候使用的话则是得不偿失。当宏函数的代码仅短短三五行时,用宏                                                                        函数比函数要好,因为在调用、返回函数时本身开销是比较大的,那如果额外开销还大于做功开销(执行代码的开销),那就不值得了。

                                       从通用性来看:宏函数因为是替换,所以可以用于不同类型的参数,而函数是比较单一的,只能用于固定的参数类型和返回值。

                            缺点:

                                       从代码长度上看:因为宏函数是替换,而函数是调用,若代码本身比较长,则宏函数的使用相对调用函数来说会使代码增长。

                                       从结果上看,宏函数比价不容易预测,除了内外要加两层次的()外,还要注意参数不能有副作用

二、文件包含

          补充说明:文件中只能是有(其它源文件的外部变量的外部声明extern 、其它源文件的函数声明、宏定义)三种东西。
          文件包含有两种形式:
          本地头文件包含:---------- 以用户为出发点
                                          #include " xxx.h"      
                                          #include ”/up/cmp/xxx.h"
                                         本地包含首先是在当前目录或者是相对路径目录上进行对头文件的查找,若是找不到再到函数库头文件的标准路径/user/include上进行查找。

          函数库头文件包含:---------以标准库为出发点

                                           #include<stdio.h

                                          直接进入标准库头文件路径/user/include进行查找        

          ------------------------  ” “ 与< > 的作用是差不多的,但由于路径查询的原因,本地头文件不能用< > ,而标准函数库头文件用" " 也是可以的,因为< > 的查找比" " 更窄。

三、条件编译

条件编译的方式有以下三种:

表达式方式:
#if  表达式1
        ……
#elif  表达式2
        …… 
#endif
是否定义方式:
#ifdef
……
#endif

#ifndef
……
#endif

条件编译主要用于以下几个方面:

1、用于头文件中的内容,避免重复声明和定义。因为头文件多为声明和宏定义,若是头文件被一个工程里的不同文件重复包含了,那么就会造成此头文件内的宏定义与函数声明重复定义和重复声明了。那么如何只让头文件中的内容只声明、定义一次呢,其实我们只要在头文件第一次包含的时候做这种定义、声明的动作就可以了。那么如何让编译器识别该文件是第一次还是后几次包含呢,为此,我们需要在头文件在第一次包含时做一个记号,用以标志此头文件已经包含过了,那么后面的头文件包含就可以省略了。我们通过定义一个宏的方式来做这个标记,用判断宏是否存在来判断是第一次包含需做标记并编译还是需要忽略,而用条件编译的方式来决定是否编译还是忽略。
          如:在add.h 中
                  #ifndef _ADT_H_

                  #define _ADT_H_


                 #define USE "drinking"

                 extern int pro_hour;

                 extern int pro_minute;

                 extern int pro_second;

                 int usesec(int h, int  m,int s);

                 #endif
2、用于调试的时候进行选择编译
3、用于针对不同功能时的选择编译,功能的选择可以根据在gcc编译命令添加宏定义来选择,然后根据不同的宏定义来条件选择编译
4、用于针对不同平台的参数和实现方式不同,而进行选择编译

                      

                              

                   

                                       

                                       

     
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐