导读

explicit关键字
阻止类型之间的隐式转换。

class B{
	explict B(int x = 0);
}
Void doSomething(b BObject);
doSomething(B(28)); //正确
doSomething(28); //错误

copy构造和copy赋值

Class Object{
	Object():  //default构造
	Object(const Object& obj); //copy构造
	Object& operator=(const Object& obj);	//copy赋值操作符
}
Object obj1;  //default构造
Object obj2(obj1);  //copy构造
obj1 = obj2; //copy赋值操作符
Object obj3 = obj2; //调用copy构造

如果一个新对象被定义,如obj3,一定会调用一个构造,不能调用赋值。
如果没有新对象被定义,如obj1=obj2,不会有构造调用,调用赋值

bool function(Object obj)

参数obj以传值方式传递,会被复制到函数参数中,又copy构造完成。因此尽量通过传const引用。条款20

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

const替换#define
若用 define 的可能会导致程序出出现多份目标码,而常量不会出现这种情况

1.定义一个常量字符串

const std::string name = "name";

2.class专属常量,为了将作用域限制于类内,并且最多只有一个实体

class object{
	static const int num = 5;
	int arr[num];
}

如果是class专属常量又是static且为整数类型(例如ints,chars,bools),只要不取地址,可直接声明并使用.如果去某个class专属常量的地址,必须额外提供定义式。定义式放在.cpp文件而非.h文件

const int object::num;

注:声明时已赋过值,不可再赋值
若编译器不支持声明时赋值,则定义时赋值。
但遇到数组大小这种必须知道值时。使用enum
enmu替换#define

class Object
{
public:
	enum {num=5};
	int arr[num];
};

一个属于枚举类型的数值可以充当int使用
取一个 const 的地址是合法的,而取一个 enum 的地址是非法的,而取一个#define 的地址通常也不合法。如果不想别人指针或者引用自己某个整形常量,enum 可以实现这个约束。
inline替换#define
#define函数易误用,纯文本替换需要时刻注意小括号,否则运算符优先级可能出错,甚至

#define MAX(a,b) f((a)+(b) ?(a):(b))
int a = 1, b = 2;
MAX(++a, b);	//a被累加2次
MAX(++a, b+10);	//a被累加1次

使用inline替换

template <typename T>
inline T max(const T& a,const T& b)
{
	f(a > b ? a: b);
}

总结
1.单纯常量,const对象或enums替换
2.形式函数的宏(macros),inline替换

条款3 尽可能使用const

const作用
const左修饰,最左右修饰
顶层const:指的是const修饰的变量本身是一个常量
底层const:指的是const修饰的变量所指向的对象是一个常量
(声明引用变量都是底层,引用对象是常量)
stl
stl 的迭代器基础是指针,const迭代器与const指针一样。如果希望const T* 指针,则需要const_iterator
函数返回值用const修饰

class N(...);
const  N operator*(const N &lhs, const N &rhs);

防止一些误操作造成赋值。如

N a,b,c;
...
if(a*b = c)		//如果没有const,则隐式转为true

const用于成员函数
为了确认该成员函数可作用于const对象
1.告知哪个函数可以修改成员内容,哪个函数不能
2.使“操作const对象”成为可能

两个成员函数如果只是是常量性不同,可以构成重载。

const T& getXXX() const; //get值不可修改
T& getXXX(); //get值可修改
//可以get同一个成员变量

const成员函数不可以更改对象内任何非静态成员变量。
但两种情况:
1.bitwise constness:成员函数只有在不更改对象的任何成员变量时才可以说是 const,缺陷:若返回的是内部常置针(指针常量)但可以修改指向的对象
2.logical constness:const 成员函数可以修改它所处理的对象的某些值,但只有在客户端侦测不到的情况下才得以如此。(常函数里面除了对常变量的使用,还可能包含一些变量的使用。但是编译器不同意,这时候可以使用 mutable 关键字,使得变量不受函数常量性的约束,可在const函数里修改成员变量)
避免 const 和 non-const 实现的重复
一般的,我们建议在 non-const 函数调用 const 函数的实现。因为const 函数承诺不修改其对象的逻辑状态,但如果去调用 non-const 函数,就可能冒着修改对象的风险

const int& operator[](int idx)const
{
   return num;
}
int& operator[](int idx)
{
	//return num;
	return const_cast<int&>(static_cast<const Object&>(*this)[idx]);
}

static_cast 负责将 *this 对象转换成常对象,这样才能调用常函数
const_cast 负责将常函数返回的常引用转换成普通引用

条款4:确定对象使用前已先被初始化

c++规定,对象的成员变量初始化动作发生在进入构造函数本体之前。

Class Obejct{
public:
	Object(const std::string & s);
private:
	std::string str;
	int num;
}
//std::string先defalut构造,再赋值
Object::Object(const std::string & s){
//赋值,非初始化
	str = s;
	num = 0;
}
//std::string直接copy构造,初始化,效率更高
Object::Object(const std::string & s):str(s), num(0)
{}

对多数类型,先调用default再copy赋值,效率不如直接调用一次copy构造。
即对于未在初始列指定初值的成员,编译器会自动调用默认构造函数(也就是说会先调用默认构造函数,在再执行构造函数内的赋值语句)。初值列的效率无疑更高。
对于内置型对象num,初始化和赋值成本相同。但为了一致性,也通过成员初值列初始化
如果成员变量是const或references,为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:构造函数使用成员初始化列表来赋值,而不是在构造函数里去赋值(会导致赋值两次)
C++有着非常固定的成员初始化次序。base class 更早于 derived class 被初始化。class成员总是以声明次序被初始化。即使在成员初值列中定义的次序不同,也不会有任何影响
不同编译单元非局部静态对象的初始化顺序
如果某编译单内的某个非局部对象的初始化依赖于另一个编译单元内某个非局部对象,而这个对象可能尚未被初始化,然后就会导致未定义行为。
将每一个非局部静态对象搬到自己专属函数中,函数返回静态对象的引用,然后由用户调用这些函数,而不直接涉及这些对象。类似单例模式
在多线程下,系统会带有不确定性,一个比较好的做法就是:在程序的单线程启动阶段手工调用所有的单例函数

12-27 11:30