剑指offer-赋值运算符函数
Posted lyeeer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer-赋值运算符函数相关的知识,希望对你有一定的参考价值。
题目
如下为类型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等异常,但我们还没有修改原来实例的状态,因此实例的状态还是有效的,这样就保证了异常安全性。(不知道是不是对上面疑问的解释)
希望有大佬可以给解释一下~~
以上是关于剑指offer-赋值运算符函数的主要内容,如果未能解决你的问题,请参考以下文章