Effective C++_笔记_条款02_尽量以const、enum、inline替换#define

(整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

    这个条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#define不被视为语言的一部分,那正是它的问题所在。#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。#define的基本用法有两种,都容易出现问题,C++也分别采用不同的方法进行解决。

1. 简单的宏定义

1.1 核心内容

   1: #define PI 3.14

    记号名称PI也许从未被编译器看见,在编译器处理源码之前就被预处理器移走了。即记号名称PI根本就没进入记号表内。当你运用此常量但获得一个编译错误时,可能会带来困惑,因为这个错误信息也许会提到3.14而不是PI,当这个宏被定义在非你所写的头文件中,你肯定对3.14以及它来自何处毫无概念。

    怎么解决这个问题呢?以一个常量替换上述的宏(#define)就可以了:

   1: const double Pi = 3.14  //大写名称通常用于宏,这里用改变名称写法

    作为一个语言常量,Pi肯定会被编译器看到,当然就会就会进入到记号表内。

    用常量替换#define,有两种特殊情况。

(1)定义常量指针。由于常量定义式通常被放在头文件内,因此有必要将指针声明为const。

(2)class内的专属常量。为了将常量的作用域限制于class内,必须让它成为class的一个成员;而为确保此常量至多只有一份实体,必须让它成为一个static成员。

1.2 概念补充

    为了能够真正理解#define的作用,让我们来了解一下对C语言源程序的处理过程。C++程序从源代码到可执行的二进制文件经历了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输入,它实现以下的功能:
(1)文件包含

    可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2)条件编译

    预处理器根据#ifdef和#ifndef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
(3)宏展开
    预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
    经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。

2. 带参数的宏定义

   1: #define N 2+2
   2: void main()
   3: {
   4:    int a=N*N;
   5:    cout << a << endl ; 
   6: }

    在此程序中存在着宏定义,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16。但其实结果为8,原来宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8。若要计算结果为16:

   1: #define N (2+2)

 

 

    你以为加上括号,问题就能得到圆满解决?在原书上举了一个更加极端的例子:下面这个宏夹着带宏实参,调用函数f,

   1: #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
   2: int a = 5 , b = 0 ;
   3: CALL_WITH_MAX(++a,b);//a被累加一次
   4: CALL_WITH_MAX(++a,b+10);//a被累加两次
    在这里,调用f之前,a的递增次数竟然取决于“它和谁比较”!是不是很无聊。有另外一种更好的方法让你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性—采用template inline函数。
   1: template<typename T>
   2: inline void callWithMax(const T& a , const T& b)
   3: {
   4:     f(a > b ? a : b) ;
   5: }
这个template产生一整群函数,每个函数都接受两个同类型的对象,并以其中较大者调用f。这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算多次。此外由于callWithMax是个真正的函数,遵守作用域和访问规则。
    总结:
(1)对于单纯常量,最好以const对象或enums替换#defines。
(2)对于形似函数的宏,最好改用inline函数替换#defines。
posted @ 2014-05-17 22:12  华科小涛  阅读(280)  评论(0编辑  收藏  举报