浅拷贝、深拷贝(普、简)、写时拷贝

来源:互联网 发布:mac搜狗五笔输入法 编辑:程序博客网 时间:2024/06/05 08:11

拷贝:这是 一个简单的名词,就是将右手的东西放到左手,c中有一个函数,strcpy,c++中也有拷贝。

但是你想过拷贝会出现的问题吗?下面我们来探讨一下:

浅拷贝出现的问题:……
解决方案:深拷贝、写时拷贝

先来看一个例子:

//class String//{//public://  String(const char* pStr = "")//      :size(strlen(pStr) + 1)//  {//      _pStr = new char[size];//      strcpy(_pStr, pStr);//      _pStr[size] = '\0';//  }////  String(const String& s)//      :size(s.size)//  {//      if (this != &s)//      {//          _pStr = s._pStr;//      }//  }////  ~String()//  {//      if (NULL != _pStr)//      {//          delete[] _pStr;//          _pStr = NULL;//      }//  }//private://  char*_pStr;//  int size;//};

这里写图片描述

//1、c++浅拷贝测试函数void FunTest(){    String s1("hello world");    String s2 = s1;//赋值完成之后,s1和s2公用同一块地址空间,再调用析构函数时,就会将同一块空间释放两次,出错}int main(){    FunTest();    return 0;}

那么我们就要想一下了,怎么样解决这个问题呢?

那么我们想从出问题的地方入手吧。这个问题是两个对象共用了一块空间,而释放一个对象的时候,同时释放这块空间,另一个对象并不知道这块空间已经释放了,当其再去使用空间的时候就出现了问题,也就是内存泄漏的问题。

既然是两个对象公用一块内存空间,那么我们可不可以将另一个对象也给一个内存空间呢?这样释放一块空间就不会出现这个问题了。那么接下来我们就来讨论这个问题,也就是深拷贝。

class String{public:    String(char*pStr = "")//构造函数    {        if (NULL == pStr)//如果字符串中没有字符,那么申请一个空间,放入‘\0’        {            _pStr = new char[1];            *_pStr = '\0';        }        else        {            _pStr = new char[strlen(pStr) + 1];//如果有字符,申请空间(strlen不包括‘\0’)            strcpy(_pStr, pStr);        }    }    String(const String & s)        :_pStr(new char[strlen(s._pStr) + 1])//初始化列表中将拷贝构造函数中的变量也开辟空间,避免浅拷贝出现的问题    {        strcpy(_pStr, s._pStr);    }    ~String()    {        if (NULL == _pStr)        {            delete[] _pStr;//释放空间,注意对应new[],对应delete[]            _pStr = NULL;//将指针变量指向NULL,避免其成为野指针        }    }private:    char*_pStr;};
//2、C++深拷贝测试函数void FunTest(){    String s1="Hello World";    String s2 = s1;}int main(){    FunTest();    return 0;}

这里写图片描述

上面 我们给出的方式是深拷贝的一种写法,即——将要拷贝的对象也给一块空间,这样虽然浪费空间,但是有效的解决了空间泄露的问题。

不过这里的给s2空间是不是感觉很熟悉呢?对,就是构造函数的功能之一,那么,我们可不可以走个捷径呢?直接就调用一下构造函数,不在去写具体的实施了,因为在构造函数中我们已经写好了,只需调用即可,下面就来看一下这种方式的实现吧!

//简洁版的深拷贝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(String&s)//        :_pStr(NULL)    {        String temp(s._pStr);//        std::swap(temp._pStr, _pStr);    }    /*String& operator=(String&s)    {        std::swap(_pStr, s._pStr);        return *this;    }*/    String&operator=(String&s)    {        if (this != &s)        {            String s1(s._pStr);            std::swap(_pStr, s1._pStr);        }        return *this;    }    ~String()    {        if (NULL != _pStr)        {            delete[] _pStr;            _pStr = NULL;        }    }private:    char*_pStr;};

这就是简洁版的深拷贝,也是深拷贝的现代写法,技术都是一步步更新的,同样的代码也是越来越简化,我们要学会更新思想,同样的不能忘记他的由来,所以还是两种方法都记住吧。

刚提到了一个问题,就是深拷贝虽然解决了浅拷贝的问题,同样的也带了一个另外的不大不小的问题,那就是浪费空间。问题的大小取决于具体的环境等。不纠结这个问题了,还是像个办法解决这个问题。

怎样让浪费的空间回来?

那就是深拷贝的那一套不能用了,重新想一种方法来解决浅拷贝的问题,我们先来回想一下,他的问题是:两个对象的内存空间一样,导致其中一个对象释放资源的时候,同时释放内存空间,而另一个对象并不知道,所以,再调用这段内存空间的时候就会出现内存泄漏的问题。

刚才,我们从空间入手,将另一个对象也开辟了一段空间,那么如果对象很多的情况下,就会导致浪费空间,

哎,既然对象很多,那么我们可不可以用一个计数器来确定要不要释放内存空间呢?

来捋一下思路:首先有很多对象同时拥有一段内存,我们再开辟一个内存,专门存储对象的个数,这样,假如有10个对象,那么我们释放一个对象的资源的时候,同时将个数-1,再判断一下这个个数是不是0,如果是,那么就说明没有对象在使用这块内存空间了,就可以安然的释放了,如果不是,那么不释放。

好了,具体的细节都在代码里体现。

//写时拷贝class String{public:    String(const char*pStr)    {        if (NULL == pStr)        {            char*temp = new char[1+4];            _pStr = temp + 4;            GetRefer() = 1;            _pStr = '\0';        }        else        {            char*temp = new char[strlen(pStr) + 1+4];            _pStr = temp + 4;            strcpy(_pStr, pStr);            GetRefer() = 1;        }    }    String(const String&s)        :_pStr(s._pStr)    {        GetRefer()++;    }    ~String()    {        Release();    }    int& String::GetRefer();    void Release();private:    char*_pStr;};
//写时拷贝测试函数int& String::GetRefer(){    return *((int*)_pStr - 1);}void String::Release(){    if (--GetRefer() == 0)    {        _pStr -= 4;        delete[]_pStr;        _pStr = NULL;    }}void FunTest(){    String s1("hello world");    String s2 = s1;}int main(){    FunTest();    return 0;}

这里写图片描述
我们可以看到在上图从94到9c是存储主要内容的,即“hello world”,而在这段内存空间之前,还有一段内存,就是存储对象的个数,现在只有一个对象,因此显示的数字是1。当释放这个对象的资源的时候,这个数字变成0,就可以释放内存空间了。

这里写图片描述
现在我们在加一个对象,我们发现这两个 对象的地址是一样的,然后在看内存:

这里写图片描述
同一样的额内存的内容不变,但是,上面的一段空间的内容变为2,表示这个内存里有两个对象在使用,所以释放的时候就会避免浅拷贝出现的问题。

1 0