个人主页 : zxctscl
文章封面来自:艺术家–贤海林
如有转载请先通知
文章目录
1. 前言2. 赋值运算符重载3. 前置++和后置++重载3.1 前置++重载3.2 后置++重载 4. 日期类的实现4.1 日期的计算4.1.1 日期的相加4.1.1.1 日期+=天数4.1.1.2 日期+天数4.1.1.3 +=和+的复用 4.1.2 日期的相减4.1.2.1 日期-=天数4.1.2.2 日期-天数 4.1.3 日期-日期 4.2 <<重载4.3 >>重载4.4 附代码4.4.1 Date.h4.4.2 Date.cpp4.4.3 Test.cpp
1. 前言
在前面的博客中提到了拷贝构造: 【C++】类和对象之拷贝构造函数篇,和 运算符重载【C++】类和对象之常引用与运算符重载,接下来继续来看赋值运算符重载中的赋值运算符重载。
2. 赋值运算符重载
赋值运算符重载格式参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}//private:int _year;int _month;int _day;};int main(){Date d1(2024, 1, 29);Date d2(2024, 2, 28);bool ret1 = d1 < d2;bool ret2 = d1.operator<(d2);int i = 0;int j = 1;bool ret3 = i < j;Date d3(d1); //拷贝构造d1 = d2; // 赋值重载d1.Print();d2.Print();return 0;}
d3用到了拷贝构造,同类型一个存在的对象进行初始化要创建的对象。
这里用到了赋值重载,是将已经存在的对象,一个拷贝赋值给另一个。
赋值运算符还支持连续赋值。
内置类型支持连续赋值,像下面这样
现将10赋值给j,然后这个表达式有一个返回值就是j,然后j再作为下一个返回值的右操作数,它有个返回值是i,这个i并没有再接收。
得注意运算符的优先级。
自定义类型也必须和内置类型一样符合这个规则。
void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
要支持连续赋值就得有一个返回值,那么这里返回值是什么呢?
像d1=d2,d2赋值给d1,那么就返回d1。d2 = d3,就返回d2,也就是返回左操作数。
怎么拿到左操作数呢?
不是返回this,this是个指针,要返回对象。所以是*this,就是左操作数。
Date operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
这里用到的是传值返回,返回的不是*this,他不会返回当前对象,返回的是他拷贝,又得调用拷贝构造,太浪费了。所以在这里就再加一个引用就行。
Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day; return *this;}
这里不需要加const,规定的就是返回左操作数本身。
有时候可能会出现自己给自己赋值的情况。
像这样:
但也没什么问题,在这里为了避免这样的情况发生,在写赋值运算符重载时会加上一个判断。
Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;} return *this;}
this是左操作数的地址,d是有操作数,这里取d的地址,来判断他们地址是否相等。
相等就不会进去了。
class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;};// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数Date& operator=(Date& left, const Date& right){if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;}
这里就会显示编译失败。
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}/*Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;} return *this;}*/void Print(){cout << _year << "/" << _month << "/" << _day << endl;}//private:int _year;int _month;int _day;};int main(){Date d1(2024, 1, 29);Date d2(2024, 2, 28);Date d3(d1); d1 = d2; d1.Print();d2.Print();d1 = d2 = d3;return 0;}
它是6个默认成员函数之一,用户不写,编译器也会自动生成。
总结跟拷贝构造类似。
对内置类型值拷贝,自定义类型调用对应的拷贝构造和赋值重载。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
typedef int DataType;class Stack{public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType* _array;size_t _size;size_t _capacity;};int main(){Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;}
这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
3. 前置++和后置++重载
前置++和后置++怎么区分呢?
特殊处理:解决语法逻辑不自洽,自相矛盾的问题。
为了跟前置++区分,强行增加一个int形参,够成重载区分。
class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator++(){_day += 1;return *this;}Date operator++(int){Date temp(*this);_day += 1;return temp;}private:int _year;int _month;int _day;};int main(){Date d;Date d1(2022, 1, 13);d = d1++; // d: 2022,1,13 d1:2022,1,14d = ++d1; // d: 2022,1,15 d1:2022,1,15return 0;}
3.1 前置++重载
前置++自己要加加
Date& Date::operator++(){*this += 1;return *this;}
前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
3.2 后置++重载
后置++要返回加加之前的值,所以得先拷贝。
Date Date::operator++(int){Date tmp = *this;*this += 1;return tmp;}
后置++: 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1,而tmp是临时对象,因此只能以值的方式返回,不能返回引用。
4. 日期类的实现
4.1 日期的计算
先获取某年某月的天数。
int GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return monthDays[month];}
4.1.1 日期的相加
4.1.1.1 日期+=天数
先实现日期的相加:
直接加上日期,但当天数大于那个月份的时候,月份得加一。当月份到13的时候,又得将月份改到1月,这时候年得加加。这里实现的是加等。
Date& Date::operator+=(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;}
4.1.1.2 日期+天数
但要实现加,本身不能改变,this的指向不能改变。所以这里使用拷贝构造。
出了作用域就不在了,不能用引用返回。
Date Date::operator+(int day){Date tmp(*this);//Date tmp = *this; tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;if (tmp._month == 13){++tmp._year;tmp._month = 1;}}return tmp;}
4.1.1.3 +=和+的复用
前面既有+=也有+,他们的代码类似,复用一下。
+复用+=Date& Date::operator+=(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;}Date Date::operator+(int day){Date tmp = *this; tmp += day;return tmp;}
+=复用+ Date Date::operator+(int day){Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;if (tmp._month == 13){++tmp._year;tmp._month = 1;}}return tmp;}Date& Date::operator+=(int day){*this = *this + day;return *this;}
+复用+=更好,单独调用+=没有产生对象,+的时候避免不了,要产生对象。
而+=复用+,两个都产生了,+=是间接产生的。
在网上看看在日期相对比较大的时候看看计算有没有错误
4.1.2 日期的相减
日期的相减是借位,减的时候,天数小于1,就得向相应月借位,月减到0,年就得减一,月就得变为12。
4.1.2.1 日期-=天数
Date& Date::operator-=(int day){_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}
4.1.2.2 日期-天数
这里的-再复用-=
Date Date::operator-(int day){Date tmp = *this;tmp -= day;return tmp;}
4.1.3 日期-日期
日期-日期,返回的是天数。
直接相减肯定是不行,月可能不同,还有可能是闰年。
把月小的一个加到和大的那个相同,直接算相差几年,相差几年就直接相成对应年的天数。里面有闰年也好计算。
假设左操作数大,右操作数小,如果假设错误,就重新赋值。
这里加了一个标志flag = 1,假设正确就相加,错误就相减。
int Date::operator-(const Date& d){int flag = 1;Date max = *this;Date min = d;if (*this < d){int flag = -1;max = d;min = *this;}int n = 0;while (min != max){++min;++n;}return n * flag;}
4.2 <<重载
在C++中走printf是不是有点不习惯呢,那我们能不能走内置类型的流插入呢?
这个类型只支持内置类型,不支持自定义类型,因为他不知道用户到底怎么进行流插入的打印。
自定义凡是想用流插入,得自己写流插入运算符。
cout
是一个库里面的全局ostream
的对象。
void operator<<(ostream& out){out<< _year << "年" << _month << "月" << _day <<"日" << endl;}
但是调用不了。玩什么呢?
这个是因为成员函数,操作数得匹配,左操作数是第一个参数,右操作数是第二个参数。
说明调用得这样
这里变成了控制台流入到日期类里面去,本来应该是日期类流入到控制台里面,这里却反了。
如果想让cout
占据左边的参数该怎么办?
作为成员函数重载,this指针占据第一个操作数,Date必须是左操作数。那么<<重载就不能写为成员函数,必须写为全局的。
写为全局函数,但又有一个问题,访问不了私有,那么先将它改为共有。
但这里又出现了一个链接错误,找到一个或多个链接错误。
所以这里要做声明和定义分离。
现在就行了。
这里如果要私有也能访问,就得用到友元声明(在之后的博客中会继续分享的)。
友元声明就是普通的函数在类外面是不能访问对象私有的,但是想访问,那么就和它成为“朋友”,也就能访问它私有的。
之前的运算符有一个结合性,这里想要实现。
先d1流插入到cout,这里的cout实际是一个函数调用,应该有个返回值;然后返回值作为这里的左操作数,再去继续进行流插入。这里的返回值就一个是ostream对象,所以函数得增加一个ostream返回值。
4.3 >>重载
与流插入不同的是,流提取是istream对象。
这里不加const
,这里提取到的要放在Date里面。
istream& operator>>(istream& in, Date& d){cout << "请依此输入年月日" << endl;in >> d._year >> d._month>> d._day;return in;}
4.4 附代码
4.4.1 Date.h
#pragma once#include<iostream>#include<assert.h>using namespace std;class Date{public:bool CheckInvalid();Date(int year = 1, int month = 1, int day = 1);bool operator<(const Date& d);bool operator<=(const Date& d);bool operator>(const Date& d);bool operator>=(const Date& d);bool operator==(const Date& d);bool operator!=(const Date& d); d1 + 100Date& operator+=(int day);Date operator+(int day);// d1 - 100Date operator-(int day);Date& operator-=(int day);// ++d1Date& operator++();//前置Date operator++(int);//后置//Date operator--(int);//Date& operator--();// d1 - d2int operator-(const Date& d);// 本质就是inlineint GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };// 365 自转 公转 365 5+h// 366if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return monthDays[month];}friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};ostream& operator<<(ostream& out, const Date& d);istream& operator>>(istream& in, Date& d);
4.4.2 Date.cpp
#include"Date.h"//Date::Date(int year, int month, int day){_year = year;_month = month;_day = day;if (!CheckInvalid()){cout << "日期非法" << endl;}}//bool Date::operator<(const Date& d){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;}// d1 <= d2bool Date::operator<=(const Date& d){return *this < d || *this == d;}bool Date::operator>(const Date& d){return !(*this <= d);}bool Date::operator>=(const Date& d){return !(*this < d);}bool Date::operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}bool Date::operator!=(const Date& d){return !(*this == d);}// d1 += 10//Date& Date::operator+=(int day)//{//_day += day;//while (_day > GetMonthDay(_year, _month))//{//_day -= GetMonthDay(_year, _month);//++_month;//if (_month == 13)//{//++_year;//_month = 1;//}//}////return *this;//}//Date Date::operator+(int day)//{////Date tmp(*this);//Date tmp = *this; // //tmp += day;////return tmp;//} d1 + 10Date Date::operator+(int day){Date tmp(*this);//Date tmp = *this; tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;if (tmp._month == 13){++tmp._year;tmp._month = 1;}}return tmp;}// d1 += 100Date& Date::operator+=(int day){*this = *this + day;return *this;}//Date Date::operator-(int day){Date tmp = *this;tmp -= day;return tmp;}//Date& Date::operator-=(int day){_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}//// //++d ->d.operator++()Date& Date::operator++(){*this += 1;return *this;}// d++ ->d.operator++(0)Date Date::operator++(int){Date tmp = *this;*this += 1;return tmp;}// d1 - d2int Date::operator-(const Date& d){int flag = 1;Date max = *this;Date min = d;if (*this < d){int flag = -1;max = d;min = *this;}int n = 0;while (min != max){++min;++n;}return n * flag;}bool Date::CheckInvalid(){if (_year <= 0 || _month < 1 || _month >12 || _day<1 || _day>GetMonthDay(_year, _month)){return false;}else{return true;}}ostream& operator<<(ostream& out, const Date& d){out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;}istream& operator>>(istream& in, Date& d){while(1){cout << "请依此输入年月日" << endl;in >> d._year >> d._month >> d._day;if (!d.CheckInvalid()){cout << "日期无效,请重新输入" << endl;}} return in;}
4.4.3 Test.cpp
#include"Date.h"int main(){Date d1(2024, 1, 29);Date d2 = d1 + 20;d2.Print();d1.Print(); cout << d1; cin >> d2;cout << d1 << d2;d2 -= 20;d2.Print();d1 += 10000;d1.Print();++d1;d1.operator++();d1.Print();d1++;d1.operator++(10);d1.Print();Date d4(2024, 1, 29);Date d5(2024, 8, 1);cout << d5 - d4 << endl;return 0;}