拷贝构造函数
拷贝发生在初始化阶段,初始化就是首次对内存赋值。 对象的创建包括两个阶段:第一分配内存空间,第二进行初始化。 当以一个对象初始化一个对象时,会调用拷贝构造函数。
调用拷贝构造函数的时机:(三种情况)
用类的一个对象去初始化另一个对象时。 当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用。 当函数的返回值是类的对象或引用时,会调用拷贝构造函数。因为函数体内生成的对象是临时的,函数体结束,这个对象就消失了,所以要调用拷贝构造函数复制一份。
默认拷贝构造函数
拷贝构造函数有两种:第一默认拷贝构造函数,第二显式定义地拷贝构造函数。 编译器会自动生成一个默认拷贝构造函数,用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,属于浅拷贝。 对于没有指针成员变量的类,默认拷贝构造函数是够用的,没有必要再显式地定义一个功能类似的拷贝构造函数。
拷贝构造函数的声明和定义
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
Student(string name = ""); //普通构造函数
Student(const Student &stu); //拷贝构造函数(声明)
private:
string m_name;
};
Student::Student(string name): m_name(name){ }
//拷贝构造函数(定义)
Student::Student(const Student &stu){
this->m_name = stu.m_name;
}
int main(){
Student stu1("小明");
Student stu2 = stu1; //调用拷贝构造函数
Student stu3(stu1); //调用拷贝构造函数
return 0;
}
拷贝构造函数只有一个参数,它的类型是当前类的引用,而且一般都是 const 引用。 必须是当前类的引用的原因:如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。 必须是 const 引用的原因:拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。
深拷贝与浅拷贝
浅拷贝:对于基本类型的数据以及简单的对象,它们之间的拷贝就是按位复制内存,这种默认的拷贝行为就是浅拷贝。 深拷贝:比如对于指针成员变量,要将指针指向的内容复制出一份,这种将对象所持有的其它资源一并拷贝的行为叫做深拷贝,必须显式地定义拷贝构造函数才能达到深拷贝的目的。 深拷贝的目的:让原有对象和新生对象相互独立,彼此之间不受影响。否则仅仅拷贝指针本身,两个指针指向同一份内存。 需要深拷贝的时机:如果类的成员变量没有指针,一般浅拷贝足以。当类中成员有指针变量、类中有动态内存分配时,则需要显式定义拷贝构造函数。
拷贝控制操作(三五法则)
当定义一个类时,我们显式地或隐式地指定了此类型的对象在拷贝、赋值和销毁时做什么。一个类通过定义三种特殊的成员函数来控制这些操作,分别是拷贝构造函数、赋值运算符和析构函数。 拷贝构造函数定义了当用同类型的另一个对象初始化新对象时做什么,赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么,析构函数定义了此类型的对象销毁时做什么。我们将这些操作称为拷贝控制操作。 由于拷贝控制操作是由三个特殊的成员函数来完成的,所以我们称此为“C++三法则”。在较新的C++11标准中,为了支持移动语义,又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”。也就是说,“三法则”是针对较旧的C++89标准说的,“五法则”是针对较新的C++11标准说的。为了统一称呼,后来人们干把它叫做“C++ 三/五法则”。 若类中出现了指针类型的成员,三个/五个特殊的成员函数必须显示定义,以防止浅拷贝问题。如果没有指针类型的成员,没有必要显式定义,默认的就够用了。