拷贝:
事实是,在对象拷贝过程中,如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。
浅拷贝:
缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。
浅拷贝的代码
class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
//拷贝构造函数,它是构造函数的重载
String(const String& s)
{
//浅拷贝的情况下,只复制了指针本身,对指针所指向的内存并没有进行拷贝
_str = s._str;
}
String& operator=(const String& s)
{
if (this != &s)
{
_str = s._str;
}
return *this;
}
~String()
{
if (_str)
{
//指针指向相同的内存空间,再次释放一块已经释放的空间将会出现错误
delete[] _str;
}
}
private:
char* _str;
};
浅拷贝出现的问题:
(1)在进行对象复制后,事实上s1、s2里的成员指针m_psz都指向了一块内存空间(即内存空间共享了),在s1析构时,delete了成员指针m_psz所指向的内存空间,而s2析构时同样指向(此时已变成野指针)并且要释放这片已经被s1析构函数释放的内存空间,这就让同样一片内存空间出现了重复释放 ,从而出错。
(2)而浅拷贝还存在着一个问题,因为一片空间被两个不同的子对象共享了,只要其中的一个子对象改变了其中的值,那另一个对象的值也跟着改变了
深拷贝:
为每个对象都分配内存空间,防止析构时出现错误。
深拷贝的代码:
class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
String(const String& s)
{
//内存的拷贝先要创建出一块内存
_str = new char[strlen(s._str)+1];
//然后将指针所指向的内容拷贝一份
strcpy(_str, s._str);
}
//赋值运算符的重载
String& operator=(const String& s)
{
//防止自身对自身赋值(因为会delete掉原来的内存空间,如果是自己给自己赋值,那么delete以后就找不到了)
if (this != &s)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
};
深浅拷贝的不同:
深浅拷贝的主要区别在于是否共用空间。两者的代码只有拷贝构造函数和赋值运算符重载时,深拷贝需要重新开辟空间,而浅拷贝仅仅改变了指针的指向。
深拷贝的缺点:
重复的去开辟空间和释放空间效率是很低下的。
写时拷贝的引入:
大家都知道,重复的开辟空间和释放内存是很耗时的,效率也很低下,相对于写时拷贝,深拷贝就很耗费时间了,效率自然没有写时拷贝好。
写时拷贝是用一个计数器记住当前空间被多少个指针所指向,每次调用析构函数的时候,只需要看看自减后引用计数是否为零(是否只被最后一个指针所指向)。如果是,则销毁空间,如果不是,那么只需要引用计数减减即可,无需释放空间。
写时拷贝是一种高性能的写法,不过这种写法如果不注意就会把引用计数弄错,导致错误。所以在写时拷贝的写法的时候,一定要小心改写引用计数。
执行下面的一段代码,写时拷贝写法的指针应该如何指向呢?
void test()
{
String s1("hello");
String s2(s1);
String s3("world");
s1 = s3;
}
图解上面的代码:
写时拷贝的代码:
class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str)+1+4])//+1表示字符串后面要放一个'\0',+4表示多开辟一个空间存放引用计数
{
_str += 4;//_str指向数据存放区
strcpy(_str, str);
_GetCount() = 1;//刚开始这里写成了_GetCount++,其实是不对的,因为引用计数所指向的这a块空间并没有初始化,他的值并不是0,是一个随机值,随机值++还是随机值
}
String(const String& s)
:_str(s._str)
{
_GetCount()++;
}
String& operator=(String& s)
{
if (this != &s)
{
if (--_GetCount() == 0)
{
delete[] (_str-4);
}
++s._GetCount();
_str = s._str;
}
return *this;
}
~String()
{
if (--_GetCount() == 0)
{
delete[] (_str-4);
}
}
public:
int& _GetCount()
{
return *((int*)_str-1);
}
private:
char* _str;
};