第八章 预处理
cpp源文件——(预编译)》预处理文件——(编译)》目标文件——(连接)》可执行文件
预处理:源文件在进行编译时第一遍扫描之前做的工作(词法分析和语法分析)
程序员与预处理器进行交互的工具是一种被称作预处理器指示的命令(一些以“#”号开头的单行命令)
编译的源文件test.cpp
#include <stdio.h>
#define PI 3.1415926
int main( )
{
int r = 3;
float girth, area;
girth = 2*PI*r;
area = PI*r*r;
printf("周长为:%f 面积为: %f\n",girth, area );
}
与预编译文件test.i文件
#line 713 "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\stdio.h"
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_tempnam" ". See online help for details.")) __declspec(dllimport) char * __cdecl tempnam( const char * _Directory, const char * _FilePrefix);
#line 719 "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\stdio.h"
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_fcloseall" ". See online help for details.")) __declspec(dllimport) int __cdecl fcloseall(void);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_fdopen" ". See online help for details.")) __declspec(dllimport) FILE * __cdecl fdopen( int _FileHandle, const char * _Format);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_fgetchar" ". See online help for details.")) __declspec(dllimport) int __cdecl fgetchar(void);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_fileno" ". See online help for details.")) __declspec(dllimport) int __cdecl fileno( FILE * _File);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_flushall" ". See online help for details.")) __declspec(dllimport) int __cdecl flushall(void);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_fputchar" ". See online help for details.")) __declspec(dllimport) int __cdecl fputchar( int _Ch);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_getw" ". See online help for details.")) __declspec(dllimport) int __cdecl getw( FILE * _File);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_putw" ". See online help for details.")) __declspec(dllimport) int __cdecl putw( int _Ch, FILE * _File);
__declspec(deprecated("The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: " "_rmtmp" ". See online help for details.")) __declspec(dllimport) int __cdecl rmtmp(void);
#line 731 "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\stdio.h"
}
#line 735 "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\stdio.h"
#pragma pack(pop)
#line 739 "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\stdio.h"
#line 3 "c:\\users\\hzp\\documents\\visual studio 2010\\projects\\test03\\test03\\test03.cpp"
int main( )
{
int r;
float girth, area;
girth = 2*3.1415926*r;
area = 3.1415926*x*x;
printf("周长为:%d 面积为: %f\n",girth, area );
}
对比两个文件的差别,得出预编译所做的工作:
1)引入:删除#include指示,并将stdio.h的内容加入到程序中来对其进行响应,
2)替换:删除恶劣#define指示,并将该文件所有PI替换为指定值
3)把注释替换成一个空格字符
4)删除一些不必要的空格
Ps:这个.i文件有助于调试错误程序
一、宏定义:
I.无参宏定义
#define PI 3.1415926 //指定PI的值为3.1415926
#define ARRAY_LEN 80 //指定数组长度为80
#define CR '\r' //指定CR表示换行符
作用:无参宏定义经常被用于给数值、字符、和字符串命名
优点:
A.简化输入
B.可读性
C.一改全改
II.带参宏定义
#include <stdio.h>
#define MAX(x,y) (((x)>(y)? (x):(y)))
int main( )
{
int i = 0;
int j = 1;
printf("the larger of two:%d", MAX(i,j));
}
作用:带参宏定义经常被用作模板:如上例中 (((x)>(y)? (x):(y)))如果要经常使用(或是某些语句)
若使用宏替换可以设法得到更多的结果
#include <stdio.h>
#define MAX_MIN(x,y,max,min) max =(((x)>(y)? (x):(y))); min = (((x)<(y)? (x):(y)));
int main( )
{
int i = 0;
int j = 1;
int max = 0;
int min = 0;
MAX_MIN(i, j, max,min);
printf("两个数中的最大数是:%d\n",max);
printf("两个数中的最小数是:%d\n",min);
}
说明
1) 函数调用是在程序运行时处理的,必须为形参分配临时空间,而宏的展开则是编译前进行(不分配存储单元,也不进行值传递);
2)宏名没有类型,其参数也没有类型,而函数的参数和返回值都是有特定类型的;
3)无法用一个指针来指向一个宏,但可以使用一个指针指向一个函数;
III.使用宏时注意事项
1)宏定义中的各种符号都被视为替换文本,可能会引起一些错误(编译器会直接找出错误的地方,而不会找到错误的根源——宏定义本身)
#define LENGTH = 100;//加入了分号
int a[LENGTH];
预编译后:
int a[= 100;];
2)#define的有效范围是从其定义命令开始到此源文件结束。但可以使用#undef指示宏定义的作用域
#undef PI //取消前面定义的圆周率的宏但如果前面没有定义,那么此句不起任何作用
3)宏可以嵌套定义
#define R 1.5
#define PI 3.1415926
#define L 2*PI*R
4)宏名作为整体被替换
#define LENGTH 10
int ARRAY_LENGTH //只会替换LENGTH;而不会替换ARRAY_LENGTH
5) 必要的括号
#define MUL(x, y) x*y
result = MUL(5+3,6);
//被替换成5+3*6
//正确的写法加括号
#define MUL(x, y) ((x)*(y))//因为圆括号的运算级别最高,这样替换可以按照编程者的思路进行,而不会因为其他运算符的优先级而出现非预期的结果 //且参数的每一出现都要加括号,而不是只给替换文本加括号
#define PER(x) (x/100)
j = PER(i+1 );
//预编译后:
j = i+1/100;
6)太长的情况下使用“\”:
#define PF(one, two,three,four,five) printf("%d,%d,%d,%d,%d,%d"\n), \
one ,two,three, four,five)
7) 宏不允许重复定义
IV.控制版本的宏
#include <stdio.h>
int main( )
{
printf("编译日期为: %d\n", __LINE__);
printf("编译日期为: %s\n", __FILE__);
printf("编译日期为: %s\n", __DATE__);
printf("编译日期为: %s\n", __TIME__);
}
二.条件编译
1.第一种形式
#ifdef 标识符
程序片段1
#else
程序片段2
#endif
说明 如果 标识符已经被#define指示定义过,则对程序片段1进行编译,否则对程序片段2进行编译
2.第二种形式
#ifndef 标识符
程序片段1
#else
程序片段2
#endif
说明:如果标识符未被define命令定义过,则对程序片段1进行编译,否则对程序片段2进行编译
3.第三种形式
#if 常量表达式1
程序片段1
#elif 常量表达式2
程序片段2
#else
程序片段3
#endif
说明:如果常量表达式的值为真,则编译程序片段1.......
4.条件编译的作用:
1)编写在多台机器或多种操作系统之间可移植的程序
#if defined(Windows)
.......
#elif defined (DOS)
.....
#else defined(UNIX)
.....
#endif
2)编写在不同的编译器上进行编译的程序
#if __STDC__
标准C函数原型
#else
其他C函数声明
#endif
3)防止重复包含
三.文件包含
文件包含指一个源文件可以包含另一个源文件的全部或部分内容
#include<文件名>
使用尖括号时,预处理器到存放库函数头文件所在的目录中寻找要包含的文件
#indclude"文件名"
使用双括号时,系统先在用户当前的目录中寻找要包含的文件,如果找不到,则再到存放库函数所在的文件中查找
注意事项:
1)一个include只能包含一个文件
#include<file1.h> <file2.h>//一个include只能包含一个文件
2)嵌套包含时使用条件编译来防止重复包含:
#ifndef FIRSTINCLUDE_H
#define FIRSTINCLUDE_H
.....
#endif
计算器的实例
limit.h:提供一些对于运算对象的限制和转换
#ifndef LIMT_H
#define LIMT_H
#define TO_LONG(x) (long)x //将参数转换为long型
#define IS_PLUS(x) (x >= 0 ? 1:0) //判断参数是否为正数
#define CHECK_ZERO(divisor) (divisor) == 0?1:0 //判断除数是否为0的宏
#endif
count.h:提供各种形式运算的宏定义以及运算函数原型
#ifndef COUNT_H
#define COUNT_H
#include <math.h>
#define SUM(x,y) ((x)+(y)) //计算加法的宏
#define SUB(x,y) ((x)-(y))
#define MUL(x,y) ((x)*(y))
#define DIV(x,y) ((x)/(y))
#define MOD(x,y) ((x)%(y))
#define SQUARE(x) ((x)*(x))
#define CUBE(x) ((x)*(x)*(x))
#define SQRT(x) sqrt(x)
void count(char);
#endif
myio.h:提供各种形式的输入和输出操作
#ifndef MYIO_H
#define MYIO_H
#ifdef _cplusplus
#include <iostream>
#else
#include <stdio.h>
#endif
#define CLUE_OPE printf("请输入一个运算符: +表示加法,-表示减法,*表示乘法,/表示除法,\n %%表示求余,2表示乘方,3表示立方,s表示开方\n")//输出提示信息的宏
#define CLUE_ONE printf("请输入一个运算对象:\n") //提示输入两个运算对象的宏
#define CLUE_TWO printf("请输入两个个运算对象:\n") //提示输入一个运算对象的宏
#define CLUE_ZERO printf("你输入的被除数为0,请重新输入:\n")//提示被除数不能为0的宏
#define CLUE_PLUS printf("开平方的书必须是正数,请重新输入:\n") //提示被开方的数必须为正数
#define IN_OP(x) x = getchar() //输入运算符的宏
#define IN_O(X) scanf("%lf",&x) //输入一个数据的宏
#define IN_T(x,y) scanf("%lf%lf", &x,&y) //输入两个数据的宏
#define OUT_D(x) printf("计算结果为:%f\n",x) //输出double类型的宏
#define OUT_L(x) printf("计算结果为:%lf\n",x) //输出double类型的宏
#endif
demo.c:演示各种运算
#include "count.h"
#include "limt.h"
#include "myio.h"
int main()
{
char c;
while(1)
{
CLUE_OPE;
count(IN_OP(c));
fflush(stdin);
}
return 0;
}
void count(char c)
{
double x ,y;
switch(c)
{
case '+':
CLUE_TWO;
IN_T(x,y);
OUT_D(SUM(x,y));
break;
case '-':
CLUE_TWO;
IN_T(x,y);
OUT_D(SUB(x,y));
break;
case'*':
CLUE_TWO;
IN_T(x,y);
OUT_D(MUL(x,y));
break;
case'/':
CLUE_TWO;
IN_T(x,y);
while(CHECK_ZERO(y))
{
CLUE_ZERO;
IN_T(x,y);
}
OUT_D(DIV(x,y));
break;
case'%':
CLUE_TWO;
IN_T(x,y);
while(CHECK_ZERO(y))
{
CLUE_ZERO;
IN_T(x,y);
}
OUT_L(MOD(TO_LONG(x),TO_LONG(y)));
break;
case'2':
CLUE_ONE;
IN_O(x);
OUT_D(SQUARE(x));
break;
case'3':
CLUE_ONE;
IN_O(x);
OUT_D(CUBE(x));
break;
case's':
CLUE_ONE;
IN_O(x);
while(!IS_PLUS(x))
{
CLUE_PLUS;
IN_O(x);
}
OUT_D(SQRT(x));
break;
}
}