C++学习笔记19:运算符重载基础与赋值运算符重载

目录

一、为什么需要运算符重载?

二、什么是运算符重载?

[三、operator 关键字](#三、operator 关键字)

四、成员可以写成成员函数,也可以写成全局函数。

五、赋值运算符重载

[六、参数为什么用 const 引用?](#六、参数为什么用 const 引用?)

1.使用引用,避免拷贝

[2.使用 const,防止修改右操作数](#2.使用 const,防止修改右操作数)

[七、为什么返回 Date&?](#七、为什么返回 Date&?)

[八、为什么返回 *this?](#八、为什么返回 *this?)

九、自我赋值检查

十、拷贝构造和赋值运算符的区别

十一、运算符重载的基本规则

1.不能创造新的运算符

2.不能改变操作数个数

3.不能改变优先级和结合性

4.至少有一个操作数是自定义类型

5.不能重载的运算符

十二、小结


一、为什么需要运算符重载?

在C++中,内置类型可以直接使用运算符。

例如:

cpp 复制代码
int a = 10;
int b = 20;

a = b;
cout << (a == b) << endl;

对于 int、double、char 这些内置类型,编译器知道他们应该怎么赋值、怎么比较、怎么相加。

但是对于自定义类型,比如日期类:

cpp 复制代码
class Date {
    private:
        int _year;
        int _month;
        int _day;
};

如果直接对两个日期对象进行计算,编译器不知道该怎么处理。

比如:

cpp 复制代码
Date d1;
Date d2;

d1 == d2;

对于日期对象来说,判断相等应该比较年、月、日是否都相等。

但是这个规则需要我们告诉编译器。

这就需要用到运算符重载

运算符重载的作用是:

让自定义类型也能像内置类型一样使用运算符。


二、什么是运算符重载?

运算符重载就是给已有运算符赋予适应自定义类型的新含义。

例如:

cpp 复制代码
d1 == d2;

可以判断两个日期是否相等。

cpp 复制代码
d1 = d2;

可以表示把 d2 的年月日赋值给 d1。

这样写比普通函数调用更直观。

比如:

cpp 复制代码
d1 = d2;

就比下面这种写法自然:

cpp 复制代码
d1.Copy(d2);

三、operator 关键字

C++中使用 operator 关键字进行运算符重载。

基本格式:

cpp 复制代码
返回值类型 operator运算符(参数列表) {
    // 函数体
}

例如,重载赋值运算符:

cpp 复制代码
Date& operator=(const Date& d) {
    // 赋值逻辑
    return *this;
}

这里函数名是:

cpp 复制代码
operator=

表示重载 = 运算符。


四、成员可以写成成员函数,也可以写成全局函数。

当前阶段先重点掌握成员函数形式。

例如:

cpp 复制代码
class Date {
	public:
		Date& operator=(const Date& d) {
			_year = d._year;
			_monht = d._month;
			_day = d._day;
			
			return *this;
		}
	
	private:
		int _year;
		int _month;
		int _day;
}; 

当我们写:

cpp 复制代码
d1 = d2;

编译器会自动把它转换成类似:

cpp 复制代码
d1.operator=(d2);

也就是说:

cpp 复制代码
左操作数 d1 通过 this 指针传递
右操作数 d2 作为参数传递

所以成员函数形式中,双目运算符一般只写一个显示参数。


五、赋值运算符重载

赋值运算符重载用于处理两个已存在对象之间的赋值。

例如:

cpp 复制代码
Date d1(2024, 5, 1);
Date d2(2025, 6, 1);

d1 = d2;

这里 d1 和 d2 都已经存在。

执行:

cpp 复制代码
d2 = d1;

本质上把 d2 的内容赋值给 d1。

对应的赋值运算符重载可以这样写:

cpp 复制代码
Date& operator=(const Date& d) {
    _year = d._year;
    _month = d._month;
    _day = d._day;

    return *this;
}

完整示例:

cpp 复制代码
#include <iostream>
using namespace std;

class Date {
	public:
		Date(int year = 2024, int month = 1, int day = 1) {
			_year = year;
			_month = month;
			_day = day;
		} 
		
		Date& operator=(const Date& d) {
			_year = d._year;
			_month = d._month;
			_day = d._day;
			
			return *this;
		}
		
		void Print() {
	        cout << _year << "-" << _month << "-" << _day << endl;
	    }
	
	private:
		int _year;
		int _month;
		int _day;
}; 

int main() {
	Date d1(2024, 5, 1);
	Date d2(2025, 6, 1);
	
	d1 = d2;
	
	d1.Print();
	d2.Print();
	
	return 0;
}

运行结果:

cpp 复制代码
2025-6-1
2025-6-1

六、参数为什么用 const 引用?

赋值运算符一般写成:

cpp 复制代码
Date& operator=(const Date& d)

这里参数使用 const Date&,有两个原因:

1.使用引用,避免拷贝

如果写成传值:

cpp 复制代码
Date& operator=(Date d)

传参时会产生一次对象拷贝,效率较低。

使用引用可以避免不必要的拷贝。


2.使用 const,防止修改右操作数

赋值时,右边的对象只是被读取,不应该被修改。

例如:

cpp 复制代码
d1 = d2;

这里应该把 d2 的值赋给 d1,不因该修改 d2。

所以加上const更安全。


七、为什么返回 Date&?

赋值运算符的返回类型通常写成:

cpp 复制代码
Date&

原因是为了支持连续赋值。

比如:

cpp 复制代码
d1 = d2 = d3;

赋值运算符是从右往左结合的,所以实际执行顺序可以理解为:

cpp 复制代码
d1 = (d2 = d3);

如果 d2 = d3 执行完之后没有返回值,外层 d1 = ...,就无法继续进行。

所以 operator= 需要返回当前对象。


八、为什么返回 *this?

再成员函数中,this 指针指向的是当前对象。

对于:

cpp 复制代码
d1 = d2;

调用形式可以理解为:

cpp 复制代码
d1.operator=(d2);

此时:

cpp 复制代码
this 指向 d1

所以:

cpp 复制代码
*this

就是当前对象 d1 本身。

因此赋值运算符最后通常返回:

cpp 复制代码
return *this;

这样就能支持连续赋值:

cpp 复制代码
d1 = d2 = d3;

九、自我赋值检查

有时候可能会出现自己给自己赋值:

cpp 复制代码
d1 = d1;

对于日期类之中只有普通变量的类,即使不检查通常也不会出问题。

但对于含有动态资源的类,比如栈类,如果赋值时要先释放旧的空间,再申请新的空间,就必须注意自我复制的问题。

常见写法是:

cpp 复制代码
Date& operator=(const Date& d) {
	if (this != &d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	} 
			
	return *this;
}

这里:

cpp 复制代码
this != &d

表示判断当前对象和右操作数是否是同一个对象。

如果是同一个对象,就不需要再赋值。


十、拷贝构造和赋值运算符的区别

可以这样记:

cpp 复制代码
对象还没创建:拷贝构造
对象已经存在:赋值运算符

对比表:

写法 含义 调用
Date d2(d1); 用 d1 初始化新对象 d2 拷贝构造
Date d2 = d1; 用 d1 初始化新对象 d2 拷贝构造
d2 = d1; d2 已经存在,把 d1 赋给 d2 赋值运算符

十一、运算符重载的基本规则

运算符重载不是随便定义的,需要遵守一些规则。

1.不能创造新的运算符

C++中没有的运算符不能自己创造。

比如不能写:

cpp 复制代码
operator@

只能重载C++已经存在的运算符。


2.不能改变操作数个数

比如 + 是双目运算符,重载后仍然需要两个操作数。

不能把它变成三个操作数。


3.不能改变优先级和结合性

运算符重载不能改变原来的运算符优先级。

比如:

cpp 复制代码
a + b * c

仍然是先算 *,再算 +。

4.至少有一个操作数是自定义类型

不能给内置类型乱改规则。

例如不能把:

cpp 复制代码
1 + 2

重载结果为 100。

运算符重载主要是为了让自定义类型更好用。


5.不能重载的运算符

有些运算符不能重载,常见有:

cpp 复制代码
::      作用域限定符
sizeof  计算大小
?:      三目运算符
.       成员访问运算符
.*      成员指针访问运算符

这些运算符有特殊语义,不能被用户重载。


十二、小结

本篇主要学习了运算符重载基础和赋值运算符重载。

需要记住:

  1. 运算符重载可以让自定义类型使用已有运算符;
  2. 运算符重载通过 operator 关键字实现;
  3. 成员函数形式中,左操作数通过 this 指针传递;
  4. d1 = d2 会转换成 d1.operator(d2);
  5. 赋值运算符重载用于两个已存在的对象之间赋值;
  6. 参数一般使用 cosnt 类名&;
  7. 返回值一般使用 类名&;
  8. 返回 *this 是为了方便支持连续赋值;
  9. 拷贝构造和赋值运算符的区别在于对象是否已经存在;
  10. 运算符重载不能创建新运算符,也不能改变优先级和操作数个数。

赋值运算符重载十运算符重载中的重要内容。理解它之后,后面学习日期类中的比较运算符、加减运算符、自增自减运算符会更容易。

相关推荐
Jasmine_llq7 小时前
《B4261 [GESP202503 三级] 2025》
开发语言·c++·算法·条件判断算法·位运算恒等式推导·简单算术运算
小张成长计划..7 小时前
【C++】32:智能指针
c++
无限进步_8 小时前
C++异常机制:抛出、捕获与栈展开
开发语言·c++·安全
王老师青少年编程8 小时前
csp信奥赛C++高频考点专项训练之前缀和&差分 --【一维前缀和】:宝石串
c++·前缀和·csp·高频考点·信奥赛·宝石串
梓䈑8 小时前
【算法题攻略】模拟
c++·算法
vKd0Ff21L8 小时前
如何在Dev-C++中设置TDM-GCC为默认编译器第九十一篇
java·jvm·c++
cany10009 小时前
C++ -- 型号比对和constexpr
c++
楼田莉子9 小时前
C++17新特性:结构化绑定/inline变量/if相关的变化
c++·后端·学习
翎沣9 小时前
C++面向对象三大特性
开发语言·c++