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 | 用指定的信息产生一个编译时的错误 |
使用宏可以减少代码的冗余,让代码易于修改、移植。宏的主要作用如下:
定义的一般形式为:
#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,原意定义两个字符指针,结果变成了定义一个字符指针、一个字符变量了。
#define 宏名(形参表) 字符串
#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
#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" | 具体的类型定义 |
结论:头文件的相对路径,是相它要包含它的源文件的相对路径。
#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中一定是常量表达式,常量表达式。何谓常量表达式,就是一定是常量参与运算,其表达式可以与算术运算符、逻辑运算符、关系运算符、位操作符等进行给合,但不能有赋值运算符、逗号表达式如下:
现在编译器支持的常量表达式就是整型与字符型
void print(){
int canPrint=1;
#if (canPrint)
{
printf("能打印的!\n");
}
#else
{
printf("不能打印的!\n");
}
#endif
}
如上代码,可以通过编译,但执行代码部分永远是#else部分,即#if中的canPrint与程序代码中的canPrint在编译器看来是两个不同东东。
#ifdef 宏名
程序段1
#else
程序段 2
#endif
#ifndef 宏名
程序段1
#else
程序段2
#endif
这种形式最常用来定义一个宏,避免宏多次定义。如代码:
#ifndef US
#define US 1
#endif
如希望我们是不是在源代码什么地方定义US这个宏,如果查源代码那多麻烦,要main函数中放置如下代码,在编译时如果有信息提示,就说说明我们已经定义,很方便。
#ifdef US
#pragma message("已经定义了US这个宏……")
#endif
如使用#pragma code_seg可将代码段复位,开发驱动时需要这玩意。
#pragma warning(disable:4507 34;once:4385;error:164)含义为对警告信息代号:4507、34禁止提示,代码为4385的就提一次。代码为164则当错误处理。Gcc编译器有-Werror表示把所有警告信息当错误,这个指令实现了把个别警告当作错误处理,应该实用。(好像gcc对该指令进行了忽略)
#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 请按任意键继续. . . |
评论