第10章 预处理过程
20
考虑圆括号对宏的影响: #define squ (x) x*x #define sqa (x) (x)* x #define sq (x) ((x)* (x)) //这个宏实现求表达式平方的功能 int k=squ(1+2)/squ(2+1); //宏展开为: int k=1+2*1+2/2+1*2+1; //相当于 int k=7; int m=sqa(1+2)/sqa(2+1); //宏展开为: int m=(1+2)*1+2/(2+1)*2+1; // 相当于 int m=4; int n=sq(1+2)/sq(2+1); // 展开为: int n=((1+2)*(1+2))/((2+1)*(2+1)); // 相当于 int n=1;
9
[例] #define引入CALLBACK作为占位符,标识符 CALLBACK处理为一个空白。
#include< iostream.h> #define CALLBACK CALLBACK void f (char* s) { cout<<s<<endl; } void main () //表示f当作回调函数, { void ( *pf )( char* )=&f; ( *pf ) ("CALLBACK funct"); //输出: CALLBACK funct } //在字符串中的宏名CALLBACK不被替换
4
续行符\即反斜杠\之后紧跟换行符(硬回车)结束的行 与下一行合并为单独的一行,这要求反斜杠\与换行符之间 没有空格即反斜杠\之后紧跟换行符(硬回车),标记为\, 不能写为\ 。 硬回车实际上是不可见的。可以用续行符\来处理一 行容纳不下的宏替换指令。 例如: #define LONGTEXT abc*d[ i ] \ +b[ i ]/c[ j ]- d[ k ] 合并为内在的逻辑行: #define LONGTEXT abc*d[i]+b[i]/c[j]-d[k] 预处理指令可以分布在源程序的任何位置,但应与程序 源代码控制流程分隔开来。非空的源程序结束于前面没有反 斜杠的换行符。
21
[例] 宏函数的定义和调用, SWAP(a,b)中的a,b要求匹配左 值变量。 #include<stdio.h> #define SWAP (a, b) { temp=a; a=b; b=temp; #define ArraySize (a) sizeof ((a))/sizeof (*(a)) int b[ ] = { 6,3,4,5 inline void swapi () { const int n = ArraySize (b); int temp; SWAP (b[0],b[n-1]) double x=2,y=1; inline void swapd () { double temp; SWAP (x,y) } char* c[ ]={ "4","3"
7
#include指令中的嵌入文件中可以包含另外的 #include指令,由此形成文件的嵌套包含插入,插入的过程 是将相应文件就地代入展开的过程,最后形成一个潜在的扩 大的源文件。 编译器不直接编译后缀为.h的头文件,通过仅包含一条 头文件的.cpp文件可以间接的编译头文件。例如: StdAfx.cpp文件中仅一条双引号形式预处理指令即: #include "StdAfx.h"
12
圆括号的有无导致宏展开的结果存在很大的差异。 考虑: #define X 5 #define XPLUS5 X+5 // 含运算符的文本串X+5没有外加圆括号,展开为5+5 #define XPLUS (X+5) //含运算符的文本串XPLUS外加圆括号,展开为(5+5) int k= XPLUS5*X; //宏替换为int k= 5+5*5; 相当于int k=30; int m= XPLUS*X; //宏替换为int m=(5+5)*5;相当于int m=125;
13
#define预处理指令的文本串结束于前面没有反斜杠"\“ 的换行符,分配内存的变量定义语句以分号“;”结束。 宏名不直接占有内存,宏名所代表的文本串在被替换的 具体上下文环境可以具有自身的作用范围,生存期、类型属 性和可见性等性质。 应高度关注宏名代表的文本串的这些特性。 例如: 文本串3.14159是double型的常数,相应的宏名PI视为 double型的符号常数。 常数表达式5+5*5,(5+5)*5在编译阶段进一步分别计算为 30,125 。
19
例如极值的宏函数定义为: #define max (a,b) (((a) > (b)) ? (a) : (b)) // max(a,b)宏计算极大值 在一个程序段中进行该宏函数的调用: max1= max(1111,x+y); 系统通过虚实形参的文本替换展开为: max1=(((1111) > (x+y)) ? (1111) : (x+y)); 宏函数定义时与运算符相连的宏形参尽量使用圆括号封 闭,这使得与宏形参对应的实参表达式具有较高的结合性而 被优先处理。
10
第二种语法格式是#define定义标识符同时后跟文本 串,该格式的语法形式为: #define 宏名 文本串 或 #define 标识符 文本串 宏名后的文本串是字符集里的若干字符的有序组合。宏 名与其后的文本串之间用空格分隔。 宏名是用来给一抽象的在程序中多次出现的字符序列作 一个简明的替身。 出现在程序中的宏名在预处理阶段就被文本串所替换, 这一过程称为宏替换。 替换宏名的文本串一般是文字常数包括字符串,但也可 以是变量,表达式或有效的源代码片段。
2
预处理过程是关于程序源代码的起始处理,它并不在语 法上分析处理源代码,但为源代码的处理作必要的准备. 编译预处理指令是由井字符“#”开始的字符序列,以区 别程序中引入的其它名称或语句,井字符“#”之前不能存在 非 空白字符。预处理指令如下: #include #error #endif #ifdef #define #pragma #else #ifindef #undef #line #if #elif
14
[例] 宏名n定义为一个局部变量a,p定义为文本串q[2] #include<stdio.h> #define PI 3.14159 #define n a #define LENGTH 2*PI*n double #define p q[2] void main() { int n=1; printf ("LENGTH=%f\t",LENGTH); char p [5]={"123","abcd"}; printf ("%s,%s\n",q[0],q[1]); } //输出:LENGTH=6.283180 123,abcd
6
双引号形式首先在#include指令的包含文件所在的目录 路径查找要插入的文件。 如果没有找到则执行预定的搜寻过程。该形式通常用于 将程序员自身定义的头文件包含到实现文件中。 尖括号形式指出要插入的文件位于系统登录的路径中即 直接执行预定的搜寻过程,这种形式中的嵌入文件通常是标 准库头文件。 两种用法都执行预定的搜寻即在标准文件所处的路径下 查找嵌入文件。
5
二、预处理指令 #include
预处理指令 #include存在两种相似但含义不同的格式: 1. 尖括号形式(用于定位系统库的头文件) #include <filename.ext> #include <嵌入文件.h> 2. 双引号形式(用于首先搜寻用户的头文件) #include "filename.ext " #include "嵌入文件.h’’ 嵌入文件名的扩展名不是必须的,预处理指令 #include共同之点是将其后所表明的嵌入文件中的内容原封 不动地加载插入到指令#include所在的位置,而代替指令本 身。
15
程序设计时数组的大小固定为100,可以先在头文件中 用一个size来代表它,当数组的大小变动为200时只需改变 一个地方,这是 #define指令的长处。 #define size 100 //宏名size的类型就是其等价的文本串的类型,此时为int型 float array [ size ]; //等价于 float array [100]; 在另一回合的编译阶段变动为[ #define size 200]其余 不变。 简单的宏替换功能在C++中可以用const引入的静态不 变量定义语句实现: const int size =100; //定义一个整型不变量size =100 float array[size]; //等价于float array[100];
宏名与右边圆括号之间不能有空格,若存在空格则视为 无参的格式: [#define 宏名 文本串] 宏函数的调用类似于函数的调用情况,宏形参名被宏实 参文本一对一地进行文本替换,或名称的匹配替代,编译器 不对名称进行类型的检查。 宏函数调用一次完成一次宏形参名和实参的替换和文本 的展开。 调用格式取决于宏函数定义的本质内容,其外在形式的 调用形式为: macro_name (z1,z2,...zn) z1是与v1对应的文本串,z2是与v2对应的文本串,zn 是与vn对应的文本串。宏形参名是无类型界定的文本串,具 体类型属性含义由程序员控制。
11
宏名的作用域:是指其可以被文本串有效替换的范围, 这个范围开始于定义处,直到文件的结束或直到被#undef预 处理指令取消时为止。 宏名所占有的左右两边可以用空格分割开的位置由其后 的文本串所替代,出现于有效的名称如关键字、变量名、函 数名等和C字符串中的宏名不宏替换。 根据编程习惯宏名用大写字母,以区别于小写的变量 名。 作为符号常数的宏名可以作为右值参入各种运算,也可 以出现在预处理指令构成的文本串中。 后面的宏定义可使用前面已经定义的宏名,由此形成宏 的嵌套定义和相应的层层展开。