c++复制/拷贝构造函数

来源:互联网 发布:湖南省干部培训网络 编辑:程序博客网 时间:2024/05/17 01:55

        在c++中,定义一个空类时,编译器会默认声明6个成员函数,它们分别是

class Empty {public:    Empty();                        //默认构造函数    Empty(const Empty&);            //拷贝构造函数    ~Empty();                       //析构函数    Empty& operator=(const Empty&); //赋值运算符    Empty* operator&();             //取地址运算符    const Empty* operator&() const; //取地址运算符const};
        注意 一下,编译器默认合成的析构函数不是虚函数。 

        首先,说一下什么是拷贝构造函数(也可以叫复制构造函数),它是一个特殊的构造函数,具有单个形参(此形参是对该类类型的引用,需要用const修饰,否则会无限循环调用复制构造函数)。一般这几种情况会调用它:1.根据另一个同类型的对象初始化一个对象 2.复制一个对象,将它作为实参传递给一个函数 3.从函数返回时复制一个对象。

        只要我们不显示的定义拷贝构造函数,编译器都会自动合成一个,这时候编译器只是简单对对象中的数据成员进行赋值,也就是浅拷贝。大致的内部实现如代码所示

#include <iostream>class Test {public:Test(int tmp) {this->m1 = tmp;}//自定义拷贝构造函数Test(Test& c) {std::cout << "拷贝构造函数" << std::endl;m1 = c.m1;}int m1;};int main() {Test a(1);Test b = a;std::cout << b.m1 << std::endl;system("pause");return 0;}
        当然大多数情况下,浅拷贝是可以的,但是当类成员含有指针或者其它占有系统资源的变量时,由于浅拷贝只是简单的赋值,会将新对象的指针也指向以前对象已经分配的堆内存空间,如果这时候以前的对象已经析构,新对象的指针就成为野指针,出现运行错误。

        先看下含有指针成员的的类对象浅拷贝代码

#include <iostream>class Test {public:Test(int* pi) {p = new int(100);p = pi;}~Test() {if (p != NULL) {delete p;p = NULL;}}int *p;};int main() {int i = 5;Test a(&i);Test b = a;std::cout << "对象a中的成员p的地址: " << a.p << std::endl;std::cout << "对象b中的成员p的地址: " << b.p << std::endl;std::cout << *(a.p) << std::endl;std::cout << *(b.p) << std::endl;system("pause");return 0;}
输出结果:


从输出结果可以看出对象a和b中的指针成员p指向的是同一块堆内存地址,若a对象先把内存释放后在引用b中的p就会出现野指针的运行时错误。为了防止这种情况发生,我们需要自定义拷贝构造函数,对指针成员不是简单的赋值,需要重新开辟一块内存,将老对象指针指向的数据拷贝进去。这就是深拷贝。

        看看深拷贝的实现:

#include <iostream>class Test {public:Test(int* pi) {p = new int(100);p = pi;}//自定义拷贝构造函数(深拷贝)Test(Test& c) {std::cout << "拷贝构造函数" << std::endl;p = new int;if (p != NULL) {//memcpy(p, c.p, sizeof(c.p));*p = *(c.p);}}//赋值运算符Test& operator=(const Test &rhs) {p = new int;*p = *(rhs.p);return *this;}~Test() {if (p != NULL) {delete p;p = NULL;}}int *p;};int main() {int i = 5;Test a(&i);Test b = a;std::cout << "对象a中的成员p的地址: " << a.p << std::endl;std::cout << "对象b中的成员p的地址: " << b.p << std::endl;std::cout << *(a.p) << std::endl;std::cout << *(b.p) << std::endl;system("pause");return 0;}

输出结果:


         可以看出对象a和b中的指针保存的不是同一块堆内存地址,它们中的数据是一样的,这样就可以防止出现野指针的运行时错误。由于只是做个简单的测试,代码有点偏向伪码,类属性其实需要定义为private,为了测试方便,直接使用public了。

         需要注意下,一般情况类需要重载赋值运算符,因为我们不一定是直接初始化对象Test b(a),我们可能会这样写Test b = a这种赋值初始化。所以好的建议是自定义了拷贝构造函数后顺便重载赋值运算符。

        也就是说当类中含有指针这种需要占有系统资源的变量时,我们必须自定义拷贝构造函数(深拷贝)。浅拷贝说白了,就是简单赋值,有指针成员时,拷贝的是指针,而深拷贝就是拷贝指针指向的对象。


0 0
原创粉丝点击