浅拷贝与深拷贝
浅拷贝
浅拷贝就是拷贝指向对象的指针,即拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间
,浅拷贝只是一种简单的拷贝,让几个对象公用一个内存。由于多个指针指向了同一片内存空间,如果删除其中一个指针,释放了内存空间,但删除另外一个指针时,会重复释放内存空间,假设不删除另外一个指针,该指针就会变成野指针,其指向的内容不定。前述的情况都容易导致出现内存泄漏的问题。
举例:
设计一个Cat类,包含指针成员和普通成员,它有一个拷贝构造函数:
class Cat{
unsigned short age; // 基本类型变量
char* name; // 字符指针
public:
explicit Cat(unsigned short m_age, char* m_name) : age(m_age),name(m_name){}
// 拷贝构造,浅拷贝
Cat(const Cat& obj)
{
age = obj.age;
name = obj.name;
}
// 析构函数
~Cat(){
cout << "name = " << name << endl;
cout << "age = " << age << endl;
cout << "删除Cat对象" << endl;
cout << endl;
if(name != nullptr)
{
delete name;
}
}
};
主程序测试:
char* name = new char[5];
name[0] = 'm';
name[1] = 'o';
name[2] = 'n';
name[3] = 'a';
Cat cat(12, name);
Cat cat1(cat);
运行结果:
name = mona
age = 12
删除Cat对象
name = xt
age = 12
删除Cat对象
可以看到Cat类中的拷贝构造对于指针成员的复制为浅拷贝,即两个指针均指向了同一片内存区域,导致第一次释放掉该内存区域时,第二次访问到的为随机值,显然不是我们想要的结果,而普通成员变量没有受影响。
深拷贝
深拷贝是指在拷贝对象时,会开辟一块新的内存空间来存储数据。目标对象的指针会指向新开辟出来的内存空间,里面存放的内容与源对象所指向空间的内容一致。也就是说分别操作源对象和目标对象时,两者互不影响。
更改Cat类的拷贝构造:
class Cat{
unsigned short age; // 基本类型变量
char* name; // 字符指针
public:
explicit Cat(unsigned short m_age, char* m_name) : age(m_age),name(m_name){}
// 拷贝构造
Cat(const Cat& obj)
{
age = obj.age;
name = new char[strlen(obj.name) + 1];
strcpy(name,obj.name);
}
// 析构函数
~Cat(){
cout << "name = " << name << endl;
cout << "age = " << age << endl;
cout << "删除Cat对象" << endl;
cout << endl;
if(name != nullptr)
{
delete name;
}
}
};
相同的测试
char* name = new char[5];
name[0] = 'm';
name[1] = 'o';
name[2] = 'n';
name[3] = 'a';
name[4] = '\0';
Cat cat(12, name);
Cat cat1(cat);
运行结果:
name = mona
age = 12
删除Cat对象
name = mona
age = 12
删除Cat对象
从运行结果上可以看到,两次析构函数的运行结果字符成员访问的均是自行初始化的结果,而不是随机值,说明实现了深拷贝。
深浅拷贝的具体场景
标准库的copy函数:
C++标准库提供了copy,copy_if,那么这些算法是浅拷贝还是深拷贝呢?以copy作测试
对于简单的基本类型:
vector<int> v1{1,2,3};
vector<int> v2; // v2未开辟空间
vector<int> v3(v1.size()); // 开辟了空间
// back_inserter借助push_back成员函数不断从容器尾部插入
copy(v1.cbegin(),v1.cend(),back_inserter(v2));
copy(v1.begin(),v1.end(),v3.begin());
v2.at(0) = 3;
printEle(v1,"v1,before:");
printEle(v2,"v2:");
printEle(v1,"v1,after:");
printEle(v3,"v3:");
运行结果为:
v1,before:1 2 3
v2:3 2 3
v1,after:1 2 3
v3:1 2 3
如果存放类对象,且类中含有指针成员
依然以前面设计的Cat类做测试
class Cat{
unsigned short age; // 基本类型变量
char* name; // 字符指针
public:
Cat(){cout << "调用了默认构造" << endl;}
explicit Cat(unsigned short m_age, char* m_name) : age(m_age),name(m_name){}
// 拷贝构造
Cat(const Cat& obj)
{
age = obj.age;
name = obj.name; // 浅拷贝方式
// name = new char[strlen(obj.name) + 1]; // 深拷贝方式
// strcpy(name,obj.name);
cout << "调用了拷贝构造" << endl;
}
// 析构函数
~Cat(){
cout << "name = " << name << endl;
cout << "age = " << age << endl;
cout << "删除Cat对象" << endl;
cout << endl;
if(name != nullptr)
{
delete name;
}
}
};
主函数调用测试
char* name = new char[5];
name[0] = 'm';
name[1] = 'o';
name[2] = 'n';
name[3] = 'a';
name[4] = '\0';
Cat cat(12,name);
vector<Cat> v1;
v1.push_back(cat);
vector<Cat> v2;
copy(v1.begin(),v1.end(),back_inserter(v2));
Cat类拷贝构造采取浅拷贝方式的运行结果
调用了拷贝构造
调用了拷贝构造
name = mona
age = 12
删除Cat对象
name =
age = 12
删除Cat对象
name = xt
age = 12
删除Cat对象
Cat类拷贝构造采取深拷贝方式的运行结果
调用了拷贝构造
调用了拷贝构造
name = mona
age = 12
删除Cat对象
name = mona
age = 12
删除Cat对象
name = mona
age = 12
删除Cat对象
从前面所举的例子,特别是第二个例子,可以看出标准库提供的copy算法是一种浅拷贝 方式。
赋值运算符(=)
同样使用测试copy算法的Cat类来测试赋值运算符,=。
主函数:
char* name = new char[5];
name[0] = 'm';
name[1] = 'o';
name[2] = 'n';
name[3] = 'a';
name[4] = '\0';
Cat cat(12,name);
Cat cat1 = cat;
Cat类拷贝构造采取浅拷贝方式的运行结果
调用了拷贝构造
name = mona
age = 12
删除Cat对象
name = xt}
age = 12
删除Cat对象
Cat类拷贝构造采取深拷贝方式的运行结果
调用了拷贝构造
name = mona
age = 12
删除Cat对象
name = mona
age = 12
删除Cat对象
从测试结果可以看出,赋值运算符也是一种浅拷贝 方式。
strcpy和memcpy方法
strcpy方法:
-
函数原型
char*strcpy(char*dest,const char*src) //将src复制到dest字符数组中,返回的是dest
-
测试
char* name = new char[5];
name[0] = 'm';
name[1] = 'o';
name[2] = 'n';
name[3] = 'a';
name[4] = '\0';
// name2未初始化,将变成随机值
char* name2;
strcpy(name2,name);
cout << "name = " << name << endl;
cout << "name2 = " << name2 << endl;
name2[0] = 'M';
cout << endl;
cout << "name = " << name << endl;
cout << "name2 = " << name2 << endl;
运行结果:
name = mona
name2 = mona
name = mona
name2 = Mona
当name2修改自身指向的字符内容时,并未影响到源数据,说明strcpy方法是一种深拷贝 方式。
memcpy方法:
- 函数原型
void *memcpy(void *destin, void *source, unsigned n);
-
参数
-
destin– 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
-
source– 指向要复制的数据源,类型强制转换为 void* 指针。
-
n– 要被复制的字节数。
-
-
返回值
该函数返回一个指向目标存储区destin的指针。
-
功能
-
从源source所指的内存地址的起始位置开始拷贝n个字节到目标destin所指的内存地址的起始位置中。
-
memcpy() 并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
-
需要注意的是:
-
destin指针要分配足够的空间,也即大于等于 n 字节的空间。如果没有分配空间,会出现断错误。
-
destin和 source所指的内存空间不能重叠(如果发生了重叠,使用 memmove()会更加安全)。
-
-
-
测试
-
主函数,依然使用前面的Cat类测试。这里记得将成员变量变成公有访问,方便测试
char* name = new char[5]; name[0] = 'm'; name[1] = 'o'; name[2] = 'n'; name[3] = 'a'; name[4] = '\0'; char* name1 = new char[5]; name1[0] = 'm'; name1[1] = 'i'; name1[2] = 'k'; name1[3] = 'e'; name1[4] = '\0'; Cat cat(12,name); Cat cat1(13,name1); Cat* p1 = &cat; Cat* p2 = &cat1; cout << "复制前:" << endl; cout << "p1->name = " << p1->name << ",p1->age = " << p1->age << endl; cout << "p2->name = " << p2->name << ",p2->age = " << p2->age << endl; memcpy(p2,p1,sizeof(Cat)); cout << "复制后:" << endl; cout << "p1->name = " << p1->name << ",p1->age = " << p1->age << endl; cout << "p2->name = " << p2->name << ",p2->age = " << p2->age << endl;
-
Cat类拷贝构造采取浅或深拷贝方式的运行结果
复制前: p1->name = mona,p1->age = 12 p2->name = mike,p2->age = 13 复制后: p1->name = mona,p1->age = 12 p2->name = mona,p2->age = 12 name = mona age = 12 删除Cat对象 name = mona age = 12 删除Cat对象
结合函数的功能以及运行结果来看,可以得出结论,memcpy函数也是一种浅拷贝 方式。
-
-
总结
总的来说,如果一个类中的成员只包含普通的成员,使用复制运算符,copy函数时,这时浅拷贝等价于深拷贝。但是当类对象中含有指针类型的成员变量
时,就需要注意重新设计拷贝构造函数,复制时应该为指针成员重新开辟内存空间,之后复制内容,实现深拷贝。