这里紧跟着我上一期博客C类和对象中上构造函数·析构函数·拷贝构造-CSDN博客去进行讲解。一.赋值运算符重载1.运算符重载本质为函数注意我们主要讲解的是赋值运算符重载这个默认成员函数而运算符重载是其实现方法要先进行学习。• 在C语言中运算符只能用来运算内置类型因为编译器会直接将运算符转换成对应的指令。现在C中推出了运算符重载其允许我们通过运算符重载的方式指定新的含义。C规定类类型对象使用运算符时必须转换成对应的运算符重载函数若没有对应的运算符重载则会编译报错。• 运算符重载是有特殊名字的函数其名字由operator和后面要定义的运算符共同构成。和其他函数一样它也具有返回类型、参数列表和函数体。class Date { public: Date(int year 1, int month 1, int day 1) { _year year; _month month; _day day; } //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; }这里涉及到两个问题①这里的返回类型是根据我的运算符的功能来的比如这个等于运算符其是要判断是否相等所以我要返回一个bool值。名字就是operator后面要定义的运算符之后的参数列表和函数体就根据函数的功能来进行设计。②我为了在类外也能访问到私有的成员函数把它们都放成公有了这里还有几种解决办法1Date类提供getxxx函数 2友元函数 3重载为成员函数。这个问题我们下面紧接着就会进行讲解。• 运算符重载函数的参数个数要和该运算符作用的运算对象数量一样多。一元运算符要有一个参数二元运算符要有两个参数其中二元运算符的左侧运算对象传给第一个参数右侧运算对象传给第二个参数。调用函数方法如下//正常调用可读性高 d1 d2 //当然也可以显式调用 operator(d1,d2);• 如果一个运算符重载函数是成员函数时因为this指针的存在其第一个运算对象也就是二元运算符的左运算数默认传给隐式的this指针因此运算符重载作为成员函数时参数比运算对象少一个。下面演示一下重载为成员函数的写法class Date { public: bool operator(const Date d) { return _year d._year _month d._month _day d._day; } private: int _year; int _month; int _day; };可以看到参数只有一个了因为前面有一个隐含的this指针且其会指向调用这个函数的对象的地址。• 运算符重载后其优先级和结合性与对应的内置类型运算符保持一致。• 不能通过语法中没有的符号来创造新的运算符比如operator。•.*::sizeof?:.注意以上5个运算符不能重载。选择题常考记忆下一就行。• 重载运算符至少要有一个类类型的参数不能通过运算符重载去改变内置类型对象的运算符含义如int operator(int x, int y)• 一个类要重载哪些运算符是要看哪些运算符重载后有意义比如Date日期类重载operator-就有意义但是重载operator*就没有意义。• 重载--运算符时分为前置和后置运算符重载的函数名都是operator无法很好区分。所以C规定后置重载时在参数列表里增加一个int形参跟前置构成函数重载方便区分。Date Date::operator() { //... } Date Date::operator(int) { //... }注意①这里使用了声明和定义分离先在类内部写声明在外部写函数这个函数还是内部成员函数。②后置中参数列表里的int只用作占位用来和前置形成函数重载和区分无实际用途。• 重载和时要重载成全局函数因为如果重载成成员函数this指针默认抢占了第一个形参位置而第一个形参位置是左侧运算对象调用时就会变成: 对象cout不符合使用习惯和可读性class Date { public: Date operator(); Date operator(int); Date(int year 1, int month 1, int day 1) { _year year; _month month; _day day; } void operator(ostream _cout) { _cout _year / _month / _day endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 10, 5); Date d2(2000, 1, 1); d1 cout; return 0; }而重载为全局函数把ostream/istream放到第一个形参就可以了第二个形参位置为当前类类型的对象。此外要注意一点输入输出要支持连续的输入输出也就是cin i n; cout i n endl;而连续的输入输出的诀窍就是返回值每单个输入输出后就会返回一个cout或cin去进行下一次的输入或输出cin i n; //先cini,-返回cin再cinn; cout i n endl; //先couti,返回cout再去接下来的输出所以我们在实现时也要自己加入返回值这个实例在后续的日期类实现里完整实现。2.赋值运算符重载赋值运算符重载是一个默认成员函数用于完成两个已经存在的对象直接的赋值这里要注意和拷贝构造区分拷贝构造用于一个已经存在的对象去拷贝初始化一个要创建的对象而赋值运算符重载是两个对象都已存在后的赋值//拷贝构造 Date d3 d1; //赋值运算符的重载 d1 d2赋值运算符重载的特点1.赋值运算符重载是一个运算符重载规定必须重载为成员函数因其为默认的成员函数之一。赋值运算符重载的参数建议写成const当前类类型的引用否则传值传参会有拷贝。2.有返回值且建议写成当前类类型的引用引用返回可以提高效率跳过拷贝构造有返回值的目的是为了支持连续赋值和前文的/一样的场景。3.没有显式实现时编译器会自动生成一个默认赋值运算符重载默认运算符重载行为跟拷贝构造函数类似。都是对内置类型完成值拷贝/浅拷贝一个字节一个字节的拷贝对自定义类型成员变量会调用它自己的赋值重载函数。4.像Date这样的类成员变量全是内置类型且没有指向什么资源编译器自动生成的赋值运算符重载就可以完成需要的拷贝所以不需要我们显示实现赋值运算符重载。像Stack这样的类虽然也都是内置类型但是_a指向了资源编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载也不需要我们显示实现MyQueue的赋值运算符重载。这里还有一个小技巧如果一个类显示实现了析构并释放资源那么他就需要显示写赋值运算符重载否则就不需要。这里也和拷贝构造类似。3.日期类的实现让我们现在使用所学的全部知识去实现一个日期类完成它的基本功能。这里也涉及到了const成员函数接下来就会讲解。//Date.h #includeiostream using namespace std; #includeassert.h class Date { friend ostream operator(ostream _cout, const Date d); friend istream operator(istream _cin, Date d); public: Date(int year 1, int month 1, int day 1); void Print() const; bool CheckDate() const; //因其要频繁调用所以不做声明和定义分离使其成为inline函数 int GetMonthDay(int year, int month) const { assert(month 1 month 12); int arr[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month ! 2) { return arr[month]; } if ((year % 4 0 year % 100 ! 0) || year % 400 0) { return 29; } return 28; } //比较大小相等 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的自加和自减 Date operator(); Date operator(int); Date operator--(); Date operator--(int); private: int _year; int _month; int _day; }; //输入输出流的重载 ostream operator(ostream out, const Date d); istream operator(istream in, Date d); //Date.cpp #includeDate.h Date::Date(int year, int month, int day) { _year year; _month month; _day day; assert(!CheckDate()); } void Date::Print() const { cout _year / _month / _day endl; } bool Date::CheckDate()const { if (_month 12 || _month1 || _day GetMonthDay(_year, _month) || _day 1) { return false; } else { return true; } } 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 if (_month d._month) { if (_day d._day) { return true; } return false; } } 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 d *this; } bool Date::operator(const Date d) const { return *this d || *this d; } bool Date::operator!(const Date d) const { return !(*this d); } Date Date::operator(int day) { if (day 0) { day -day; *this - day; return *this; } _day day; while (_day GetMonthDay(_year, _month)) { _day _day - GetMonthDay(_year, _month); _month 1; if (_month 12) { _month 1; _year 1; } } return *this; } Date Date::operator(int day) const { Date tmp *this; return tmp day; } Date Date::operator-(int day) { if (day 0) { day -day; *this day; return *this; } _day - day; while (_day 1) { _month - 1; if (_month 1) { _month 12; _year - 1; } _day _day GetMonthDay(_year, _month); } return *this; } Date Date::operator-(int day) const { Date tmp *this; return tmp - day; } int Date::operator-(const Date d) const { Date max *this; Date min d; int flag 1; if (max min) { max d; min *this; flag -1; } int n 0; while (min ! max) { min; n; } return flag * n; } Date Date::operator() { return *this 1; } Date Date::operator(int) { Date tmp *this; *this 1; return tmp; } Date Date::operator--() { return *this - 1; } Date Date::operator--(int) { Date tmp *this; *this - 1; return tmp; } ostream operator(ostream out, const Date d) { out d._year 年 d._month 月 d._day 日 endl; return out; } istream operator(istream in, Date d) { cout 请输入年月日 endl; while (1) { in d._year d._month d._day; if (!d.CheckDate()) { cout 输入信息不合法请重新输入 endl; } else { break; } } return in; }二.取地址运算符的重载1.const成员函数• 规定把const修饰的成员函数称之为const成员函数const修饰成员函数要放到成员函数参数列表的后面。int GetMonthDay(int year, int month) const;• 这里const实际修饰的是该成员函数的this指针表明不能对类的任何成员进行修改假设const修饰Date类的Print成员函数那Print隐含的this指针发生如下变化Date* const this - const Date* const this没加const修饰函数之前自带的const是修饰this本身的意思是你this就一直指向调用它的那个对象。现在又加一个const修饰的是*this指的是不能对this指向的对象本身进行改变不能去改变对象和其中的具体内容了。• 加上const修饰函数还有一个优点可以调用const修饰的对象了。const Date d1; Date d2; //这里如果Print函数没有加上const修饰会编译报错 d1.Print(); d2.Print();其原因在于d1在取地址后是const Date*类型的然后要把这个类型传给this指针若this指针没被const修饰则会权限放大编译报错。2.取地址运算符重载取地址运算符重载分为①普通取地址运算符重载和②const取地址运算符重载一般编译器自动生成的这两个函数就够我们使用了不需要去显式实现。除非一些很特殊的场景比如我们不想让别人去取到当前类对象的地址时就可以去显式实现一个返回一个空地址。因其为默认成员函数这两个也要重载成成员函数。class Date { public: Date* operator() { return this; //return nullptr; } const Date* operator()const//通过这个const修饰this指针完成函数重载 { return this; //return nullptr; } private: int _year; int _month; int _day; };三.结语类和对象中到这里就结束了我们完成了对六个默认成员函数的基本学习但是并不完全如构造函数在类和对象下还有要补充的知识点让我们努力前进吧