Effective C++

Chapter 1. 让自己习惯C++(Accustoming Yourself to C++)

Item 2. 尽量以 const, enum, inline 替换 #define

(prefer consts, enums and inlines to #define)

这个条款或许可以改为“宁可以编译器替换预处理器”比较好,因为或许 #define 不被视为语言的一部分。

对于

#define ASPECT_RATIO 1.653

记号名称 ASPECT_RATIO 也许从未被编译器看见, 也许在编译器开始处理源码之前它就被预处理器移走了,解决之道是以一个常量替代上述的宏(#define):

const double AspectRatio = 1.653;             //大写名称常用于宏,因此这里改变名称写法

作为一个语言常量,AspectRatio 一定会被编译器看到,而且对于浮点常量(float point constant)而言,使用常量可能比使用 #define 导致较小量的码,因为预处理器“盲目地将宏名称 ASPECT_RATIO 替换为1.653”可能导致目标码出现多份1.653,若改为常量 AspectRatio 绝不会出现相同的情况。

当以常量替换 #define, 有两种情况值得注意:

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

但一般 string 对象通常比其前辈 char*-based 合宜,所以定义成这样往往更好:

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

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

const int GamePlayer::NumTurns;                    //NumTurns的定义

将这个式子放进实现文件而非头文件,由于 class 常量在声明时获得初值,因此定义时不可以再设初值。

旧式编译器也许不支持上述语法,它们不允许 static 成员在其声明式上获得初值。此外所谓的“in-class 初值设定”也只允许对整数常量进行。此时你可以将初值放在定义式:

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

当你在 class 编译期间需要一个 class 常量值,例如在上述的 GamePlayer::scores 的数组声明中(编译器坚持必须在编译期间知道数组的大小)。这时万一编译器(错误地)不允许“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 可以帮你实现这个约束。此外虽然优秀的编译器不会为“整数型 const 对象”设定另外的存储空间(除非你创建一个 pointer 或 reference 指向该对象),不够优秀的编译器却可能如此,而这可能是你不想要的。Enums 和 #defines 一样绝不会导致非必要的内存分配。

  • 第二,实用主义。许多代码用了它,所以看到它时必须认识它。事实上“enum hack“是 template metaprogramming (模板元编程)的基础技术。

另一个常见的 #define 误用是以它实现宏(macros)。宏看起来像函数,却不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数 f :

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

无论何时写出这种宏,必须记住为宏中的所有实参加上小括号,否则在表达式中调用这个宏时可能会遭遇麻烦。但纵使为所有的实参添加小括号,还是会有不可思议的事情发生:

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

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

幸运的是,只要写出 template inline 函数,便可获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety):

template<typename T>
inline void callWithMax(const T& a, const T& b) //由于不知道 T 是什么,故采用 pass by reference-to-const
{
f(a > b ? a: b);
}

这个 template 产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用 f 。此外由于 callWithMax 是个真正的函数,它遵守作用域和访问规则,例如你可以写出一个”class 内的 private inline 函数“。一般而言宏无法完成此事。

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

请记住:

  • 对于单纯常量,最后以 const 对象或 enum 替换 #define。

  • 对于形似函数的宏(macros),最好改用 inline 函数替代 #define 。

Effective C++ 之 Item 2:尽量以 const, enum, inline 替换 #define的更多相关文章

  1. Effective C++ -----条款02:尽量以const, enum, inline替换 #define

    class GamePlayer{private: static const int NumTurns = 5; int scores[NumTurns]; ...}; 万一你的编译器(错误地)不允许 ...

  2. Effective C++ 条款02:尽量以const,enum,inline替换 #define

    换一种说法就是宁可以编译器替换预处理器 举例 #define ASPECT_RATIO 1.653 记号ASPECT_RATIO也许从未被编译器看见:也许在编译起开始处理源码前它就被预处理器移走了,于 ...

  3. Effective C++学习笔记 条款02:尽量以const,enum,inline替换 #define

    尽量使用const替换 #define定义常量的原因: #define 不被视为语言的一部分 宏定义的常量,预处理器只是盲目的将宏名称替换为其的常量值,导致目标码中出现多分对应的常量,而const定义 ...

  4. Effective C++阅读笔记_条款2:尽量以const,enum,inline替换#define

    1.#define缺点1 #define NUM 1.2 记号NUM可能没有进入记号表,在调试或者错误信息中,无法知道1.2的含义. 改善:通过const int NUM = 1.2; 2.#dein ...

  5. NO.2: 尽量以const,enum,inline 替换 #define

    1.首先#define 定义不重视作用域(scope),虽然可以#undef控制,但是不美观,还存在多次替换的问题,以及没有任何封装性. 2.const XXX_XX,保证其常量性以及可控的作用域,如 ...

  6. 读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define

    其实这个条款分成两部分介绍会比较好,第一部分是用const和enum替换不带参的宏,第二部分是用inline替换带参的宏. 第一部分:用const和enum替换不带参宏 宏定义#define发生在预编 ...

  7. 条款2:尽量以const, enum, inline替换#define

    原因: 1. 追踪困难,由于在编译期已经替换,在记号表中没有. 2. 由于编译期多处替换,可能导致目标代码体积稍大. 3. define没有作用域,如在类中定义一个常量不行. 做法: 可以用const ...

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

    目录 1. 总结 2. 使用const常量或enum替换宏常量 class外部的常量指针 class专属常量 1. 总结 对于单纯常量,最好以const常量或enum替换#define 对于宏代码段, ...

  9. Effective C++之条款2:尽量以const enum inline替换 #define

    本文的标题也可以改成“用编译器替换预处理器”: const double AspectRatio = 1.653; //最好使用上述代码替换下述代码: #define ASPECT_RATIO 1.6 ...

  10. 条款2:尽量使用const ,enum,inline替换define

    宁可使用编译器而不用预处理器 假设我们使用预处理器: #define ABC 1.56 这标识符ABC也许编译器没看到,也许它在编译器处理源码前就被预处理器移走了,于是“标识符”ABC没有进入标识符列 ...

随机推荐

  1. Closest Binary Search Tree Value I & II

    Closest Binary Search Tree Value Given a non-empty binary search tree and a target value, find the v ...

  2. 学习Selenium2Library的好例子

    最近好几个人问我有没有好的例子可以帮助学习Selenium2Library怎么用.对于公司同事,可以把脚本直接给过去,其他人则不行了.所以一直想做一个好的学习样例,这个样例应该有如下特性: 能够非常好 ...

  3. Robot Framework + Selenium2Library环境下,结合Selenium Grid实施分布式自动化测试

    最近一段时间,公司在推行自动化测试流程,本人有幸参与了自定义通用控件的关键字封装和脚本辅助编写.数据驱动管理.测试用例执行管理等一系列工具软件的研发工作,积累了一些经验,在此与大家做一下分享,也算是做 ...

  4. 【编程题目】输出 1 到最大的 N 位数

    65.输出 1 到最大的 N 位数(运算)题目:输入数字 n,按顺序输出从 1 最大的 n 位 10 进制数.比如输入 3,则输出 1.2.3 一直到最大的 3 位数即 999. 思路:肯定要考虑数字 ...

  5. HDU 5752 Sqrt Bo (思维题) 2016杭电多校联合第三场

    题目:传送门. 题意:一个很大的数n,最多开5次根号,问开几次根号可以得到1,如果5次还不能得到1就输出TAT. 题解:打表题,x1=1,x2=(x1+1)*(x1+1)-1,以此类推.x5是不超过l ...

  6. JS 保留两位小数问题收集

    1.使用四舍五入的方法,保留小数点后的两位小数: toFixed里面的参数表示保留的小数的位数,范围是0-20,超过20位就会报错了 <script> var num=22.127456; ...

  7. Jquery如何判断Radiobutton是否选中

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. BigInteger类型的解析_超详细解析

    /*9876543210987654234522345 214748364723453452323452345 2147483647234523452323452345 181760911432744 ...

  9. nyoj1007(euler 函数)

    euler(x)公式能计算小于等于x的并且和x互质的数的个数: 我们再看一下如何求小于等于n的和n互质的数的和, 我们用sum(n)表示: 若gcd(x, a)=1,则有gcd(x, x-a)=1: ...

  10. xml配置文件

    xml文件的层级结构      configuration 配置  properties 属性     settings 设置     typeAliases 类型命名     typeHa ...