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