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 #includeiostream #includeassert.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; };#includeDate.h using namespace std; bool Date::CheckDate() { if (_month 0 || _month 12 || _day1 || _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 变为 constDate* const this 表示对于指针本身以及指向的内容均不能进行修改。