[C] 第10章 预处理命令

预处理命令

(1)‘#’开头的为预处理命令,如包含命令#include,宏定义命令#define等;
(2)在源程序中,这些命令都放在函数之外,而且一般都放在程序源文件的钱前面,称它们为程序预处理部分。

预处理概述

(1)预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作;
(2)预处理由预处理程序负责完成;
(3)当对一个源程序进行编译时,系统将自动引入预处理程序对源程序中的预处理部分进行处理,处理完毕后自动进入对源程序的编译。

宏定义

(1)在C51语言程序中允许用一个标识符来表示一个字符串,称为“宏”;
(2)被定义为“宏”的标识符称为“宏名”;
(3)在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中字符串去代换,这称为“宏代换”或“宏展开”。

无参宏定义

宏定义的基本形式
#define 标识符 字符串
  • 说明
(1)‘#’表示这是一条预处理命令;
(2)“define”为宏定义命令;
(3)“标识符”为所定义的宏名;
(4)“表达式”可以是常数、表达式、格式串等。
  • 举例(对程序中反复使用的表达式进行宏定义)
#define S (x*x + 5*x)

// 作用:指定标识符 S 来代替表达式 (x*x + 5*x) 。
  • 举例(宏定义举例)
#define S  (x*x + 5*x)
void main(void)
{
	int s, x;
	x = 5;
	s = 3*S + 4*S + 5*S;
	printf("s = %d\n", s);
}
 
// 1. 定义 S 来替代表达式 (x*x + 5*x),在 s = 3*M + 4*m + 5*M 中进行了宏调用。在预处理时经宏展开后该语句变为:
s = 3*(x*x + 5*x) + 4*(x*x + 5*x) + 5*(x*x + 5*x);
// 2. 需要注意的是,在宏定义中,表达式 (x*x + 5*x) 两边的括号不能少,否则会发生错误。若宏定义为下列语句时:
#define S  x*x + 5*x
// 3. 预处理时宏展开得到下述语句
s = 3*x*x + 5*x + 4*x*x + 5*x + 5*x*x + 5*x;
// 4. (3.)中的显然与题意要求不符,计算结果必然是错误的。因此应十分注意,应保证在宏代换之后程度原意不发生错误。
宏定义的注意事项
(1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串可以是常数、表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序是才能发现。
(2)宏定义不是说明或者语句,在行末不必加分号,如加上分号则连分号也一起置换;
(3)宏定义必须写在函数之外,其作用域为宏定义命令起到原程序结束。如要终止其作用域,可用 #undef 命令;
(4)宏名在源程序中若使用引号括起来,则预处理程序不对其作宏代换。
(5)宏定义允许嵌套;
(6)习惯上宏名用大写字母表示,以便与变量区别,当然也允许使用小写字母;
(7)可使用宏定义表示数据类型,使书写方便;
(8)注意区分宏定表示数据类型和使用 typedef 定义数据说明符的区别;
	宏定义只是简单的字符串代换,是预处理时完成的,而 typedef 是在编译时处理的,他不是简单的代换,而是对类型说明的重命名;
	被重命名的标识符具有类型定义说明的功能。
(9)对“格式输出”做宏定义,可以减少书写麻烦。
  • 举例(宏作用域)
// 在函数 f1 前使用了 #undef PI,这表示 PI 只在main函数中有效,在 f1 中无效。 
#define PI 3.14159
void main(void)
{
	...
}
#undef PI
f1()
{
	...
}
  • 举例(预处理程序不对在源程序中使用引号括起来的宏举例)
// 宏名 OK 表示500,但是在 printf 语句中,OK 被引号括起来,因此不作宏代换。程序的运行结果为 OK。这表明预处理程序并没有把“OK”进行宏代换,而是把“OK”当字符串处理。
#define OK 500
void main(void)
{
	printf("OK");
	printf("\n");
}
  • 举例(宏定义嵌套)
#define PI 3.1415926
#define R 3
#define S PI*R*R	// PI 是已经定义的宏名
#define L 2*PI*R	// PI 是已经定义的宏名

// 若使用以下语句
printf("%f", S);
// 则在预处理程序将其进行宏代换后变为:
printf("%f", 3.1415926*3*3);
  • 举例(使用宏定义表示数据类型)
#define STU struct stu	// 将 STU 定义为 struct stu 的宏
#define INTEGER int		// 将 INTEGER 定义为 int 的宏
STU body[5], *p;		// 预处理程序中将使用 struct stu 替换 STU,所以本条语句等同于 struct stu body[5], *p;
INTEGER a, b;			// 预处理程序中将使用 int 替换 INTEGER,所以本条语句等同于 int a, b;
  • 举例(宏定义表示数据类型与 typedef 定义数据说明符的区别)
// 语句 PIN1 a,b; 在宏代换之后变成:int *a, b; 则表示 a 是指向整型的指针变量,而 b 是整型变量;
// 语句 PIN2 a,b; 在宏代换之后变成:int *a, *b; 则表示 a,b 都是指向整形的指针变量,因为 PIN2 是一个类型说明符;
#define PIN1 int *
typedef (int *) PIN2;
PIN1 a,b;				// 在宏代换后变成等同于 int *a, b;表示 a 是指向整形的指针变量,而 b 是整型变量
PIN2 a,b;				// 表示 a, b 都是指向整形的指针变量
  • 举例(对“输出格式”作宏定义)

#define P printf
#define D "%d\n"
#define F "%f\n"
void main(void)
{
	int a = 5,c = 8, e = 11;
	float b = 3.8, d = 9.7, f = 21.08;
	P(D F,a,b);
    P(D F,c,d);
    P(D F,e,f);
}

带参宏定义

**形参:**在宏定义中的参数称为形式参数;

**实参:*子啊宏调用中的参数称为实际参数。

带参宏定义的一般形式
#define 宏名(形参表) 字符串
带参调用的一般形式
宏名(实参表);
  • 举例(带参宏定义与调用)
#define S(x) (x*x + 5*x)	/*宏定义*/
...
k = S(5);				/*宏调用*/
...

// 宏展开
k = 5*5 + 5*5;
  • 举例(用带参宏定义求最大值)
#define MAX(a,b) (a>b)?a:b
void main(void)
{
	int x,y,max;
	x = 100,y = 99;
	max = MAX(x,y);		// 用宏名 MAX 表示条件表达式 (a>b)?a:b
	printf("max = %d\n", max);
}
带参宏定义需要注意的事项
(1)带参宏定义中,宏名和形参表之间不能有空格出现;
(2)在带参宏定义,形式参数并不分配内存单元,因此不必做类型定义;
	函数中的情况则不同:
	在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋给形参,进行“值传递”。
	而在带参宏传递中,只是符号代换,不存在值传递问题。
(3)在宏定义中的形参是标识符,而宏调用中的形参可以是表达式;
(4)在宏定义中,字符串内的形参通常要用括号括起来,以避免出错;
(5)带参得宏和带参函数很相似,但本质不同,除上面各点外,把各表达式用函数与用宏处理两者得结果有可能是不同的;
(6)宏定义可以定义多个语句,在宏调用时,把这些语句又带换到源程序内。
  • 举例(带参宏定义中,宏名和形参表之间不能有空格出现)
#define MAX(a,b) (a>b)?a:b
// 改写为:
#define MAX (a,b) (a>b)?a:b
// 被改写后的 MAX 宏将被认为是无参宏定义,宏名 MAX 代表字符串 (a,b) (a>b)?a:b 。宏展开时,调用语句:
max = MAX(x,y);
// 将被变成:
max = (a,b) (a>b)?a:b;
  • 举例(带参宏定义调用中,实参是表达式)
#define SQ(y) (y)*(y)
void main(void)
{
	int a, sq;
	a = 100;
	sq = SQ(a+1);			// sq = (a+1) * (a+1);
	printf("sq = %d\n", sq);
}
  • 举例(在宏定义中,字符串内的形参通常要用括号括起来,以避免出错;)
#define SQ(y) y*y
void main(void)
{
	int a, sq;
	a = 100;
	sq = SQ(a+1);			// sq = a + 1*a + 1;同上面正确示范相比,显然去掉括号,容易出现问题
	printf("sq = %d\n", sq);
}
  • 举例(带参宏定义)
// 要求:运行结果为10;
// 实际运行:sq = 160;
// 原因:“/” 和 “*” 的运算符优先级和结合性相同,则先计算 160/(3+1) 得40,再计算 40*(3+1) 最后得160.
#define SQ(y) (y)*(y)
void main(void)
{
	int a, sq;
	a = 3;
	sq = 160/SQ(a+1);			
	printf("sq = %d\n", sq);
}
// 修改后程序
#define SQ(y) ((y)*(y))
void main(void)
{
	int a, sq;
	a = 3;
	sq = 160/SQ(a+1);			
	printf("sq = %d\n", sq);
}
  • 举例(同一表达式用函数处理与宏处理得结果不同)
#include <stdio.h>

// 函数版本
int SQ_func(int y) {
    return y * y;
}

// 宏版本
#define SQ_macro(y) ((y)*(y))

void main(void) {
    int i = 3;
    int a = SQ_func(i++); // 使用函数处理
    int b = SQ_macro(i++); // 使用宏处理

    printf("a = %d\n", a); // 输出 a 的值
    printf("b = %d\n", b); // 输出 b 的值
}

输出结果:
a = 9
b = 16
  • 举例(宏定义也可以用来定义多个语句)
#define abcdV(s1,s2,s3,v) s1 = 1*w;s2 = 1*h;s3 = w*h;v = w*1*h;
void main(void)
{
	int l = 3,w = 4,h = 5,sa,sb,sc,vv;
	abcdV(sa,sb,sc,vv);
	printf("sa = %d\nsb = %d\nsc = %d\nvv = %d\n",sa,sb,sc,vv);
}

文件包含

文件包含命令的一般形式

#include "文件名"
  • 举例
#include "reg51.h"
#include <stdio.h>
#include <math.h>
说明
(1)文件说明命令的功能是把指定得文件插入该命令行,从而被指定的文件和当前得源程序文件连接成一个源文件;
(2)一个大的程序可以分为很多模块,又多个程序员分别编程;
(3)有些公用的的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件可使用;
(4)文件包含,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少程序出错。

使用文件包含命令行应注意的事项

(1)包含命令中的文件名可以用双引号括起来,也可以用尖角括号括起来(<>);
(2)一个 include 只能指定一个被包含文件,若有多个文件要包含,必须使用多个 include 命令;
(3)文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
  • 举例(包含命令中的文件名可以用双引号括起来,也可以用尖角括号括起来)
#include "stdio.h"
#include <math.h>

// 区别:
// (1)尖括号:表示在包含文件目录去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
// (2)双引号:使用双引号则表示首先在当前源文件目录中查找,若未找到才去包含目录中查找。

条件编译

预处理程序提供了条件编译的功能。可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。有时候可根据需要对程序进进行裁剪,以使用不同客户的需求。

条件编译的4种形式
形式1
  • 有程序段2
#ifdef 标识符
	程序段1
#else
	程序段2
#endif
  • 说明
功能:如果标识符已被 #definne 命令定义过,则对程序段1进行编译;否则对程序段2进行编译。
  • 无程序段2
#ifdef 标识符
	程序段
#endif
  • 举例(条件编译)
#include <stdio.h>
#define NUM NO
void main(void)
{
	struct stu
	{
		int num;
		char *name;
		char sex;
		float sscore;
	}*ps,stu1;
	ps = &stu1;
	ps -> num = 102;
	ps -> name = "zhang ping";
	ps -> sex = 'M';
	ps -> score = 62.5;
	#ifdef NUM
	printf("Nimber = %d\nScore = %f\n",ps -> num, ps -> score);
	#else
    printf("Nimber = %s\nScore = %c\n",ps -> name, ps -> sex);
    #endif
}

// 程序的第 17 行插入了条件编译预处理命令,因此要根据 NUM 是否已被定义来决定编译那一条 printf 语句;
// 由于首行已对 NUM 作过宏定义,因此应对第一个printf语句作编译,故运行结果输出了学号和成绩;
// 在程序的第一行宏定义中,定义 NUM 表示字符串 NO,其实也可以为任何字符串,甚至不给出任何字符串,也也可写为:
// #define NUM
形式2
#ifndef
	程序段1
#else
	程序段2
#endif
  • 说明
(1)形式2与形式1的区别是将 “ifdef” 改为 “ifndef”;
(2)功能:如果标识符未被 #define 定义过,则对程序段1进行编译,否则对程序段2进行编译,这刚好和形式1的功能相反。 
形式3
#if 常量表达式
	程序段1
#else
	程序段2
#endif
  • 说明
功能:如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。
  • 举例
#include <stdio.h>
#define R 1
void main(void)
{
	float c,r,s;
	printf("input a number: ");
	scanf("%f", &c);
#if R
	r = 3.14159*c*c;
	printf("area of round is: %f\n",r);
#else
	s = c*c;
	printf("area of square is: %f\n",s);
#endif	
}
形式4
#if defined(标识符)
	程序段1
#else
	程序段2
#endif
  • 说明
(1)功能:如常量表达式的值被定义过,则对程序段1进行编译,否则对程序段2进行编译;
(2)它与 #ifdef 语句功能类似,但 #ifdef 只能判断一个常量表达式,而使用 #if defined 则可以判断多个常量表达式是否被定义过。
  • 举例
#if defined (abc) && !defined(cba)
/*Option Menu*/
extern void mmi_camera_exit_option_menu_screen(void);
extern void mmi_camera_entry_option_menu_screen(void);
extern void mmi_camera_init_option_menu(void);
#endif // (abc) && !defined(cba)
  • 说明
(1)上面程序中,首行使用了 #if defined (abc) && !defined(cba) 语句,因而在条件编译时,只有当 abc 与 cba 同时被定过,才会执行 #if defined (abc) && !defined(cba) 和 #endif 之间的语句。
  • 举例
// 加入 DMA 被定义过,则编译 #if defined(DMA) 和 #else 之间的程序段,否则编译 #else 与 #endif 之间的程序段。
#if defined(DMA)
static void mmi_imgview_drm_callback_hdlr(kal_int32 result, kal_int32 id);
static void imgview_drm_ret_enum_imgview_progress_drm_hdlr(void);
static void mmi_imgview_drm_progress_drm_right(void);
#else
static S32 mmi_imgview_create_filelist(void);
static S32 mmi_imgview_file_sort_callback(fmgr_filelist_handle f1_hd1, S32 result, S32 progress, S32 total);
static void mmi_imgview_exit_sorting_screen(void);
static void mmi_imgview_cancel_sorting(void);
static void mmi_imgview_enter_sorting_screen(void);
#endif

总结

预处理:
(1)预处理是在源程序正式编译前由预处理程序完成的;
(2)预处理命令;
宏:
(1)宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式;
(2)在宏调用中,将用该字符串替换宏名;
(3)宏名可以带有参数,宏调用时以实参代换形参,而不是“值传递”;
(4)为了避免宏定义代换时发生错误,宏定义的字符串应加括号,字符串中出现的形式参数两边也应加括号;
文件包含:
(1)文件包含是预处理的重要功能,它可以用来把多个源程序连接成一个源文件进行编译,结果生成一个目标文件;
条件编译:
(2)条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少内存的开销提升程序的效率;
(3)使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值