条款1:指针与引用的区别
1.任何情况下都不能让引用指向空值,相反指针可以,引用应该被初始化
当然,引用不能为空也意味着使用引用的效率高于指针,因为在使用前无需判空
void func(const int &x)
{
cout << x;
}
void func(const int *x)
{
if(x){ //需要判空
cout <<x;
}
}
2.指针可以被重新赋值以指向另一个不同的对象,但引用总是指向初始化时指定的对象,以后不能改变
条款2:尽量使用C++风格的类型转换
static_cast
const_cast
转换掉对象的const属性
dynamic_cast
被用于安全地沿着类的继承关系向下进行类型转换
dynamic_cast 把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时)
class B;
class D:public B
{};
B *b;
//b经过转换后指向派生类D
func(dynamic_cast<B*>(b));
reinterpret_cast
转换结果几乎都是执行期定义( implementation-defined ),难移植
常见用途是函数指针之间的转换
typedef void (*Func)();//Func是函数指针,无参数,返回类型void
Func f[10]; //f是一个数组,容纳10个Func函数指针
//现在希望把doSomething函数的指针存入f数组中
int doSomething();
//由于返回类型不一样,因此需要转换
f[0] = &doSomething; //ERROR!类型不匹配
f[0] = reinterpret_cast<Func>(&doSomething);
条款3:不要对数组使用多态
例
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 类
}
}
BST BSTArray[10];
printBSTArray(cout, BSTArray, 10); // 运行正常
BalancedBST bBSTArray[10];
printBSTArray(cout, bBSTArray, 10); //ERROR
这里的 array[I]只是一个指针算法的缩写:它所代表的是*(array)。我们知道 array是一个指向数组起始地址的指针,但是 array 中各元素内存地址与数组的起始地址的间隔究竟有多大呢?它们的间隔是 i*sizeof(一个在数组里的对象),因为在 array 数组[0]到[I]间有 I 个对象。编译器为了建立正确遍历数组的执行代码,它必须能够确定数组中对象的大小,这对编译器来说是很容易做到的。参数 array 被声明为 BST 类型,所以 array 数组中每一个元素都是 BST 类型,因此每个元素与数组起始地址的间隔是 i*sizeof(BST)。
但是如果你把一个含有 BalancedBST 对象的数组变量传递给 printBSTArray 函数,你的编译器就会犯错误。在这种情况下,编译器原先已经假设数组中元素与 BST 对象的大小一致,但是现在数组中每一个对象大小却与 BalancedBST 一致。派生类的长度通常都比基类要长。我们料想 BalancedBST 对象长度的比 BST 长。如果如此的话,printBSTArray 函数生成的指针算法将是错误的, 没有人知道如果用 BalancedBST 数组来执行 printBSTArray 函数将会发生什么样的后果。不论是什么后果都是令人不愉快的。
条款4:避免无用的缺省构造函数
如果class中没有default构造函数,那么在下面几种情况下可能会出现问题
1.建立类对象数组时
class B
{
public:
B(int x);
};
B b1[10]; //ERROR
B *b = new B[10]; //ERROR
PS:解决方法:(1)使用非堆数组时,定义时可以一一赋值
(2)利用指针数组来代替
typedef B* Pb;
Pb b1[10]; //OK
Pb *b2 = new Pb[10]; //OK
PS:此方法缺点:必须删除数组里每个指针所指向的对象//增加内存分配量
2.无法在许多基于模版的容器里使用
因为实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。
条款5:谨慎定义类型转换函数
条款6:自增、自减操作符前缀形式与后缀形式的区别
class A
{
public:
//前缀
A &operator ++();
A &operator --();
//后缀
const A operator ++(int);
const A operator --(int);
};
注意:后缀返回的是const对象。因为返回的是增加前的值
const A A::operator ++(int)
{
A oldValue = *this;
++(*this);
return oldValue;
}
条款7:不要重载“&&”,“||”或“,”
C++通常运用布尔表达式短路求值法来确定真假,如果重载“&&“或“||”,那就是用函数调用法来替代短路求值,两者之间会有所区别:首先当函数被调用时,需要运算其所有参数,所以调用函数 functions operator&& 和 operator||时,两个参数都需要计算,换言之,没有采用短路计算法。第二是 C++语言规范没有定义函数参数的计算顺序,所以没有办法知道表达式 1 与表达式 2 哪一个先计算。完全可能与具有从左参数到右参数计算顺序的短路计算法相反。
条款8:理解不同含义的new和delete
你使用的 new 是 new 操作符。这个操作符就象 sizeof 一样是语言内置的,你不能改变它的含义,它的功能总是一样的。它要完成的功能分成两部分。第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new 操作符总是做这两件事情,你不能以任何方式改变它的行为。你所能改变的是如何为对象分配内存 。new 操作符为分配内存所调用函数的名字是 operator new。
函数 operator new 通常这样声明:void * operator new(size_t size);
返回值类型是 void*,因为这个函数返回一个未经处理(raw)的指针,未初始化的内存。
参数 size_t 确定分配多少内存。你能增加额外的参数重载函数 operator new,但是第一个参数类型必须是 size_t。
placement new
在一个已存在的对象上调用构造函数是没有意义的,因为构造函数用来初始化对象,而一个对象仅仅能在给它初值时被初始化一次。但是有时你有一些已经被分配但是尚未处理的(raw)内存,你需要在这些内存中构造一个对象。你可以使用一个特殊的 operator new ,它被称为 placement new。
class Widget
{
public:
Widget(int widgetSize);
};
Widget * constructWidgetInBuffer(void *buffer,
int widgetSize)
{
return new (buffer) Widget(widgetSize);
}
在 constructWidgetInBuffer 里面,返回的表达式是:new (buffer) Widget(widgetSize)这初看上去有些陌生,但是它是 new 操作符的一个用法,需要使用一个额外的变量(buffer),当 new 操作符隐含调用 operator new 函数时, 把这个变量传递给它。 被调用的 operator new函数除了待有强制的参数 size_t 外,还必须接受 void*指针参数,指向构造对象占用的内存空间。这个 operator new 就是 placement new,它看上去象这样:
void * operator new(size_t, void *location)
{
return location;
}
operator new和new operator的区别
你想在堆上建立一个对象,应该用 new 操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用 operator new 函数;它不会调用构造函数。如果你想定制自己的在堆对象被建立时的内存分配过程,你应该写你自己的 operator new 函数,然后使用 new 操作符,new 操作符会调用你定制的 operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该用 placement new。Deletion and Memory Deallocation
// 分配足够的内存以容纳 50 个 char没有调用构造函数
void *buffer = operator new(50*sizeof(char));
operator delete(buffer); // 释放内存 没有调用析构函数
条款9:使用析构函数防止资源泄漏
void F()
{
B *b = new B(x);
b->func();
delete b;
}
若func()函数出现异常,那么delete b语句将不会执行,则内存泄漏,虽然try..catch可以解决,但是语句容易重复不简洁,更好的方法是将delete类似的操作放入析构函数或使用智能指针,这样就无需担心异常的出现了。