深拷贝与浅拷贝

定义:

浅拷贝:简单的赋值拷贝操作。

深拷贝:在堆区重新申请空间进行拷贝。

#include "iostream"
using namespace std;

class Person //类
{
public:
	// 有参构造函数
	Person(int a,int H) 
	{
		age = a;               //浅拷贝
		height = new int(H);   //深拷贝    创建堆区数据,返回地址。
		cout << "有参初始化OK  " << endl;
	};
	Person(const Person &p)
	{
		age = p.age; 
		//height = p.height;  //编译器默认实现的拷贝,会将new int(H)返回的地址复制给P2
							  //导致拷贝得到的p2和p1指向同一片地址。在delete的时候,就会释放两次同一片内存,导致程序崩溃。
		height = new int(*p.height);  //解决办法:将p1指向的height的内容(即*p.height)重新分配一个内存,这样就不会导致非法操作。
	}
	~Person() // 析构函数
	{
		if (height != NULL)
		{
			delete height;
			height = NULL;
		}
		cout << "退出" << endl;
	};
	int age;
	int* height;
};

void test01()
{
	Person p1(18,160);
	cout << "p1年龄为" << p1.age << endl;
	cout << "p1身高为" << *p1.height << endl;
	Person p2(p1);         //如果不写拷贝函数,则编译器会默认使用浅拷贝,
						   //则会导致new int(H)申请的堆区数据释放两次,形成非法操作。

	cout << "p2年龄为" << p2.age << endl;

}
int main()
{
	test01();
	//Person po;
	system("pause");
	return  0;
}
  • 上面的代码中,类中的有参构造(7-13行)为浅拷贝和深拷贝的基本操作。
  • 当我们创建一个类的时候,会默认的有无参构造函数,析构函数,和拷贝函数。在创建Person p1(18,160)的时候,p1中age = 18; height = 0x0011(假设160的地址为0x0011);  当我们写下Person p2(p1)的时候,并且在这个时候没有写拷贝函数,程序就会执行默认的拷贝函数,也就是17行代码注释的部分,不执行第19行。这里会带来一个问题。p2.height = 0x0011;于是两个相同的指针指向同一片地址。在test01()函数执行完毕时,析构函数会执行两次,会释放同一片地址两次。会导致内存非法访问,程序异常。所以,深拷贝不可用默认的拷贝函数。
  • 为了解决这个问题,思路就是让p1的由堆内存搞出来的height在拷贝到p2的时候,我们在分配一个内存(不为0x0011),但要保证内容相同(都为160),于是就有了上述第19行的写法。将160传入,重新在堆区分配地址。即可避免非法访问。也就是所谓的深拷贝。

总结:这么看来,深拷贝其实就是拷贝的内存地址,浅拷贝就是拷贝的值。内容可以复制,但内存地址复制了就会导致非法访问。需要特别注意类的默认拷贝函数。

====================2022/5/13更新==================================================

  • 浅拷贝带来的问题:
#include <stdio.h>
#include <iostream>

class Person
{
public:

    int* aa;
};
int main(int argc, char *argv[])
{
    printf( "lbw %s %s \n", __DATE__, __TIME__);
    Person A;
    A.aa = new int(100);
    Person B(A);

// 指针aa的地址
    std::cout << "A.aa=" << &(A.aa) << std::endl;  // 0x7ffed6ee1528
    std::cout << "B.aa=" << &(B.aa) << std::endl;  // 0x7ffed6ee1530
    std::cout << "==============" << std::endl;
// 指针aa的内容
    std::cout << "A.aa=" << A.aa << std::endl;   // 0x55da375d1280
    std::cout << "B.aa=" << B.aa << std::endl;   // 0x55da375d1280
    std::cout << "==============" << std::endl;
// 指针aa的指向地址的内容
    std::cout << "A.aa=" << *A.aa << std::endl;    // 100
    std::cout << "B.aa=" << *B.aa << std::endl;    // 100
    std::cout << "==============" << std::endl;
// 由于默认拷贝构造是浅拷贝,带来object A和B都指向同一个内存地址
    *A.aa = 10;
    std::cout << "A.aa=" << *A.aa << std::endl;    // 10
    std::cout << "B.aa=" << *B.aa << std::endl;    // 10

    delete A.aa;
}

  • 一般通过一个对象来拷贝构造一个新的对象,他只会将栈区的内容拷贝一份。比如上述例子的Person类有一个指针对象int* A; Person B(A); 的操作只会把A的值拷贝到对象B的int* A; 这样会带来一个问题:A和B同时操作一片内存。导致B对象能修改A对象的变量。如上述*A.aa = 10;导致*B.aa的值也变为10了!!!
  • 拷贝值并不存在深浅拷贝的问题
  • 下图从网上摘录,很形象。

  •  深拷贝解决堆上的内存不被赋值
#include <stdio.h>
#include <iostream>

class Person
{
public:

    int* aa;
    Person()
    {
        aa = new int(100);
    };
    Person (Person& A)
    {
        int tmp = *A.aa;
        aa = new int(tmp);
    }
};
int main(int argc, char *argv[])
{
    printf( "lbw %s %s \n", __DATE__, __TIME__);
    Person A;
    Person B(A);

// 指针aa的地址
    std::cout << "A.aa=" << &(A.aa) << std::endl;  // 0x7ffed6ee1528
    std::cout << "B.aa=" << &(B.aa) << std::endl;  // 0x7ffed6ee1530
    std::cout << "==============" << std::endl;
// 指针aa的内容
    std::cout << "A.aa=" << A.aa << std::endl;   // 0x55da375d1280
    std::cout << "B.aa=" << B.aa << std::endl;   // 0x55da375d1280
    std::cout << "==============" << std::endl;
// 默认拷贝构造,让两个的值一样
    std::cout << "A.aa=" << *A.aa << std::endl;    // 100
    std::cout << "B.aa=" << *B.aa << std::endl;    // 100
    std::cout << "==============" << std::endl;
// 覆写了构造函数是深拷贝,所以各自的值是对的
    *A.aa = 10;
    std::cout << "A.aa=" << *A.aa << std::endl;    // 10
    std::cout << "B.aa=" << *B.aa << std::endl;    // 100

    delete A.aa;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值