浅拷贝:
1.什么是浅拷贝? 浅拷贝会出现什么问题?
所谓浅拷贝,指的是在对象复制时,只是对对象中的数据成员进行简单的复制,默认拷贝构造函数执行的也是浅拷贝。简单的说,浅拷贝就是值传递,将源空间里面的内容复制到目标空间中。
存在缺陷:多个指针可能共用管理一块内存空间,在释放时,导致对一块空间的多次释放,造成内存泄露。
深拷贝:
2. 什么是深拷贝?
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间。
深拷贝与浅拷贝:
3.浅拷贝与深拷贝的不同之处:
用深动形象的语言来说,如果把拷贝看作下面这幅图,那么,浅拷贝只能拷走美人鱼的头,水下部分它将无法操作,而深拷贝 不仅可以拷走头,还可以操作水下隐含的部分。
好了,说到这里,咱们可能对深浅拷贝或多或少的有了一些认识,那么,下面,我们将实现一个String 类,来更加具体权威的解释深浅构造函数。
4.完成String类-普通版(浅拷贝)
#include<iostream>
using namespace std;
class String
{
public :
String(const char*pStr="")//构造函数
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)//拷贝构造函数
:_pStr(s._pStr)
{
//s2已经释放,但s1不知道,会对空间再次释放
}
String& operator=(const String& s)//赋值运算符重载
{
if(this!=&s)
{
_pStr=s._pStr;//内存泄露
}
return *this;
}
~String ()//析构函数
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
};
void FunTest()
{
String s1("hello");
//String s2(s1);
String s3;
s3=s1;
}
int main ()
{
FunTest();
/*String s1("hello");
String s2(s1);
String s3(NULL);
String s4(s1);
s3=s4;
s3=s1;*/
return 0;
}
该例证明了浅拷贝会存在多个对象共用同一块空间,在调用析构函数销毁空间时,会出现对一块空间多次释放的情况,导致内存崩溃
5. 完成String类深拷贝—简洁版
#include<iostream>
using namespace std;
class String
{
public :
//构造函数************************************************
String(const char*pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
//拷贝构造函数********************************************
String(const String& s)
:_pStr(NULL)//选择最佳
{
//_pStr=new char[1];//第二选择
String strTemp(s._pStr );
swap(_pStr,strTemp._pStr);
}
//赋值 运算符重载******************************************
//方式一:
String& operator=(const String& s)
{
if(this!=&s)
{
String strTemp(s._pStr);
//String strTemp(s) ;
swap(_pStr,strTemp._pStr) ;
}
return *this;
}
//方式二:
//String& operator=(const String& s)
// {
// String strTemp(s) ;
// swap(_pStr,strTemp._pStr) ;
//return *this;
//}
//方式三:
//String& operator=( String s)
//{
// swap(_pStr,s._pStr );
// return *this;
//}
//三种赋值运算符重载解决方案, 第一种方式为最优方案
//析构函数*****************************************
~String ()
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
int _count;
};
void FunTest()
{
String s1("Hello");
String s2(s1);
s2=s1;
}
int main ()
{
FunTest();
/*String s1("hello");
String s2(s1);
String s3(NULL);
String s4(s1);
s3=s4;
s3=s1;*/
return 0;
}
6.引用计数:
A.什么是引用计数?
在开辟空间时,为了记录该空间有多少对象在共用它,也就是说有多少指针指向它,采用再开辟一个空间的方式,记录该空间被指向的次数,这种方式被称为引用计数。
B.用引用计数实现String时,引用计数可以普通的成员变量?为什么?
解析:引用计数不可以为普通的成员变量,因为一旦出了作用域,该空间被销毁,达不到想要的效果
C.用引用计数实现String时,引用计数可以类的静态成员变量吗? 为什么?
解析:类的静态成员变量,但在需要另外开辟空间时,采用这种方式就
#include<iostream>
using namespace std;
class String
{
public :
//构造函数
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
_count=1;
}
String (const String& s)
:_pStr(s._pStr)
{
_count++;
}
~String ()
{
if(_pStr&&(0==--*_count))
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
static int _count;
};
int String:: _count=0;
void FunTest()
{
String s1("hello");
String s2(s1);
String s3;
}
int main()
{
FunTest();
return 0;
}
为了在释放的时候,防止忘记释放引用计数所开辟的空间,所以尽量采用new [ ]
的方式来开辟空间,与delete[ ]搭配使用。
7.完成引用计数版本的String类—该引用计数也属于浅拷贝
//************引用计数*******************************
#include<iostream>
using namespace std;
class String
{
public :
//构造函数
String(const char* pStr="")
:_pCount(new int (1))
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String (const String& s)
:_pStr(s._pStr)
,_pCount(s._pCount)
{
++(*_pCount);
}
String& operator=(const String& s)
{
if(_pStr!=s._pStr)//被赋值的对象与当前对象不是同一块空间
{
if(_pStr&&0==--*_pCount)
{
delete [] _pStr;
delete _pCount;
}
_pStr =s._pStr;
_pCount=s._pCount;
++_pCount;
}
return *this;
}
~String ()
{
if(_pStr&&(0==--*_pCount))
{
delete[] _pStr;
_pStr=NULL;
delete _pCount;
_pCount=NULL;
}
}
private:
char* _pStr;
int* _pCount;
};
void FunTest()
{
String s1("hello");
String s2(s1);
String s3;
}
int main()
{
FunTest();
return 0;
}
7. 完成COW(写时拷贝版的String)
(COW不是奶牛)
//****写时拷贝:如果要朝当前对象写东西,最好使用这种方式*********
//单线程不会有问题
#include<iostream>
using namespace std;
class String
{
public :
//构造函数
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1+4];
_pStr+=4;
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1+4];
_pStr+=4;
strcpy(_pStr,pStr);
}
GetRaf()=1;
}
String (const String& s)
:_pStr(s._pStr)
{
GetRaf()++;
}
String& operator=(const String& s)
{
if(_pStr!=s._pStr)//被赋值的对象与当前对象不是同一块空间
{
Release();
_pStr =s._pStr;
++GetRaf();
}
return *this;
}
~String ()
{
Release();
}
char& operator [] (size_t index)
{
if(GetRaf()>1)//如果当前空间不止存放的一个对象
{
char* pTemp=new char [strlen(_pStr)+1+4];
*(int*)pTemp=1;
pTemp+=4;
strcpy(pTemp,_pStr);
--GetRaf();//一定是在改变指针指向之前
_pStr=pTemp;
}
return _pStr[index];
}
const char& operator [](size_t index)const
{
return _pStr[index];
}
private:
int& GetRaf()
{
return *((int*)_pStr-1);
}
void Release()
{
if(_pStr&&(0==--GetRaf()))
{
_pStr-=4;
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
};
void FunTest()
{
String s1("hello");
String s2(s1);
s2[0]='w';
}
int main()
{
FunTest();
return 0;
}
下面,在这里提出两点关于string类来说非常重要点:
一.熟悉库中string类的每个接口,查文档,熟悉库中的string类。
二.调研vs和linux系统下string类的结构,他们是否采用用深拷贝原理实现。