C++:从拷贝构造函数到深浅拷贝
拷贝构造函数
当实例化一个新对象并使用同类型对其进行初始化时,会显式调用类的拷贝构造函数,拷贝构造函数写法:形参为const修饰的同类型类引用。拷贝构造函数有个需要注意的点,形参为何是只允许传递引用呢?原因在于若传递的是值,那么在调用拷贝构造函数时形参拷贝到实参时会再次发生拷贝,就会无休止的调用拷贝构造函数。
A(const A& a) { cout << "拷贝构造函数" << endl; }
赋值运算符重载
赋值运算符重载与拷贝构造函数有些相似,同样也是用同类型的对象初始化本对象。区别在于若对象已经存在,无需重新实例化时,赋值运算符操作则会调用重载函数。赋值运算符重载函数一般写成返回值为类的引用,形参为const修饰的类引用。
A& operator=(const A& a) {
cout << "重载赋值运算符" << endl;
return *this;
}
若没有手动实现这两个函数,在发生拷贝构造或赋值运算时,程序会调用按照位拷贝的方式生成默认函数并调用。位拷贝的方式为“浅拷贝”。若类中没有申请的内存,这种拷贝方式是安全的,但是若类中成员变量存在申请的内存char* buf = new char[1024]
,那么这种拷贝方式会造成指针垂挂,可能会出现二次析构等不安全的情况。这种时候就需要手动使用深拷贝的方式实现赋值函数与拷贝构造函数。
浅拷贝
浅拷贝的意思是使用指针指向同一个内存空间,类似于C++中的引用。
A* a = new A;
A* b = nullptr;
b = a;
这种方式的效果是两个指针的解引用值是一样的,不管修改哪一个,另外一个输出出来都是一样的,因为他们根本指向的是同一块地址
// 赋值操作
int main()
{
int* i = new int(1);
int* j;
j = i;
*i = 5;
cout << *i << " - " << *j << endl;
delete i;
}
// 拷贝构造
int main()
{
int* i = new int(1);
int* j = i;
*i = 5;
cout << *i << " - " << *j << endl;
delete j;
}
/*****************************************
* 结果为 5 - 5
* /
深拷贝
深拷贝的意思是重新申请一份内存,然后将原内存的数据存入到新内存中,这样两份副本是完全独立的,互不影响的
int main()
{
int* i = new int(1);
int* j = new int(*i);
*i = 5;
cout << *i << " - " << *j << endl;
delete i;
delete j;
}
总结
那么在类中若是存在指针的情况下,尽可能都手动实现赋值函数和拷贝构造函数,并使用深拷贝的方式去实现
class A {
public:
A(int i) { Pi = new int(i); }
~A() { if (Pi) delete Pi; }
A(const A& a) {
if (Pi)
delete Pi;
Pi = new int(*a.Pi);
}
A& operator=(const A& a) {
if (this != &a)
{
if (Pi)
delete Pi;
Pi = new int(*a.Pi);
}
return *this;
}
int* Pi = nullptr;
};
int main()
{
A a(0);
A b = a;
A c(0);
c = a;
*a.Pi = 5;
cout << *a.Pi << " - " << *b.Pi << " - " << *c.Pi << endl;
}
/* 输出为: 5 - 0 - 0 */