剑指Offer之赋值运算符函数解析

来源:互联网 发布:java图形输出的编程题 编辑:程序博客网 时间:2024/06/07 15:25

1.赋值运算符函数

AssignmentOperator

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

class CMyString{public:    CMyString(char *p = nullptr);    CMyString(const CMyString& str);    ~CMyString();private:    char *m_pData;}

首先对于C++一个类中的6个默认的函数分别是构造,拷贝构造,赋值运算符重载,析构,以及取地址操作符重载以及const修饰的取地址操作符重载
对于拷贝构造以及赋值运算符重载,默认的使用是浅拷贝,也就是说将该对象的内存原封不动地挪动到新对象的内存中,因此对于含有指针的类,我们往往需要自己实现copying的操作来完成深拷贝(除非你就需要以浅拷贝的方式完成该操作),否则很有可能造成有多个指针指向同一块空间,在析构时候同一块空间析构多次导致崩溃。
常用的解决办法是重新分配内存并拷贝或者实现引用计数版本,对于此题,我们选择前者。

我给出的代码如下:

CMyString& CMyString::operator=(const CMyString& other){    if (this != &other)    {        delete[] m_pData;        m_pData = nullptr;        m_pData = new char[strlen(other.m_pData) + 1];        strcpy(m_pData, other.m_pData);    }    return *this;}

因为之前模拟写过String类的简单实现,所以我想了一下,就写了上面的代码。在这里,几个值得注意的点:
1. 赋值运算符的重载函数的声明,需要返回类型的引用,也就是CMyString& ,这里是为了考虑到形如 a = b = c这样的连续赋值操作,因此需要在函数结束前加上return *this;
2. 函数传参需要引用,这样避免了调用一次拷贝构造函数提高效率,同时为了不改变传入实例,需要加上const
3. 重新分配内存时候,必须要释放之前自己已有的空间,否则会导致内存泄漏
4. 要考虑到自己赋值给自己,即this == &other时候,其实不需要执行任何操作,同时更为重要的是:对于我自己写的代码如果不加上if (this != &other)代码就是错的,因为我是先释放内存再根据other需要的空间开辟一块新空间,对于自己赋值给自己的情况,由于已经自己指向的那块空间已经释放了,所以再也找不到需要赋值的内容了。

写完对照书发现和书上大差不离。。嗯,适合初级程序员,不过想了想我这现在水平估计还不如初级呢(一脸懵逼,哈哈哈,下面看看书上是怎么给出了考虑异常安全性的的解法

对于我之前写的代码,我是先释放之前的内存再开辟新空间,如果此时内存不足导致new时抛出异常,那么此时m_pData已经为空指针,容易导致程序崩溃,这样违背了异常安全性(Exception Safety)的原则,因此可以采用先分配新空间,分配成功后再释放原来的内容,当然书上给出了一个更好的方法,先创建一个临时实例,再交换临时实例和原来的实例。代码如下:

CMyString& CMyString::operator=(const CMyString& other){    if (this != &other)    {        //先分配空间        char *pTemp = new char[strlen(other.m_pData) + 1];        strcpy(pTemp, other.m_pData);        //分配成功后再释放原来的内存        delete[] m_pData;        m_pData = nullptr;        m_pData = pTemp;    }    return *this;}

更好的办法:

CMyString& CMyString::operator=(const CMyString& other){    if (this != &other)    {        CMyString temp(other);        swap(temp.m_pData, m_pData);            }    return *this;}

在这个函数中,我们创建了一个临时对象temp,然后交换了temp.m_pData和m_pData指向的空间,此时temp指向的空间即为m_pData之前的空间,由于temp是个局部对象,运行到if作用域外,就用自动调用temp的析构函数从而完成了内存的释放,同时也完成了相应的拷贝工作。代码也简洁的多,确实是个更好的办法。当然我觉得,代码可以在此基础上更简便一点:

CMyString& CMyString::operator=(CMyString other){    swap(other.m_pData, m_pData);           return *this;}

这样写需要改变参数的传参,传值而非引用,由于传值时这个参数仅仅只是调用者的一份拷贝(不用考虑自己给自己的情况),相当于上面创建临时对象,此时交换析构原理和上面基本类似不再赘述。

0 0
原创粉丝点击