首页 > 技术文章 > 剑指offer-赋值运算符函数

lyeeer 2019-10-24 23:02 原文

题目

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

class CMyString
{
public:
    CMyString(char* p_Data = NULL);
    CMyString(const CMyString& str);
    ~CMyString(void);
private:
    char* m_pData;
};

 

思路

1.首先回顾一下C++中类的相关知识点:

//类的定义和基本形式
类其实是定义一个特殊的数据结构,定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作
class student
{
   public:   //公有成员在程序中类的外部是可访问的
      int gender; 
      int getid(void);
      student();   //构造函数
      ~student();    //析构函数
      student(const student &obj);  //拷贝构造函数
      student& operator=(const student& other);  //重载赋值操作符函数
      student* operator & ();    //取地址运算符
      const student* operator & () const;    //const修饰的取地址运算符

   private:    //私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员
      int id;

   protected:   //保护成员变量或函数与私有成员十分相似,只是保护成员在派生类(即子类)中是可访问的
      double weight;
};

//成员函数定义
student::student(void){
    cout<<"ok"<<endl;
}
student::~student(void){
    cout<<"sorry"<<endl;
}
student& student::operator=(const student& other){
    ...
return *this; } student
* student::operator&(){ return this; } const student* student::operator&() const{ return this; } int student::getid(void){ return id; } //类的六大默认成员函数 构造函数:构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void
       可用于为某些成员变量设置初值(这个时候构造函数要带参数) 析构函数:析构函数的名称与类的名称是完全相同的,只是在前面加了个
~作为前缀,它不会返回任何值,也不能带有任何参数。
析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。在每次删除所创建的对象时执行 拷贝构造函数:一种特殊的构造函数,其在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象 重载赋值操作符函数:对赋值操作符
"="进行重载,解决了对象赋值的情况下,析构函数中过程中多次释放同一块内存的问题 取地址运算符:返回当前对象的地址,对于成员函数来讲,this指针就是它的地址,需要返回指针。 const修饰的取地址运算符:(与上面这个函数,不写的话编译器也会默认生成,默认返回this指针)

 

2.赋值运算符函数又具体是啥呢?在这个函数里面具体要实现一些什么内容呢?

那么通过第1点对于类的介绍,我们知道这个函数主要就是用来重载"="这个运算符,以避免使用默认赋值运算符带来的一些麻烦。也就是自己设计一个赋值操作来代替默认的"="来实现赋值这个操作。

自己定义赋值运算符时呢,要注意在函数结束前必须返回实例自身的引用,结合上下文也就是*this。否则返回void会导致不能连续赋值或调用时不能进行隐式类型转换。

 

解法(剑指offer上的解法)

 

CMyString& CMyString::operator = (const CMyString &str)//实现的目标是把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);  //通过字符串复制操作,给m_pData赋值

    return *this;
}

 

借这里复习一下delete的用法:

//函数原型,这并不是重载new和delete的表达式
void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

解法二:之前的解法通过先释放之前的内存再开辟新空间,但是如果此时内存不足导致new char操作抛出异常,那么此时m_pData已经为空指针,再将一个空指针通过strcpy复制给另一个空值,容易导致程序崩溃,这样违背了异常安全性(Exception Safety)的原则。

因此可以采用先分配新空间,分配成功后再释放已有的内容。

书上给出了另一个解法,也就是不要先对原来的值进行操作,而是类似于我们初学C语言时常用到的交换,即交换a,b等价于temp=b;b=a;a=temp

即先创建一个临时实例,再交换临时实例和原来的实例。这里的临时实例是局部变量,运行到if外面(即离开该变量自身的作用域),就会调用临时变量的析构函数,把这一块的内存给释放掉。 

CMyString& CMyString::operator = (const CMyString &str){
    if(this != &str){
          CMyString strTemp(str);  //创建一个临时实例

          char* pTemp=strTemp.m_pData; //把传进来的参数的m_pData先提取出来,之后就直接使用这个值来进行赋值操作
          strTemp.m_pData=m_pData;   //把strTemp.m_pData和实例自身的m_pData进行交换
          m_pData=pTemp;
    }
return *this; }

为什么还需要strTemp.m_pData=m_pData;这一步呢?strTemp出了这个循环就被释放掉了呀,那还多一步保存m_pData原来的值有啥意义?也就是为什么要交换,直接赋值不就够了吗?

书上有一段话是:在新代码中,在CMyString的构造函数中用new分配内存。如果由于内存不足抛出诸如bad_alloc等异常,但我们还没有修改原来实例的状态,因此实例的状态还是有效的,这样就保证了异常安全性。(不知道是不是对上面疑问的解释)

希望有大佬可以给解释一下~~

 

推荐阅读