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

        在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这种赋值初始化。所以好的建议是自定义了拷贝构造函数后顺便重载赋值运算符。

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值