定义:
浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区重新申请空间进行拷贝。
#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;
}