第10章 预处理过程课件.ppt

上传人:小** 文档编号:23632705 上传时间:2021-06-10 格式:PPT 页数:50 大小:398.50KB
返回 下载 相关 举报
第10章 预处理过程课件.ppt_第1页
第1页 / 共50页
第10章 预处理过程课件.ppt_第2页
第2页 / 共50页
第10章 预处理过程课件.ppt_第3页
第3页 / 共50页
点击查看更多>>
资源描述
1 2 一 、 编 译 预 处 理 指 令 概 述 预处理过程是关于程序源代码的起始处理,它并不在语法上分析处理源代码,但为源代码的处理作必要的准备。 预处理过程完成源程序的转换:插入源文件所必须的辅助性说明如函数原型信息,过滤五花八门的注释成空白,免费省时地执行数据的文本替换,宏代码的原地展开,确定程序段的去留等。 此后才进入编译阶段对源代码进行语法分析。 3 预处理过程是关于程序源代码的起始处理,它并不在语法上分析处理源代码,但为源代码的处理作必要的准备. 编译预处理指令是由井字符“#”开始的字符序列,以区别程序中引入的其它名称或语句,井字符“#”之前不能存在非空白字符。预处理指令如下: #include #define #undef #error #pragma #line #if #endif #else #elif #ifdef #ifindef 4 编译程序首先作的事情就是进行编译预处理。 在该阶段编译器读入头文件、根据条件编译指令确定处理合适的源程序段并进行必要的文本替换。 预处理阶段的优越性在于可以在程序运行之前执行一些特定的运算,这些运算与程序的运行时间没有关系。 预处理指令以行的换行符自然结束,不需要使用分号;。 相邻字符串合并为单一字符串,例如: ab cde合并为abcde 5 续行符即反斜杠之后紧跟换行符(硬回车)结束的行与下一行合并为单独的一行,这要求反斜杠与换行符之间没有空格即反斜杠之后紧跟换行符(硬回车),标记为,不能写为 。 硬回车实际上是不可见的。可以用续行符来处理一行容纳不下的宏替换指令。 例如: #define LONGTEXT abc*d i +b i /c j - d k 合并为内在的逻辑行: #define LONGTEXT abc*di+bi/cj-dk 预处理指令可以分布在源程序的任何位置,但应与程序源代码控制流程分隔开来。非空的源程序结束于前面没有反斜杠的换行符。 6 二 、 预 处 理 指 令 #include 预处理指令 #include存在两种相似但含义不同的格式: 1. 尖括号形式(用于定位系统库的头文件) #include #include 2. 双引号形式(用于首先搜寻用户的头文件) #include filename.ext #include 嵌入文件.h 嵌入文件名的扩展名不是必须的,预处理指令 #include共同之点是将其后所表明的嵌入文件中的内容原封不动地加载插入到指令#include所在的位置,而代替指令本身。 7 双引号形式首先在#include指令的包含文件所在的目录路径查找要插入的文件。 如果没有找到则执行预定的搜寻过程。该形式通常用于将程序员自身定义的头文件包含到实现文件中。 尖括号形式指出要插入的文件位于系统登录的路径中即直接执行预定的搜寻过程,这种形式中的嵌入文件通常是标准库头文件。 两种用法都执行预定的搜寻即在标准文件所处的路径下查找嵌入文件。 8 #include指令中的嵌入文件中可以包含另外的#include指令,由此形成文件的嵌套包含插入,插入的过程是将相应文件就地代入展开的过程,最后形成一个潜在的扩大的源文件。 编译器不直接编译后缀为.h的头文件,通过仅包含一条头文件的.cpp文件可以间接的编译头文件。例如:StdAfx.cpp文件中仅一条双引号形式预处理指令即: #include StdAfx.h 9 三 、 #define指 令 #define指令用于程序员自行定义文本替换的规则。 1. #undef取消标识符,定义#undef指令的语法格式为:#undef 标识符 #undef SYMBOL 2. 不带参的宏替换: 宏替换是通过#define指令实现的。#define 引入的标识符称为宏名,宏名遵循标识符的命名约定。存在两种不带参的#define指令格式。 第一种语法格式是#define仅定义标识符而不跟文本串: #define IDENTIFIER #define 标识符 #define 宏名 10 例 #define引入CALLBACK作为占位符,标识符CALLBACK处理为一个空白。 #include#define CALLBACKCALLBACK void f (char* s) coutsendl; void main () /表示f当作回调函数, void ( *pf )( char* )= ( *pf ) (CALLBACK funct); /输出: CALLBACK funct /在字符串中的宏名CALLBACK不被替换 11 第二种语法格式是#define定义标识符同时后跟文本串,该格式的语法形式为: #define 宏名 文本串 或 #define 标识符 文本串 宏名后的文本串是字符集里的若干字符的有序组合。宏名与其后的文本串之间用空格分隔。 宏名是用来给一抽象的在程序中多次出现的字符序列作一个简明的替身。 出现在程序中的宏名在预处理阶段就被文本串所替换,这一过程称为宏替换。 替换宏名的文本串一般是文字常数包括字符串,但也可以是变量,表达式或有效的源代码片段。 12 宏名的作用域:是指其可以被文本串有效替换的范围,这个范围开始于定义处,直到文件的结束或直到被#undef预处理指令取消时为止。 宏名所占有的左右两边可以用空格分割开的位置由其后的文本串所替代,出现于有效的名称如关键字、变量名、函数名等和C字符串中的宏名不宏替换。 根据编程习惯宏名用大写字母,以区别于小写的变量名。 作为符号常数的宏名可以作为右值参入各种运算,也可以出现在预处理指令构成的文本串中。 后面的宏定义可使用前面已经定义的宏名,由此形成宏的嵌套定义和相应的层层展开。 13 圆括号的有无导致宏展开的结果存在很大的差异。考虑:#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; 14 #define预处理指令的文本串结束于前面没有反斜杠“的换行符,分配内存的变量定义语句以分号“;”结束。 宏名不直接占有内存,宏名所代表的文本串在被替换的具体上下文环境可以具有自身的作用范围,生存期、类型属性和可见性等性质。 应高度关注宏名代表的文本串的这些特性。例如: 文本串3.14159是double型的常数,相应的宏名PI视为double型的符号常数。 常数表达式5+5*5,(5+5)*5在编译阶段进一步分别计算为30,125 。 15 例 宏名n定义为一个局部变量a,p定义为文本串q2 #include #define PI 3.14159 #define n a #define LENGTH 2*PI*n double #define p q2 void main() int n=1; printf (LENGTH=%ft,LENGTH); char p 5=123,abcd; printf (%s,%sn,q0,q1); /输出:LENGTH=6.283180 123,abcd 16 程序设计时数组的大小固定为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 arraysize; /等价于float array100; 17 例 宏名具有穿透性#includevoid 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; %sn,a,s); 18 3. 带参的宏定义带参的宏定义亦可合适地称为宏函数。其语法格式为: #define 宏名(无类型的参数表)宏指令体或: #define macro_name (v1,v2,.vn) 含v1,v2,.vn的文本串序列 无类型的参数表中的每个名称必须是唯一的,文本串系列中出现的参数可以自由地加上圆括号( )以确保复杂实际参量的正确展开。 含v1,v2,.vn的文本串序列可以使用续行符“”分为多行,称为宏指令体。参数表中的名称称为宏形参,宏形参可有效替换的范围贯穿至结束整个文本串序列的换行符。宏形参名遵循标识符的命名规定。 19 宏名与右边圆括号之间不能有空格,若存在空格则视为无参的格式: #define 宏名 文本串 宏函数的调用类似于函数的调用情况,宏形参名被宏实参文本一对一地进行文本替换,或名称的匹配替代,编译器不对名称进行类型的检查。 宏函数调用一次完成一次宏形参名和实参的替换和文本的展开。 调用格式取决于宏函数定义的本质内容,其外在形式的调用形式为: macro_name (z1,z2,.zn) z1是与v1对应的文本串,z2是与v2对应的文本串,zn是与vn对应的文本串。宏形参名是无类型界定的文本串,具体类型属性含义由程序员控制。 20 例如极值的宏函数定义为: #define max (a,b) (a) (b) ? (a) : (b) / max(a,b)宏计算极大值在一个程序段中进行该宏函数的调用: max1= max(1111,x+y);系统通过虚实形参的文本替换展开为: max1=(1111) (x+y) ? (1111) : (x+y); 宏函数定义时与运算符相连的宏形参尽量使用圆括号封闭,这使得与宏形参对应的实参表达式具有较高的结合性而被优先处理。 21 考虑圆括号对宏的影响: #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; 22 例 宏函数的定义和调用, SWAP(a,b)中的a,b要求匹配左值变量。#include#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 (b0,bn-1) double x=2,y=1;inline void swapd () double temp; SWAP (x,y) char* c = 4,3 ; 23 c2inline void swapp () char* temp; SWAP(c0,c1) void main (void) swapd (); printf ( %1.0f,%1.0f; ,x,y ); swapp (); printf ( %s,%s; ,c0, c1 ); swapi (); printf ( %d,%d; ,b0, b3 ); 说明:续行符“”之后的字符视为宏函数定义的一部分,因此双斜杠注释置于其后引起另外的语义解释或误解; 函数调用编译器检查形参和实参数据类型的匹配。宏函数不是函数只是一个类似函数的文本替换。 24 宏调用是用文本实参去一一地替代相应的宏形参,宏调用一次相应的代码就在调用处展开一次。因此宏形参和实参的类型匹配和变量的作用域内存分配等问题需要仔细考虑。 宏形参a,b,temp可以匹配不同的数据类型。 例如:SWAP(b0,bn-1)和SWAP(c0,c1)展开的代码分别为: temp=b 0; b 0= b n-1; bn-1= temp; temp=c 0; c 0= c 1; c1= temp; 函数的虚实结合是表达式值的入栈或变量名地址属性的入栈,函数调用一次需要来回在主控函数和被调函数之间跃动,这其中时间消耗比较大。 宏调用则有效的避免了如此不足,程序在内存中顺序执行。宏展开在宏函数较长时源程序也随之变长。 25 26 四 、 宏 调 用 和 内 联 函 数 宏调用是C语言编译器早已有之的,C+鉴于宏调用文本参数类型上的缺陷,特地引进了内联函数。 宏调用并不真正是C语言的内在部分而是编译器对C语言的重要充实,内联函数属于C+语言本身。 类似于带参的宏指令,内联函数在进行了类型匹配之后将函数体的代码指令直接扩展于源程序的调用处。 宏调用比内联函数更易发生副作用,内联函数减少宏调用的副作用。内联函数不能回避函数所固有的副作用,只是将副作用约束到一处。 27 内联函数难以取代宏调用,宏调用展开是文本串的替换,这种替换总被实际执行,宏调用是更少中间环节的代码展开形式。inline函数调用未必真的内联展开。 内联展开过程比宏调用付出多余的临时单元的代价。在定义点仅由inline界定的函数是内部连接属性的。 预处理器中一些文本字符串操作符对于源程序的编写具有莫大的好处, 而这些文本字符串操作符是编译阶段的内联函数所无法直接利用的。宏调用无视参数的类型给通用参数的程序编写提供了便利。例如前面介绍的交换两个内存数据的SWAP宏: #define SWAP(a,b) temp=a; a=b;b=temp; 如果换成函数以匹配各种数据类型,则交换不同类型变量或指针都应提供相关的函数。 28 例 带参的宏定义和内联函数#include#define MAX (a,b) (a) (b) ? (a) : (b)inline max (int a,int b) return ab ? a : b;void main (void) int b=2; int a=3;int c=MAX (a+,b+); printf (a=%d,b=%d,MAX (a,b)=%dn,a,b,c); b=4; a=3; c=MAX (a+,b);printf (a=%d,b=%d,MAX (a,b)=%dn,a,b,c); b=2; a=3; c=max (a+,b+); printf (a=%d,b=%d,max (a,b)=%dn,a,b,c);b=2; a=3; c=max (a+,a+); printf (a=%d,b=%d,max (a,b)=%dn,a,b,c); 29 宏调用: int c=MAX (a+,b+);展开为: int c= (a+) (b+) ? (a+) : (b+); 根据三目条件运算符的路由机制(a+) (b+)先完成求值计算,如果比较的结果为真执行其后的a+,否则执行b+。三目表达式的结果就是后置运算表达式的结果即变化前的值。因此对于a=3,b=2执行的结果是c=4,b=3,a=5。变量a增值两次。 宏调用: c=MAX (a+,b);展开为: c=(a+) (b) ? (a+) : (b); 对于b=4,a=3执行的结果是c=4,b=4,a=4。变量a增值一次。内联函数调用: c=max (a+,b+); 30 vc 6.0编译器对于函数的虚实结合实参表达式的求值次序为从右到左,如此就地展开为: int tb=b+; / 先对右边第一个实参进行类型匹配,tb=2 int ta=a+; / 后对左边第一个实参进行类型匹配,tb=3 c= ta tb ? ta : tb; /相当于c= 3 2 ? 3 : 2; 对于a=3,b=2执行的结果是c=3,b=3,a=4。变量a增值一次。 类似地vc 6.0编译器对于内联函数调用:c=max(a+,a+);可能展开为: int tb=a; int ta=a; a+; a+; c= ta tb ? ta : tb; 因此对于a=3,b=2执行的结果是tb=3;ta=3; 得到c=3,b=2,a=5的输出结果。变量a增值二次。 31 五 、 条 件 编 译 指 令 条件编译的作用有: 维护同一程序的不同版本,使源程序可适用不同的操作系统,在程序的调试阶段可以使用条件编译指令放置一些显示信息以便跟踪程序的错误或运行的动态情况,一旦程序正常运行就可以重置条件编译的测试条件,使不关程序运行的跟踪代码为编译器所忽略,达到注释掉程序段的目的。 32 条件编译指令有如下三种格式:1. #if条件编译指令 (如果常量条件表达式非零时编译)条件编译指令 #if有下面三种不同的用法: (1) #if#endif条件编译指令#if常量表达式 程序段 /当常量表达式的结果非零时,编译或保留程序段 #endif 预处理阶段的常量表达式是可以预先静态求值的由运算符和整型常数枚举常数构成的整型表达式。sizeof(1)不是预处理阶段的常量。-1+0 xab+!072*5/64=5+c是常量表达式。 33 (2) #if#else #endif条件编译指令 #if常量表达式 程序段1 /当常量表达式1的结果非零时,编译或保留程序段1 #else 程序段0 /当常量表达式的结果为0时, 编译或保留程序段0 #endif(3) #ifelif#else #endif条件编译指令 #if常量表达式i 程序段i /当常量表达式i的结果非零时,编译或保留程序段i 34 #elif常量表达式k 程序段k /当常量表达式k的结果非零时,编译或保留程序段k . #elif常量表达式j 程序段j /当常量表达式j的结果非零时, 编译或保留程序段j . #else 程序段0 /当常量表达式的结果均为0时, 编译或保留程序段0 #endif 35 上面的程序段仅只有一个进入编译,C/C+预处理从上到下测试每一条常量表达式的值,找到其中一条表达式非零,就编译相应的程序段。 这类似于ifelse ifelse语句的执行情况,在ifelse ifelse语句形式中仅执行非零条件下的复合语句。方括号包括的内容表示可以省略的项目,只有 #if#endif是必不可少的。 #endif指令是条件编译语法必须的伴生指令,只要使用了#if、#ifdef和#ifndef就必须用结束条件块的#endif指令。 36 2. #ifdef条件编译指令( 如果标识符已定义则进行编译)该指令使用下面两种等价的形式: #ifdef symbol program segment1 / if symbol is defined ,compile program segment1 #else program segment2 / if symbol is undefined ,compile program segment2#endif或: #if defined(标识符) 程序段1 /标识符已经#define定义,编译或保留程序段1 37 #else 程序段2 /标识符未经#define定义或被#undef清除,编译或保留程序/段2 #endif 上面方括号的项目表示可以省略的内容,defined是预处理器操作符,其语法格式为: defined(标识符) 或 defined标识符 标识符已定义,defined(标识符)表达式的结果非零,否则结果为0。defined预操作符用于#if#endif指令中,此时标识符的具体值并不重要,关键是标识符的定义与否。 38 3. #ifndef 条件编译指令(如果标识符未定义则进行编译) 该指令可以由上面格式 2的指令反向轮换1与2得到下面两种等价的形式: #ifndef symbol program segment2 / if symbol is undefined ,compile program segment2 #else program segment1 / if symbol is defined ,compile program segment1 #endif或: #if !defined(标识符) 程序段2 39 /标识符未经#define定义或被#undef清除,编译程序段2 #else 程序段1 /标识符已经#define定义,编译程序段1 #endif 上面方括号的项目表示可以省略的内容,#ifndef的妙用之处就是确保文件仅包含一次或标识符仅定义一次,例如:#ifndef SYMBOL#include “symbol.h”#define SYMBOL#endif 40 #ifndef NULL #ifdef _cplusplus #define NULL 0 #else #define NULL (void *)0) #endif #endif 41 可以用条件编译预处理指令注释掉一些源代码,但又容易将这些代码恢复,这比用注释符“/*-*/”可以更有效的控制程序段的去留。 下面的程序为了调试跟踪代码,运用了简单的编译预处理指令,屏显语句printf可以酌情地予以去留。 预处理中的defined 操作符比#ifdef和#ifndef 具有更多的用法,可以含逻辑表达式等。预处理指令的条件编译是确定程序段的去留。 而流程控制ifelse ifelse语句经过预处理的筛选后则用于实际地控制程序运行时的流程走向。两者的机制一致但分工不同目标各异。 42 例 条件编译指令确定版本的交付 #include #define STUDY #define PROFESSION void main(void) #if defined(BRIEF) #elif defined (STUDY) #elif defined (PROFESSION) #else printf (splendidn); #endif /输出professional 43 六 、 字 符 串 预 处 理 操 作 符 1. 字符串操作符# 字符串操作符#的使用格式为: #s #宏形参 井字号#跟一个称为宏形参的标识名。字符串操作符组合#s仅用于#define引入的宏定义中,旨在将相应的宏实参转换为字符串常数。例如:对于带参的宏定义 #define sout(s) cout#sendl相应的宏调用: sout(this string is in double quote);将导致宏展开: cout “this string is in double quote” endl; 44 例 字符串双引号展开 #include #define sout(s) cout#sendl #define dout(s,v) cout#svendl void main(void) sout ( this string is in double quote ); sout ( that string is in double quote ); sout ( the string include an escaped quote); dout ( cout#sendl; ,cout#sendl ); dout ( a=,10000 ); 45 2. 字符合并操作符# 编译器本身可将相邻的字符串合并,例如: “ab-” “-cd”合并为“ab-cd” 字符合并操作符#提供更多的灵活性。其使用格式为:文本#宏形参 例如: text#macro 合并为textmacro 字符串#字符串 例如: %d#%s 合并为%d%s 字符合并操作符#仅用于#define引入的带参的宏定义中。 由于其用于将两个单独的标记合并为一个串联在一起的文本串,双井字号#之前有操作数而其后跟宏形参名。 46 例如: #include #define showd(v,s) printf (- %d#s,v) #define shows(s,v) printf (#s#%s,v) void main (void) shows(11 ,aaa); showd(22,bbb); /输出11aaa-22bbb 47 字符合并操作符#可用于有规律的对象名和函数名的简化操作中。如变量名序列: classt1 classt2 classt3 等可以宏定义为: #define object(type) class# type这样: t1 object(t1); t2 object(t2); t3 object(t3);就等价于定义变量: t1 classt1; t2 classt2; t3 classt3; 一般t1,t2和t3等是用户引入的类名序列。通过如此操作,对象名或变量名具有强烈的规律性,便于程序统一的进行字符处理,动态类型跟踪等。 48 例 字符合并操作符#进行动态类型跟踪 #include #define sout (s) cout#sendl #define dout (s,v) cout#svn; switch(n) case 1 : dout (-t1-,object(t1); break; case 2 : dout (-t2-,object(t2); break; case 3 : dout (-t3-,object(t3); break; default: sout (please enter num 1-3); break; while (n!=1 50
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 大学资料


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!