第10章 预处理过程 一、编译预处理指令概述 二、预处理指令 #include 三、#define指令
一、编译预处理指令概述 预处理过程是关于程序源代码的起始处理,它并不在语 法上分析处理源代码,但为源代码的处理作必要的准备。 预处理过程完成源程序的转换:插入源文件所必须的辅 助性说明如函数原型信息,过滤五花八门的注释成空白,免 费省时地执行数据的文本替换,宏代码的原地展开,确定程 序段的去留等。 此后才进入编译阶段对源代码进行语法分析。
#include #define #undef 预处理过程是关于程序源代码的起始处理,它并不在语 法上分析处理源代码,但为源代码的处理作必要的准备. 编译预处理指令是由井字符“#”开始的字符序列,以区 别程序中引入的其它名称或语句,井字符“#”之前不能存在非 空白字符。预处理指令如下: #include #define #undef #error #pragma #line #if #endif #else #elif #ifdef #ifindef
编译程序首先作的事情就是进行编译预处理。 在该阶段编译器读入头文件、根据条件编译指令确定处 理合适的源程序段并进行必要的文本替换。 预处理阶段的优越性在于可以在程序运行之前执行一些 特定的运算,这些运算与程序的运行时间没有关系。 预处理指令以行的换行符自然结束,不需要使用分号 ";"。 相邻字符串合并为单一字符串,例如: "ab" "cde"合并为"abcde"
续行符\即反斜杠\之后紧跟换行符(硬回车)结束的行 与下一行合并为单独的一行,这要求反斜杠\与换行符之间 没有空格即反斜杠\之后紧跟换行符(硬回车),标记为\, 不能写为\ 。 硬回车实际上是不可见的。可以用续行符\来处理一 行容纳不下的宏替换指令。 例如: #define LONGTEXT abc*d[ i ] \ +b[ i ]/c[ j ]- d[ k ] 合并为内在的逻辑行: #define LONGTEXT abc*d[i]+b[i]/c[j]-d[k] 预处理指令可以分布在源程序的任何位置,但应与程序 源代码控制流程分隔开来。非空的源程序结束于前面没有反 斜杠的换行符。
二、预处理指令 #include 预处理指令 #include存在两种相似但含义不同的格式: 1. 尖括号形式(用于定位系统库的头文件) #include <filename.ext> #include <嵌入文件.h> 2. 双引号形式(用于首先搜寻用户的头文件) #include "filename.ext " #include "嵌入文件.h’’ 嵌入文件名的扩展名不是必须的,预处理指令 #include共同之点是将其后所表明的嵌入文件中的内容原封 不动地加载插入到指令#include所在的位置,而代替指令本 身。
双引号形式首先在#include指令的包含文件所在的目录 路径查找要插入的文件。 如果没有找到则执行预定的搜寻过程。该形式通常用于 将程序员自身定义的头文件包含到实现文件中。 尖括号形式指出要插入的文件位于系统登录的路径中即 直接执行预定的搜寻过程,这种形式中的嵌入文件通常是标 准库头文件。 两种用法都执行预定的搜寻即在标准文件所处的路径下 查找嵌入文件。
#include指令中的嵌入文件中可以包含另外的 是将相应文件就地代入展开的过程,最后形成一个潜在的扩 大的源文件。 编译器不直接编译后缀为.h的头文件,通过仅包含一条 头文件的.cpp文件可以间接的编译头文件。例如: StdAfx.cpp文件中仅一条双引号形式预处理指令即: #include "StdAfx.h"
1. #undef取消标识符,定义#undef指令的语法格 式为: 三、#define指令 #define指令用于程序员自行定义文本替换的规则。 1. #undef取消标识符,定义#undef指令的语法格 式为: #undef 标识符 #undef SYMBOL 2. 不带参的宏替换: 宏替换是通过#define指令实现的。#define 引入的标 识符称为宏名,宏名遵循标识符的命名约定。存在两种不带 参的#define指令格式。 第一种语法格式是#define仅定义标识符而不跟文本串: #define IDENTIFIER #define 标识符 #define 宏名
[例] #define引入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不被替换
第二种语法格式是#define定义标识符同时后跟文本 串,该格式的语法形式为: #define 宏名 文本串 或 #define 标识符 文本串 宏名后的文本串是字符集里的若干字符的有序组合。宏 名与其后的文本串之间用空格分隔。 宏名是用来给一抽象的在程序中多次出现的字符序列作 一个简明的替身。 出现在程序中的宏名在预处理阶段就被文本串所替换, 这一过程称为宏替换。 替换宏名的文本串一般是文字常数包括字符串,但也可 以是变量,表达式或有效的源代码片段。
宏名的作用域:是指其可以被文本串有效替换的范围, 这个范围开始于定义处,直到文件的结束或直到被#undef预 处理指令取消时为止。 宏名所占有的左右两边可以用空格分割开的位置由其后 的文本串所替代,出现于有效的名称如关键字、变量名、函 数名等和C字符串中的宏名不宏替换。 根据编程习惯宏名用大写字母,以区别于小写的变量 名。 作为符号常数的宏名可以作为右值参入各种运算,也可 以出现在预处理指令构成的文本串中。 后面的宏定义可使用前面已经定义的宏名,由此形成宏 的嵌套定义和相应的层层展开。
圆括号的有无导致宏展开的结果存在很大的差异。 考虑: #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;
#define预处理指令的文本串结束于前面没有反斜杠"\“ 的换行符,分配内存的变量定义语句以分号“;”结束。 宏名不直接占有内存,宏名所代表的文本串在被替换的 具体上下文环境可以具有自身的作用范围,生存期、类型属 性和可见性等性质。 应高度关注宏名代表的文本串的这些特性。 例如: 文本串3.14159是double型的常数,相应的宏名PI视为 double型的符号常数。 常数表达式5+5*5,(5+5)*5在编译阶段进一步分别计算为 30,125 。
[例] 宏名n定义为一个局部变量a,p定义为文本串q[2] #define PI 3.14159 #define n a #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
程序设计时数组的大小固定为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];
[例] 宏名具有穿透性 #include<stdio.h> void f (void) { const char *s="xxxx"; #define a "aaaa" } //上面这个宏定义在局部范围,但宏名a不受局部范围的限制 void main() { printf ("%s,",a); #define a "bbbb " //warning: 'a' : macro redefinition const char *s="cccc"; printf ("%s; %s\n",a,s); }
3. 带参的宏定义 带参的宏定义亦可合适地称为宏函数。其语法格式为: #define 宏名(无类型的参数表)宏指令体 或: #define macro_name (v1,v2,...vn) 含v1,v2,...vn的文本串序列 无类型的参数表中的每个名称必须是唯一的,文本串系 列中出现的参数可以自由地加上圆括号( )以确保复杂实际参 量的正确展开。 含v1,v2,...vn的文本串序列可以使用续行符“\”分为多 行,称为宏指令体。参数表中的名称称为宏形参,宏形参可 有效替换的范围贯穿至结束整个文本串序列的换行符。宏形 参名遵循标识符的命名规定。
宏名与右边圆括号之间不能有空格,若存在空格则视为 无参的格式: [#define 宏名 文本串] 宏函数的调用类似于函数的调用情况,宏形参名被宏实 参文本一对一地进行文本替换,或名称的匹配替代,编译器 不对名称进行类型的检查。 宏函数调用一次完成一次宏形参名和实参的替换和文本 的展开。 调用格式取决于宏函数定义的本质内容,其外在形式的 调用形式为: macro_name (z1,z2,...zn) z1是与v1对应的文本串,z2是与v2对应的文本串,zn 是与vn对应的文本串。宏形参名是无类型界定的文本串,具 体类型属性含义由程序员控制。
例如极值的宏函数定义为: #define max (a,b) (((a) > (b)) ? (a) : (b)) // max(a,b)宏计算极大值 在一个程序段中进行该宏函数的调用: max1= max(1111,x+y); 系统通过虚实形参的文本替换展开为: max1=(((1111) > (x+y)) ? (1111) : (x+y)); 宏函数定义时与运算符相连的宏形参尽量使用圆括号封 闭,这使得与宏形参对应的实参表达式具有较高的结合性而 被优先处理。
考虑圆括号对宏的影响: #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;
[例] 宏函数的定义和调用, 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" };
c[2] inline void swapp () { char* temp; SWAP(c[0],c[1]) } void main (void) { swapd (); printf ( "%1.0f,%1.0f; ",x,y ); swapp (); printf ( "%s,%s; ",c[0], c[1] ); swapi (); printf ( "%d,%d; ",b[0], b[3] ); } 说明:续行符“\”之后的字符视为宏函数定义的一部 分,因此双斜杠注释置于其后引起另外的语义解释或误解; 函数调用编译器检查形参和实参数据类型的匹配。宏函 数不是函数只是一个类似函数的文本替换。
宏调用是用文本实参去一一地替代相应的宏形参,宏调 用一次相应的代码就在调用处展开一次。因此宏形参和实参 的类型匹配和变量的作用域内存分配等问题需要仔细考虑。 宏形参a,b,temp可以匹配不同的数据类型。 例如:SWAP(b[0],b[n-1])和SWAP(c[0],c[1])展开的代 码分别为: { temp=b [0]; b [0]= b [n-1]; b[n-1]= temp; } { temp=c [0]; c [0]= c [1]; c[1]= temp; } 函数的虚实结合是表达式值的入栈或变量名地址属性 的入栈,函数调用一次需要来回在主控函数和被调函数之间 跃动,这其中时间消耗比较大。 宏调用则有效的避免了如此不足,程序在内存中顺序执 行。宏展开在宏函数较长时源程序也随之变长。
请打开“第10章 (2).ppt” 请打开“第11章.ppt”