C++深拷贝与浅拷贝(实现String类)

来源:互联网 发布:安川伺服软件下载 编辑:程序博客网 时间:2024/06/06 18:56

浅拷贝:

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类的结构,他们是否采用用深拷贝原理实现。

原创粉丝点击