C++守则——尽量以const、enum、inline代替#define

我在整里C语言预处理指令的时候曾具体说明了#define的用法:浅谈C语言预处理指令而今天我们来看看为什么又最好不要用#define。
我们写这样一行代码:

#define NUM 1.653

NUM可能不会被编译器看见;也许再变一起开始处理源码之前它就被预处理器移走了。于是记号名NUM可能未进入记号表内。于是当你运用此常量但获得一个错误信息时,就会不知所措,因为这个错误信息也许会提到100而不是NUM。当NUM被定义在一个不是你自己写的头文件中的时候,肯定会对其所指代的东西感到陌生,于是你开始找寻它的定义,浪费了时间。这个问题可能出现在记号式调试器中,原因相同,你可能使用的名称并未进入到记号表中。
于是我们就可以用下面的代码去代替它:

const double NUM = 1.653;    //大写名称通常用于宏

为什么说问题解决了呢?它是一个语言常量,因此NUM肯定会被编译器看到。尤其是对浮点常量,使用常量会比使用#define导致更少的代码量,因为预处理器盲目地将宏NUM替换成1.653可能会导致目标码出现多份1.653。

用常量代替#define有两种情况值得注意:
一、定义常量指针:常量通常放在头文件中,方便移植,因此有必要将指针(并非指针所指之物)声明为const。例如想在头文件内定义一个常量的char*-based字符串,必须这么写:

const char* const authorName = "Scott Meyers";

但值得注意的是,string对象通常会比其前辈char*-based更合适。所以上面的这样写会比较好:

const std::string authorName("Scott Meyers");

二、class专属常量。这种情况不可以用#define的方式去定义,因为#define是不重视作用域这种东西的,一旦被定义,在之后就全部有效,除非碰到#undef。而我们想定义一个class专属变量时只需要将变量定义在类中即可,充分利用它的作用域。而为了确保此常量至多只有一个实体,为此我们最好将它定义为一个static成员:

class GamePlayer
{
private: 
	static const int NumTurns = 5;     //声明常量
	int scores[NumTurns];        //使用常量
};

上面的类中定义了一个成员的NumTurns ,然后用它来定义了一个数组。注意,此处的NumTurns 是一个声明式而非定义式。通常C++要求你所使用的所有东西都要有定义式,但如果它是一个class专属常量又是static且为整数类,你可以声明并使用他们而无需提供定义式。但如果你取某个class专属常量的地址或你的编译器坚持要看到一个定义是,你就必须额外提供一个定义式:

const int GamerPlayer::NumTurns;  //NumTurns 的定义式

由于在class中声明的手已经给它富国值,这里自然在定义的时候就不用赋值了。
旧式编译器不允许static成员在其声明式上获得初值,此外所谓的"in-class处置设定"也只允许对整数常量进行。不支持也没关系,我们可以这么写:

class CostEstimate
{
private:
	static  const double FudgeFactor;     //static class常量声明
	...
};
const double CostEstimate::FudgeFactor = 1.35;    //常量定义

其实这么做还会存在一个潜在的问题:第一次定义的那个类GamePlayer里面的数组大小是用的我们自己声明的常量。如果编译器不支持在类中声明变量的同时给变量赋值,那么就会报错,因为编译器坚持必须在编译器器件知道数组的大小。这个时候就要用所谓的“the enum hack”补偿法。其理论依据是:一个属于枚举类型的数值可以被当作int值来使用,于是可以修改如下:

class CostEstimate
{
	enum { NumTurns = 5 };      //令NumTurn成为5的一个记号名称
	
	int scores[NumTurns];
	...
}

其实enum hack更像是#define而不是#define,但有时候我们就是想这么干!例如取一个const的地址是合法的,但取一个enum的地址就不合法,同样的,我们也不可以取#define的地址。如果你不想别人获得一个指针指向某个整数常量,enum可以帮助你实现这个要求。另外,一些编译器会为“整数型const对象”设定另外的存储空间。但是enum和#define一样是不会导致非必要内存分配的。
还有一点很重要的是,我之前在#define的博客里说#define的函数某种程度上来说是很好用的。那是因为C语言中没有Template模板,用#define可以实现类似泛型编程。但是在C++中,我们大可不必这么做,我们强大的C++支持泛型编程。用#define需要注意的地方我也在那篇博客中提到过了。那么我们来看看为什么在C++不推荐用宏

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))    //f()是一个函数,这个宏选择f()的参数

这个定义看着就不是特别舒服,括号太多了,接下来来调用:

int a = 5, b = 0;
 
CALL_WITH_MAX(++a, b);        //a被累加两次
CALL_WITH_MAX(++a, b+10);    //a被累加一次

第一次调用,由于a递增后比较大,传入函数中,又递增一次;第二次调用,a递增后比不过b,于是就传入b,不会递增。说简单点,a的递增次数取决它被拿来和谁比较。
而我们好好利用C++的Template模板:

template <typename T>
inline void callWithMax(const T& a, const T& b)
{
	f(a > b ? a : b); 
}

这样我们照样完成了泛型编程,我们不需要要管传入的数据类型,并且内联函数保证其不会有调用函数那多出来的时间。此外,由于callWithMax是一个真正的函数,其有自己的作用域,所以可以写一个class内的private inline函数。
所以,我们发现,预处理器确实有很多不足,而我们也找到了方法去代替它,但我们不肯能完全取代它,#include是必需品,#ifdef系列可以控制程序编译。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值