谈“浅复制”和“深复制”之前先介绍如下知识点:
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>源文件strngbad.cpp(包含类的定义)
#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
#include "strngbad.h"源文件vegnews.cpp
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;
}
<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";
}
运行结果如下:
运行结果出现了问题(原因复制构造函数和赋值运算符进行的是“浅复制”)
第一处原因:
函数callme2是按值传递,产生临时对象,调用默认复制构造函数。因为“浅复制”复制的是指针,不会复制指针所指向的内容,所以把headline2.str指针值付给了新的临时对象。函数结束时,临时对象析构。该指针所指内存被释放,所以第一处乱码。
第二处原因:
knot = headline1;也是“浅复制”。headline1把指针赋给了knot。在main函数结束后对象都要进行析构,因为函数内部局部变量存储于栈中,按先进后出原则析构。所以knot先析构,接下来sport,再接下来headline2(前面str内存已被释放,所以是乱码),在接下来是headline1,如果能显示出来的话也是乱码(headline1.str被knot释放),但停止工作。
通俗的话说,你把自己的指针给了别人,别人先把指向的内存释放了,那你还能用个蛋蛋。
解决办法:
定义显示复制构造函数和赋值运算符,采用“深复制”。及不复制指针,而复制指针所指向的内容。用strcpy函数。
还有一问题复制构造函数计数器没有变化,可在显式定义中实现。
总结:
浅复制:只复制指针,不复制指向内容。
深复制:不复制指针,但复制指向内容。