【条款01】 视C++为一个语言联邦
① C语言部分 ② Object-Oriented C++ ③ Template C++泛型编程 ④ STL :容器、迭代器、算法、函数对象
高效的传值方式: pass-by-value对于内置类型 和 (迭代器、函数对象)[指针实现]
pass-by-reference对于用户自定义类来说比较高效,pass-by-value的时候会调用复制构造函数。
【条款02】 尽量以const, enum, inline 代替 #define
#define定义常量容易被编译器处理掉并未进入符号表,不会有类型检查,当出现错误时报字面信息,不容易定位;而且不能定义局部常量, 定义函数容易出现副作用 (尽量用template<typename T> inline 函数代替#define定义函数,有类型检查且不会有副作用)。
用const可以定义常量,有类型检查,可以定义在scope中。
const static 类型在类中编译器可能不支持初值, 可以用 enum常量来mock,解决这个问题,如下:
class GamePlayer {
private:
enum { NumTurns = 5};
int scores[NumTurns];
};
【条款03】 尽可能的使用const
(const可以指定一个语义约束,指明哪些对象是不能被改变的)
1. const指针
char[] str= “hello”;
char* p = str; // non-const pointer; non-const data
const char * p = str; //non-const pointer; const data
char const * p = str; //non-const pointer; const data
char * const p = str; //const pointer, non-const data
const char * const p = str; // const pointer, const data
const出现在星号左边时,指针指向的内存内容不可变,但可以指向其它对象的地址;const出现在星号的右边时,指针不可改变其指向,但可以改变内容;const出现在星号左右两边时,其指向和内容都不可以改变。
2. const迭代器
vector<int> vec;
const vector<int>::iterator iter = vec.begin(); //不能改变iter的指向,但可以更改其内容;相当于 char* const p
const vector<int>::const_iterator iter = vec.begin(); //可以改变其指向,但不可以改变其内容; 相当于 const char* p
3.函数返回值设为const对象, 可以防止函数返回值被另修改
const Object operator*(const Object& lhs, const Object& rhs);
Object a, b, c;
a * b = c; // 若返回值不为const类型,返回结果就会被c覆盖;返回const类型后则会报错
if (a*b == c) // 若误写为 if(a*b = c)同样会报错
4.const成员函数
该成员函数可作用于const对象之上,类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。
在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于改变数据成员的成员函数不能加 const。所以 const 关键字对成员函数的行为作了更加明确的限定:有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内),只能读取数据成员,不能改变数据成员;没有 const 修饰的成员函数,对数据成员则是可读可写的。
除此之外,在类的成员函数后面加 const 还有什么好处呢?那就是常量(即 const)对象可以调用const 成员函数,而不能调用非const修饰的函数。正如非const类型的数据可以给const类型的变量赋值一样,反之则不成立。
两个成员函数如果只是常量性不同,可以被重载。
如string类型的[]操作符就有const和non-const两种,const型返回const char&,非const型返回char&;
当const和non-const成员函数有着实质的等价实现时,另non-const版本调用const版本,可以简化代码实现。
在const成员函数中不可调用non-const成员函数,const函数不能改变non-const成员变量的值,如在其中调用non-const函数则可能改变成员变量的值。
因此,由non-const成员函数调用const成员函数,不存在限制条件。 但若两个函数仅仅是采用const型的重载,在non-const调用const实现时,需要用static_cast对this指针的对象强制转换为const类型对象来调用const成员函数,否则就是non-const成员函数自己调用自己,是死循环。
static_cast<const Object>(*this).func();若const函数返回值为const类型,non-const函数返回值为non-const类型,则需要用const_cast对const对象的返回值进行去const转换,然后再作为non-const函数的返回值返回。
return const_cast<R&> ( static_cast<const Object&>(*this).func());
【条款04】 确定对象被使用前被初始化
c part-of-C++为保证效率,不确保其内容已被初始化,如array; 而STL来自part-of-C++则保证其内容被初始化了。
因此,要慎重处理,必须永远保证使用对象之前将其初始化。 内置成员变量必须手动初始化。
保证类的每个构造函数都将对象的每一个成员初始化。
【条款20】 宁以pass-by-reference-to-const 替换 pass-by-value
pass-by-value传参会调用类的构造函数生成对象副本,结束时还要调用析构函数销毁临时副本,这都大大增加了开销。
pass-by-reference-to-const实质上是传递的对象指针,不会有临时对象生成,没有相应的构造和析构开销。
对象切割问题:当形参为基类对象,实参为派生类对象以pass-by-value方式传入时,仅会调用基类的copy构造函数生成临时对象,派生类的特质部分全被切割掉了。
若想传入不同派生类的对象,则应使用基类的指针或引用,即pass-by-reference 或 pass-by-pointer方式,为防止传入对象被修改,应采用const修饰。
同理,由于容器内保存的也是调用类的copy-ctor函数生成的临时对象, 若想在同一容器内保存不同派生类对象时,也需要保存派生类对象的指针,否则也会产生对象切割问题 。 如 vector<Base> vec; // error vector<Base*> vec; // 可以用来保存派生类指针