谈一谈C++类的“浅复制”和“深复制”

时间:2022-08-23 12:46:18

谈“浅复制”和“深复制”之前先介绍如下知识点:

C++类的特殊成员函数:

1.默认构造函数

2.默认析构函数

3.复制构造函数

4.赋值运算符

5.地址运算符

默认构造函数和默认析构函数,大家坑定都非常了解这里就不多说。这里主要说一说"复制构造函数"和“赋值运算符”


复制构造函数

将一个对象复制到新创对象当中。函数原型如下

Class_name(const Class_name &);


假设motto是StringBad的一个对象,一下四种声明都将调用构造函数:

1.StringBad diitto(motto);

2.StringBad diitto = motto;

3.StringBad diitto = StringBad(motto);

4.StringBad diitto = new StringBad(motto);

编译器生成对象副本时也将调用。比如说函数按值传递对象时和返回对象时,都将创建临时副本,也调用复制构造函数。

如果复制构造函数没有显式的定义将使用默认复制构造函数,不会复制静态成员


赋值运算符

将已有对象复制到另一已有对象(初始化不使用)

函数原型如下

Class_name & Class_name::Operator=(const Class_name &);

如果赋值运算符没有显式的定义将使用默认赋值运算符。


下面看一段代码

头文件strngbad.h  (包含类的声明)

#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
private:
char * str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
public:
StringBad(const char * s); // constructor
StringBad(); // default constructor
~StringBad(); // destructor
// friend function
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
#endif
源文件strngbad.cpp(包含类的定义)

#include "strngbad.h"
using std::cout;

// initializing static class member
int StringBad::num_strings = 0;

// class methods

// construct StringBad from C string
StringBad::StringBad(const char * s)
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
cout << num_strings << ": \"" << str
<< "\" object created\n"; // For Your Information
}

StringBad::StringBad() // default constructor
{
len = 4;
str = new char[4];
std::strcpy(str, "C++"); // default string
num_strings++;
cout << num_strings << ": \"" << str
<< "\" default object created\n"; // FYI
}

StringBad::~StringBad() // necessary destructor
{
cout << "\"" << str << "\" object deleted, "; // FYI
--num_strings; // required
cout << num_strings << " left\n"; // FYI
delete [] str; // required
}

std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
源文件vegnews.cpp

<pre name="code" class="cpp">// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std::cout;

#include "strngbad.h"

void callme1(StringBad &); // pass by reference
void callme2(StringBad); // pass by value

int main()
{
using std::endl;
StringBad headline1("Celery");
StringBad headline2("Lettuce");
StringBad sports("Spinach");
callme1(headline1);
cout << "headline1: " << headline1 << endl;
callme2(headline2);//问题1
cout << "headline2: " << headline2 << endl;

cout << "Assign one object to another:\n";
StringBad knot;
knot = headline1;//问题2
cout << "knot: " << knot << endl;
cout << "End of main()\n";
return 0;
}

void callme1(StringBad & rsb)
{
cout << "String passed by reference:\n";
cout << " \"" << rsb << "\"\n";
}

void callme2(StringBad sb)
{
cout << "String passed by value:\n";
cout << " \"" << sb << "\"\n";
}

 
运行结果如下: 


谈一谈C++类的“浅复制”和“深复制”

运行结果出现了问题(原因复制构造函数和赋值运算符进行的是“浅复制”)


第一处原因:

函数callme2是按值传递,产生临时对象,调用默认复制构造函数。因为“浅复制”复制的是指针,不会复制指针所指向的内容,所以把headline2.str指针值付给了新的临时对象。函数结束时,临时对象析构。该指针所指内存被释放,所以第一处乱码。

第二处原因:

knot = headline1;也是“浅复制”。headline1把指针赋给了knot。在main函数结束后对象都要进行析构,因为函数内部局部变量存储于中,按先进后出原则析构。所以knot先析构,接下来sport,再接下来headline2(前面str内存已被释放,所以是乱码),在接下来是headline1,如果能显示出来的话也是乱码(headline1.str被knot释放),但停止工作。


通俗的话说,你把自己的指针给了别人,别人先把指向的内存释放了,那你还能用个蛋蛋。


解决办法:

定义显示复制构造函数和赋值运算符,采用“深复制”。及不复制指针,而复制指针所指向的内容。用strcpy函数。

还有一问题复制构造函数计数器没有变化,可在显式定义中实现。


总结:

浅复制:只复制指针,不复制指向内容。

深复制:不复制指针,但复制指向内容。