[剑指-Offer] 1. 赋值运算符函数(编程语言、细节处理、代码优化)

1. 题目来源

《剑指-Offer》第二版,P25,面试题1:赋值运算符函数

2. 题目说明

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

class CMyString {
public:
	CMyString(char* pData = nullptr);
	CMyString(const CMyString& str);
	~CMyString(void);

private:
	char* m_pData;
};

3. 题目解析

3.1 C++六大默认成员函数

首先复习 C++ 的六大默认成员函数:构造、拷贝构造、析构、赋值操作符重载、取地址操作符重载、const取地址操作符重载
在这里插入图片描述

3.2 C++运算符重载及5点注意

再复习下运算符重载:
C++ 为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,
函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字 operator 后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如 operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型 +,不 能改变其含义
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
  • 操作符有一个默认的形参 this,限定为第一个形参
  • .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现

3.3 C++赋值运算符及5点注意

现在开始看看赋值运算符,赋值运算符主要有 5 点需要注意:

  1. 参数类型
  • 主要考虑是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加,上const关键字。
  1. 返回值类型
  • 是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即 *this)。只有返回一个引用,才可以允许连续赋例自身的引用(即*this)。只有返回一个引用,才可以允许连续赋赋值。假设有 3 个 CMyString 的对象: str1、 str2 和str3 ,在程序中语句 str1=str2=str3不能通过编译。
  1. 检测是否自己给自己赋值
  • 是否判断传入的参数和当前的实例(*this)是不是同一个实例。如果是同一个,则不进行赋值操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当 *this 和传入的参数是同-一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了
  1. 释放自己的内存
  • 是否释放实例自身已有的内存。如果忘记在分配新内存之前释放放自身已有的空间,程序将出现内存泄露。
  1. 避免浅拷贝问题
  • 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝,即默认的使用是浅拷贝,也就是说将该对象的内存原封不动地挪动到新对象的内存中,因此对于含有指针的类,我们往往需要自己实现 copying 的操作来完成深拷贝(除非你就需要以浅拷贝的方式完成该操作),否则很有可能造成有多个指针指向同一块空间,在析构时候同一块空间析构多次导致崩溃。

3.4 初级程序员写法

经过上面的复习和深思熟虑,可以写出下面的经典写法,在《剑指-Offer》描述中就是:适用于初级程序员…

CMyString& CMyString::operator=(const CmyString &str) {
	if (this == &str)
		return *this;
	
	delete []m_pData;
	m_pData = nullptr;

	m_pData = new char[strlen(str.m_pData) + 1];
	strcpy(m_pData, str.m_pData);

	return *this;
};

对于初级程序员 OK~,但还达不到高级程序员的标准

3.5 考虑异常安全性的解法,高级程序员必备

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

CMyString& CMyString::operator=(const CMyString &str) {
    if(this !=&str) {
        CMyString strTemp(str); // 先创建一个临时对象strTemp
        char* pTemp=strTemp.m_pData;
        strTemp.m_pData=m_pData; // 再把strTemp.m_pData和自身的m_pData进行交换。
        m_pData=pTemp;
        // 由于stremp是个局部对象,运行到if作用域外,就会自动调用strtemp的析构函数从而完成了内存的释放。
        // 由于strTemp.m_pData指向的内存就是m_pData的内存,就相当于自动调用析构函数释放实例的内存。
        // 同时也完成了相应的拷贝工作。
    }
    return *this;
}

在新的代码中,在CMyString的构造函数里用new分配内存。如果由于内存不足抛出诸如bad_ alloc等异常,我们还没有修改原来实例的状态,因此实例的状态还是有效的,这也就保证了异常安全性。

理解到此地,面试就通过了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值