C语言预处理指令
C语言中允许在源程序中加入一些“预处理指令”,以改进程序设计环境,提高编程效率。这些预处理指令是由C标准建议的,但他不是C语言本身的组成部分,不能用C编译系统直接对它们进行编译(因为编译程序不能识别他们)。必须在对程序进行正式编译之前,先对程序中这些特殊的指令进行**“预处理**”(也称“编译预处理”或“预编译”)。把预处理指令进行的预处理工作,是由称为C预处理器的程序负责处理的。
C提供的预处理功能常用的主要有一下3种
1.宏定义
2.文件包含
3.条件编译
这些功能分别用宏定义指令、文件包含指令和条件编译指令来实现。为了与C语言相区别,这些指令以符号"#"开头,指令后面没有分号。
1.1宏定义
1.1.1 不带参数的宏定义
概念:不带参数的宏定义是比较简单的,就是用一个指定的标识符来代表一个字符串,它的一般形式为:
#define 标识符 字符串
例:
#define PI 3.1415926
作用:
在本程序文件中用指定的标识符PI来代替3.1415926这个字符串。在进行预处理时,将程序中凡事在该指令以后出现的所有的PI都用3.1415926代替。因此把这个标识符称为
“宏名”,在预处理时将宏名替换成字符串的过程称为"宏展开"。#define就是宏定义指令。
说明:
①宏定义只是用宏名代替一个字符串,也就是简单的置换,不作正确性检查。
如写成
#define PI 3.14l5926
预处理时不作任何语法检查,只有对已被宏展开后的源程序进行编译时才会发现语法错误并报错。
②宏定义不是C语句,不用在结尾加分号,如果加了分号则一起进行置换。
#define PI 3.1415926
area=PI*r*r;
经过宏展开后,改语句应为:
area=3.1415926;*r*r;
③#define 指令出现在程序中的函数的外面,宏名的有效范围为该指令行起打本源文件结束。通常,#define 指令写在文件开头,函数之前,作为文件的一部分,在整个文件范围内有效。
④可以用#undef指令终止宏定义的作用域。例如:
#define G 9.8
int main()
{
...
}
//G的有效范围到此结束
#undef G
f1()
{
...
}
⑤再进行宏定义时,可以引用已定义的宏名,即可以层层置换。
⑥在程序中用双撇号括起来的字符串内的字符,即使与宏相同,也不进行置换。例如
printf("L=%f \n S=%f\n",L,S);
printf函数内有两个L。在双撇号内不被宏置换,另一个被置换。
⑦宏定义与定义变量的含义不同,不分配存储空间。不带参数的宏定义只作简单的字符串替换,千万不要把宏名当作变量名使用。
1.1.2带参数的宏定义
带参数的宏定义不是进行简单的字符串替换,还要进行参数替换。其定义的一般形式为:
#define 宏名(参数表) 字符串
字符串中包含在括号中所指定的参数。例如:
#define S(a,b) a*b
...
area=S(3,2)
因此赋值语句置换为:
area=3 * 2 ;
说明:
①函数调用时,先求出实参表达式的值,然后代入形参。而使用带参数的宏只是进行字符替代。
②函数调用是在程序运行时处理的,为形参分配临时的内存单元。而宏置换则是在预处理阶段进行的,在置换时并不分配内存单元,不进行值的传递处理,也没有"返回值"的概念。
③对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换。而宏不存在类型问题,宏名无类型,它的参数也是无类型,只是一个符号代表,置换时,代入指定的字符串即可。定义宏时,字符串可以是任何类型的数据。
④调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
⑤使用宏次数多了,宏展看后源程序变长,因为每展开一次都使程序增长,而函数调用不会使源程序变长。
⑥宏替换不占运行时间,只占预处理时间,而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
1.2“文件包含”处理
#include"文件名"
所谓"文件包含"处理是指一个源文件可以将另外一个源文件的全部内容包含进来, 即将另外的文件内容包含到本文之中,插入到当前的位置。C语言用#include指令来实现“文件包含”的操作。其一般形式为:
#include"文件名"
或
#incldue <文件名>
这两者的区别在于:
用尖括号(如<stdio.h>)形式时,系统到存放C库函数头文件的目录中寻找要包含的文件,这称为标准方式。
用双撇号(“file2.h”)形式时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。
说明:
①一个#include 指令只能指定一个被包含文件,如果要包含n个文件,要用n个#include 指令。
②如果文件1需要包含文件2,文件2 又要用到文件3,则可在文件1中用到两个 #include 指令分别包含文件2和文件3,并且文件3应出现在文件2之前,即在file1.c中定义 :
#include "file3.h"
#include "file2.h"
由于file3.h的位置在file2.h之前,file2和file1都可以用file3.h的内容。
③头文件除了可以包含函数原型和宏定义外,也可以包含结构体类型定义和全局变量定义等。
1.3条件编译
条件编译的以下三种形式:
第一种:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的作用是:若所指的标识符以及被#define 指令定义过,则在程序编译阶段对程序段1 进行编译;否则编译程序段2。即在最后提供编译的源程序中只包含程序段1,或只包含程序段2。
条件编译中的#else部分可以没有,即:
#ifdef 标识符
程序段
#endif
这里的“程序段”可以是语句组,也可以是指令行。这种条件编译对于提高C源程序的通用性是很有好处的。如果一个C源程序在不同计算机系统上运行,而不同的计算机又有一定的差异(有的机器以16位来存放一个整数),而有的则以32位存放一个整数,在这样的不同计算机上编译程序时往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译来处理:
#ifdef COMPUTER_A
#define INTEGER 16
#else
#define INTEGER_SIZE 32
#endif
第二种
#ifndef 标识符
程序段1
#else
程序段2
#endif
同第一种形式相比,只是第一行不同:将ifdef
改成ifndef。它的作用是:若指定的标识符未被定义过,则编译程序段1;否则编译程序段2。这种形式与第一种的形式相反。
第三种
#if 表达式
程序段1
#else
程序段2
#endif
它的作用是当指定的表达式值为真(非零)时就编译程序段1;否则编译程序段2。可以事先给定条件,使程序在不同的条件下执行不同的功能。
本文来源:《C程序设计》谭浩强