第九章 编译预处理
编译指令(编译预处理指令)C源程序除了包含程序命令(语句)外,还可以使用各种编译指令(编译预处理指令)。编译指令(编译预处理指令)是给编译器的工作指令。这些编译指令通知编译器在编译工作开始之前对源程序进行某些处理。编译指令都是用“#”引导。
编译预处理:编译前根据编译预处理指令对源程序的一些处理工作C语言编译预处理主要包括宏定义、文件包含、条件编译。
编译工作实际分为两个阶段:编译预处理、编译。广义的编译工作还包括连接。
9、1  宏定义
宏定义:用标识符来代表一个字符串(给字符串取个名字)。C语言用“#define”进行宏定义。C编译系统在编译前将这些标识符替换成所定义的字符串。
宏定义分为不带参数的宏定义和带参数宏定义。
9、1、1
#define 标识符 字符串
不带参数宏定义(简单替换)
1、不带参数宏定义格式:                          其中:标识符-宏名。
2、宏调用:在程序中用宏名替代字符串。
3、宏展开:编译预处理时将字符串替换宏名的过程,称为宏展开。
#define PI 3.14
main()
{
  float r=3,s,c;
  s=PI*r*r;  c=2*PI*r;
  printf(r,s,c);
}
main()
宏展开
{
  float r=3,s,c;
  s=3.14*r*r;  c=2*3.14*r;
  printf(r,s,c);
}
宏定义,其中PI宏名:代表3.14
例如:
说明:
(1) 宏名遵循标识符规定,习惯用大写字母表示,以便区别普通的变量。
(2) #define之间不留空格,宏名两侧空格(至少一个)分隔。
字符串常量是一对用什么括起来的
(3) 宏定义字符串不要以分号结束,否则分号也作为字符串的一部分参加展开。从这点上看宏展开实际上是简单的替换。
例如:#define PI 3.14;  展开为s=3.14*r*r(导致编译错误)
(4) 宏定义用宏名代替一个字符串,并不管它的数据类型是什么,也不管宏展开后的词法和语法的正确性,只是简单的替换。是否正确,编译时由编译器判断。
例如:#define PI 3.I4 照样进行宏展开(替换),是否正确,由编译器来判断。
(5) #define宏定义宏名的作用范围从定义命令开始直到本源程序文件结束。可以通过#undef终止宏名的作用域。
#define E 2.71828
main()
{
  fun();
  printf(“%f”,E);
}
#undef E
#define E “abc”
fun()
{
  char *s=E;
  printf(“%s\n”,s);
}
#deine PI 3.14
#define R 3.0
#define L 2*PI*R
#define S PI*R*R
main()
{  printf(“L=%f,S=%f”,L,S);
}
(6) 宏定义中,可以出现已经定义的宏名,还可以层层置换。
(7) 宏名出现在双引号“”括起来的字符串中时,将不会产生宏替换。(因为出现在字符串中的任何字符都作为字符串的组成部分)
(8) 宏定义是预处理指令,与定义变量不同,它只是进行简单的字符串替换,不分配内存。
(9) 使用宏的优点:
程序中的常量可以用有意义的符号代替,程序更加清晰,容易理解(易读)。
常量值改变时,不要在整个程序中查,修改,只要改变宏定义就可以。比如,提高PI精度值。
带参数宏定义比函数调用具有更高的时间效率,因为相当于代码的直接嵌入。(空间效率:多次调用占用空间较多,一次调用没有什么影响)。
9、1、2 带参数宏定义
带参数宏定义不只是进行简单的字符串替换,还要进行参数替换。
1、 带参数宏定义的格式:
例如:
#define S(a,b) a*b
其中S-宏名,a,b是形式参数。程序调用S(3,2)时,把实参3,2分别代替形参ab
area=S(3,2); =>  area=3*2;
2、 带参数宏定义展开规则:
在程序中如果有带实参的宏定义,则按照#define命令行中指定的“字符串”从左到右进行置换(扫描置换)。如果串中包含宏定义中的形参,则将程序中相应的实参代替形参,其它字符原样保留,形成了替换后的字符串。
注意:还是一个字符串的替换过程,只是将形参部分的字符串用相应的实参字符串替换。
宏展开:
a,b用i,j替换,其它照抄
例9-1:用带参数宏定义表示两数中的较大数
#define MAX(a,b)  (a>b)?a:b
main()
{
  int i=15,j=20;
  printf(“MAX=%d\n”,MAX(i,j));=>  printf(“MAX=%d\n”,(i>j)?i:j);
}
注意:
(1)正因为带参宏定义本质还是简单字符替换(除了参数替换),所以容易发生错误。
例如:
#define S(a,b) a*b
程序中area=S(a+b,c+d);=>area=a+b*c+d;明显和我们的意图不同。
假如:宏定义的字符串中的形参用()括号括起来,即:
#define S(a,b) a*b
此时程序中:
area=S(a+b,c+d);=>area=a+b*c+d;符合我们的意图。
为了避免出错,建议将宏定义“字符串”中的所有形参用括号()括起来。以后,替换时括号作为一般字符原样照抄,这样用实参替换时,实参就被括号括起来作为整体。不至于发生类似错误。
(2)定义带参数宏时还应该注意宏名与参数表之间不能有空格。有空格就变成了不带参数的宏定义。
如:#define S  (r)  PI*r*r
area=S(3.0);  => area=(r) PI*r*r(3.0);
(3)带参数的宏定义在程序中使用时,它的形式及特性与函数相似,但本质完全不同。区别在下面几个方面:
函数调用,在程序运行时,先求表达式的值,然后将值传递给形参;带参宏展开只在编译时进行的简单字符置换。
函数调用是在程序运行时处理的,在堆栈中给形参分配临时的内存单元;宏展开是在编译时进行,展开时不可能给形参分配内存,也不进行“值传递”,也没有“返回值”。
函数的形参要定义类型,且要求形参、实参类型一致。宏不存在参数类型问题。
如:程序中可以MAX(3,5)也可以MAX(3.4,9.2)
(4)许多问题可以用函数也可以用带参数的宏定义。
(5)宏占用的是编译时间,函数调用占用的是运行时间。在多次调用时,宏使得程序变长,而函数调用不明显。
例9-3:计算三角形面积。(使用带参数宏定义)
作业:(1)将S(a,b,c)改为S(a1,b1,c1);AREA(a,b,c)改为AREA(a2,b2,c2).main()中a,b,c改为i,j,k重新写9-3程序。(2)对最后printf进行宏展开(分两步走)。
9、2 文件包含
文件包含:一个C源文件可以使用文件包含命令将另外一个C源文件的全部内容包含进来。
#include “文件名#include <文件名>
格式:
        file1.c                              file2.c                                    file1.c
说明:
(1) 被包含的文件常常被称为“头文件”(#include一般写在模块的开头)。头文件常常以“.h”为扩展名(也可以用其它的扩展名,.h只是习惯或风格)。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。