类和动态内存分配
整理自《C++ Primer Plus》
1. 动态内存和类
-
静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象共享同一个静态成员。静态数据成员在类声明中声明,在包含类方法的文件中初始化。
-
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。按值传递意味着创建原始变量的一个副本。由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。
默认的复制构造函数逐个复制非静态成员(成员复制也被称为浅复制),复制的是成员的值。
深度复制。必须定义复制构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。
2. 在构造函数中使用new时应注意的事项
- 如果在构造函数中使用new来初始化对象的指针成员时,则应在析构函数中使用delete。
- new 和delete 必须相互兼容。new对应于delete, new[] 对应于delete[]。
- 如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。
- 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
- 应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。该方法完成这些操作:检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用。
3. 有关返回对象的说明
- 返回指向const对象的引用
使用const引用的常见原因是旨在提高效率,但对于何时可以采用这种方式存在一些限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高其效率。
Vector force1(50, 60);
Vector force2(10, 70);
Vector max;
max = Max(force1, force2);
// version1
Vector Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
{
return v2;
}
}
// version2
const Vector & Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
{
return v2;
}
}
说明:首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本所作的工作更少,效率更高。其次,引用指向的对象应该在调用函数执行时存在。第三,v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配。
-
返回指向非const对象的引用。
两种常见的返回非const对象情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。 -
返回对象
如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。 -
返回const对象
总之,如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类的对象,它必须返回一个指向这种对象的引用。最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。 -
在下述情况下析构函数将被调用
如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序结束时调用对象的析构函数。
如果对象是用new创建的,则仅当您显示使用delete删除对象时,其析构函数才会被调用。 -
类方法
对于const数据成员,必须在执行到构造函数体之前,即创建对象时进行初始化。C++提供了一种特殊的语法来完成上述工作,它叫做初始化成员列表。
成员初始化列表由逗号分隔的初始化列表组成(前面带冒号)。它位于参数列表的右括号之后、函数体左括号之前。如果数据成员的名称为mdata,并需要将它初始化为val,则初始化器为mdata(val)。只有构造函数可以使用这种初始化列表语法。不能将成员初始化列表语法用于构造函数之外的其他类方法。