预处理命令概述
- 所谓编译预处理就是在编译程序对C源程序进行编译前,由编译预处理程序对这些编译预处理指令行进行处理的过程。
- C语言中,以 “#” 开头的行,都称为编译预处理指令行,每行的末尾 没有“;” 。
C提供的预处理功能主要有以下3种:
- 宏定义
- 文件包含
- 条件编译
宏定义
无参宏
无参宏的定义格式:
#define
标识符
字符串
define
为宏定义命令。标识符
为所定文的宏名,通常用大写字母表示,以便于与变量区别。字符串
可以是常数、表达式等。
例如:
#define PAI 3.1415926
- “宏”:用一个标识符来表示一个字符串。
- “宏名”:被定义为“宏”的标识符。
- “宏替换”:在编译预处理时,预处理程序将程序中所有出现的“宏名”,都用宏定义中的字符串去替换。
完成后,才将程序交给编译程序去处理。
使用宏定义的优点:
- 可提高源程序的可维护性;
- 可提高源程序的可移植性;
- 减少源程序中重复书写字符串的工作量。
关于宏定义几点说明:
- 宏名一般用大写字母表示,以示与变量区别。但这并非是语法规定。
- 宏定义不是C语句,所以不能在行尾加分号。
- 在宏展开时,预处理程序仅按宏定义简单替换宏名,而不作任何检查。
- 宏定义命令#define出现在函数的外部,宏名的有效范围是:从定义命令之后,到本文件结束。
在进行宏定义时,可以引用已定义的宏名。
- 例如:
# defineR 3
# define PI 3.14159
# define S PI*R*R
对双引号括起来的字符串内的字符,即使与宏名同名,也不进行宏展开。
- 例如:
printf("R=%f,S=%f",R,S)
- 例如:
符号常量
- 在定义无参宏时,如果宏定义中的“字符串”是一个常量,则相应的““宏名” 称为“符号常量”。
- 例子:
#include<stdio.h>
#define A 1+2 // 没有括号
void main() {
int a;
a = A * 2; // 替换后 a = 1+ 2 * 2 所以a = 5
printf("a = %d,A = %d\n", a, A);
}
有参宏
有参宏的定义格式:
#define
宏名(参数表)
字符串
例如:
#define
ADD(X,Y)
(X+Y)
有参宏的调用和宏展开
- 调用格式:
宏名(实参表)
- 宏展开:用宏调用提供的实参字符串,直接替换宏定义命令行中相应形参字符串,非形参字符保持不变。
- 调用格式:
- 例子:
#include<stdio.h>
#define PAI 3.1415926
#define S(r) PAI*(r)*(r)
void main() {
double a;
printf("请输入半径:\n");
scanf_s("%lf", &a);
printf("半径为%.2f的圆的面积:%.2f\n", a, S(a));
}
- 关于有参宏的几点说明:
1.定义有参宏时,宏名与左圆括号之间不能留有空格。
#define
S(r)
PAI*(r)*(r)
- 上例中在 S 和 (r) 之间,不能有空格。
如果写成了#define
S
(r)
PAI*(r)*(r)
- 表示 宏名S 所替换的字符串为
(r) PAI*(r)*(r)
。
2.有参宏定义中,形参不分配内存单元,因此形参不必做类型定义;
而宏替换中的实参有具体的值要用它们去代换形参,因此实参必须做类型说明。- 在有参宏中,只是符号替换。
- 3.调用有参宏名时,一对圆括号必不可少,圆括号中实参的个数应该与形参个数相同,
如果有多个参数,参数之间用逗号隔开。 - 4.在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
宏替换时对实参表达式不做计算直接照原样替换。 - 5.在宏定义中,字符串内的形参和整个表达式通常要用括号括起来以避免出错。
- 例子:
# include< stdio.h >
# define A(X,Y) X*Y
void main()
{
int a, s;
float w;
printf("请输入a的值:\n");
scanf_s("%d", &a);
s = A(a, a + 1);
w = 6 / A(a, a);
printf("s = a*a+1 = %d,w = 6/a*a = %.2f\n", s, w);
}
- 写成如下形式:
#define
A(X,Y)
(X)*(Y)
- 第1次调用宏计算 s 值时,宏替换后的语句:
s = ((a)*(a+1));
- 第2次调用么计算 w 值时,宏替换后的语句:
w = 6/((a)*(a));
- 结果就是
s = ((a)*(a+1)) = 42, w = 6/((a)*(a))= 6.00
- 宏定义时应在参数两侧加括号,也应在整个字符串外加括号。
文件包含
- 文件包含是指在一个文件中,去包含另一个文件的全部内容。
C语言用#include指令实现文件包含的功能。 文件包含的语法格式:
- 首先在源码当前目录下面寻找该头文件,此方法通常用于包含自己定义的头文件。
#include
"文件名"
- 首先在编译器默认的include目录下寻找该头文件,此方法通常用于包含标准库头文件。
#include
<文件名>
例如:
#include
<stdio.h>
#include
<math.h>
#include
"diy.h"
- 在编译预处理阶段,预处理程序将用指定文件中的内容来替换此行。
从而把指定的文件和当前的源程序文件连成一个源文件。 - 如果程序很大,最好分为几个不同的文件,每个文件含有一组函数。这些文件用#include将它们包含在主程序的开始处。
- 有一些函数和宏几乎所有的程序中都会用到。可以将这些常用函数和宏定义存放在一个文件中,
将这个文件包含在所写的程序中,该文件的内容就会插到程序中。 - 被包含的文件扩展名可以为
.h
,此扩展名为头文件。一般包含在程序的头部。 - 所有库函数被分成不同的类别,存放于不同的文件中。
使用文件包含命令时要注意以下几点:
- 1.当被包含文件修改后,包含该文件的源程序必须重新进行编译连接。
- 2.一个文件包含命令只能指定一个被包含文件,如果要包含多个文件,则应使用多个文件包含命令。
#include
<stdio.h>
#include
<string.h>
#include
<math.h>
- 3.文件包含允许嵌套,即在一个被包含的文件中又可包含另一个文件。
- 在编译预处理时,要对 #include 命令进行”文件包含”处理,将 f2.c 的全部内容插入到 #include"f2.c" 命令处,得到所示的结果.在编译时,对 f1.c 作为一个源文件单位进行编译。
条件编译
- 如果希望程序中的一部分只在满足一定条件时才进行编译,也就是对这部分内容指定编译的条件,可以使用条件编译实现。
条件编译有以下几种形式:
- 形式一
#ifdef 标识符
程序段1
#else
程序段2
#endif
// 或者
#ifdef 标识符
程序段1
#endif
- 功能:若标识符是已被宏定义指令定义过的宏名,则只对程序段1进行编译,程序段2不参加编译;
否则只对程序段2进行编译,程序段1不参加编译。
- 形式二
#ifndef 标识符 // if n def
程序段1
#else
程序段2
#endif
- 功能:若标识符是未被宏定义指令定义过的宏名,则只对程序段1进行编译,程序段2不参加编译;
否则只对程序段2进行编译,程序段1不参加编译。与形式一刚好相反。
- 形式三
#if 常量表达式
程序段1
#else
程序段2
#endif
- 功能:如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。
因此可以使程序在不同条件下,完成不同的功能。 - 例子
#include <stdio.h>
#define DEBUG 0
void main()
{
#if DEBUG
printf("Debugging...\n");
#else
printf("Running...\n");
#endif
}
#define DEBUG 1
- 上面介绍的条件编译当然也可以用条件语句来实现。
但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长; - 而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。
如果条件选择的程序段很长,采用条件编译的方法是十分必要的。 - 有利于程序的可移植性,增加程序的灵活性。
评论