C++拷贝构造函数(深拷贝+浅拷贝)
在程序中,经常使用已有对象完成新对象的初始化。例如,在定义变量 inta=3 后,再定义新变量 intb=a。在类中,需要定义拷贝构造函数才能完成这样的功能。接下来,本文将针对拷贝构造函数进行详细讲解。
拷贝构造函数的定义格式如下所示:
【示例1】下面通过案例演示拷贝构造函数的定义与调用,C++ 代码如下:
由运行结果可知,对象 sheepA 与对象 sheepB 的信息是相同的。
程序首先调用构造函数创建了对象 sheepA,然后调用拷贝构造函数创建了对象 sheepB。程序运行结束之后,调用析构函数先析构对象 sheepB,然后析构对象 sheepA。
当涉及对象之间的赋值时,编译器会自动调用拷贝构造函数。拷贝构造函数的调用情况有以下三种。
例如,如果类中有指针类型的数据,默认的拷贝构造函数只是进行简单的指针赋值,即将新对象的指针成员指向原有对象的指针指向的内存空间,并没有为新对象的指针成员申请新空间,这种情况称为浅拷贝。
浅拷贝在析构指向堆内存空间的变量时,往往会出现多次析构而导致程序错误。C++ 初学者自定义的拷贝构造函数往往实现的是浅拷贝。
【示例2】下面通过案例演示浅拷贝,C++ 代码如下:
【示例2】是对【示例1】的修改,在绵羊类 Sheep 中增加了一个 char 类型的指针变量成员 _home,用于表示绵羊对象的家。增加了 _home 成员变量之后,类 Sheep 的构造函数、拷贝构造函数、析构函数都进行了相应修改。
示例分析:
在这个过程中,使用对象 sheepA 初始化对象 sheepB 是浅拷贝过程,因为对象 sheepB 的 _home 指针指向的是对象 sheepA 的 _home 指针指向的空间。
浅拷贝过程如图 2 所示。

图1 触发异常断点

图2 浅拷贝过程
由图 2 可知,在浅拷贝过程中,对象 sheepA 中的 _home 指针与对象 sheepB 中的 _home 指针指向同一块内存空间。
当程序运行结束时,析构函数释放对象所占用资源,析构函数先析构对象 sheepB,后析构对象 sheepA。
例如,在【示例1】中,使用对象 sheepA 初始化对象 sheepB 时,为对象 sheepB 的指针 _home 申请一块新的内存空间,将数据复制到这块新的内存空间。
下面修改【示例2】中的拷贝构造函数,实现深拷贝过程。修改后的拷贝构造函数代码如下所示:
在深拷贝过程中,对象 sheepB 中的 _home 指针指向了独立的内存空间,是一份完整的对象拷贝,如图 3 所示。

图3 深拷贝过程
由图 3 可知,对象 sheepA 中的 _home 指针与对象 sheepB 中的 _home 指针指向不同的内存空间,在析构时,析构各自对象所占用的资源不会再产生冲突。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
拷贝构造函数的定义
拷贝构造函数是一种特殊的构造函数,它具有构造函数的所有特性,并且使用本类对象的引用作为形参,能够通过一个已经存在的对象初始化该类的另一个对象。拷贝构造函数的定义格式如下所示:
class 类名
{
public:
构造函数名称(const 类名& 对象名)
{
函数体
}
... //其他成员
};
const
修饰引用的对象。【示例1】下面通过案例演示拷贝构造函数的定义与调用,C++ 代码如下:
#include<iostream> using namespace std; class Sheep //定义绵羊类Sheep { public: Sheep(string name,string color); //声明有参构造函数 Sheep(const Sheep& another); //声明拷贝构造函数 void show(); //声明普通成员函数 ~Sheep(); //声明析构函数 private: string _name; //声明表示绵羊名字的成员变量 string _color; //声明表示绵羊颜色的成员变量 }; Sheep::Sheep(string name, string color) { cout<<"调用构造函数"<<endl; _name=name; _color=color; } Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数 { cout<<"调用拷贝构造函数"<<endl; _name=another._name; _color=another._color; } void Sheep::show() { cout<<_name<<" "<<_color<<endl; } Sheep::~Sheep() { cout<<"调用析构函数"<<endl; } int main() { Sheep sheepA("Doly","white"); cout<<"sheepA:"; sheepA.show(); Sheep sheepB(sheepA); //使用sheepA初始化新对象sheepB cout<<"sheepB:"; sheepB.show(); return 0; }运行结果:
调用构造函数
sheepA:Doly white
调用拷贝构造函数
sheepB:Doly white
调用析构函数
调用析构函数
- 第 3~13 行代码定义了一个绵羊类 Sheep,该类有两个成员变量,分别是 _nam e、_color。此外,该类还声明了有参构造函数、拷贝构造函数、普通成员函数 show() 和析构函数;
- 第 20~25 行代码,在类外实现拷贝构造函数,在函数体中,将形参sheepA的成员变量值赋给类的成员变量;
- 第 37~39 行代码,在 main() 函数,创建了 Sheep 类对象 sheepA,并输出 sheepA 的信息;
- 第 40 行代码创建 Sheep 类对象 sheepB,并使用对象 sheepA 初始化对象 sheepB,在这个过程中编译器会调用拷贝构造函数;
- 第 41~42 行代码输出对象 sheepB 的信息。
由运行结果可知,对象 sheepA 与对象 sheepB 的信息是相同的。
程序首先调用构造函数创建了对象 sheepA,然后调用拷贝构造函数创建了对象 sheepB。程序运行结束之后,调用析构函数先析构对象 sheepB,然后析构对象 sheepA。
当涉及对象之间的赋值时,编译器会自动调用拷贝构造函数。拷贝构造函数的调用情况有以下三种。
- 使用一个对象初始化另一个对象。【示例1】就是使用一个对象初始化另一个对象。
- 对象作为参数传递给函数。当函数的参数为对象时,编译器会调用拷贝构造函数将实参传递给形参。
- 函数返回值为对象。当函数返回值为对象时,编译器会调用拷贝构造函数将返回值复制到临时对象中,将数据传出。
浅拷贝
拷贝构造函数是特殊的构造函数,如果程序没有定义拷贝构造函数,C++ 会提供一个默认的拷贝构造函数,默认拷贝构造函数只能完成简单的赋值操作,无法完成含有堆内存成员数据的拷贝。例如,如果类中有指针类型的数据,默认的拷贝构造函数只是进行简单的指针赋值,即将新对象的指针成员指向原有对象的指针指向的内存空间,并没有为新对象的指针成员申请新空间,这种情况称为浅拷贝。
浅拷贝在析构指向堆内存空间的变量时,往往会出现多次析构而导致程序错误。C++ 初学者自定义的拷贝构造函数往往实现的是浅拷贝。
【示例2】下面通过案例演示浅拷贝,C++ 代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string.h> using namespace std; class Sheep //定义绵羊类Sheep { public: Sheep(string name,string color,const char* home); //声明有参构造函数 Sheep(const Sheep& another); //声明拷贝构造函数 void show(); //声明普通成员函数 ~Sheep(); //声明析构函数 private: string _name; //声明表示绵羊名字的成员变量 string _color; //声明表示绵羊颜色的成员变量 char* _home; //声明表示绵羊家的成员变量 }; Sheep::Sheep(string name, string color,const char* home) { cout<<"调用构造函数"<<endl; _name=name; _color=color; //为指针成员home分配空间,将形参home的内容复制到_home指向的空间 int len=strlen(home)+1; _home=new char[len]; memset(_home,0,len); strcpy(_home,home); } Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数 { cout<<"调用拷贝构造函数"<<endl; _name=another._name; _color=another._color; _home=another._home; //浅拷贝 } void Sheep::show() { cout<<_name<<" "<<_color<<" "<<_home<<endl; } Sheep::~Sheep() { cout<<"调用析构函数"<<endl; delete []_home; _home!=NULL) } int main() { const char *p = "beijing"; Sheep sheepA("Doly","white",p); cout<<"sheepA:"; sheepA.show(); Sheep sheepB(sheepA); //使用sheepA初始化新对象sheepB cout<<"sheepB:"; sheepB.show(); return 0; }运行程序抛出异常,在第 43 行代码处触发异常断点,如图 1 所示。
【示例2】是对【示例1】的修改,在绵羊类 Sheep 中增加了一个 char 类型的指针变量成员 _home,用于表示绵羊对象的家。增加了 _home 成员变量之后,类 Sheep 的构造函数、拷贝构造函数、析构函数都进行了相应修改。
示例分析:
- 第 17~27 行代码实现构造函数,在构造函数内部,首先为 _home 指针申请堆内存空间,然后调用 strcpy() 函数将形参 home 的内容复制到 _home 指向的空间。
- 第 28~34 行代码实现拷贝构造函数,在拷贝构造函数内部,对指针成员只进行了简单的赋值操作,即浅拷贝。
- 第 39~44 行代码实现析构函数,在析构函数内部,使用 delete 运算符释放 _home 指向的内存空间。
- 第 47~53 行代码,在 main() 函数中,先创建对象 sheepA,再创建对象 sheepB,并用对象 sheepA 初始化对象 sheepB。
在这个过程中,使用对象 sheepA 初始化对象 sheepB 是浅拷贝过程,因为对象 sheepB 的 _home 指针指向的是对象 sheepA 的 _home 指针指向的空间。
浅拷贝过程如图 2 所示。

图1 触发异常断点

图2 浅拷贝过程
由图 2 可知,在浅拷贝过程中,对象 sheepA 中的 _home 指针与对象 sheepB 中的 _home 指针指向同一块内存空间。
当程序运行结束时,析构函数释放对象所占用资源,析构函数先析构对象 sheepB,后析构对象 sheepA。
在析构 sheepB 对象时释放了 _home 指向的堆内存空间的数据,当析构 sheepA 时 _home 指向的堆内存空间已经释放,再次释放内存空间的资源,程序运行异常终止,即存储“beijing”的堆内存空间被释放了两次,因此程序抛出异常,这种现象被称重析构(double free)。
深拷贝
所谓深拷贝,就是在拷贝构造函数中完成更深层次的复制,当类中有指针成员时,深拷贝可以为新对象的指针分配一块内存空间,将数据复制到新空间。例如,在【示例1】中,使用对象 sheepA 初始化对象 sheepB 时,为对象 sheepB 的指针 _home 申请一块新的内存空间,将数据复制到这块新的内存空间。
下面修改【示例2】中的拷贝构造函数,实现深拷贝过程。修改后的拷贝构造函数代码如下所示:
Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数 { cout<<"调用拷贝构造函数"<<endl; _name=another._name; _color=another._color; //完成深拷贝 int len = strlen(another._home)+1; _home=new char[len]; strcpy(_home,another._home); }拷贝构造函数修改之后,再次运行程序,程序不再抛出异常。
在深拷贝过程中,对象 sheepB 中的 _home 指针指向了独立的内存空间,是一份完整的对象拷贝,如图 3 所示。

图3 深拷贝过程
由图 3 可知,对象 sheepA 中的 _home 指针与对象 sheepB 中的 _home 指针指向不同的内存空间,在析构时,析构各自对象所占用的资源不会再产生冲突。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。