More Effective C++学习笔记(1)

目录

  • 条款1:仔细区别pointers和references
  • 条款2:尽量使用C++风格的类型转换
  • 条款3:绝对不要以多态方式处理数组
  • 条款4:非必要不提供默认构造函数

条款1:仔细区别pointers和references

  • 在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某个对象,必须有初值。
  • 如果变量指向可修改,且有可能指向null,就把变量设为指针;如果变量总是必须代表一个对象(不可能为null),就把变量设为引用
  • 引用可能比指针更高效,因为不必像指针那样在使用前判断它是否有效(no null)
//使用引用之前不需要测试它的合法性。
void printDouble(const double& rd)
{
    cout << rd;         // 不需要测试rd,它
}                       // 肯定指向一个double值
//相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
  if (pd) {             // 检查是否为NULL
    cout << *pd;
 }
}

条款2:尽量使用C++风格的类型转换

  • 旧式C的转型操作符缺点:(1)不同转型意图不明确;(2)难以辨识,与C++中其他用到“(类型)”地方难以区分。
  • C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是static_cast, const_cast, dynamic_cast, 和reinterpret_cast
  • static_cast:在功能上基本上与C风格的类型转换一样强大,含义也一样;不能从表达式中去除const属性。
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
//如果用上述新的类型转换方法,你应该这样写:
double result = static_cast<double>(firstNumber)/secondNumber;
  • const_cast:用于类型转换掉表达式的const或volatileness属性。
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
SpecialWidget sw;                // sw 是一个非const 对象。
const SpecialWidget& csw = sw;   // csw 是sw的一个引用
                                // 它是一个const 对象 
update(&csw);  // 错误!不能传递一个const SpecialWidget* 变量
               // 给一个处理SpecialWidget*类型变量的函数 
update(const_cast<SpecialWidget*>(&csw));
			// 正确,csw的const被显示地转换掉(
			// csw和sw两个变量值在update
			//函数中能被更新) 
  • dynamic_cast:用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。它不能被用于缺乏虚函数的类型上。
Widget *pw;
...
update(dynamic_cast<SpecialWidget*>(pw));
			// 正确,传递给update函数一个指针
			// 是指向变量类型为SpecialWidget的pw的指针
			// 如果pw确实指向一个对象,
			// 否则传递过去的将使空指针。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
                         //正确。 传递给updateViaRef函数
                         // SpecialWidget pw 指针,如果pw 
                         // 确实指向了某个对象
                         // 否则将抛出异常

  • reinterpret_cast:最普通的用途就是在函数指针类型之间进行转换;转换函数指针的代码是不可移植的。
typedef void (*FuncPtr)();      // FuncPtr is 一个指向函数
                                // 的指针,该函数没有参数
				// 返回值类型为void
FuncPtr funcPtrArray[10];       // funcPtrArray 是一个能容纳
                                // 10个FuncPtrs指针的数组
int doSomething();
funcPtrArray[0] = &doSomething;     // 错误!类型不匹配 
reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
funcPtrArray[0] =                   // this compiles
  reinterpret_cast<FuncPtr>(&doSomething);

条款3:绝对不要以多态方式处理数组

  • 数组中难免会遇到数组索引运算符array[ i ],本质上式一个指针算术表达式*(array+i),通过数组原类型的大小计算每个数组元素的地址。如果将派生类对象数组实参传递给原本为基类对象数组的形参(类似多态的方式),那么代码将仍以基类对象占用的内存大小计算对象数组的每个元素地址,问题的关键在于派生类对象几乎总是比基类对象要大,这样计算出来的元素地址就会有问题,行为是未定义的。
class BST { ... }; 
class BalancedBST: public BST { ... };

void printBSTArray(ostream& s,
                   const BST array[],
                   int numElements)
{
  for (int i = 0; i < numElements; ) {
    s << array[i];          //假设BST类
  }                         //重载了操作符<<
}

BalancedBST bBSTArray[10]; 
... 
printBSTArray(cout, bBSTArray, 10); //报错
  • 用delete [ ] 删除数组时,数组元素中每一个析构函数会被调用,依然会涉及到“指针算术表达式”,所以多态与数组不能混用:
delete [] array;
//它会这样生成代码:
// 以与构造顺序相反的顺序来
// 解构array数组里的对象
for ( int i = 数组元素的个数 1; i >= 0;--i)
 {
    array[i].BST::~BST();                     // 调用 array[i]的
  }                                           // 析构函数

条款4:非必要不提供默认构造函数

  • 需要外部数据信息来建立对象的类则不必拥有缺省构造函数。但是,如果类不提供默认构造函数,会在两个情况下遇到困难:(1)建立类对象数组时,大多数情况下需要默认构造函数进行初始化;(2)将不适用于它们无法在许多基于模板(template-based)的容器类里使用。
  • 解决问题(1)的方法1使用非堆数组(non-heap arrays)
int ID1, ID2, ID3, ..., ID10;            // 存储设备ID号的
                                         // 变量
... 
EquipmentPiece bestPieces[] = {          // 正确, 提供了构造
  EquipmentPiece(ID1),                   // 函数的参数
  EquipmentPiece(ID2),
  EquipmentPiece(ID3),
  ...,
  EquipmentPiece(ID10)
};
  • 解决问题(1)的方法2利用指针数组来代替一个对象数组。缺点在于必须记得对指针数组所指的所有对象进行删除,此外,内存总量会变大(指针+对象)
typedef EquipmentPiece* PEP;             //  PEP 指针指向
                                         //一个EquipmentPiece对象
PEP bestPieces[10];                      // 正确, 没有调用构造函数
PEP *bestPieces = new PEP[10];           // 也正确
//在指针数组里的每一个指针被重新赋值,以指向一个不同的EquipmentPiece对象:
for (int i = 0; i < 10; ++i)
  bestPieces[i] = new EquipmentPiece( ID Number );
  • 解决问题(1)的方法3使用placement new方法在内存中构造对象,指定一块大的连续内存中,在指定地址构建对象。避免了方法2中的过度使用内存的问题。缺点在于大部分程序员不熟悉placement new,维护比较困难;当你不想让它继续存在使用时,必须手动调用数组对象的析构函数,然后调用操作符delete[]来释放raw memory(已经有placement delete/delete []操作符了,它会自动调用析构函数)。
// 为大小为10的数组 分配足够的内存
// EquipmentPiece 对象;
// operator new[] 函数
void *rawMemory =
  operator new[](10*sizeof(EquipmentPiece));
// make bestPieces point to it so it can be treated as an
// EquipmentPiece array
EquipmentPiece *bestPieces =
  static_cast<EquipmentPiece*>(rawMemory);
// construct the EquipmentPiece objects in the memory
// 使用"placement new" (参见条款8)
for (int i = 0; i < 10; ++i)
  new (&bestPieces[i]) EquipmentPiece( ID Number );
// 以与构造bestPieces对象相反的顺序
// 解构它。
 - List item

for (int i = 9; i >= 0; --i)
  bestPieces[i].~EquipmentPiece(); 
// deallocate the raw memory
operator delete[](rawMemory);

//如果你忘记了这个要求而使用了普通的数组删除方法,那么你程序的运行将是不可预测的。这是因为:直接删除一个不是用new操作符来分配的内存指针,其结果没有被定义。
delete [] bestPieces;                    // 没有定义! bestPieces
                                    //不是用new操作符分配的。
  • 针对于问题2没有默认构造函数的类将不适用于它们无法在许多基于模板(template-based)的容器类里使用。因为实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。
template<class T>
class Array {
public:
  Array(int size);
  ... 
private:
  T *data;
}; 
template<class T>
Array<T>::Array(int size)
{
  data = new T[size];                    // 为每个数组元素 
  ...                                    //依次调用 T::T()
}
  • 虚基类应该有一个默认构造函数,因为如果随着继承体系增加,每个派生类都需要记得基类的有参构造并了解其含义是一件痛苦的事情。
  • 添加无意义的默认构造函数有时会影响类class的效率。因为成员函数会需要测试有参和无参的情况下,某些字段是否已经被初始化。

你可能感兴趣的:(c++,学习,笔记)