基础议题
检索:pointers、references、casts(类型转换)、arrays(数组)、constructors(构造函数)
条款1:仔细区别pointers和references
references(*)和pointers(->)的差异?
- 没有 null references 【也就是一个引用必须总代表一个对象】
//示例:使用引用指向空指针【非法操作】
char* pc = 0; //空指针
char& rc = *pc; //引用代表一个 null pointer的解引值
- 正是由于没有null references,所以意味着references更有效率,因为不需要做空指针检测
- pointers可以被重新赋值
如何确定何时使用references or pointers?
references | 指向某个东西,且绝对不会改变指向其它东西 |
实现一个操作符而其语法需求无法由pointers完成,例如:operator[] | |
pointers | 其它 |
条款2:最好使用C++转型操作符
- static_cast<type>(expression) 类似C旧式转型相同的意义和相同的限制【1.无法将struct转int 1.无法将double转pointer】
- const_cast<type>(expression) 【常用】去除常量性且唯一去除其常量性
- dynamic_cast<type>(expression) 【常用】 应用于继承体系中向下安全转型操作:父类->子类,转型失败返回null
- reinterpret_cast<type>(expression) 【不常用】转换函数指针类型
条款3:绝对不要以多态处理数组
主要原因是数组下标在偏移的时候需要偏移一个数组所指元素的大小,如果使用多态则不能正确获取其子类的大小:举个例子
class BST{}; //基类对象【可能是虚基类】
class BlancedBST:public BST{}; //子类对象
//打印数组内容
//如果是基类对象数组则运行正常
//如果是使用多态存储的BlancedBST对象数组则有问题
void printBSTArray(ostream& s,const BST array[],int num)
{
for(int i = 0;i < num;i++)
{
s << array[i]; //array[i] -> *(array+i)
}
}
c++语言规范:通过父指针删除一个由 子类对象构成的数组,其结果未定义
条款4:非必要不提供 default constructor
default constructor指没有任何外来信息的情况将对象初始化,主要是防止一些无意义变量初值,虽然不提供的话会有很多限制
class EquipmentPiece{
public:
EquipmentPiece(int id);
private:
string _id;
};
由于EquipmentPiece缺乏 default constructor会在3中情况出现问题:
1.产生数组时
EquipmentPiece* array = new EquipmentPiece[4]; //Error
EquipmentPiece array[4]; //Error 无法调用ctors
此情况下的解决方案:
a. non-heap,直接调用赋值
//使用 non-heap 初始化数组
int ID1,ID2,ID3;
EquipmentPiece array[] = {
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3)
};
b.使用指针数组
typedef EquipmentPiece* PEP;
PEP array1[10]; //不用调用ctor
PEP* array = new PEP[10]; //同样没有问题
for(int i = 0;i < 10;i++)
{
array[i] = new EquipmentPiece(i);
}
//缺点:
//1.必须记得删除所指对象
//2.需要的内存总量比较大,需要同时放置指针和所指对象
//array内存模型
//array(PEP) -> [1] [2] [3] ... [10] //空间上连续 多出空间来放指针
// | | | |
// | | | |
// obj1 obj2 obj3 .. obj10 //空间上非连续
c.使用placement new,同时能够解决上述过度使用内存问题
//由指针构成数组的方法允许缺乏default ctor 情况仍能产生对象数组,但不意味着可以回避ctor提供的自变量
void* rawMemory = operator new[](10*sizeof(EquipmentPiece));
EquipmentPiece* arrayPtr = static_cast<EquipmentPiece*>(rawMemory);
//利用placement new构造对象
for(int i = 0;i < 10;i++)
{
new ( &arrayPtr[i] ) EquipmentPiece(i);
}
//删除是要点:1.手动调用destructors 2.调用 operator delete[]
for(int i = 9;i>= 0;--i)
arrayPtr[i].~EquipmentPiece();
operator delete[](rawMemory);
//使用一般数组删除语法将产生不可预期的行为
delete []arrayPtr //禁止此操作
2.存在模板类型时
此情况需要谨慎设计template,可以消除default ctor的需求(参考vector template:会产生行为类似“可扩展数组”的各种classes),需要读vector底层源码
template<class T>
class Array{
public:
Array(int size)l
private:
T* data;
};
template<class T>
Array<T>::Array(int size)
{
data = new T(size);
}
3.在使用虚基类时
如果缺乏default ctor,则继承体系中,要求所有的子类,不论距离(派生的层次)有多远,都要求初始化那个变量
通常做法是发现有这种继承体系的话会对它设置默认值,不过这会使得情况变得复杂,因为不知道这个参数的含义是什么,设置默认值很可能会出问题,同时添加无意义的default ctor也会
影响效率,因为需要检测字段是否真正初始化,需要调用者编写测试代码,浪费空间和时间。