1
四、宏调用和内联函数五、条件编译指令六、字符串预处理操作符
2
四、宏调用和内联函数宏调用是 C语言编译器早已有之的,C++鉴于宏调用文本参数类型上的缺陷,特地引进了内联函数。
宏调用并不真正是 C语言的内在部分而是编译器对 C语言的重要充实,内联函数属于 C++语言本身。
类似于带参的宏指令,内联函数在进行了类型匹配之后将函数体的代码指令直接扩展于源程序的调用处。
宏调用比内联函数更易发生副作用,内联函数减少宏调用的副作用。内联函数不能回避函数所固有的副作用,只是将副作用约束到一处。
3
内联函数难以取代宏调用,宏调用展开是文本串的替换,这种替换总被实际执行,宏调用是更少中间环节的代码展开形式。 inline函数调用未必真的内联展开。
内联展开过程比宏调用付出多余的临时单元的代价。在定义点仅由 inline界定的函数是内部连接属性的。
预处理器中一些文本字符串操作符对于源程序的编写具有莫大的好处,而这些文本字符串操作符是编译阶段的内联函数所无法直接利用的。宏调用无视参数的类型给通用参数的程序编写提供了便利。 例如前面介绍的交换两个内存数据的 SWAP宏:
#define SWAP(a,b) { temp=a; a=b;b=temp;}
如果换成函数以匹配各种数据类型,则交换不同类型变量或指针都应提供相关的函数。
4
[例 ] 带参的宏定义和内联函数
#include<stdio.h>
#define MAX (a,b) (((a) > (b))? (a),(b))
inline max (int a,int b) {return a>b? a,b;}
void main (void)
{ int b=2; int a=3;
int c=MAX (a++,b++);
printf ("a=%d,b=%d,MAX (a,b)=%d\n",a,b,c);
b=4; a=3; c=MAX (a++,b);
printf ("a=%d,b=%d,MAX (a,b)=%d\n",a,b,c);
b=2; a=3; c=max (a++,b++);
printf ("a=%d,b=%d,max (a,b)=%d\n",a,b,c);
b=2; a=3; c=max (a++,a++);
printf ("a=%d,b=%d,max (a,b)=%d\n",a,b,c);
}
5
宏调用,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++);
6
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增值二次。
7
五、条件编译指令条件编译的作用有:
维护同一程序的不同版本,使源程序可适用不同的操作系统,在程序的调试阶段可以使用条件编译指令放置一些显示信息以便跟踪程序的错误或运行的动态情况,一旦程序正常运行就可以重置条件编译的测试条件,使不关程序运行的跟踪代码为编译器所忽略,达到注释掉程序段的目的。
8
条件编译指令有如下三种格式:
1,#if条件编译指令 (如果常量条件表达式非零时编译 )
条件编译指令 #if有下面三种不同的用法,
(1) #if~#endif条件编译指令
#if常量表达式程序段
//当常量表达式的结果非零时,编译或保留程序段
#endif
预处理阶段的常量表达式是可以预先静态求值的由运算符和整型常数枚举常数构成的整型表达式。 sizeof(1)不是预处理阶段的常量。 -1+0xab+!072*5/6>4==5+?c?是常量表达式。
9
(2) #if~#else ~#endif条件编译指令
#if常量表达式程序段 1
//当常量表达式 1的结果非零时,编译或保留程序段 1
#else
程序段 0
//当常量表达式的结果为 0时,编译或保留程序段 0
#endif
(3) #if~elif~#else ~#endif条件编译指令
#if常量表达式 i
程序段 i
//当常量表达式 i的结果非零时,编译或保留程序段 i
10
[ #elif常量表达式 k
程序段 k ]
//当常量表达式 k的结果非零时,编译或保留程序段 k
...
[ #elif常量表达式 j
程序段 j ]
//当常量表达式 j的结果非零时,编译或保留程序段 j
...
[#else
程序段 0 ]
//当常量表达式的结果均为 0时,编译或保留程序段 0
#endif
11
上面的程序段仅只有一个进入编译,C/C++预处理从上到下测试每一条常量表达式的值,找到其中一条表达式非零,就编译相应的程序段。
这类似于 if~else if~else语句的执行情况,在 if~else
if~else语句形式中仅执行非零条件下的复合语句。方括号包括的内容表示可以省略的项目,只有 #if~#endif是必不可少的。
#endif指令是条件编译语法必须的伴生指令,只要使用了 #if,#ifdef和 #ifndef就必须用结束条件块的 #endif指令。
12
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
13
[#else
程序段 2 ]
//标识符未经 #define定义或被 #undef清除,编译或保留程序
//段 2
#endif
上面方括号的项目表示可以省略的内容,defined是预处理器操作符,其语法格式为,
defined(标识符 ) 或 defined标识符标识符已定义,defined(标识符 )表达式的结果非零,
否则结果为 0。 defined预操作符用于 #if~#endif指令中,此时标识符的具体值并不重要,关键是标识符的定义与否。
14
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
15
//标识符未经 #define定义或被 #undef清除,编译程序段 2
[ #else
程序段 1 ]
//标识符已经 #define定义,编译程序段 1
#endif
上面方括号的项目表示可以省略的内容,#ifndef的妙用之处就是确保文件仅包含一次或标识符仅定义一次,
例如:
#ifndef SYMBOL
#include,symbol.h”
#define SYMBOL
#endif
16
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
17
可以用条件编译预处理指令注释掉一些源代码,但又容易将这些代码恢复,这比用注释符,/*----*/”可以更有效的控制程序段的去留。
下面的程序为了调试跟踪代码,运用了简单的编译预处理指令,屏显语句 printf可以酌情地予以去留。
预处理中的 defined 操作符比 #ifdef和 #ifndef 具有更多的用法,可以含逻辑表达式等。预处理指令的条件编译是确定程序段的去留。
而流程控制 if~else if~else语句经过预处理的筛选后则用于实际地控制程序运行时的流程走向。两者的机制一致但分工不同目标各异。
18
[例 ] 条件编译指令确定版本的交付
#include<stdio.h>
#define STUDY
#define PROFESSION
void main(void)
{ #if defined(BRIEF)&&!defined(STUDY)
printf (" brief info\n");
#elif defined (STUDY)&&!defined (PROFESSION)
printf (" for studying\n");
#elif defined (PROFESSION)&&!defined (SPLENDOR)
printf ("professional\n");
#else printf ("splendid\n");
#endif
}
//输出 professional
19
六、字符串预处理操作符
1,字符串操作符 #
字符串操作符 #的使用格式为:
#s #宏形参井字号 #跟一个称为宏形参的标识名。字符串操作符组合 #s仅用于 #define引入的宏定义中,旨在将相应的宏实参转换为字符串常数。
例如:对于带参的宏定义
#define sout(s) cout<<#s<<endl
相应的宏调用,sout(this string is in double quote);
将导致宏展开:
cout<<,this string is in double quote” <<endl;
20
[例 ] 字符串双引号展开
#include<iostream.h>
#define sout(s) cout<<#s<<endl
#define dout(s,v) cout<<#s<<v<<endl
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<<#s<<endl;,"cout<<#s<<endl ");
dout ( a=,10000 );
}
21
2,字符合并操作符 ##
编译器本身可将相邻的字符串合并,
例如,,ab--”,--cd”合并为,ab----cd”
字符合并操作符 ##提供更多的灵活性。其使用格式为:
文本 ##宏形参例如,text##macro 合并为 textmacro
字符串 ##字符串例如,"%d"##"%s" 合并为 "%d%s"
字符合并操作符 ##仅用于 #define引入的带参的宏定义中。
由于其用于将两个单独的标记合并为一个串联在一起的文本串,双井字号 ##之前有操作数而其后跟宏形参名。
22
例如:
#include<stdio.h>
#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
23
字符合并操作符 ##可用于有规律的对象名和函数名的简化操作中。
如变量名序列:
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等是用户引入的类名序列。通过如此操作,对象名或变量名具有强烈的规律性,便于程序统一的进行字符处理,动态类型跟踪等。
24
[例 ] 字符合并操作符 ##进行动态类型跟踪
#include<iostream.h>
#define sout (s) cout<<#s<<endl
#define dout (s,v) cout<<#s<<v<<endl
#define object (type) class##type
void main (void)
{ typedef short t1;
t1 object(t1)=1;
typedef long t2;
t2 object(t2)=2;
typedef double t3;
t2 object(t3)=3;
25
sout (please enter num ); int n;
do{ cin>>n;
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&&n!=2&&n!=3);
}
26