01:View C++ as a federation of languages.
4个次语言
-
- C:区块(blocks)、语句(statement)、预处理(preprocessor)、内置数据类型(built-in and type)、数组(arrays)、指针(pointers)
- Object-Oriented C++:class、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数(动态绑定)
- Template C++:
-
STL:
02:尽量以const, enum, inline替代#define(Prefer consts, enums, and inlinest to #defines.)
#define ASPECT_RATIO 1.653
替换为
const double AspectRatio = 1.653
const char * const authorName = "Scott Meyers"
这样更好
const std::string authorName("Scott Meyers")
为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员(member);而为确保比常量至少只有一份实体,你必须让它成为一个static成员:
class GamePlayer{
private:
static const int NumTurns = 5; //常量声明式
int scores[NumTurns]; //使用该常量
...
};
const int GamePlayer::NumTurns; //NumTurns的定义:
旧式的编译器也许不支持上述语法,他们不允许static成员在声明式的时候获得初值,你可以把初值放在定义式中:
class CostEstimate{
private:
static const double FudgeFactor; //static class常量声明
... //位于头文件中
};
const double CostEstimate::FudgeFactor = 1.35; //位于实现文件中
万一你的编译器不允许“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的地址通常也是不合法的。
- 实用主义:许多代码用了它,事实上“enum hack”是template metaprogramming(模板元编程)的基础技术
//以a和b的较大值调用f
#define CALL_WITH_MAX(a, b) f((a) > f(b) ? (a) : (b) )
但是写成这样依然会有一些麻烦
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //a被累加两次
CALL_WITH_MAX(++a, b+10) //a被累加一次
你可以获得获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety)---------只要你写出template inline函数
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
请记住
- 对于单纯常量,最好以const对象或enums替换#defines。
-
对于形似函数的宏(macros),最好改用inline函数替换#defines.
03:尽可能的使用const (Use const whenever possible)
const语法虽然变化多端,但是并不高深莫测。如何关键字const出现在星号左边,表示被指物是常量;如何星号出现在右边,表示指针自身是常量;如何出现在星号两边,表示被指物和指针两者都是常量。
STL佚代器以指针为根据塑造出来,所以佚带器的作用就像个T*指针。
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); //iter的作用就像个T*const
*iter = 10; //没问题,改变的是iter所指的值
++iter; //错误,iter是const
std::vector<int>::const_iterator cIter = vec.begin(); //相当于const T*
*cIter = 10; //错误
++cIter; //正确
令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃高安全性和高效性
class Rational { ... };
const Rational operator* ( const Rational& lhs, const Rational& rhs );
为什么要返回一个const类型,原因如下
Rational a, b, c;
...
(a * b) = c;
或者
if(a * b = c)... //其实是想做一个比较(comparison)动作,而const可以报错
请记住
- 将某些东西声明为const可以帮助编译器侦测出错误用法。const可被施加于任何作用域内对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
-
当const和non_const成员函数有着实质的等价的实现时,令non_const版本调用const版本可避免代码重复。
04:确定对象被使用之前已被初始化(Make sure that object are initialized before they're used)
至于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。
重要的是别混淆了赋值(assignment)和初始化(initialization)。考虑一下例子,表现通讯簿的class
这会导致ABEntry对象带有你期望的值,但在这不是最佳做法,C++规定对象的成员函数的初始化动作发生在进入构造函数本体之前。在ABEnty构造函数内,theName,theAddress和thePhone都不是被初始化,而是被赋值。初始化的时间发生得更早,发生于这些成员的default构造函数被自动调用之时(比进入ABEntry构造函数本体的时间更早)。
但是对numTimesConsultde不为真,因为它属于内置类型,不保证一定在你所看到的那个赋值动作时间点之前获得初值。
ABEntry构造函数的一个较佳写法是,使用所谓的member initialization list(成员初值列)替换赋值动作:
非本地STAITC(non_local static)对象被local static替换了
请记住
- 为内置对象进行手工初始化,因为不保证初始化他们。
- 构造函数最好使用成员初始列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和他们在class中的声明次序相同。
- 为免除“跨编译单元之初始化次序”的问题,请以local static 对象替换non_local static对象