author:&Carlton
tags:C语言,暑期实践
topic:预处理指令— —宏定义、文件包含、条件编译
book:谭浩强 《C语言程序设计》第五版
date:2023年7月10日
目录
预处理指令
C语言允许源程序加入”预处理指令“(preprocessing directive),这些预处理指令是由C标准建议的,但它并不是C语言本身的组成部分,C编译程序不能识别它们。
需要对这些指令进行预处理(preprocess,也称”编译预处理“或”预编译“),由C预处理器(preprocessor)的程序负责处理(现在的C编译系统把C预处理器作为其系统的一个组成部分,编译时一气呵成)
主要有3个功能:
①宏定义
②文件包含
③条件编译
这些指令以”#“开头,且指令后面没有分号。
宏定义
使用宏定义记得在宏定义指令处添加必要的括号
常见的问题有四则运算优先顺序导致的矛盾
#define P 2+5
P*3 //变为2+5*3,不符
#define P (2+5)
P*3 //变为(2+5)*3,正确
不带参数的宏定义
#define 标识符 字符串
核心是简单的字符替换,宏名习惯全部用大写字母表示,并不会检查语法错误,可以引用已定义的宏名,层层替换。
其作用域为该指令行到本源文件结束,可以用#undef指令终止宏定义的作用域:
#define G 9.8
…… //将G替换为9.8
#undef G
…… //停止替换效果
使用宏定义的优点
见名知义,一改全改,提高程序通用性。
带参数的宏定义
#define 宏名(参数表) 字符串
要进行参数替换及参数以外的字符替换
如果预处理指令处字符串中包含参数表中的参数,则将程序语句中相应的实参(不限制数据类型,可以是常量、变量或表达式)代替形参。如果不是则按照字符替换的方式保留。
#define S(a,b) a*b
……
area=S(3,2)
实际效果即为:
area=3*2
应注意:
在定义宏时,应当在字符串中的形参外面加上括号,以防带来运算优先顺序带来的问题。
宏名与带参数的括号之间不要有空格,否则会识别为第一种不带参数的宏定义,做简单的字符替换。
带参数的宏与函数的区别
①对实参处理方式不同。函数调用时会先求出实参表达式的值
②处理时间、对形参处理方式不同。函数调用是在程序运行时处理的,为形参分配临时的内存单元。
③对参数的数据类型要求不同。函数的实参和形参都要定义类型,且类型要保持一致。
④可得到的结果数量不同。函数本身只可得到一个返回值,宏可以设法得到多个结果:
#define PI 3.14
#define CIRCLE(R,L,S,V) L=2*PI*R;S=PI*R*R;V=4.0/3.0*PI*R*R*R
CIRCLE(r,l,s,v); //运行时得到r,l,s,v四个结果
⑤对源程序长度影响不同。使用宏次数多时,宏展开源程序变长。
⑥处理时间不同。宏替换不占运行时间,只占预处理时间,而函数则占用运行时间(分配单元、保留现场、值传递、返回)。
宏定义的好处
善于使用宏定义可以实现程序的简化,可以写出各种输入输出的格式、或者定义一些标准数据。
单独编成一个文件,相当于一个”格式库“,其他文件只需将这个文件包含进就可以方便使用这些标准。(即结合”文件包含“的预处理方式)
”文件包含“处理
#include ”文件名“
#include <文件名>
用尖括号形式时,系统到存放C库函数头文件的目录中寻找要包含的文件,称为标准方式。
用双撇号时,系统先在用户当前目录中寻找,若找不到再按照标准方式寻找。若文件没有放在用户当前目录(源程序存放的目录中),应在双撇号内给出文件路径。
(如 #include"C:\wang\file2.h")
文件包含指令的作用
将file2.c文件的全部内容复制、取代在file1.c中对file2.c文件包含指令#include <file2.c>,完成file1.c包含file2.c文件的操作,进行编译时将经过预处理的file1.c文件作为一个源文件单位进行编译。
这些常用在文件头部的被包含的文件称为”标题文件“或”头文件“,常以”.h“为后缀(header)
一个#include指令只能包含一个文件,文件包含可以嵌套。
使用”文件包含“指令的好处
①可以节省程序设计人员的重复劳动,可以将定义的各种格式宏作为一个头文件,供大家使用。
②修改某些常用的参数时,不必修改每个程序,仅仅修改放置参数的头文件即可。但被包含文件修改后,凡包含此文件的所有文件都要全部重新编译。
条件编译
有时希望程序中的一部分内容只在满足一定条件下才进行编译,即对这一部分内容指定编译的条件,这就是”条件编译“。
有以下3种形式:
//(1)
#ifdef 标识符
程序段1
#else //#else部分可以没有
程序段2
#endif
//(2)
#ifndef 标识符 //多了个"n",not
程序段1
#else //#else部分可以没有
程序段2
#endif
//(3)
#if 表达式
程序段1
#else
程序段2
#endif
条件编译的作用
(1)若所指定的标识符已经被#define指令定义过,则在程序编译阶段对程序段1进行编译(保留程序段1,忽略/删除程序段2),否则对程序段2进行编译。
(2)情况与(1)相反。
(3)指定的表达式值为真时编译程序段1,否则编译程序段2。(注意条件编译是预处理指令,在预处理过程起效,程序还未正式编译运行,故指定的表达式通常结合宏定义使用)
#define LETTER 1
……
#if LETTER
……
#endif
条件编译的好处
①提高程序通用性。
如果一个C源程序在不同计算机系统上运行,而不同计算机又有一定的差异,如对int类型整数存放字节数的差异(2或4),用条件编译处理:
#ifdef COMPUTER_A
#define INTEGER_SIZE 16
#else
#define INTEGER_SIZE 32
#endif
只要在前头出现过#define COMPUTER_A 等定义COMPUTER_A的语句就会在预编译时包括#define INTEGER_SIZE 16
②有开关的作用,一发而动全身。
调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。
为了避免一 一删改printf语句,利用条件编译只需删除前面一条#define DEBUG或者加上一条#define RUN即可。
③可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。
#define DEBUG //退出调试时,删除此宏定义即可
#ifdef DEBUG
printf("x=%d,y=%d,z=%d\n",x,y,z);
#endif
#define RUN //退出调试并要运行程序时,加上此宏定义即可
#ifndef RUN
printf("x=%d,y=%d,z=%d\n",x,y,z);
#endif
预处理功能是C语言特有的,有利于程序的可移植性,增加程序的灵活性,在学习深入并编写较大的程序时,善于利用预处理指令,对提高程序的质量很有帮助。
欢迎指正与分享,谢谢!