1 让自己习惯C++
条款01:视C++为一个语言联邦
1.C++是个多重范型编程语言:procedural (过程形式)、object-oriented (面向对象形式)、functional (函数形式)、generic (泛型形式)、metaprogramming (元编程形式 )
2.C++的4个次语言:C、Object-Oriented C++、Template C++、STL
3.小结:C++高效编程守则会视状况而变化,取决于使用C++的哪一个部分
条款02:尽量以const、enum、inline替换#define
1.#define 所设置的内容会在预处理器的时候被移走(预处理时全部替换),因此编译器会看不到它,有可能对我们追踪、查错造成很多不必要的时间上的浪费
2.浮点常量而言,使用常量const可能比#define导致较小量的码
3.定义常量指针。由于常量定义式通常被放在头文件内,因此有必要将指针声明为const;
4.类的专属常量。为了将常量的作用域限制于class内,必须让其成为该class的一个成员,而为确保此常量至多只有一份实体,必须让其成为一个static成员。这个用#define做不到,因为#define 并不重视 作用域,一旦被定义,它在其后的编译过程都有效,除非在某处#undef。注意如下代码:
class GamePlayer{
private:
static const int NumTurns = 5; //常量声明,此处是声明而非定义。
int scores[NumTurns]; //使用该常量
...
};
//class内的常量static而且integral type(整数类型),只要不取其地址,可以声明并使用而无需提供定义式
//如果要取其地址,或编译器要求提供定义式则如下定义(在.cpp文件中):
const int GamePlayer::NumTurns;
// 常量已在声明时获得初值,就无须再设初值
5.“the enum hack”:一个属于枚举类型的数值可权充int被使用
GamePlayer{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
...
};
6.另一个常见#define误用情况是用它来实现macros(宏)
// 以a和b的较大值调用f
#define CALL_WITH_MAX(a,b) f( (a) > (b) ? (a) : (b) )
a = 5 , b = 0 ;
CALL_WITH_MAX(++a,b); // a被累加两次
CALL_WITH_MAX(++a,b+10); // a被累加一次
/*宏替换后如下:
f((++a)>(b)?(++a):(b));
f((++a)>(b+10)?(++a):(b+10));
?:运算符就是先判断?前面的表达式是否为真,为真的话就运行:前面的代码,否则就运行:后面的代码*/
这里,调用f之前,a的累加次数,竟然取决于“它被拿来和谁比较”!这时,你就需要一个 template inline 函数 解决问题:
template<typename T>
inline void callWithMax( const T& a, const T& b){
f( a > b ? a : b );
}
7.小结:
<1>对于单纯常量,最好以const 对象或者enum 替换 #define;
<2>对于形似函数的宏,最好改用 inline函数 替换 #define
条款03:尽可能使用const
1.如果const出现在星号(*)左边,表示被指针所指的物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针都是常量
2.令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。避免(a*b)=c的错误
3.对于函数参数,除非你有需要改动参数或local const对象,否则请将它们声明为const。传递指向常量的引用
4.将const实施于成员函数的目的:它们使classes接口比较容易理解(可以明确知道哪个函数可以改动对象内容,而哪个不能);它们使“操作const对象”成为可能。 这个是 “pass by reference to const 方式传递对象”技术的基础。
5.小结:
<1> 将某些东西声明为 const 可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
<2> 编译器强制实施bitwise constness,但你编写程序时应该使用 conceptual constness(概念上的常量性)。
<3> 当const和non-const 成员函数有着实质等价的实现时,令non-const版本用const版本可避免代码重复。
条款04:确定对象被使用前已先被初始化
1.如果使用C part of C++ 而且初始化可能招致运行期成本,那么就不保证发生初始化,但若进入non-C parts of C++ 规则就会有些变化。例如来自C part of C++ 的array 不保证它的内容被初始化,而来自STL part of C++的 vector 却有这个保证。
2.对于无任何成员的内置类型,必须手工完成初始化;而对于非内置类型,初始化的责任就在 constructors(构造函数)身上:确保每一个构造函数都将对象的每一个成员初始化。
3.注意别混淆赋值和初始化:
class PhoneNumber {...};
class ABEntry {
public:
ABEntry( const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones );
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones)
{
theName = name;// 这些都是赋值,并非初始化
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones)
:theName(name),// 这些都是初始化
theAddress(address),
thePhone(phones),
numTimesConsulted(0)
{// 构造函数本体不必有任何动作
}
4.总是使用成员初值列,列出所有成员变量
5.许多classes 拥有多个构造函数,每个构造函数有自己的成员初值列,多份成员初始列就会导致重复动作。
解决:这种情况就可以合理的在初值列中遗漏那些“赋值操作和初始化一样好”的成员变量,把它们用赋值操作,并将这些操作移到一个private函数,供所有构造函数使用,这种做法尤其适用在:“成员变量的初值是由文件或数据库读入”的时候。当然相对于这种的“伪初始化“,还是用成员初值列的“真正初始化“通常更可取。
6.C++ 有着十分固定的“成员初始化次序”,次序就是 base classes 更早于其 derived classes 被初始化。classes成员变量总是以其声明的顺序次序初始化。
7.C++ 对“定义于不同编译单元内的non-local static 对象”的初始化相对次序并无明确定义。
解决:将每一个non-local static对象搬到自己的专属函数内(该对象在此函数声明为static),这些函数返回一个reference指向它所含的对象,这样 non-local static 对象被 local static对象替换了。而C++保证,函数内的local static 对象会在“该函数被调用期间”“首次遇上该对象定义式”时被初始化。而且这样做以后,如果在执行期间,没有调用 non-local static 的仿真函数,就绝对不会引发构造和析构成本,这也是原来的non-local static没有的好处。
8.小结:
<1>为内置型对象进行手工初始化,因为C++不保证初始化它们。
<2>构造函数最好用 成员初值列 ,而不要在构造函数中使用赋值操作。初值列列出的成员变量,应该按照其在类内的声明次序进行排序。
<3>为免除”跨编译单元之初始化次序“问题,请以local static对象替换 non-local static对象。