浅拷贝、深拷贝与引用计数

浅拷贝:

浅拷贝:也称位拷贝,编译器只是直接将指针的值拷贝过来,结果多个对象共用同一块内存,当一个对象将这块内存释放掉之后,另一些对象不知道该块空间已经还给了系统,以为还有效,所以在对这段内存进行操作的时候,发生了访问违规。

当类中有指针对象时,对此对象进行拷贝构造、赋值运算符重载时,拷贝构造和赋值运算符重载只进行值拷贝,两个对象指向同一块内存,但是再调用析构函数时在对象销毁时该空间被释放了两次,因此会造成程序崩溃!

如下面这段代码:假如我们要实现一个string类如下面这段代码时

#include <iostream>
using namespace std;

class String
{
public:
    String(const char * pData = " ")//构造函数
        : _pData(new char[strlen(pData) + 1])
    {
        strcpy(_pData, pData);
    }
    ~String()//析构函数
    {
        if (NULL != _pData)
        {
            delete[] _pData;
            _pData = NULL;
        }
    }
private:
    char *_pData;
};
void Test()
{
    String s1;
    String s2("hello world");
    String s3(s2);
    String s4;
    s4 = s3;
}
int main()
{
    Test();
    return 0;
}

运行出现如下错误:

这里写图片描述

分析调试一下:不难发现,以上程序在调试时我们可以发现在每个对象的构造时都没有出现问题,而是在最后阶段调用构造函数时出现错误导致程序崩溃。。。

进一步调试程序,发现我们可以在用拷贝构造函数和赋值运算符重载时,由于没有定义此类函数,那么在调用时只能调用系统给的拷贝构造函数和赋值运算符重载,那么在调用此类函数时系统只是将指针成员所保存的地址原封不动的拷贝赋值过来,那么,用这样的方式去构造对象,很明显有一个重大缺陷:即有多个对象的指针成员都指向同一块地址空间,而在对象在自己的作用域将要结束时,调用自身析构函数时,本来只有一块内存空间,但是,多个对象均指向它,在第一次析构对象时,已经将内存空间释放,在析构第二个对象时,又要进行释放,简言之,就是对一块内存空间释放多次,将会引发错误造成系统崩溃,那么,为什么一块内存空间不能释放多次呢???

动态内存也就是堆区,在申请内存时,实际上系统是把一块标记为未使用的内存地址返回给你,然后把那个地址标记为已使用。当你释放的时候,实际上就是把那块内存标记为未使用。你要对一个已经标记为未使用的内存再标记成未使用,程序的逻辑已经不再清楚,程序不清楚这块内存到底有没有被释放,还有另一种可能,当第一次将内存释放后,这块内存随之就被其他程序所使用,然而,你却再次释放,很有可能已经影响了其他程序的行为,,,因此,这应被看作立即停止程序运行的一项致命错误,因为程序行为已经出现了异常。

类在这样创建时,使用浅拷贝存在这样的问题,那么,如何解决这个问题呢???

解决方法:

1.深拷贝

普通深拷贝的步骤:
对于拷贝构造函数:开辟新空间,在进行内容的拷贝使其各自有各自的空间以至于在析构的时候各自有各自空间不至于程序运行崩溃对于“=”运算符的重载:开辟新空间,释放原空间,拷贝内容。这样就不是进行简单的值拷贝,程序就不会因此崩溃。
(1.)普通版:用常规方法进行深拷贝,指针拷贝的同时将指针所指向的内存空间也拷贝过来
class String
{
public:
    String(const char* str = " ")
    {
        if ( NULL == str)//String a(NULL);这种情况
        {
            _str = new char[1];
            strcpy(_str, '\0');
        }
        else
        {
            _str = new char[strlen(str) + 1];
            strcpy(_str, str);
        }

    }
    String(const String&str)
    {
        this->_str = new char[strlen(str._str) + 1];
        strcpy(this->_str, str._str);
    }
    String& operator=(String &str)
    {
        if (this == &str)//自己给自己赋值情况,a = a;
        {
            return *this;
        }
        else
        {
            char* pTemp = new char[strlen(str._str) + 1];//开辟新空间
            strcpy(pTemp, str._str);
            delete[] this->_str;//释放当前对象
            this->_str = pTemp;//传递
        }

    }
    ~String()
    {
        if ( NULL != _str )
        {
            delete[] _str;
            _str = NULL;
        }
    }

private:
    char* _str;
};
(2.)简洁版:采用交换函数和构造函数进行将内存地址复制
class String
{
public:
    String(const char* str = " ")//构造函数
    {
        if ( NULL == str)//String a(NULL);这种情况
        {
            _str = new char[1];
            strcpy(_str, '\0');
        }
        else
        {
            _str = new char[strlen(str) + 1];
            strcpy(_str, str);
        }

    }
    String(const String&str)//拷贝构造函数
        :_str(NULL)
    {
        String temp(str._str);
        std::swap(this->_str,temp._str);
    }
    String& operator=(const String &str)//赋值运算符重载
    {
        if (this == &str)//自己给自己赋值情况,a = a;
        {
            return *this;
        }
        else
        {
            String temp(str._str);
            std::swap(this->_str,temp._str);
            temp._str = NULL;
        }

    }
    ~String()
    {
        if ( NULL != _str )
        {
            delete[] _str;
            _str = NULL;
        }
    }

private:
    char* _str;
};

2.引用计数

以上我们知道造成程序崩溃的原因是由于使用了系统提供的拷贝构造函数和赋值运算符重载,造成在最后执行析构函数时,一块内存空间被多次释放引起的,那么,我们有这样一个思路:将同一块内存空间的使用次数记录下来,到最后进行析构时,只有条件满足时才进行析构,也就是说如果多个对象指向同一块内存空间我们只析构一次。。。
class String
{
public:
    String(const char* str = "")
    {
        if (NULL == str)
        {
            _str = new char[1];
            _str = '\0';
        }
        else
        {
            _str = new char[strlen(str) + 1 + 4];
            _str += 4;
            GetRef(this->_str) = 1;
            strcpy(this->_str,str); 
        }
    }
    String(const String&str)//拷贝构造函数
    {
        this->_str = str._str;
        ++this->GetRef(this->_str);
    }
    ~String()
    {
        if (0 == (--GetRef(this->_str)))
        {
            _str -= 4;
            delete[] _str;
        }
    }
    String& operator=(const String& str)
    {
        if (this->_str == str._str)//自己和自己赋值
        {
            return *this;
        }
        else
        {
            if ((--(GetRef(this->_str)))== 0)//判断删除左边原来的内存
            {
                _str -= 4;//删除原来左边内存
                delete[]_str;
            }
            this->_str = str._str;
            ++GetRef(this->_str);
        }
    }

private:
    char* _str;
    int& GetRef(char *Str) //返回使用次数
    {
        return *(int *)(Str - 4);
    }

};

面试中的写法:

class My_String
{
public:
    My_String(const char* str = "")    //构造
        :_str(new char[strlen(str)+1])
    {
        strcpy(_str, str);
    }
    My_String(const My_String& s)    //拷贝构造
        :_str(new char[strlen(s._str)+1])
    {
        strcpy(_str, s._str);
    }
    My_String& operator=(const My_String& s)   //赋值运算符重载
    {
        if (this != &s)
        {
            My_String tmp(s);
            swap(this->_str,tmp._str);
        }
        return *this;
    }
    ~My_String()
    {
        delete[] _str;
    }
    const char* c_str()const
    {
        return this->_str;
    }

private:
    char* _str;
};
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值