拷贝构造函数最常见的是当我们创建的对象是用该类的另一个对象来进行初始化的,此时调用的构造函数就是拷贝构造函数。拷贝构造函数实质上就是构造函数的重载。当你不显式定义拷贝构造函数的时候,C++会给你提供一个默认拷贝构造函数,这和它提供默认构造函数是一样的。但是当你一旦显式定义了构造函数和拷贝构造函数,那么C++将不再提供默认构造函数和默认拷贝构造函数。
下面这三种情形是常见的需要拷贝构造函数的场景。
拷贝构造函数原型:类名(类名&对象名);通常我们我们会使用const关键字来成为一个常量引用。
下面给出一段代码来演示一下上面这三种情形。
#include<iostream>
#include<cstdlib>
using namespace std;
class MyClass
{
public:
MyClass();
MyClass(const MyClass& a);
MyClass Show(MyClass a);
~MyClass() //析构函数
{
}
private:
int a;
};
MyClass::MyClass() :a(3) //无参构造函数
{
}
MyClass::MyClass(const MyClass &a) //拷贝构造函数
{
this->a = 100;
}
MyClass MyClass::Show(MyClass a) //这个函数的参数是对象做值传递,并且该函数的返回值也是对象做值传递。
{
return a;
}
int main()
{
MyClass C;
MyClass C1(C);
C.Show(C1);
MyClass C2 = C1.Show(C);
return 0;
}
我们的关注点主要在与main函数中,看一看这些创建对象,调用函数都会发生什么?
MyClass C;这句话无疑会调用MyClass类的无参构造函数。然后接下来执行MyClass C1(C),这句符合上述的条件3.即:一个对象用于给另一个对象初始化时,需要调用拷贝构造函数。这时候调用的就是拷贝构造函数。下面是C.Show(C1);这句话的执行我们必须好好的进行分析。首先,C1作为参数进行值传递,需要调用一次拷贝构造函数;其次MyClass::Show()函数返回值也是进行值传递,又需要调用一次拷贝构造函数。然后参数a的生命周期结束,调用析构函数释放对象;而该函数返回一个匿名对象,该对象没有被使用,因此生命周期也结束,然后调用析构函数。
接下来执行MyClass C2 = C1.Show(C);这句话。注意:如果按照C.Show(C1)来接着分析,我们理所当然的会得出结论是这行代码将会调用3次拷贝构造函数,调用两次析构函数。实际上并非如此,C++的编译器设计者做了一定的优化措施。将匿名对象的内存空间直接给了C2对象。C2代替了匿名对象。因此只会调用两次拷贝构造函数,一次析构函数。
剩余的析构函数调用则是在程序结束之前调用。
默认拷贝构造函数就是执行浅拷贝操作。浅拷贝它只完成了值拷贝。
默认拷贝构造函数以内存拷贝的方式将旧有对象的内存空间的内容拷贝到新对象的内存空间。
下面来一段代码。
//类CMan
class CMan
{
private:
int age;
char* name;
public:
CMan(); //无参构造函数
CMan(int age, char* p_name); //有参构造函数
~CMan(); //析构函数
};
//CMan类的构造函数和析构函数的实现
CMan::CMan(int age, char* p_name)
{
this->age = age;
this->name = (char*)malloc(strlen(p_name)+1);
strcpy(this->name, p_name);
}
CMan::CMan()
{
this->age = 33;
this->name = (char*)malloc(10);
strcpy(this->name, "ZhanSan");
}
CMan::~CMan()
{
free(name);
}
int main()
{
CMan man1;
int age;
char name[50];
std::cout << "请输入年龄和姓名:";
std::cin >> age >> name;
CMan man2(age, name);
CMan man3(man2);
return 0;
}
我们来分析CMan man3(man2);这行代码执行以后,默认拷贝构造函数做了什么?
默认拷贝构造函数在这个类中其实被实现为下面这样。
CMan::CMan(const CMan& man)
{
this->age = man.age;
this->name = man.name; //这里只进行了值复制,没有去分配内存空间。
//这就导致man2和man3的name是同一块内存空间,在程序执行结束调用析构函数的时候,
//就会发生释放两次内存,然后程序出错
}
这就是程序在CMan man3(man2);这行代码处出现的错误。
这时候就需要我们自己动手实现一个拷贝构造函数。在构造函数中没有出现分配内存或者数组的情形下,我们使用默认拷贝构造函数就足够了。
深拷贝是需要在拷贝构造函数中进行内存分配或者是数组赋值操作。上面这个类的拷贝构造函数可以写成这样。
CMan::CMan(const CMan& man)
{
this->age = man.age;
this->name = (char*)malloc(strlen(man.name)+1);
strcpy(this->name, man.name);
}
这样就不会造成错误了。