浅拷贝是指类或结构包含指针类型成员变量,发生拷贝时,Object2=Object1,只是将Object1的变量指针的值赋给Object2,两者指向同一内存,如果Object1修改这块内存数据,Object2的指针变量所指向的数据也会发生变化。如果Object1释放了这块内存而Object2继续使用就可能导致程序出错。
下列程序所发生的就是浅拷贝
class CopyTest
{
public:
CopyTest()
{
ptr = nullptr;
memset(arr, 0, 8);
}
char* ptr;
char arr[8];
};
int main()
{
std::cout << "sizeof(CopyTest)=" << sizeof(CopyTest) << std::endl;//CopyTest所占内存大小为16字节,不同编译器中指针大小不一样,这里是8字节
CopyTest* pTest1 = new CopyTest;
pTest1->ptr = new char[20];
memset(pTest1->ptr, 0, 20);
//给pTest1的ptr和arr赋值
memcpy(pTest1->ptr, "hello world!", 12);
memcpy(pTest1->arr, "1234567", 7);
std::cout << "sizeof(*pTest1)=" << sizeof(*pTest1) << std::endl;//*pTest1所占内存大小为16字节
std::cout << std::hex;//以十六进制显示整数
std::cout << "pTest1:" << std::endl;
//显示pTest1的ptr和arr的地址和内容
std::cout << "ptr address(0x" << (void*)pTest1->ptr << ") content=" << pTest1->ptr << std::endl;
std::cout << "arr address(0x" << (void*)pTest1->arr << ") content=" << pTest1->arr << std::endl;
CopyTest test2 = *pTest1;
std::cout << "test2:" << std::endl;
//显示test2的ptr和arr的地址和内容,由于浅拷贝,test2.ptr与pTest1->ptr相同,包括地址和内容
std::cout << "ptr address(0x" << (void*)test2.ptr << ") content=" << test2.ptr << std::endl;
//数组为深拷贝,内容被复制到test2的内存中,地址与pTest1的arr不一样
std::cout << "arr address(0x" << (void*)test2.arr << ") content=" << test2.arr << std::endl;
delete[] pTest1->ptr;//释放ptr
delete pTest1;//释放pTest1
std::cout << "test2:" << std::endl;
//显示test2的ptr和arr的地址和内容,到这里test2.ptr已经被pTest1释放,变为无效地址,打印出乱码
std::cout << "ptr address(0x" << (void*)test2.ptr << ") content=" << test2.ptr << std::endl;
//test2.arr仍然可用
std::cout << "arr address(0x" << (void*)test2.arr << ") content=" << test2.arr << std::endl;
std::cout << std::dec;//重置为10进制显示
system("pause");
return 0;
}
运行结果:
默认拷贝构造函数(test2=*pTest1)只是将*pTest1的内存数据拷贝到test2,相当于memcpy,*pTest1内存中只包含一个指针值ptr和一个8字节的数组arr,并不包含"hello world!"字符串, 因此两者ptr指向同一块内存,无论谁修改两者都会受影响,而arr则各自有一份数据,互不影响。
delete[] pTest1->ptr;调用之后test2的ptr也会被释放,所以打印出来是随机的字符串(一般是乱码,如果这块内存没有被其他程序修改,也有可能打印正常,但是不能再写入数据)。
通常我们会在类的析构函数中释放成员变量(定义了析构函数之后要将delete[] pTest1->ptr这一行注释掉。),如下所示:
class CopyTest
{
public:
CopyTest()
{
ptr = nullptr;
memset(arr, 0, 8);
}
virtual ~CopyTest()//析构函数一般定义为虚函数
{
if (ptr)
{
delete[] ptr;//假定我们分配的始终是char数组
ptr = nullptr;
}
}
char* ptr;
char arr[8];
};
这样再重新跑一遍程序就会出错,原因是delete pTest1后ptr就已经被释放,main函数结束之后会调用test2的析构函数,test2又会对ptr调用delete[] ptr,对无效的地址再次释放会导致程序出错。
这个问题的一个解决办法就是再定义一个拷贝构造函数,将pTest1的ptr所指向的内存也拷贝一份到新的内存,使test2的ptr指向这块内存,这样两个对象实例的ptr相互独立,彼此不受影响,这也就是深拷贝。再次修改类结构如下:
class CopyTest
{
public:
CopyTest()
{
ptr = nullptr;
memset(arr, 0, 8);
}
CopyTest(const CopyTest& InOther)
{
int len = strlen(InOther.ptr);//假定是字符串,否则长度值要由其他方法获取
ptr = new char[len + 1];
memcpy(ptr,InOther.ptr,len);
ptr[len] = '\0';
memcpy(arr, InOther.arr,8);
}
virtual ~CopyTest()//析构函数一般定义为虚函数
{
if (ptr)
{
delete[] ptr;//假定我们分配的是char数组
ptr = nullptr;
}
}
char* ptr;
char arr[8];
};
运行后程序就正常了。