ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C++ 运算符重载详解 日期类的实现

C++ 运算符重载详解  日期类的实现

5.1 运算符重载

基本概念

1.运算符重载允许用户为类类型的对象赋予运算符新的含义,使自定义类型也能像内置类型一样使用+、-、==等运算符。

2.使用运算符时,本质上是在调用对应的重载函数。若没有对应的重载函数,则编译报错。

3.由关键字operator和指定的运算符共同构成,与其他函数一样,包含返回值类型、参数列表和函数体。

// 以 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。

// 前置 ++ 与后置 ++ 的重载 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.需要严格区分拷贝构造与赋值运算符重载。拷贝构造用于对象初始化(创建新对象),而赋值运算符重载用于已存在对象之间的赋值。

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.若未显式实现,编译器会自动生成默认赋值运算符重载。默认行为是逐字节拷贝(浅拷贝),对自定义类型成员会调用其自身的赋值重载。

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.如果一个类显式实现了析构函数(说明释放了资源),那么它也需要显式实现赋值运算符重载,通常还需要显式实现拷贝构造。

// 涉及资源管理时,必须实现深拷贝 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日期类实现

#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; };
#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 表示对于指针本身以及指向的内容均不能进行修改。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进