如果没有定义,C++会自动提供下面这些成员函数:
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型如下:
Class_name(const Class_name &);
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。例如,假设motto是一个StringBad对象,则下面4种声明都将调用复制构造函数:
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);
每当程序生成了对象副本时,编译器都将使用复制构造函数。具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。
默认的复制构造函数逐个复制非静态成员(成员复制,也称浅复制),复制成员的值。
如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造函数来处理计数问题。
如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是复制指针,这被称为深度复制。而浅复制只是复制指针值,而不会深入挖掘以复制指针引用的结构。
C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。这种运算符的原型如下:
Class_name & operator=(const Class_name &);
将已有的对象赋给另一个对象时,将使用重载的复制运算符:
StringBad haedline1(“Celery Stalks at Midnight”);
String knot;
knot = headline1;
对于由于默认赋值运算符不合适而导致的问题,解决办法是提供赋值运算符(进行深度复制)定义。赋值运算符的定义应遵循以下原则:
对于中括号运算符,一个操作数位于第一个中括号的前面,另一个操作数位于两个中括号之间。
假设city是一个用户定义的String类的对象,在表达式city[0]中,city是第一个操作数,[]是运算符,0是第二个操作数。下面是该方法的简单实现:
char & String::operator[](int i)
{
return str[i];
}
可以将类成员函数声明为静态的(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static)。
不能通过对象调用静态成员函数;实际上,静态成员函数不能使用this指针。如果静态成员函数是在共有部分声明的,则可以使用类名和作用域解析运算符来调用它。
由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。
总的来说,再用new初始化对象的指针成员时必须要特别小心。具体地说,应该注意下面这些事项:
在类Magazine中,类成员的类型为String类或标准string类:
Class Magazine
{
private:
String title;
string publisher;
//...
};
String和string都使用动态内存分配,这是否意味这要为Magazine类编写复制构造函数和赋值运算符?不一定。逐成员复制具有一定的智能。在将一个Magazine对象复制或赋值给另一个Magazine对象时,逐成员复制将使用成员类型定义的复制构造函数和赋值运算符。
如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。因为在被调用函数执行完毕时,局部对象将调用其析构函数。因此,当控制权回到调用函数时,引用指向的对象将不存在。在这种情况下,将使用复制构造函数来生成返回的对象。
如果方法或函数要返回一个没有共有复制构造函数的类(如ostream类)的对象,它必须返回一个指向这种对象的引用。
如果有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为引用的效率更高。
如果希望方法和函数的返回对象不被修改(或不能作为赋值语句的左半部分,防止发生错误),应返回const对象。
使用对象指针时,应该注意一下几点:
String * glamour;
String * first = &sayings[0];
String * favorite = new String(sayings[choice]);
String * gleep = new String;
String * glop = new String(“my my my”);
String * favorite = new String(sayings[choice]);
if(sayings[i].length() < shorest->length())
//...
if(sayings[i] < *first)
first = &sayings[i];
如果在一块内存缓冲区中用定位new运算符创建了对象,当使用delete释放这块缓冲区时不会自动调用这些对象的析构函数。所以要显式地为使用定位new运算符创建的对象调用析构函数。这是需要显式地调用析构函数的少数集中情形之一。
对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。因为晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。
如果Classy是一个类,而mem1、mem2、mem3都是这个类的数据成员,则类构造函数可以使用如下的成员初始化列表语法来初始化数据成员:
Classy::Classy(int n, int m) :mem1(n), mem2(0),mem3(n * m + 2)
{
//...
}
使用这种语法要注意以下几点:
C++11允许使用类内初始化方式进行初始化:
class Classy
{
int mem1 = 10;
const int mem2 = 20;
//...
};
这与在构造函数中使用成员初始化列表等价:
Classy::Classy() : mem1(10), mem2(20) {...}
成员mem1和mem2将分别被初始化为10和20,除非调用使用成员初始化列表的构造函数,在这种情况下,实际列表将覆盖这些默认初始值。