5.1 运算符重载
基本概念
1.运算符重载允许用户为类类型的对象赋予运算符新的含义,使自定义类型也能像内置类型一样使用 +、-、== 等运算符。
2.使用运算符时,本质上是在调用对应的重载函数。若没有对应的重载函数,则编译报错。
3.由关键字 operator 和指定的运算符共同构成,与其他函数一样,包含返回值类型、参数列表和函数体。
cpp
// 以 Date 类为例,重载 == 运算符
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;
};
参数与调用规则
1.重载函数的参数个数与运算符的操作数个数一致,一元运算符一个参数,二元运算符两个参数。
2.当运算符重载为成员函数时,左侧对象通过隐式的 this 指针传递,因此参数列表比操作数少一个。例如二元运算符重载为成员函数时,只需显式写出一个参数。
特性
重载后的运算符,其优先级和结合性与对应的内置类型运算符保持一致,不会因为重载而改变。
重要限制与注意事项
1.不能通过连接语法中没有的符号来创建新的操作符,例如 operator@ 是非法的。
2..*、::、sizeof、?:、. 这五个运算符不能被重载,是选择题的常考点。
3.重载运算符必须至少有一个参数是类类型,不能改变内置类型对象的含义。例如 int operator+(int x, int y) 是非法的。
4.重载运算符应具有实际意义。例如 Date 类重载 -(计算日期差)是有意义的,而重载 + 则没有明确语义。
5.重载后置 ++ 时,需要额外增加一个无用的 int 形参,用于与前置 ++ 区分。
6.必须重载为全局函数,且第一个形参为 ostream/istream 对象,第二个形参为自定义类型对象。(要是重载为成员函数,第一个参数默认传给 this 指针,所以就会变成 对象在前,cin/cout 在后,出现倒反天罡的形式,所以说只能重载为全局函数,不需要 this 指针,直接传参,将第一个形参来接收 cin/cout。
cpp
// 前置 ++ 与后置 ++ 的重载
class Date {
public:
// 前置 ++:返回引用,减少拷贝
Date& operator++() {
_day += 1;
return *this;
}
// 后置 ++:增加一个无用的 int 形参用于区分
// 这里的int只是用于区分,并无其他用处,传不传参都可
Date operator++(int) {
Date tmp(*this); //使用拷贝构造,等价于 Date tmp = *this;
_day += 1;
return tmp;
}
};
// << 与 >> 必须重载为全局函数
// 这里要求将 istream/ostream 作为第一个参数是因为 输入输出是 cin/cout在前,直接传参
std::ostream& operator<<(std::ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
5.2 赋值运算符重载
基本概念
1.赋值运算符重载是一个默认成员函数,用于完成两个已存在对象之间的拷贝赋值。
2.需要严格区分拷贝构造与赋值运算符重载。拷贝构造用于对象初始化(创建新对象),而赋值运算符重载用于已存在对象之间的赋值。
cpp
Date d1(2024, 1, 1);
Date d2(d1); // 拷贝构造:d2 是正在初始化的新对象
Date d3;
d3 = d1; // 赋值运算符重载:d3 已存在,进行赋值
核心特点
1.赋值运算符重载规定必须重载为成员函数,不能写成全局函数。(适用赋值重载时,默认会将第一个参数传给 this 指针,类外不支持,所以将赋值重载写在类外面会报错)。有依赖 this 指针实现的函数就实现为成员函数,反之实现为全局函数。
2.建议写成 const 当前类类型引用(const T&),避免传值传参产生拷贝。
3.有返回值,且建议写成当前类类型引用(T&),目的是支持连续赋值,例如 a = b = c;。
4.若未显式实现,编译器会自动生成默认赋值运算符重载。默认行为是逐字节拷贝(浅拷贝),对自定义类型成员会调用其自身的赋值重载。
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;
};
实现场景与技巧
1.若类成员全是内置类型且无资源管理(如 Date 类),编译器生成的默认赋值即可满足需求。
2.若类涉及资源管理(如 Stack 类中有指针指向堆内存),必须实现深拷贝,对资源也进行拷贝,否则会出现两个对象指向同一块内存的问题。
3.若类内部成员是自定义类型(如 MyQueue 中有 Stack 成员),编译器生成的默认赋值会调用成员变量的赋值重载,无需额外实现。
4.如果一个类显式实现了析构函数(说明释放了资源),那么它也需要显式实现赋值运算符重载,通常还需要显式实现拷贝构造。
cpp
// 涉及资源管理时,必须实现深拷贝
class Stack {
public:
Stack(int capacity = 4)
: _a(new int[capacity])
, _top(0)
, _capacity(capacity)
{}
~Stack() {
delete[] _a;
_a = nullptr;
}
Stack(const Stack& st)
: _a(new int[st._capacity])
, _top(st._top)
, _capacity(st._capacity) {
memcpy(_a, st._a, sizeof(int) * _top);
}
Stack& operator=(const Stack& st) {
if (this != &st) {
int* tmp = new int[st._capacity]; // 先申请新空间,保证异常安全
memcpy(tmp, st._a, sizeof(int) * st._top);
delete[] _a; // 释放旧空间
_a = tmp;
_top = st._top;
_capacity = st._capacity;
}
return *this;
}
private:
int* _a;
int _top;
int _capacity;
};
小结
| 对比项 | 拷贝构造 | 赋值运算符重载 |
|---|---|---|
| 作用时机 | 对象初始化时 | 两个已存在对象之间赋值 |
| 是否可重载为全局 | 可以 | 必须是成员函数 |
| 返回值建议 | 无特殊要求 | 返回 T&,支持连续赋值 |
| 默认行为 | 浅拷贝 | 浅拷贝 |
| 资源管理类 | 需显式实现深拷贝 | 需显式实现深拷贝 |
5.3日期类实现
cpp
#pragma once
#include<iostream>
#include<assert.h>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
void Print() const;
//频繁调用,所以在类里面实现,默认inline函数
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
int Day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))))
return 29;
return Day[month];
}
bool CheckDate();
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;
// d1 += 天数
Date& operator+=(int day);
Date operator+(int day) const;
// d1 -= 天数
Date& operator-=(int day);
Date operator-(int day) const;
// d1 - d2
int operator-(const Date& d) const;
// ++d1 -> d1.operator++()
Date& operator++();//前置++
Date operator++(int);//后置++
Date& operator--();
Date operator--(int);
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
cpp
#include"Date.h"
using namespace std;
bool Date::CheckDate()
{
if (_month < 0 || _month >12 || _day<1 || _day > GetMonthDay(_year, _month))
return false;
else
return true;
}
Date::Date(int year,int month,int day)
{
if (!CheckDate())
cout << "日期非法" << endl;
year = _year;
month = _month;
day = _day;
}
void Date::Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
{
return true;
}
else if(_year == d._year)
{
if (_month < d._month)
return true;
else
return _day < d._day;
}
return false;
}
bool Date::operator==(const Date& d) const
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
bool Date::operator<=(const Date& d) const
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
//日期 + 天数
Date& Date::operator+=(int day)
{
if (_day < 0)
{
return *this -= -day;
}
_day += day;
while(_day > GetMonthDay(_year, _day))
{
_day -= GetMonthDay(_year, _day);
++_month;
if (_month == 13)
{
_month = 1;
++_year;
}
}
return *this;
}
Date Date::operator+(int day) const
{
Date tmp = *this;//保证自己不被修改,同时返回修改之后的值
tmp += _day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (_day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)
{
--_month;//先更新_month,防止出现用 _month == 0 去取天数
if (_month == 0)
{
_month = 12;
_year--;
}
_day -= GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day) const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date tmp = *this;
tmp += 1;
return tmp;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date tmp = *this;
tmp -= 1;
return tmp;
}
int Date::operator-(const Date& d)
{
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;
}
5.4 const 成员函数
1.将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后
⾯。
2.const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const
Date* const this 表示对于指针本身以及指向的内容均不能进行修改。