1
一、编译预处理指令概述二、预处理指令 #include
三,#define指令
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*d[i]+b[i]/c[j]-d[k]
预处理指令可以分布在源程序的任何位置,但应与程序源代码控制流程分隔开来。非空的源程序结束于前面没有反斜杠的换行符。
6
二、预处理指令 #include
预处理指令 #include存在两种相似但含义不同的格式:
1,尖括号形式 (用于定位系统库的头文件)
#include <filename.ext> #include <嵌入文件,h>
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< 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不被替换
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定义为文本串 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
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 array[size];
//等价于 float array[100];
17
[例 ] 宏名具有穿透性
#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);
}
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<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" };
23
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] );
}
说明,续行符,\?”之后的字符视为宏函数定义的一部分,因此双斜杠注释置于其后引起另外的语义解释或误解 ;
函数调用编译器检查形参和实参数据类型的匹配。宏函数不是函数只是一个类似函数的文本替换。
24
宏调用是用文本实参去一一地替代相应的宏形参,宏调用一次相应的代码就在调用处展开一次。因此宏形参和实参的类型匹配和变量的作用域内存分配等问题需要仔细考虑。
宏形参 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; }
函数的虚实结合是表达式值的入栈或变量名地址属性的入栈,函数调用一次需要来回在主控函数和被调函数之间跃动,这其中时间消耗比较大。
宏调用则有效的避免了如此不足,程序在内存中顺序执行。宏展开在宏函数较长时源程序也随之变长。
25
一、编译预处理指令概述二、预处理指令 #include
三,#define指令
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*d[i]+b[i]/c[j]-d[k]
预处理指令可以分布在源程序的任何位置,但应与程序源代码控制流程分隔开来。非空的源程序结束于前面没有反斜杠的换行符。
6
二、预处理指令 #include
预处理指令 #include存在两种相似但含义不同的格式:
1,尖括号形式 (用于定位系统库的头文件)
#include <filename.ext> #include <嵌入文件,h>
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< 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不被替换
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定义为文本串 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
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 array[size];
//等价于 float array[100];
17
[例 ] 宏名具有穿透性
#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);
}
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<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" };
23
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] );
}
说明,续行符,\?”之后的字符视为宏函数定义的一部分,因此双斜杠注释置于其后引起另外的语义解释或误解 ;
函数调用编译器检查形参和实参数据类型的匹配。宏函数不是函数只是一个类似函数的文本替换。
24
宏调用是用文本实参去一一地替代相应的宏形参,宏调用一次相应的代码就在调用处展开一次。因此宏形参和实参的类型匹配和变量的作用域内存分配等问题需要仔细考虑。
宏形参 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; }
函数的虚实结合是表达式值的入栈或变量名地址属性的入栈,函数调用一次需要来回在主控函数和被调函数之间跃动,这其中时间消耗比较大。
宏调用则有效的避免了如此不足,程序在内存中顺序执行。宏展开在宏函数较长时源程序也随之变长。
25