宏小结

Part 1

   C++中有那么多灵活的特性,例如重载、类型安全的模板、const关键字等等,为什么程序员还要写“#define”这样的预处理指令?

   典型的一个例子,大家都知道“const int a=100;”就比“#define a 100”要好,因为const提供类型安全、避免了预处理的意外修改等。

   然而,还是有一些理由让我们去使用#define

一、使用预处理宏

1   守护头文件

为了防止头文件被多次包含,这是一种常用技巧。

#ifndef MYPROG_X_H

#define MYPROG_X_H

// … 头文件x.h的其余部分

#endif

2   使用预处理特性

在调试代码中,插入行号或编译时间这类信息通常很有用,可以使用预定义的标准宏,例如__FILE____LINE____DATE____TIME__


__DATE__
进行预处理的日期(“Mmm dd yyyy”形式的字符串文字)

__FILE__
代表当前源代码文件名的字符串文字

__LINE__
代表当前源代码中的行号的整数常量

__TIME__
源文件编译时间,格式微“hhmmss”

__func__
当前所在函数名

_ STDC_

如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是

非标准的。

3   编译时期选择代码

A.  调试代码

选择性的输出一些调试信息:

void f()

{

#ifdef _DEBUG

   cerr<<”调试信息”<<endl;

#endif

// .. f()的其他部分

}

通常我们也可以用条件判断来代替:

void f()

{

   if(_DEBUG)

   {

   cerr<<”调试信息”<<endl;

}

// .. f()的其他部分

}

B.  特定平台代码

同一函数同一功能在不同的编译平台上可能有不同的表现形式,我们可以通过定义宏来区分不同的平台。

C.  不同的数据表示方式

<<深入浅出MFC>>这本书对MFC框架中宏的使用解析的很透彻,也让我们领略到宏的强大功能。可以参看DECLARE_MESSAGE_MAP(),

BEGIN_MESSAGE_MAP, END_MESSAGE_MAP的实现。

4   #pragma的使用,例如用#pragma禁止掉无伤大雅的警告,用于可移植性的条件编译中。例如,

包含winsock2 lib文件:

#pragma comment(lib,”ws2_32”)

用如下预处理宏,可以使结构按1字结对齐:

#pragma pack(push)

#pragma pack(1)

// … 结构定义

#pragma pack(pop)

      禁止掉某些警告信息:

#pragma warning( push )

#pragma warning( disable : 4705 )

#pragma warning( disable : 4706 )

#pragma warning( error : 164 )// 164号警告作为错误报出

// Some code

#pragma warning( pop )

 

二、宏的常见陷阱

   下面示范如何写一个简单的预处理宏max();这个宏有两个参数,比较并返回其中较大的一个值。在写这样一个宏时,容易犯哪些错误?有四大易犯错误。

1   不要忘记为参数加上括号

// 1:括号陷阱一:参数

//

#define max(a, b) a < b ? b : a

例如:

max(i += 2, j)

展开后:

i += 2 < j ? j : i += 2

考虑运算符优先级和语言规则,实际上是:

i += ((2 < j) ? j : i += 2)

这种错误可能需要长时间的调试才可以发现。

2   不要忘记为整个展开式加上括号

// 2:括号陷阱二:展开式

//

#define max(a, b) (a) < (b) ? (b) : (a)

   例如:

   m = max(j, k) + 42;

   展开后为:

   m = (j) < (k) ? (j) : (k) + 42;

考虑运算符优先级和语言规则,实际上是:

   m = ((j) < (k)) ? (j) : ((k) + 42);

   如果j >= k, m被赋值k+42,正确;如果j < k, m被赋值j,是错误的。如果给展开式加上括号,就解决了这个问题。

3   当心多参数运算

// 3:多参数运算

//

#define max(a, b) ((a) < (b) ? (b) : (a))

max(++j, k);

   如果++j的结果大于kj会递增两次,这可能不是程序员想要的:

((++j) < (k) ? (k) : (++j))

   类似的:

max(f(), pi)

展开后:

((f()) < (pi) ? (pi) : (f()))

如果f()的结果大于等于pif()会执行两次,这绝对缺乏效率,而且可能是错误的。

4   名字冲突

宏只是执行文本替换,而不管文本在哪儿,这意味着只要使用宏,就要小心对这些宏命名。具体来说,这个max宏最大的问题是,极有可能会和标准的max()函数模板冲突:

// 4:名字冲突

//

#define max(a,b) ((a) < (b) ? (b) : (a))

#include <algorithm> // 冲突!

<algorithm>中,有如下:

template<typename T> const T&

max(const T& a, const T& b);

宏将它替换为如下,将无法编译:

template<typename T> const T&

((const T& a) < (const T& b) ? (const T& b) : (const T& a));

所以,我们尽量避免命名的冲突,想出一个不平常的,难以拼写的名字,这样才能最大可能地避免与其他名字空间冲突。

 

宏的其他缺陷:

5   宏不能递归

   容易理解。

6   宏没有地址

你可能得到任何自由函数或成员函数的指针,但不可能得到一个宏的指针,因为宏没有地址。宏之所以没有地址,原因很显然===宏不是代码,宏不会以自身的形势存在,因为它是一种被美化了的文本替换规则。

7   宏有碍调试

在编译器看到代码之前,宏就会修改相应的代码,因而,他会严重改变变量名称和其他名称;此外,在调试阶段,无法跟踪到宏的内部。

 

 Part 2


VC中用于调试程序的几个宏的使用技巧

 

一、TRACE

  当选择了Debug目标,并且afxTraceEnabled变量被置为TRUE时,TRACE宏也就随之被激活了。但在程序的Release版本中,它们是被完全禁止的。下面是一个典型的TRACE语句:    

 

        int nCount =9;

 

        CString strDesc("total");

 

        TRACE("Count =%d,Description =%s/n",nCount,strDesc);

 

        …

 

    可以看到,TRACE语句的工作方式有点像C语言中的printf语句,TRACE宏参数的个数是可变的,因此使用起来非常容易。如果查看MFC的源代码,你根本找不到TRACE宏,而只能看到TRACE0TRACE1TRACE2TRACE3宏,它们的参数分别为0123

二、ASSERT

 

  如果你设计了一个函数,该函数需要一个指向文档对象的指针做参数,但是你却错误地用一个视图指针调用了这个函数。这个假的地址将导致视数据的破坏。现在,这种类型的问题可以被完全避免,只要在该函数的开始处实现一个ASSERT测试,用来检测该指针是否真正指向一个文档对象。一般来讲,编程者在每个函数的开始处均应例行公事地使用assertionASSERT宏将会判断表达式,如果一个表达式为真,执行将继续,否则,程序将显示一条消息并且暂停,你可以选择忽视这条错误并继续、终止这个程序或者是跳到Debug器中。下面一例演示了如何使用一个ASSERT宏去验证一个语句。

 

  void foo( char p, int size )  

        {

               ASSERT( p != 0  ); //确认缓冲区的指针是有效的

         ASSERT( ( size >= 100  ); //确认缓冲区至少有100个字节

              // Do the foo calculation

  }

 

  这些语句不产生任何代码,除非—DEBUG处理器标志被设置。Visual C++只在Debug版本设置这些标志,而在Release版本不定义这些标志。当—DEBUG被定义时,两个assertions将产生如下代码:

  //ASSERT( p != 0 );

       do{

       if( !(p != 0) && AfxAssertFailedLine(FILE,LINE) )

              AfxDebugBreak();

       }while(0);

 

       //ASSERT((size = 100);

       do{

       if(!(size = 100) &&AfxAssertFailedLine(FILE,LINE))

              AfxDebugBreak();

  }while(0);

  Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。

 

  Dowhile循环将整个assertion封装在一个单独的程序块中,使得编译器编译起来很舒畅。If语句将求取表达式的值并且当结果为零时调用AfxAssertFailedLine()函数。这个函数将弹出一个对话框,其中提供三个选项“取消、重试或忽略”,当你选取“重试”时,它将返回TRUE。重试将导致对AfxDebugBreak()函数的调用,从而激活调试器。

 

AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。

 

  AfxAssertFailedLine()是一个未正式公布的函数,它的功能就是显示一个消息框。该函数的源代码驻留在afxasert.cpp中。函数中的—FILE—和—LINE—语句是处理器标志,它们分别指定了源文件名和当前的行号。

三、VERIFY

  因为assertion只能在程序的Debug版本中起作用,在表达式中不可以包含赋值语句、增加语句(++)或者是减少语句(--),因为,这些语句实际改变数据。可有时你可能想要验证一个能动的表达式,使用一个赋值语句。那么就到了用VERIFY宏来替代ASSERT。例如:

 

  void foo(char p, int size )

        {

               char q;

               VERIFY(q = p);

               ASSERT((size = 100);

 

               // Do the foo calculation

               // Do the foo calculation

        }

  在Debug模式下,ASSERTVERIFY是一回事,但是在Release模式下,VERIFY宏仍然测试表达式而assertion却不起任何作用。可以说,在Release模式下,ASSERT语句被删除了。

   请注意,如果你在一个ASSERT语句中错误地使用了一个能动的表达式,编译器将不做任何警告地忽略它。在Release模式下,该表达式就会被无声息地删除掉,这将会导致程序的错误运行。由于Release版的程序通常不包含Debug信息,这类错误将很难被发现。

Part 3

<script src="http://blog.csdn.net/count.aspx?ID=1791542&Type=Rank" type="text/javascript"> </script>

重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

typedef  unsigned char      boolean;     /* Boolean value type. */ 
typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */
...

得到指定地址上的一个字节或字

#define  MEM_B( x )  ( *( (byte *) (x) ) )

#define  MEM_W( x )  ( *( (word *) (x) ) )

得到一个field在结构体(struct)中的偏移量

#define FPOS( type, field ) /

/*lint -e545 */ ( (dword) &(( type *) 0)-> field ) /*lint +e545 */

得到一个字的高位和低位字节

#define  WORD_LO(xxx)  ((byte) ((word)(xxx) & 255))

#define  WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8))

返回一个比X大的最接近的8的倍数

#define RND8( x )       ((((x) + 7) / 8 ) * 8 )

防止溢出的一个方法

#define  INC_SAT( val )  (val = ((val)+1 > (val)) ? (val)+1 : (val))

返回数组元素的个数

#define  ARR_SIZE( a )  ( sizeof( (a) ) / sizeof( (a[0]) ) )

对于IO空间映射在存储空间的结构,输入输出处理

  #define inp(port)         (*((volatile byte *) (port)))

  #define inpw(port)        (*((volatile word *) (port)))

  #define inpdw(port)       (*((volatile dword *)(port)))

  

  #define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val)))

  #define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val)))

  #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))

变参实现

IA32平台上的定义

     typedef char *  va_list;

   // 变长参数表数据类型,便于程序的移植

     #define _INTSIZEOF(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

  // int的倍数计算类型n的长度

     #define va_start(ap,v)  (ap = (va_list) &v + _INTSIZEOF(v))

  // 变长参数表起始于第一个参数之后

     #define va_arg(ap,t)    (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

  // t规定的类型返回ap所指向的内容,修改ap的值使之指向下一个参数

 

     #define va_end(ap)      (ap = (va_list) 0)

宏中"#""##"的用法
一、一般用法
我们使用#把宏参数变为一个字符串,##把两个宏参数贴合在一起.
用法:
#include<cstdio>
#include<climits>
using namespace std;

#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)

int main()
{
    printf(STR(vck));           // 
输出字符串"vck"
    printf("%d/n", CONS(2,3));  // 2e3 
输出:2000
    return 0;
}

二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用''#''''##''的地方宏参数是不会再展开.

1, 
''#''''##''的情况
#define TOW      (2)
#define MUL(a,b) (a*b)

printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d/n", (2), (2), ((2)*(2)));
MUL
里的参数TOW会被展开为(2).

2, 
当有''#''''##''的时候
#define A          (2)
#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)

printf("int max: %s/n",  STR(INT_MAX));    // INT_MAX #include<climits>
这行会被展开为:
printf("int max: %s/n", "INT_MAX");

printf("%s/n", CONS(A, A));               // compile error 
这一行则是:
printf("%s/n", int(AeA));

INT_MAX
A都不会再被展开然而解决这个问题的方法很简单加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A           (2)
#define _STR(s)     #s
#define STR(s)      _STR(s)          // 
转换宏
#define _CONS(a,b)  int(a##e##b)
#define CONS(a,b)   _CONS(a,b)       // 
转换宏

printf("int max: %s/n", STR(INT_MAX));          // INT_MAX,int
型的最大值,为一个变量 #include<climits>
输出为: int max: 0x7fffffff
STR(INT_MAX) -->  _STR(0x7fffffff) 
然后再转换成字符串;

printf("%d/n", CONS(A, A));
输出为:200
CONS(A, A)  -->  _CONS((2), (2))  --> int((2)e(2))

三、''#''''##''的一些应用特例
1
、合并匿名变量名
#define  ___ANONYMOUS1(type, var, line)  type  var##line
#define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line)
#define  ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int);  : static int _anonymous70;  70表示该行行号;
第一层:ANONYMOUS(static int);  -->  __ANONYMOUS0(static int, __LINE__);
第二层:                        -->  ___ANONYMOUS1(static int, _anonymous, 70);
第三层:                        -->  static int  _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

2
、填充结构
#define  FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE};
typedef struct MSG{
  IDD id;
  const char * msg;
}MSG;

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
              {CLOSE, "CLOSE"}};

3
、记录文件名
#define  _GET_FILE_NAME(f)   #f
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);

4
、得到一个数值类型所对应的字符串缓冲大小
#define  _TYPE_BUF_SIZE(type)  sizeof #type
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
char  buf[TYPE_BUF_SIZE(INT_MAX)];
     -->  char  buf[_TYPE_BUF_SIZE(0x7fffffff)];
     -->  char  buf[sizeof "0x7fffffff"];
这里相当于:
char  buf[11]; 


 
Part 4

 条件编译有三种形式,下面分别介绍:
1.
第一种形式:
#ifdef
标识符
程序段1
#else
程序段2
#endif
它的功能是,如果标识符已被 #define命令定义过则对程序段1
进行编译;否则对程序段2进行编译。
如果没有程序段2(它为空),本格式中的#else可以没有, 即可
以写为:
#ifdef
标识符
程序段
#endif

#define NUM ok
main(){
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
#ifdef NUM
printf("Number=%d/nScore=%f/n",ps->num,ps->score);
#else
printf("Name=%s/nSex=%c/n",ps->name,ps->sex);
#endif
free(ps);
}
由于在程序的第16行插入了条件编译预处理命令, 因此要根据
NUM
是否被定义过来决定编译那一个printf语句。而在程序的第一行
已对NUM作过宏定义,因此应对第一个printf语句作编译故运行结果
是输出了学号和成绩。
在程序的第一行宏定义中,定义NUM表示字符串OK,其实也可以
为任何字符串,甚至不给出任何字符串,写为:
#define NUM
也具有同样的意义。 只有取消程序的第一行才会去编译第二个
printf
语句。读者可上机试作。
2.
第二种形式:
#ifndef
标识符
程序段1
#else
程序段2
#endif
与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能
是,如果标识符未被#define命令定义过则对程序段1进行编译,
则对程序段2进行编译。这与第一种形式的功能正相反。
3.
第三种形式:
#if
常量表达式
程序段1
#else
程序段2
#endif
它的功能是,如常量表达式的值为真(0),则对程序段1 进行
编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完
成不同的功能。
#define R 1
main(){
float c,r,s;
printf ("input a number: ");
scanf("%f",&c);
#if R
r=3.14159*c*c;
printf("area of round is: %f/n",r);
#else
s=c*c;
printf("area of square is: %f/n",s);
#endif
}
本例中采用了第三种形式的条件编译。在程序第一行宏定义中,
定义R1,因此在条件编译时,常量表达式的值为真, 故计算并输
出圆面积。
上面介绍的条件编译当然也可以用条件语句来实现。 但是用条
件语句将会对整个源程序进行编译,生成的目标代码程序很长,
采用条件编译,则根据条件只编译其中的程序段1或程序段2 生成
的目标程序较短。如果条件选择的程序段很长, 采用条件编译的方
法是十分必要的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值