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

拷贝:这是 一个简单的名词,就是将右手的东西放到左手,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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值