登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

仙剑奇侠传的博客

羽化成仙

 
 
 

日志

 
 

C语言中的预处理  

2011-08-10 16:57:44|  分类: C |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

C预处理器是一种简单的宏处理器,从概念上说,它在编译器读取源程序之前对c程序的源文本进行处理。在c的有些实现中,预处理器实际上是一个独立的程序,它读取最初的源文件并写入到一个新的"经过预处理"的源文件,后者可以作为c编译器的输入。在c的其他实现中由一个程序一次完成对源文件的预处理和编译。预处理命令以#符号开头,不包括#开头的称源程序文本行。在gcc中.i后缀文件名为预处理完成之后的文件。用gcc –o demo.i –E demo.c等类同指令生成。

预处理器命令列表

命令

含义

#define

定义一个预处理器宏

#undef

取消一个预处理器宏

#include

插入另一个源文件的文本

#if

根据一个常量表达式的值,有条件的包含一些文本

#ifdef

根据一个宏是否定义,有条件地包含一些文本

#ifndef

根据与#ifdef正好相反的定义,有条件地包含一些文本

#else

如果它前面的#if\#ifdef\#ifndef\#eif测试失败就包含一些文本

#endif

终止条件文本

#line

提供用于编译信息的行号

#elif

如果它前面的#if\#ifdef\#ifndef\#elif测试失败,根据另一个常量表达式的值包含一些文本

defined

如果一个名称已经被定义为预处理器宏,则返回1,否则0

#操作符

用一个包含参数值的字符串常量替换一个宏参数

##操作符

结合两个相邻的标记,创建单个标记

#pragma

指定依赖编译器信息

#error

用指定的信息产生一个编译时的错误

 

 

  1. 使用宏

    使用宏可以减少代码的冗余,让代码易于修改、移植。宏的主要作用如下:

  • 实现替换,减少编码的手工录入量
  • 具有 一定的模板作用,避免因数据类型不同需要重复定义大量相近的函数
  1. 无参宏的定义

    定义的一般形式为:

    #define 标识符 字符串

    例:

    #define PI 3.1415926

    宏定义说明:

    1)、编写代码时宏定义,编译器进行编译是叫宏展开。是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

    2)宏定义的范围为:#define开始至文档末尾处或#define开始到#undef结束。这儿的文档指的是把 include展开之后的文档。

     

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

#define SWAP(a,b) a=a^b;b=a^b;a=a^b

#include<stdio.h>

#include<stdlib.h>

int main(){

    printf("我的第一个宏程序");

    int a=12,b=21;

    float aF=12.01,bF=21.01;

    wchar_t ac='是',bc='否';

    printf("返回最大的值%d",MAX(25,28));

SWAP(a,b);

    printf("变量a的值是%d,b的值是%d\n",a,b);

/*SWAP(aF,bF);//浮点数不能作异或操作

    printf("变量a的值是%f,b的值是%f\n",aF,bF);*/

#undef SWAP(a,b)

    //以下代码会出问题,宏定义已取消

SWAP(ac,bc);

printf("变量ac的值是%lc,bc的值是%lc\n",ac,bc);

    return 1;

}

3)c编译器在宏展开时默认会在字符串末尾增加";",所以宏定义的字符串最好不要以";"结尾

4)可以使用宏定义数据类型,但是易引起误解。如#define pchar char*;

用pchar a,b得到宏展开后为:char *a,b,原意定义两个字符指针,结果变成了定义一个字符指针、一个字符变量了。

  1. 带参数宏的定义

    #define 宏名(形参表) 字符串

    1. 形参表与与宏名之间不能有空隔,有空隔就把变成了无参数宏定义了,显然不符合原义。
    2. 宏展开之后,可能产生原义有差别。如宏定义

      #define Ex(y) y*y

      调用:

      int i=8;

      printf("%d",Ex(i++));

      期盼输出为81,结果输出为64

      因为宏的形参不会自动计算,只会进行替换。

      同理Ex(i+1)展开之后变了i+1*i+1=8+8+1=17,原意为9*9=81

    3. 宏的形参只存在符号替换,不存在形参计算
  1. 文件包含

#include表示文件包含。#include<>表示在编译器设定的系统目录去寻找相关文件,#include""表示在源目录去寻找相关文档。如目录结构如下:

如上所示目录结构,假定demo.c需要demo.h,demo.h需要defineDemo.h,那么在三个文件中定义的#include内容如下:

Demo.c

Demo.h

defineDemo.h

#include"stdlib/demo.h"

#include"head/defineDemo.h"

具体的类型定义

 

结论:头文件的相对路径,是相它要包含它的源文件的相对路径。

  1. 条件编译
    1. 第一种形式:

#if 常量表达式 (或defined表达式)

程序段1

#elif 常量表达式(或defined表达式)

程序段2

……

#else

程序段n

#endif

如果常量表达式不为0,则编译#if段代码,否则为#else段代码。常量表达式为整型与字符型,其他类型一律无效。标准C89引入defined关键字,用来判断一个宏是否已经定义,#if defined(宏名)=#ifdef 宏名,但是#if defined更灵活。示例如下:

#include<stdio.h>

#include<stdlib.h>

#ifndef US

#define US 1

#endif

#define CHINA 1

#define JPN 1

int main(){

    #if defined(US)&&defined(CHINA)

printf("中国人说美式英语\n");

    #elif defined(US)&&!defined(CHINA)

     printf("世界人说美式英语\n");

    #elif defined(US)&&defined(JPN)

printf("日本人说美式英语\n");

    #else

printf("世界人说英式英语\n");

    #endif

system("pause");

return 1;

}

注意事项

1)#elif不是写成#elsif,这个语句不是pascal与oracle的elsif。

2)一定要有#endif来结尾,表示条件编译到此结束

3)#if或elif中一定是常量表达式,常量表达式。何谓常量表达式,就是一定是常量参与运算,其表达式可以与算术运算符、逻辑运算符、关系运算符、位操作符等进行给合,但不能有赋值运算符、逗号表达式如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

现在编译器支持的常量表达式就是整型与字符型

  1. 表达式不能引用程序中的任何变量,即使引用,其表达式值为0

    void print(){

        int canPrint=1;

        #if (canPrint)

        {

    printf("能打印的!\n");

        }

        #else

        {

         printf("不能打印的!\n");

        }

        #endif

    }

    如上代码,可以通过编译,但执行代码部分永远是#else部分,即#if中的canPrint与程序代码中的canPrint在编译器看来是两个不同东东。

  1. 第二种形式

    #ifdef 宏名

    程序段1

    #else

    程序段 2

    #endif

  2. 第三种形式

    #ifndef 宏名

    程序段1

    #else

    程序段2

    #endif

    这种形式最常用来定义一个宏,避免宏多次定义。如代码:

#ifndef US

#define US 1

#endif

 

  1. #pragma指令,这是一个与编译器相关的指令。作用是设定编译器的状态或指示编译器完成一些特定的动作。用法为#pragma para .其中para为参数。下面看para常用参数:
    1. message参数,能够在编译信息输出窗口输出信息,对源代码信息的控制很重要,要一些关键宏是否定义了,可以用这个来提示。使用为:#pragma message("消息文档")

      如希望我们是不是在源代码什么地方定义US这个宏,如果查源代码那多麻烦,要main函数中放置如下代码,在编译时如果有信息提示,就说说明我们已经定义,很方便。

      #ifdef US

      #pragma message("已经定义了US这个宏……")

      #endif

    2. code_seg参数。可以设置函数代码存放的代码段,格式为:#pragma code_seg([/section-name/[,/section-class/]])

      如使用#pragma code_seg可将代码段复位,开发驱动时需要这玩意。

    3. once参数,只要在头文件的最开始加入这条指令,能保证头文件只编译一次,提高编译速度。使用格式:#pragma once
    4. hdrstop参数,在头文件与代码中使用,表示预编译头文件到此为止,后面的文件不进行预编译。BCB可以预编译头文件以加快连接的速度,但如果所有头文件进行预编译又占用磁盘空间,所以使用这个选项排除一些头文件。可以用#pragma startup指定编译优先级。
    5. resource参数,表示关联相关资源。如#pragma resource /*.dfm/ 表示把*.dfm文件中的资源加入工程。Dfm文件是delphi、c++buider的窗体定义文件。
    6. warning,对编译器警告信息的处理。如:

      #pragma warning(disable:4507 34;once:4385;error:164)含义为对警告信息代号:4507、34禁止提示,代码为4385的就提一次。代码为164则当错误处理。Gcc编译器有-Werror表示把所有警告信息当错误,这个指令实现了把个别警告当作错误处理,应该实用。(好像gcc对该指令进行了忽略)

    7. comment,将一个注释放一个对象文件或可执行文件
    8. pack(n)改变c编译器的字节对齐方式(在对齐中专讲)示例:

      #include<stdio.h>

      #include<stdlib.h>

      #ifndef US

      #define US 1

      #endif

      #define CHINA 1

      #define JPN 1

      #ifdef US

      #pragma message("已经定义了US这个宏……")

      #endif

      #pragma pack(4)

      typedef struct

      {

          int a;

          int b;

          char c;

          char c1;

          float d;

          double e;

      }demoStruct;

      int main(){

          //#pragma code_seg

          #if (US^0B00000001)==(CHINA+1)

           printf("中国人说美式英语\n");

          #elif (US)&&!(CHINA)

           printf("世界人说美式英语\n");

          #elif defined(US)&&defined(JPN)

      printf("日本人说美式英语\n");

          #else

              printf("世界人说英式英语\n");

          #endif

          demoStruct ds;

          printf("ds.a的地址是:%0x\n",&ds.a);

          printf("ds.b的地址是:%0x\n",&ds.b);

      printf("ds.c的地址是:%0x\n",&ds.c);

          printf("ds.c1的地址是:%0x\n",&ds.c1);

          printf("ds.d的地址是:%0x\n",&ds.d);

          printf("ds.e的地址是:%0x\n",&ds.e);

          printf("ds的结构大小为:%d",sizeof(ds));

      system("pause");

      return 1;

      }

      如以代码如把pack(4)改成pack(1)则sizeof(ds)的大小为22,如是pack(4) 则sizeof(ds)的大小为24.

五、#error,作用是在编译是输出错误消息,而#pragma message是编译时译出消息,使用例子如下:

#include<stdio.h>

#include<stdlib.h>

int main(int arg,char * argc[]){

    #define US 1

    #ifdef US

        printf("世界人说美式英语\n");

    #else

        printf("世界人说英式英语\n");

    #endif

    #undef US

    #ifndef US

        #error 没有定义宏--US

    #endif

 

    system("pause");

    return 1;

}

 

编译时提示如下:

errorDemo.c: In function 'main':

errorDemo.c:12:4: error: #error 没有定义宏--US

 

从例子可以看出,使用#error相当于自定义编译器错误信息

六、#line.此命令强制编译器按指定的行号开始对源代码进行重新编号。使用格式为

#line 行号 "filename".示例为:errorDemo.c

#include<stdio.h>

#include<stdlib.h>

int main(int arg,char * argc[]){

    #define US 1

    #ifdef US

        printf("世界人说美式英语\n");

    #else

        printf("世界人说英式英语\n");

    #endif

    #line 89 "hello.c"

    #undef US

    #ifndef US

        #error 没有定义宏--US

    #endif

 

    system("pause");

    return 1;

}

编译时指令:gcc -errorDemo errorDemo.c –Wall,提示如下:

hello.c: In function 'main':

hello.c:91:4: error: #error 没有定义宏--US

 

从编译出的信息可看,文件名已变成hello.c,行号成了91行了。

七、运算符#与##.

#的作用是在字符串中实现文本替换。如:

#include<stdio.h>

#include<stdlib.h>

#define HI(x) printf("hello!"#x"\n");

int main(int arg,char * argc[]){

HI(John);

system("pause");

return 1;

}

输出是:

hello!John

##的作用就是串连接,例子如下:

#include<stdio.h>

#include<stdlib.h>

#define HI(x) printf("hello!"#x"\n");

#define CONNECT(x,y) x##y

int main(int arg,char * argc[]){

HI(John);

int a1,a2;

CONNECT(a,1)=100;

CONNECT(a,2)=110;

printf("a1的值是%d,a2的值是%d\n",a1,a2);

system("pause");

return 1;

}

输出如下:

hello!John

a1的值是100,a2的值是110

请按任意键继续. . .

 

 

 

 

  评论这张
 
阅读(353)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018