学渣笔记之C++深拷贝与浅拷贝

实际上,C++类的拷贝有三种。除了“深拷贝”与“浅拷贝”以外,还有一种“默认拷贝”。“默认拷贝”指的是不需要我们自己定义拷贝构造函数,系统就能帮我们完成一切的拷贝方式。比如定义一个类,用赋值语句将之前定义过的一个类赋值给新定义的类,或者说当函数返回类型是对象的时候,比如我上一篇文章中的return *this。

那么,“浅拷贝”与“深拷贝”,究竟有何意义呢?它们之于“默认拷贝”,又有何独到之处?而它们二者之间,又有何不同?

首先,无论“浅拷贝”抑或是“深拷贝”,都是我们自己定义的拷贝构造函数。首先我们要先知道“默认构造函数”的局限性在哪。先来看看以下代码:
#include
using namespace std;
class People
{
public:
People()
{
cin>>name;
count++;
cout<<"构造函数已调用!"<<endl;
}
People(People &p)
{
name=p.name;
cout<<"复制构造函数已调用!"<<endl;
}
~People()
{
count--;
cout<<"析构函数已调用!"<<endl;
}
static int getCount()
{
return count;
}
private:
string name;
static int count;
};
int People::count= 0;
int main()
{
People man1;
cout<<"人数:"<<People::getCount()<<endl;
People man2(man1);
cout<<"人数:"<<People::getCount()<<endl;
return 0;
}

这个代码无非就是创建一个People类的对象man1,然后通过拷贝构造函数构造一个新的People对象man2。根据常识,此时已经有两个People类的对象了,因此静态成员数据count理应是2。但是执行完这段代码之后,我们可以得到如下结果:

默认构造函数运行结果
为了让结果更加地直观,我对系统默认的构造函数和拷贝构造函数进行了重写,加上了文字提示。

不难发现。运行完整段代码之后,count的值依然为1。而我们也可以从运行结果里看到,析构函数调用了两次。构造函数也调用了两次。其中一次是构造函数,一次是拷贝构造函数。我们不妨来探究一下究竟是哪里出了问题,才导致我们count的数量没有增加。

这是因为,一开始我们构造了People类的man1,此时count确实+1了。之后调用拷贝构造函数创建man2。而系统默认的拷贝构造函数是没有对count数据进行操作的部分,因此实际上count并没有+1;而最终当析构函数调用了之后,count应该会变成-1。这里count输出结果是1是因为我们输出人数的时候系统函数还没有被调用。如果把main函数改成以下内容,就可见一斑了:

int main()
{
{
People man1;
cout<<"人数:"<<People::getCount()<<endl;
People man2(man1);
cout<<"人数:"<<People::getCount()<<endl;
}
cout<<"人数:"<<People::getCount()<<endl;
return 0;
}

这个时候,我们得到的运行结果,会在最后一行发现,输出人数变成-1了。

由上面这个例子可以知道。系统默认的拷贝构造函数,实际上只能传递成员数据,但是没办法改变成员数据的值。这显然在很多时候都是不可行的。因此,我们才需要自己写一个新的构造函数。

“浅拷贝”可以理解成是对成员数据进行简单赋值操作的一个步骤。比方说我只要把上面例子中的拷贝构造函数修改成以下内容:

People(People &p)
{
name=p.name;
count++;
cout<<"复制构造函数已调用!"<<endl;
}

那这个构造函数就是一个满足我们需求的构造函数。因为它只对成员数据进行简单的赋值操作,因此它是一个“浅拷贝”的函数。从这个意义上来说,系统默认的拷贝构造函数,也是一种“浅拷贝”。

那么“深拷贝”是什么?“深拷贝”最大的特点在于,它能够对动态成员数据(堆)进行操作。

如果没有深拷贝的话,代码如下:

#include <iostream>
using namespace std;
class People
{
public:
People()
{
name=new string;
*name="王明";
count++;
cout<<"构造函数已调用!"<<endl;
cout<<name<<endl;
}
People(const People& p)
{
cout<<p.name<<endl;
name=p.name;
count++;
cout<<"复制构造函数已调用!"<<endl;
cout<<name<<endl;
cout<<p.name<<endl;
}
~People()
{
count--;
cout<<"析构函数已调用!"<<endl;
delete name;
}
static int getCount()
{
return count;
}
private:
string* name;
static int count;
};
int People::count= 0;
int main()
{
People man1;
cout<<"人数:"<<People::getCount()<<endl;
People man2(man1);
cout<<"人数:"<<People::getCount()<<endl;
cout<<"人数:"<<People::getCount()<<endl;
return 0;
}

这个时候等于是把man1中的name的地址赋给了man2中的name。这样两个对象name的地址都一样,在析构函数的时候会导致同一地址内存空间被释放两次,可能会报错(好像有的编译器即使释放N次都不会报错。)。

因此我们要把以上代码修改成如下,就是一个“深拷贝”了:

#include <iostream>
using namespace std;
class People
{
public:
People()
{
name=new string;
*name="王明";
count++;
cout<<"构造函数已调用!"<<endl;
cout<<name<<endl;
}
People(const People& p)
{
cout<<p.name<<endl;
name= new string;
*name=*p.name;
count++;
cout<<"复制构造函数已调用!"<<endl;
cout<<name<<endl;
cout<<p.name<<endl;
}
~People()
{
count--;
cout<<"析构函数已调用!"<<endl;
delete name;
}
static int getCount()
{
return count;
}
private:
string* name;
static int count;
};
int People::count= 0;
int main()
{
People man1;
cout<<"人数:"<<People::getCount()<<endl;
People man2(man1);
cout<<"人数:"<<People::getCount()<<endl;
cout<<"人数:"<<People::getCount()<<endl;
return 0;
}

发表评论

电子邮件地址不会被公开。