目录
[1.1 基本概念](#1.1 基本概念)
[1.2 核心思想](#1.2 核心思想)
[2.1 成员函数形式](#2.1 成员函数形式)
[2.2 全局函数形式](#2.2 全局函数形式)
[2.3 调用方式](#2.3 调用方式)
[3.1 赋值运算符重载](#3.1 赋值运算符重载)
[3.2 算术运算符重载](#3.2 算术运算符重载)
[3.3 关系运算符重载](#3.3 关系运算符重载)
[3.4 递增递减运算符重载](#3.4 递增递减运算符重载)
[3.5 流运算符重载](#3.5 流运算符重载)
[3.6 下标运算符重载](#3.6 下标运算符重载)
[4.1 取地址运算符重载](#4.1 取地址运算符重载)
[4.2 不能重载的运算符](#4.2 不能重载的运算符)
[4.3 类型转换运算符(补充)](#4.3 类型转换运算符(补充))
[5.1 头文件(Date.h)](#5.1 头文件(Date.h))
[5.2 实现技巧与最佳实践](#5.2 实现技巧与最佳实践)
[6.1 默认赋值运算符](#6.1 默认赋值运算符)
[6.2 何时需要自定义](#6.2 何时需要自定义)
[7.1 选择成员函数还是全局函数?](#7.1 选择成员函数还是全局函数?)
[7.2 返回值选择](#7.2 返回值选择)
[7.3 效率考虑](#7.3 效率考虑)
[7.4 一致性原则](#7.4 一致性原则)
一、什么是运算符重载?
1.1 基本概念
运算符重载是C++中允许我们为自定义类型(类)重新定义运算符行为的强大特性。通过运算符重载,我们可以让自定义类型的对象像内置类型一样使用运算符,使代码更加直观和易读。
1.2 核心思想
-
目的:增强代码的可读性和简洁性
-
本质:特殊的成员函数或全局函数
-
限制:
-
不能创建新的运算符
-
不能改变运算符的优先级和结合性
-
至少有一个操作数是自定义类型
-
二、运算符重载的基本语法
2.1 成员函数形式
cpp
class Date {
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {}
// 成员函数形式的运算符重载
bool operator==(const Date& d) const {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
2.2 全局函数形式
cpp
class Date {
public:
// 声明为友元以便访问私有成员
friend bool operator==(const Date& d1, const Date& d2);
private:
int _year;
int _month;
int _day;
};
// 全局函数形式的运算符重载
bool operator==(const Date& d1, const Date& d2) {
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
2.3 调用方式
cpp
int main() {
Date d1(2024, 7, 5);
Date d2(2024, 7, 6);
// 隐式调用
bool result1 = d1 == d2;
// 显式调用
bool result2 = d1.operator==(d2); // 成员函数形式
bool result3 = operator==(d1, d2); // 全局函数形式
return 0;
}
三、常用运算符重载详解
3.1 赋值运算符重载
基本特点
-
必须重载为成员函数
-
返回当前对象的引用,支持连续赋值
-
需要处理自赋值情况
cpp
class Date {
public:
// 赋值运算符重载
Date& operator=(const Date& d) {
// 检查自赋值
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; // 支持连续赋值
}
private:
int _year;
int _month;
int _day;
};
注意事项
-
赋值运算符重载与拷贝构造函数的区别:
-
赋值运算符:两个已存在对象之间的赋值
-
拷贝构造:用已存在对象创建新对象
-
cpp
int main() {
Date d1(2024, 7, 5);
Date d2 = d1; // 拷贝构造
Date d3;
d3 = d1; // 赋值运算符重载
return 0;
}
3.2 算术运算符重载
日期类示例
cpp
class Date {
public:
// += 运算符重载
Date& operator+=(int day) {
if (day < 0) {
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13) {
++_year;
_month = 1;
}
}
return *this;
}
// + 运算符重载(基于+=实现)
Date operator+(int day) const {
Date tmp = *this;
tmp += day;
return tmp;
}
// 日期相减(返回天数差)
int operator-(const Date& d) const {
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d) {
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max) {
++min;
++n;
}
return n * flag;
}
};
3.3 关系运算符重载
完整实现
cpp
class Date {
public:
// 小于运算符
bool operator<(const Date& d) const {
if (_year < d._year) return true;
if (_year == d._year) {
if (_month < d._month) return true;
if (_month == d._month) {
return _day < d._day;
}
}
return false;
}
// 基于<实现其他关系运算符
bool operator<=(const Date& d) const {
return *this < d || *this == d;
}
bool operator>(const Date& d) const {
return !(*this <= d);
}
bool operator>=(const Date& d) const {
return !(*this < d);
}
bool operator==(const Date& d) const {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator!=(const Date& d) const {
return !(*this == d);
}
};
3.4 递增递减运算符重载
前后置区分
cpp
class Date {
public:
// 前置++
Date& operator++() {
*this += 1;
return *this;
}
// 后置++(int参数仅用于区分,不使用)
Date operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
// 前置--
Date& operator--() {
*this -= 1;
return *this;
}
// 后置--
Date operator--(int) {
Date tmp = *this;
*this -= 1;
return tmp;
}
};
3.5 流运算符重载
输入输出流
cpp
class Date {
public:
// 声明友元函数
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
// 输出流重载
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
// 输入流重载
istream& operator>>(istream& in, Date& d) {
cout << "请依次输入年月日:>";
in >> d._year >> d._month >> d._day;
// 添加验证逻辑
if (!d.CheckDate()) {
cout << "日期非法" << endl;
}
return in;
}
3.6 下标运算符重载
数组类示例
cpp
class MyArray {
public:
MyArray(int size) : _size(size) {
_data = new int[size];
}
~MyArray() {
delete[] _data;
}
// 返回引用以支持 arr[i] = value
int& operator[](int index) {
if (index < 0 || index >= _size) {
throw out_of_range("Index out of range");
}
return _data[index];
}
// const版本,用于const对象
const int& operator[](int index) const {
if (index < 0 || index >= _size) {
throw out_of_range("Index out of range");
}
return _data[index];
}
private:
int* _data;
int _size;
};
四、特殊运算符和注意事项
4.1 取地址运算符重载
cpp
class Date {
public:
// 普通取地址运算符
Date* operator&() {
return this; // 通常返回this
// return nullptr; // 特殊场景:不想让别人获取地址
}
// const版本
const Date* operator&() const {
return this;
}
};
4.2 不能重载的运算符
以下运算符不能重载:
-
.(成员访问运算符) -
.*(成员指针访问运算符) -
::(作用域解析运算符) -
?:(三目条件运算符) -
sizeof(大小运算符)
4.3 类型转换运算符(补充)
cpp
class Rational {
public:
Rational(int num = 0, int den = 1)
: numerator(num), denominator(den) {}
// 转换为double类型
operator double() const {
return static_cast<double>(numerator) / denominator;
}
private:
int numerator;
int denominator;
};
int main() {
Rational r(3, 4);
double d = r; // 调用operator double()
return 0;
}
五、实战:完整的日期类实现
5.1 头文件(Date.h)
cpp
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date {
// 友元声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1);
// 工具函数
int GetMonthDay(int year, int month) const;
bool CheckDate() const;
// 关系运算符
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;
// 算术运算符
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
// 日期相减
int operator-(const Date& d) const;
// 递增递减
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
// 输出
void Print() const;
private:
int _year;
int _month;
int _day;
};
5.2 实现技巧与最佳实践
-
基于已实现的运算符实现其他运算符
cpp
// 基于<和==实现<= bool Date::operator<=(const Date& d) const { return *this < d || *this == d; } -
复用代码减少重复
cpp
// 基于+=实现+ Date Date::operator+(int day) const { Date tmp = *this; tmp += day; return tmp; } -
处理边界情况
cpp
// 处理负数天数 Date& Date::operator+=(int day) { if (day < 0) { return *this -= -day; } // ... 正数处理逻辑 } -
const成员函数
-
不修改成员变量的函数应声明为const
-
允许const对象调用
-
六、编译器生成的默认运算符
6.1 默认赋值运算符
如果类没有显式定义赋值运算符,编译器会自动生成一个。自动生成的赋值运算符对内置类型执行逐成员赋值(浅拷贝),对自定义类型调用其赋值运算符。
6.2 何时需要自定义
以下情况需要自定义赋值运算符:
-
类管理动态内存(如Stack)
-
需要进行深拷贝
-
有引用或const成员(通常需要初始化列表)
cpp
class Stack {
public:
// 需要自定义赋值运算符
Stack& operator=(const Stack& st) {
if (this != &st) {
free(_a);
_a = (int*)malloc(sizeof(int) * st._capacity);
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
return *this;
}
private:
int* _a;
size_t _capacity;
size_t _top;
};
七、总结与建议
7.1 选择成员函数还是全局函数?
-
必须为成员函数 :
=、()、[]、-> -
建议为成员函数 :
+=、-=、前缀++、-- -
建议为全局函数 :
+、-、<<、>> -
对称运算符 (如
==、+)通常应为全局函数
7.2 返回值选择
-
修改自身的运算符(如
+=)返回引用 -
创建新对象的运算符(如
+)返回值 -
关系运算符返回bool
7.3 效率考虑
-
避免不必要的拷贝
-
使用引用参数传递大对象
-
利用返回值优化(RVO)
7.4 一致性原则
-
相关运算符应一起重载(如
+和+=) -
保持运算符的常规语义
-
提供完整的运算符集合
运算符重载是C++面向对象编程的重要特性,合理使用可以大幅提升代码的简洁性和可读性。掌握运算符重载的原则和技巧,能够帮助你设计出更加优雅和高效的C++类。