c++primer plus第十二章-再谈new运算符
1)如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete。
new和delete必须相互兼容。new对应于delete,new[]对应于delete[]。
2)如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr),这是因为delete(无论是带中括号还是不带中括号)可以用于空指针。
3)应该定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象,通常,这种构造函数与下面类似。
String::String(const String &st)
{
num_strings++;
len=st.len;
str= new char [len+1];
std::strcpy(str, st.str);
}
具体来说,复制构造函数应该分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址。另外,还应该更新所有受影响的静态成员。
4)应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。通常,该方法与下面类似:
String & String::operator=(const String & st)
{
if(this == &st)
return *this;
delete [] str;
len = st.len;
str=new char [len + 1];
std::strcpy(str, st.str);
return *this;
}
5)返回指向const对象的引用
使用const引用的常见原因在于提高效率。
const Vector &Max(const Vector &v1, const Vector &v2)
{
if(v1.magval() >v2.magval())
return v1;
else
return v2;
}
这里有3点说明:首先,返回对象将调用复制构造函数,而返回引用不会。其次,引用指向的对象应该在调用函数执行时存在。第三,v1,v2被声明为const引用,因此返回类型必须为const类型,这样才匹配。
6)返回对象
如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。
Vector Vector::operator+(const Vector & b) const
{
return Vector(x+b.x, y+ b.y);
}
构造函数调用Vector(x+b.x, y+ b.y)创建一个方法operator+()能够访问的对象;而返回语句引发的对复制构造函数的隐式调用创建一个调用程序能够访问的对象。这种情况下,存在调用复制构造函数来创建被返回的对象的开销,也是无法避免的。
7)总之,如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须返回一个指向这种对象的引用。最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。
8)在下述情况下,析构函数会被调用
1,如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
2,如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序结束时将调用对象的析构函数。
3,如果对象是用new创建的,则仅当您显式使用delete删除对象时,其析构函数才会被调用。
9)指针和对象的总结
1,使用常规表示法来声明指向对象的指针:
String * glamour;
2,可以将指针初始化为指向已有的对象:
String * first = &sayings[0];
3,可以使用new来初始化指针,这将创建一个新的对象:
String * favorite = new String( sayings [choice]);
4,对类使用new将调用相应的类构造函数来初始化新创建的对象:
String * gleep = new String;
String * glop = new String("my my my");
String * favorite = new String( sayings [choice]);
5,可以使用->运算符通过指针访问类方法:
if(sayings[i].length() < shortest->length())
6,可以对对象应用解除引用运算符(*)来获得对象:
if(sayings[i] <*first)
first = &sayings[i];
10)程序员必须负责管用定位new运算符用从中使用的缓冲区内存单元。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。
pc1 = new (buffer) JustTesting;
pc3 = new (buffer + sizeof(JustTesting)) JustTesting("Better Idea", 6) ;
其中指针pc3相对于pc1的偏移量为JustTesting对象的大小。
11)new的使用总结
1,对于指向的内存是由new分配的所有类成员,都应在类的析构函数中对其使用delete,该运算符将释放分配的内存。
2,如果析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或将它设置为空指针。。
3,构造函数中要么使用new[],要么使用new,而不能混用。如果构造函数使用的是new[],则析构函数应使用delete[],如果构造函数使用的是new,则析构函数也应该使用delete。
4,应该定义一个分配内存(而不是将指针指向已有的内存)的复制构造函数。这样程序将能够将类对象初始化为另一个类对象。这种构造函数的原型如下:
className(const className &)
12)成员初始化列表的语法
第一:这种格式只能用于构造函数;
第二:必须用这种格式来初始化非静态const数据成员;
第三:必须使用这种格式来初始化引用数据成员。
数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的排列顺序无关。
警告:不能将成员初始化列表语法用于构造函数之外的其他类方法。
Queue::Queue(int qs):qsize(qs), front(NULL), rear(NULL), items(0)
{... ...}