特殊成员函数:程序员没有进行定义,但C++会自动提供的成员函数
特殊成员函数如下:
·默认构造函数
·默认析构函数
·复制构造函数
·赋值运算符
·地址运算符
·移动构造函数
·移动赋值运算符
如果程序使用对象的方式需要,编译器会自动生成复制构造函数、赋值运算符、地址运算符三个函数的定义。例如,需要将一个对象赋给另一个对象,编译器将提供赋值运算符的定义。
移动构造函数和移动赋值运算符是C++11中提供的两个特殊成员函数。这两个函数会有单独的文章讲解,此处不做解读。
如果没有提供任何构造函数,C++将创建默认构造函数。默认构造函数不接受任何一个参数,也不执行任何操作。 以Klunk类为例,Klunk类的默认构造函数如下:
Klunk::Klunk(){ }//implicit default constructor
**如果定义了构造函数,C++将不会定义默认构造函数。如果希望在创建对象时不显式的对它进行初始化,则必须显式地定义默认构造函数。
默认构造函数存在两种形式,一种与编译器自动生成的类似,没有任何参数,可以使用它来设置特定的值。
Klunk::Klunk()//explicit default constructor
{
klunk_ct = 0;
...
}
还有一种格式是 带参数的构造函数,只要所有参数都有默认值。 Klunk类可以包含如下内联构造函数:
Klunk(int n = 0) {klunk_ct = n;}
需要注意的是 默认构造函数只能有一个,也就是说上述两种形式的默认构造函数只能在程序中出现一个。 如果Klunk中存在如下定义,则会产生二义性。
Klunk::Klunk(){klunk_ct = 0;}//constructor#1
Klunk(int n = 0) {klunk_ct = n;}//ambiguous constarutor#2
当使用 Klunk bus; 语句来创建对象时,既与构造函数#1匹配,也与构造函数#2(使用默认参数0)匹配。这将导致编译器发出一条错误消息。
在未定义任何析构函数时,编译器会自动生成默认析构函数,自动创建的默认析构函数与默认构造函数相似,不接受任何参数,也不执行任何操作。以Klunk类为例,默认析构函数如下:
Klunk::~Klunk(){}
复制构造函数用于将一个对象复制到新创建的对象中。用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。 类的复制构造函数原型通常如下:
Class_name(const Class_name &);
它接受一个指向类对象的常量引用作参数。例如,Klunk类的复制构造函数原型如下:
Klunk(const Klunk &);
对于复制构造函数必须要知道的两点:何时调用和有何功能。
1.最常见的情况是将新对象显式地初始化为现有的对象。假设klunk是一个Klunk对象,以下4种声明都将调用复制构造函数:
Klunk ditto(metto);
Klunk metoo = metto;
Klunk also = Klunk(metto);
Klunk *pKlunk = new Klunk(metto);
其中中间两种可能会使用复制构造函数直接创建metoo何also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。最后一种声明使用metto初始化一个匿名对象,并将新对象的地址赋给pKlunk指针。
2.每当程序生成了对象副本时,编译器都将使用复制构造函数。具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着创建原始变量的一个副本。
void function(Class_name);//函数定义声明,参数为值传递方式
function(object_name);
3.编译器在生成临时对象时,也将使用复制构造函数。例如两个对象相加时,编译器会生成一个临时的类对象来保存中间结果。
由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员函数。静态成员不受影响,因为它们属于整个类,而不是各个对象。
赋值运算符接受并返回一个指向类对象的引用。其函数原型如下:
Class_name & Class_name::operator=(const Class_name &);
将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
Class_name objectaB(m,n,"string");
...//对两个
Class_name objectaA;
objectaA = objectB;//此处将调用赋值运算符
赋值运算符与复制构造函数相似,赋值运算符的隐式实现也是对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制成员,但静态数据成员不受影响。
如果类中存在new动态开辟内存的情况,编译器自动提供的赋值运算符就不够用了,自动生成的赋值运算符和复制构造函数只是对指针对象进行浅拷贝,调用对象的指针成员指向被调用对象的指针成员指向的内存,当对两个对象进行清理工作时,会出现错误,对同一片内存空间进行重复清理。所以当类中存在动态内存分配时,需要自定义复制构造函数和赋值运算符,进行深拷贝,这样就可以解决对已清除的空间再进行清理的问题。