通过写时拷贝实现String类

今天看到剑指offer上的一道题:

如下为类型CMyString的声明,请你为该类型添加赋值运算符函数

class CMyString

{

public:

    CMyString(char * pData = NULL);

    CMyString(const CMyString & str);

    ~CMyString(void);

private:

    char * m_pData;

};

打算研究一下这道题,忽然又想起来以前有写过带有写时拷贝的String类的实现,所以准备先回顾一下以前的东西,再解决这道题。

1.浅拷贝:

在实现String类的时候,如果没有自己实现赋值函数或拷贝构造函数,使用默认赋值函数或拷贝构造函数会以浅拷贝实现赋值函数,这样就很有可能在释放的时候造成程序的崩溃。

因为是浅拷贝,所以两个对象的str指针同时指向了一块空间,当这两个对象生命周期结束时,都会调用析构函数,那么这一块空间就被析构了两次,所以会崩溃;

还存在一个问题,如果改变str1对象的指针指向的空间,由于两对象的指针指向同一块空间,所以str2对象的指针所指向的空间也一块变了,这不是我们想看到的。

2. 深拷贝:

为了解决浅拷贝的问题,可以采用深拷贝来实现赋值函数或拷贝构造函数。

深拷贝的时候,虽然解决了浅拷贝中存在的问题,但是又出现了新的问题:每一次在进行深拷贝的时候,都需要进行拷贝数据,代价过大。如果我们新拷贝出来的数据只需要进行读操作,而不需要进行写操作,这样也会浪费内存空间。

3.写时拷贝:

什么是写时拷贝,就是写的时候才拷贝,也就是说,你如果要改变,就需要拷贝一份,改变的是拷贝的这一份。写时拷贝是通过增加一个引用计数确定是否需要进行拷贝。这其中的改变包括 * 、[ ] 运算。 所以我们还需要重载这两个运算符,同时输出流运算符也是需要重载的。

加了引用计数后:

加了写时拷贝的String类的实现如下:

//String类的写时拷贝的实现
class Mstring
{
public:
    //构造函数的实现
    //加引用计数的string前四个字节存放四个字节的整型,作为引用计数
    Mstring(const char *str = NULL)//NULL = '\0'
    {
        if (NULL == str)
        {
            _str = new char[1 + 4];//多申请四个字节,存放引用计数
            getNum() = 1; //初始化引用计数为1
            *(getHead()) = 0;
            _len = 0;
            return;
        }
        _str = new char[strlen(str) + 1 + 4];
        getNum() = 1;
        _len = strlen(str);
        strcpy_s(getHead(), _len + 1, str); //
    }
    //拷贝构造函数的实现

    Mstring(const Mstring &src)
    {
        _str = src._str;
        _len = src._len;
        getNum()++; //引用计数 +1

    }

    //重载 = 运算符

    Mstring& operator=(const Mstring& src)
    {
        if (&src == this) //防止自赋值
        {
            return *this;
        }
        Mdelete();
        _str = src._str;
        _len = src._len;
        getNum()++;
        return*this;
    }

    //重载解引用运算符

    char &operator*()

    {

        if (getNum() == 1)
        {
            return *getHead();
        }

        char* tmp = new char[_len + 1 + 4];
        strcpy_s(tmp + 4, _len + 1, getHead());
        getNum()--;
        _str = tmp;
        getNum() = 1;

        return *getHead();
    }
    //重载 [] 运算符

    char& operator[](int sit)
    {
        if (getNum() == 1)
        {
            return getHead()[sit];
        }
        char*tmp = new char[_len + 1 + 4];
        strcpy_s(tmp + 4, _len + 1, getHead());
        getNum()--;
        _str = tmp;
        getNum() = 1;
        return getHead()[sit];

    }

    //重载 输出流运算符
    ostream& operator << (ostream& out)
    {
        out << getNum() << "  ";
        out << getHead() << endl;
        return out;
    }
    //析构函数  调用自己实现的Mdelete
    ~Mstring()
    {
        Mdelete();
    }
private:
    int& getNum()//获取当前的引用计数

    {
        return *((int *)_str);
    }

    char* getHead()//获取字符串的首元素
    {
        return _str + 4;
    }
    void Mdelete()//自己实现delete
    {
        if (--getNum() == 0)
        {
            delete _str;
        }
    }
    char*_str; //字符串
    int _len; //字符串长度
};

int main()

{

    Mstring str1 = "hello";

    str1.operator<<(cout);



    Mstring str2 = str1;

    str1.operator<<(cout);

    str2.operator<<(cout);


    Mstring str3;

    str3.operator<<(cout);

    str3 = str1;

    str1.operator<<(cout);

    str2.operator<<(cout);

    str3.operator<<(cout);

    cout << "==========================" << endl;



    //str3[2] = 'm';

    *str2 = 'm';

    str1.operator<<(cout);

    str2.operator<<(cout);

    str3.operator<<(cout);

    return 0;

}

实现了上面的代码,就能很容易的解决最开始的问题:

CMyString& operator =(const CMyString & str)
        {
            if (this == &str)
            {
                return *this;
            }

            //释放原内存区域,防止内存泄漏
            delete[]m_pData;
            m_pData = NULL;
            m_pData = new char[strlen(str.m_pData) + 1];
            strcpy(m_pData, str.m_pData);
            return *this;
    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值