深拷贝与浅拷贝深究
深拷贝与浅拷贝。我们可以通过一个故事来了解一下:甲有一本漫画很好看,这时乙也想看。第一种情况,甲将书借给了乙(浅拷贝:资源重复利用了);第二种情况,甲因自己还没看完,没有借给乙,乙就自己重新买了一本(深拷贝:重新弄一个相同的)。
在这里,我会从构造函数,拷贝构造函数、=(赋值)重载、析构函数进行深究。
首先我们通过一个例子来了解一下:
#include<iostream> using namespace std; class String { public: String(const char *str=""); //构造函数 String(const String &s); //拷贝构造函数 String& operator=(const String &s); //重载=运算符 ~String(); //析构函数 } void main() { String s="Hello"; String s1=s;
String s2; s2=s1; }
以上的代码是一个简单String类,一般像较为简单的函数,我们可以将函数实现写在类中,这样形成内联函数,执行效率会大大提高。
1.为什么在函数中要对参数进行const限制?
答:在函数中,我们使用const对参数进行限制,为了保证数据的安全性。
2.给形参设置一个默认值(const char *str="")有什么好处?
答:在构造函数中,我们给形参设定一个空的的默认值。避免在函数体内进行空指针判断,提高了代码的简约性。
3.在拷贝构造函数中,为什么对参数要加引用?
答:为了避免产生拷贝构造后,无休止的拷贝构造。
初级程序员标准如下代码:
class String{ public: String(const char *str="") //构造函数 { m_data=new char[strlen(str)+1]; strcpy(m_data,str); } String(const String &s) //拷贝构造函数 { m_data=new char[strlen(s.m_data)+1]; strcpy(m_data,s.m_data); } String& operator=(const String &s) //重载= { if(this != &s) { delete []m_data; m_data=new char[strlen(s.m_data)+1]; //问题4:异常安全性原则 strcpy(m_data,s.m_data); } return *this; } ~String()//析构函数 { delete[] m_data; m_data=NULL; } private: char *m_data; };
4.当在重载=运算符时,s1=s2,s1存储数据所需要的空间申请不足时,会导致之前s1对象delete的数据丢失,如何避免这一情况? 这称为“异常安全性原则”。
答:①我们先申请相应的空间,在将复制实例释放空间。 ②我们申请一个局部实例,然后交换临时实例和原来实例。
高级程序员标准如下代码:
String& operator=(const String &s) //重载= { if(this != &s) { String temp_str(s);
char *temp_data=temp_str.m_data;
temp_str.m_data=m_data;
m_data=temp_data; } return *this; }
5.当我们用到浅拷贝时,那我们在执行析构函数时,就要注意释放空间的时机。在这里我们通过两种方法过度:①我们引用静态常量(static int count)来记住指向当前数据的实例个数,当count==0时,执行释放空间。
class String { public: String(const char *str = "") { m_data = new char[strlen(str)+1]; strcpy(m_data, str); use_count++; } String(const String &s) { m_data = s.m_data; use_count++; } //String& operator=(const String &s); ~String() { if(--use_count == 0) { delete []m_data; m_data = NULL; } } private: char *m_data; static int use_count; }; int String::use_count = 0; void main() { String s("Hello"); String s1 = s; String s2("Linux"); }
注意:当我们调试发现,当重新定义一个不一样的实例时,也会改变use_count的值,这是我们不想看到的。由此当我们执行程序完成,继第二次之后执行构造函数所分配的空间不会被释放,造成内存泄漏。
②我们引用技术类将实例,将String对象的初始化和use_count的增减操作分离。
class String; ostream& operator<<(ostream &out, const String &s); class String_rep { friend class String; friend ostream& operator<<(ostream &out, const String &s); public: String_rep(const char *str="") : use_count(0) { m_data = new char[strlen(str)+1]; strcpy(m_data, str); } String_rep(const String_rep &rep)
{
m_data=new char[strlen(rep.m_data)+1];
strcpy(m_data,rep.m_data);
}
String_rep& operator=(const String_rep &rep)
{
if(this!=&rep)
{
/* 同问题4:会发生异常
delete m_data;
m_data=new char[strlen(rep.m_data)+1];
strcpy(m_data,rep.m_data)*/
String_rep temp_rep(rep);
char *temp_data=temp_rep.m_data;
temp_rep.m_data=m_data;
m_data=temp_data;
}
return *this;
} ~String_rep() { delete []m_data; m_data = NULL; } public: void increment() {++use_count;} void decrement() {
if(--use_count==0)
delete this;
} private: char *m_data; int use_count; }; //////////////////////////////////////////////////////// class String { friend ostream& operator<<(ostream &out, const String &s); public: String(const char *str = "") : rep(new String_rep(str)) { rep->increment(); } String(const String &s) { rep = s.rep; rep->increment(); } String& operator=(const String &s); ~String() { rep->decrement(); } public:
//当完成写实拷贝,如若用户想要对数据进行修改,则需要发生深拷贝,将原类的实例引用常量-1,将新生实例的引用常量+1. void to_upper()
{
rep->decrement();
rep=new String_rep(rep->m_data);
rep->increment();
char *ch = rep->m_data;
while(*ch++ != '\0')
*ch = toupper(*ch);
}
private: String_rep *rep; }; ostream& operator<<(ostream &out, const String &s) { out<<(s.rep)->m_data; return out; } void main() { String s("Hello"); String s1 = s; cout<<"s = "<<s<<endl; cout<<"s1 = "<<s1<<endl; s.to_upper(); //写实拷贝 cout<<"s = "<<s<<endl; cout<<"s1 = "<<s1<<endl; }
对s进行写实拷贝,执行结果如下: