《Effective C++》阅读笔记条款01 02

条款01:视C++为一个语言联邦

C++支持5种编程范式

  • 面向过程编程范式
  • 面向对象编程范式
  • 函数式编程范式
  • 泛型编程范式
  • 元编程范式

正因为C++编程范式众多所以C++的编码似乎没有一定之规,其实可以根据范式将C++看做为一个语言集合{C、面向对象C++、模板C++、STL}。对于C++的编码其实就可以看做,不同的子语言的使用,使用哪种子语言就应用哪种子语言所对应的范式下的“恰当实践、编码准则”。

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

宏定义的问题

问题1 没有进入符号表

# define ASPECT_RATIO 1.653

宏定义是在预处理阶段处理的,直接将源代码中的ASPECT_RATIO替换为1.653,而非编译阶段,因此,编译器的符号表中根本不会出现宏定义的ASPECT_RATIO。所以这就导致编译阶段报错的话就只会提到1.653而不是ASPECT_RATIO,难以定位问题所在,尤其是在第三者使用一个有ASPECT_RATIO定义的头文件时。此外,编译器是将源代码转换为汇编代码,所以简单的替换也会产生很多重复的1.653,增加内存中代码区的代码量使用调试器时也会出现无法追踪的问题。

解决方案

用const常量替换宏。

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

常量是在编译阶段处理的东西,会被记入符号表,容易定位错误、减少汇编代码量、容易追踪调试。

问题2 #define不重视作用域

一旦宏被定义,这个宏就在之后整个编译过程都有效 (在没有被#undef之前) ,所以宏定义不具备任何封装性,也无法定义class专属常量。

解决方案

见class专属常量部分。

问题3 #define误用——实现一个宏“函数”

这种宏看起来很像函数,但它不会有任何的函数调用过程带来的额外开销,这是优点,它的开销比函数小,但同时也带来很大的问题。

// 以 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被累加一次

a的累加次数都是不确定,取决于“它被拿来和谁比较”。这很有可能与编码者的预期不同。

解决方案

详见后文template inline函数

使用const的注意事项

const指针

因为常量通常定义在.h文件中,一遍会被不同的.cpp包含,所以要保证指针的确定性,指针本身的指向以及指针所指之物都不应该变化,因此,指针要const两部分。

const char* const authorName = "Scott Meyers";

注:string对象通常比char*合宜,所以上述这么定义更好

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

class专属常量

首先,将常量的作用域限制在class中,使其成为class的一个成员。之后,为了确保该常量至多只有一份实体,再用static进行限制。这就实现了一个class的专属常量。

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

同时,我们也要注意上述专属常量只是声明而非定义。通常C++要求你对你所使用的任何东西提供一个定义式,但如果是类型为integral type (ints, chars, bools) 的class专属常量,就只需要在一些情况下提供定义式,需要提供定义式的情况是:

  • 取某个class专属常量的地址
  • 编译器要求必须定义

除此之外,是可以不提供定义式的。
定义式一般放入实现文件而非头文件。格式如下:

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

因为NumTurns已经在声明式中就赋予初值了,因此定义时就不可以再设置初值了。否则,会报重复初始化的错误。

专属常量的初值设置

上述的 in-class 初值设置方式在一些情况并不适用:

  • 一些老旧版本的编译器可能不支持static成员在声明时设置初值
  • 非integral type (ints, chars, bools) 类型的常量不能在声明时设置初值

这些情况下,就要将初值放在定义式

// 位于头文件内
class CostEstimate {
private:
	static const double Fudgefactor; // static class 常量声明
	...
};
// 位于实现文件内
const double CostEstimate::FudgeFactor = 1.35; // static class 常量定义
the enum hack补偿做法

还有一种情形,有的class在编译期间需要一个明确的class常量值,但是编译器并不支持 in-class 初值设置方式,这是就可以使用the enum hack补偿做法。因为**一个enum类型的数值可充当 ints 使用。

class GamePlayer {
private:
	enum { NumTurns = 5 }; // "the enum hack": 令NumTurns成为5的一个记号名称
	int scores[NumTurns]; // OK
	...
};

the enum hack的两点独到之处:

  • 行为更像#define而非const。enum和#define都不能被取地址;enum和#define一样绝不会有非必要的内训分配
  • enum hack是模板元编程的基础技术

template inline函数

首先inline函数有着与#define相似的效率,以及具有一般函数的所有可预期行为以及类型安全性。同时,template带来了泛化性。

template<typename T>
inline void callWithMax(const T& a, const T& b) // 因为不清楚T的类型,所以采用pass by reference-to-const
{
	f(a > b ? a : b);
}

template inline函数同样可以被作用域限制,这也是宏无法完成的。

总结

  • 对于单纯常量,最好以const对象或者enum替换#define
  • 对于形似函数的宏,最好改用inline函数替换#define
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值