C++之深拷贝和浅拷贝
最近一直在刷链表的题目,近期会来一次链表的总结。今天来苏州开会,晚上没事就复习了下C++基础知识,于是就写下了这篇博客。其他还有像this,内联函数,虚函数,构造函数和析构函数,内存,对象模型等等这些问题,这都是面试常问的内容,当然我以后也会更新这些,希望和大家一起学习。
讲深拷贝之前先来说一下浅拷贝,浅拷贝对于某些问题并不适用,于是才有了深拷贝,至于是什么问题,下面我会一一介绍。先说明,程序是我看侯捷老师 的视频跟着敲下来的,稍微有点改动,后面也会附上侯捷老师的视频地址。
先给出浅拷贝和深拷贝的精髓:
以是否重新分配内存资源的角度来理解深浅拷贝。
浅拷贝:对象的指针成员拷贝后会出现两个指针指向同一个内存空间。
深拷贝:对象的指针成员拷贝后会出现两个指针指向两个内存空间。
浅拷贝
首先,我们从下面这个程序开始。
class A {
private:
char *data;//指针成员
public:
A(const char *s);//构造函数
~A();
};
A::A(const char*s) {
if (s) {
data = new char[strlen(s) + 1];
strcpy_s(data, strlen(s)+1,s);
}
else {
data = new char[1];
*data = '\0';
}
cout << "构造函数" << endl;
}
A::~A() {
cout << "析构函数" << endl;
delete[] data;
}
int main() {
A a1("21");
//拷贝构造
A a2(a1);//两个指针指向同一块内存,会导致这个内存被释放两次,同时一个指针不知道指向哪里
//A a2("21");当然,这种也是会崩的,不仅有上述问题,并且一块内存已经没有指针指向他了,发生了内存泄漏。
//a2=a1;
return 0;
}
程序执行:调用一次构造函数,两次析构函数,然后就崩了。。
原因:当构造函数执行的时候还是没问题的,但是当进行拷贝构造的时候,由于我们没有自己去定义拷贝构造函数,编译器就会默认使用浅拷贝的方式来执行将一个对象初始化另一个对象的操作,这里的浅拷贝指的是:对于两个对象,当有指针成员数据的时候,只是拷贝了其中一个对象(a1)的指针成员本身的值,并没有拷贝这个指针指向对象的值。也就是只拷贝了一个指向字符串“21”的指针,并没有拷贝“21”这个字符串的值,这就导致两个对象的指针成员(拷贝之后指针成员本身的值是相同的)指向的内容相同。当调用析构函数的时候,会对同一块内存资源进行两次释放,当进行一次释放的时候,已经不知道另外一个指针会指向什么内容了
程序中给出了两种可能的情况:
当只有一个对象进行了初始化,然后通过这个对象初始化另外一个对象和两个对象都进行了初始化然后进行赋值会发生的问题。
所以说,当类中有指针成员的时,一定要自己去定义拷贝构造函数。
深拷贝:
还是从这个程序开始
当只是通过拷贝将一个对象初始化为另一个对象时,只有拷贝构造函数就够了,但是如果要是需要通过赋值运算符将一个对象赋值给另外一个对象,就必须再自己定义拷贝赋值函数。
class A {
private:
char *data;//指针成员
public:
A(const char *s);//构造函数
A(const A&s);//拷贝构造函数
A&operator=(const A&);//拷贝赋值函数
~A();
};
A::A(const A&str){
data = new char[strlen(str.data) + 1];
//memcpy(data, str.data,strlen(str.data));
strcpy_s(data, strlen(str.data) + 1, str.data);
cout << "拷贝构造函数" <<data<< endl;
}
A::A(const char*s) {
if (s) {
data = new char[strlen(s) + 1];//
strcpy_s(data, strlen(s)+1,s);
}
else {
data = new char[1];//
*data = '\0';
}
cout << "构造函数" << endl;
}
A&A::operator=(const A&str) {
cout << "拷贝复制函数" << endl;
if (this == &str)return *this;//先检测自我赋值
delete[]data;//先删除自己拥有的内存
data = new char[strlen(str.data) + 1];//再申请新的内存空间
strcpy_s(data, strlen(str.data) + 1, str.data);//然后进行数据的赋值
return *this;//
}
A::~A() {
cout << "析构函数" << endl;
delete[] data;
}
int main() {
A a1("21");
//拷贝构造
A a2(a1);
//a2 = a1;//赋值的时候也一定要自己定义拷贝赋值函数,即重载=运算符
return 0;
}
总结:
在对象拷贝过程中,如果我们没有自己定义拷贝构造函数,编译器就会默认采用浅拷贝的方式来进行拷贝,但是浅拷贝会有下面几种问题:
1.一个对象直接初始化为另一个对象:两个指针指向同一块内存的问题,因此会多次释放同一块内存资源(堆内存)。
2.当用到了=:如果两个对象都有对象数据占用内存资源,那原本一个指针指向的内存已经没有指针再指向它了,会发生内存泄漏。
因此当类中有指针成员时候,一定要自己定义拷贝构造函数和拷贝赋值函数来进行深拷贝。深拷贝通过完全拷贝指针的值和指针指向对象的值使得两个对象之间相互独立,避免了因为指针的存在浅拷贝发生的问题。
参考:
侯捷老师面向对象讲解
书本:C++ Primer Plus