[基础知识]4.赋值运算符重载函数

下面是类CMyString的声明,请为该类添加一个赋值运算符函数?

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

private:
    char *m_pdata;
};

答案:

class CMyString{
public:
    CMyString(char* pData = nullptr);
    CMyString(const CMyString& str);
    ~CMyString(void);
    // 加上const可以避免对用来进行赋值的“原版”做任何修改。
    // 形参用引用&可以避免在函数调用时对实参进行拷贝,从而提升效率。
    // 返回值是被赋值者的引用(即*this)既可以避免在函数返回时进行拷贝,
    // 也可以更高效地实现连续赋值操作。(“传值调用”也能低效地实现连续赋值操作)
    CMyString &operator= (const CMyString &s){
		if(this == &s) return *this; // 防止s=s的赋值
		// 不是*this==s,这样是判断两实例是否相等,而不是是否为同一个实例
		
		delete []m_pdata; // 释放掉原区域
		// 不是delete m_pdata,这样只调用了第一个元素的析构,而不是所有元素
		m_pdata = nullptr; // 防止野指针(内存泄漏)
		// delete一个指针之后,只是回收指针指向位置的空间,
		// 而指针本身的值不变(也就是说还是指向那个地址的)。
		// 另外,delete nullptr什么也不会发生。
		
		m_pdata = new char[strlen(s.m_pdata)+1]; // 分配新区域
		strcpy(m_pdata, s.m_pdata); // 字符串复制
		
		return *this;
	}

private:
    char *m_pdata;
};

分析:

  1. 将返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。只有返回一个引用,才可以允许连续的赋值。否则如果该函数的返回值是void,应用该赋值运算符不能做连续的赋值。假设有3个连续的CMyString对象:str1=str2=str3将不能通过编译。
  2. 如果不返回左值的引用,将会生成临时的无名对象,同时会调用拷贝构造函数和析构函数处理这个临时对象,从而降低效率。
  3. 把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态的,因此应该为传入的引用参数加上const关键字。
  4. 释放实例自身已有的内存。如果我们忘记在分配新内存之前释放自身已有的空间,则程序将出现内存泄漏。
  5. 判断传入的参数与当前的实例(*this)是不是同一个实例。如果是同一个,则不进行赋值操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

相关知识点:

  1. nullptr是C++11标准引入的关键字,它代表空指针,且不能被转换成数字。在C++中,NULL为整数0,因此,当一个指针的值为0时,即认为该指针为空指针。nullptr解决了在函数重载时,使用NULL作为函数参数会被当做整数0而不是空指针的问题。
  2. 采用默认赋值函数实现的数据成员逐域赋值的方法是一种浅层复制方法。通常,默认赋值函数是能够胜任工作的。但是,对于类似指针悬挂的问题来说,还需要用户根据实际自己对赋值运算符进行重载(进行深层复制)。
  3. 浅拷贝:源对象的指针和拷贝对象的指针都指向同一个空间;
    深拷贝:源对象的指针指向一个空间,拷贝对象的指针指向另一个新的空间(两个空间的数据成员相同)。
  4. 类的赋值运算符“=”只能重载为成员函数,而不能把它重载为友元函数。
  5. 定义成员运算符重载函数的语法形式:
    5.1 在类的内部定义成员运算符重载函数:
函数类型 operator运算符 (形参表){
	函数体
}

5.2 在类中声明成员运算符重载函数的原型,在类外定义:

class X{
	// ...
	函数类型 operator运算符 (形参表);
	// ...
}
函数类型 X::operator运算符 (形参表){
	函数体
}
  1. C++为成员函数提供了一个名字为this的指针,这个指针称为自引用指针。每当创建一个对象时,系统就把this指针初始化为指向该对象,即this指针的值是当前调用成员函数的对象的起始地址。
  2. 不同对象的存储单元中存放的数据值通常是不相同的,而不同对象的函数代码是相同的,不论调用哪一个对象的成员函数,其实调用的都是相同内容的代码。C++的编译系统只用了一段空间来存放这个共同的函数代码段,在调用各对象的成员函数时,都去调用这个公用的函数代码。因此,每个对象的存储空间都只是该对象数据成员所占用的存储空间,而不包括成员函数代码所占用的空间,函数代码是存储在对象空间之外的。成员函数通过作为参数隐式传递的this指针来辨别当前调用自己的是哪个对象。
  3. ● <string.h>是C版本的头文件,包含比如strcpy、strcat之类的字符串处理函数。
    ● <cstring>在C++标准化(1998年)过程中,为了兼容以前,标准化组织将所有这些文件都进行了新的定义,加入到了标准库中,加入后的文件名就新增了一个"c"前缀并且去掉了.h的后缀名,所以string.h头文件成了cstring头文件。但是其实现却是相同的或是兼容以前的,这就是<cstring>的来源,不要觉得又多了一个东西。相当于标准库组织给它盖了个章,说“你也是我的标准程序库的一份子了”。
    ● <string>是C++标准定义的头文件,它定义了一个string的字符串类,里面包含了string类的各种操作,如s.size(), s.erase(), s.insert()等。注意,string中并不包括string.h可以的所有操作,比如strcpy等。
    ● 没有<cstring.h>这样的头文件。
  • 注意:Code::Blocks使用的minGW沿用linux习惯,strlen声明放在string.h中,仅仅#include 只是引入了std::string,还需要#include <string.h>。

参考文章
C++学习笔记之NULL vs nullptr
一文说尽C++赋值运算符重载函数(operator=)
为类重载赋值运算符返回引用值是为了连续赋值?
<string> 与<string.h>、<cstring>的区别
strlen()没法编译通过
delete [] m_data;与delete m_data;的问题?
C++在delete指针之后是否要置为null

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值