1.让自己习惯C++(Accustoming Yourself to C++)(条款2)

条款02:尽量以const,enum,inline替换#define

这个条款或许叫“宁可以编译器替换预处理器”比较好,因为或许#define不被视为语言的一部分。
当你写了如下代码的时候

#define ASPECT_RATIO 1.653

你有没有去想过这名称也许从未进入记号表(symbol table)(被编译器看见);因为编译器在预处理阶段就将他移走了。于是当你运用这个常量但获得一个1.653的编译错误信息时,你可能为此感到困惑。那如果这不是你写的呢?你为此追踪多半会浪费时间。这个问题也可能出现在记号式调试器(symbolic debugger)中,原因相同:你所使用的名称可能并未进入记号表(symbol table)。

  • 解决之道是以一个常量替换上述的宏(#define):
const double AspectRatio = 1.653; //大写名称通常用于宏,
                                  //因此这里改变名称写法。

现在可以放心了,因为它作为一个语言常量,肯定会被编译器看到,当然就会进入记号表内。另外,对浮点数而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目地将宏名称替换”可能导致目标码(object code)出现多份“1.653”,所以改用常量则可以避免。

当我们以常量替换#define,有两种特殊情况值得说说。

  1. 定义常量指针(constant pointers)
    由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针(而不只是指针所指之物)声明为const。例如:
//定义一个常量的(不变的)char*-based字符串,你必须写const两次
const char* const authorName = "Scott Meyers";

关于const的意义和使用(特别是当它与指针结合),条款3有完整的讨论。这里值得提醒的是,string对象通常比其前辈char*-based合宜,即如下:

const std::string authorName("Scott Meyers");
  1. class专属常量
    为了将常量的作用域(scope)限制于class内,你必须让它成为class内的一个成员;而为确保此常量至多只有一份实体,你必须用static修饰它:
class GamePlayer{
private:
   static const int NumTurns = 5;//常量声明式
   int scores[NumTurns]; //使用该常量
   ...
};

通常C++要求你对所使用的任何东西提供一个定义式,但是如果它是个class专属常量又是static且为整数类型(integral type,例如ints,chars,bools),则需特殊处理。只要不取他们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持看到一个定义式,你就必须提供如下定义式:

const int GamePlayer::NumTurns; //NumTurns的定义;
                                //放进实现文件而非头文件
                                //下面告诉你为什么没有给予数值

由于class常量已在声明时获得初值(例如先前声明NumTurns时为它设值5),因此定义时不可以再设初值。

ps:我们无法用#define创建一个class专属常量,因为#define并不重视作用域。一旦宏被定义,它在其后的编译过程中有效(除非在某处被#undef)。所以说也不能定义class专属常量,也不能提供封装性,也就是没有private #define。而当然const成员变量是可以被封装的,NumTurns就是。

但旧的编译器也许不支持上述语法,它们不允许static成员在声明式上获得初值。你可以将初值放进定义式。

ps:所谓的“in-class初值设定"也只允许对整数常量进行。

class CostEstimate{
private:
   static const double FudgeFactor;//static class 常量声明
   ...                             //位于头文件内
};
const double                       //static class 常量定义
   CostEstimate::FudgeFactor = 1.35;//位于实现文件内

这几乎是在任何时候唯一需要做的事。唯一例外:当你在class编译期间需要一个class常量值,例如:上述的GamePlayer::score的数组声明式中(编译器坚持必须在编译期间知道数组的大小)。那如果编译器不允"static整数型class常量"完成"in class初值设定",可改用所谓的"the enum hack"补偿做法。其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充ints被使用”,于是GamePlayer可定义如下:

class GamePlayer{
private:
   enum{NumTurns = 5 };//"the enum hack"——令NumTurns
                       //成为5的一个记号名称
   int scores[NumTurns];//这就没问题了.
   ...
};

基于数个理由enum hack值得我们认识。第一,enum hack的行为某方面说比较像#define而不像const,有时候这正是你想要的。例如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。

如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。(条款18对于“通过撰码时的决定设计上的约束条件”谈得更多。)

此外虽然优秀的编译器不会为“整数型const对象”设定另外的存储空间(除非你创建一个pointer或reference指向该对象),不够优秀的编译器却可能如此,而这可能是你不想要的。Enums和#define一样绝不会导致非必要的内存分配。

认识enum hack的理由纯粹是为了实用主义。许多代码用了它,所以看到它时你必须认识它。事实上“enum hack”是template metaprogramming(模板元编程,见条款48)的基础技术。

把焦点拉回预处理器。另一个常见的误用#define情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用参数f:

//以a和b的较大值调用f
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))

这般长相的宏有着太多缺点,光是想到它们就让人痛苦不堪。

无论何时当你写出这种宏,你必须记住为宏中的所有实参加上小括号,否则某些人在表达式中调用这个宏时可能会遭遇麻烦。但纵使你为所有实参加上小括号,看着下面不可思议的事情:

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

在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!

幸运的是你不需要对这种无聊事情提供温床。你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety)——只有你写出template inline函数(见条款30)

template<typename T>                          //由于我们不知道
inline void callWithMax(const T& a,const T& b)//T是什么,所以采用
{                                             //pass by reference-to-const.
   f(a > b ? a : b);                          //见条款20.
}                                             

这个template产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用f。这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算(求值)多次…等等。此外由于callWithMax是个真正的函数,它遵守作用域和访问规则。例如你绝对可以写出一个“class内的private inline函数”。一般而言宏无法完成此事。

有了consts、enums、和inlines,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。目前还不到预处理器全面引退的时候,但你应该明确地给予它更长更频繁的假期。

请记住

  • 对于单纯常量,最好以const对象或enums替换#defines。
  • 对于形似函数的宏,最好改用inline函数替换#defines。
  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全天

加油,大佬们!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值