=========================================================================
相关代码gitee自取:
C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
【C++初阶】四、类和对象
(构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)-CSDN博客
=========================================================================
一 . 日期类的完善
此次日期类的成员函数,采用声明和定义(实现)分离的方式实现
成员函数声明和定义(实现)分离的好处:
将成员函数的声明写在头文件中,方便我们查看这个类中有哪些成员函数,
其具体实现再到成员函数实现文件(.cpp文件)中查看
(详细解释在图片的注释中,代码分文件放下一标题处)
Date构造函数 -- 全缺省构造函数优化
- 当实例化对象时,所给的初始化参数可能不合理(比如给了负数的日期),
需要进行特殊处理(报错)
- 注意:
全缺省构造函数的缺省参数只能在声明或实现中给,
不能两边都给缺省参数,不然编译器不知道要使用哪边的缺省参数,
而且两边的缺省参数给得还可能不一样,
所以这里选择在声明中给缺省参数,实现时并没有给图示:
测试:
---------------------------------------------------------------------------------------------
Print函数 -- 打印日期函数
图示:
---------------------------------------------------------------------------------------------
GetMonthday“辅助”函数 -- 获取当前月份日期函数
- 因为考虑到各月份日期可能不一样,二月还需要考虑闰年还是平年,
所以可以单独写一个函数处理月份的情况,方便后续成员函数的实现
- assert断言:防止year(年份)或month(月份)传错
- 一年有12个月,定义一个有13个元素的数组,
不使用第一个元素,之后的12个元素分别为1~12月各个月的对应日期(平年)
- 再单独判断2月的情况,如果year是闰年,再修改2月的日期为29天
- 最后通过数组下标返回对应月的日期
图示:
---------------------------------------------------------------------------------------------
operator==函数 -- “==”运算符重载函数
- 通过隐藏的this指针来依次判断两日期的年、月、日是否相同并返回结果
图示:
---------------------------------------------------------------------------------------------
operator!=函数 -- “!=”运算符重载函数
- 复用“==”运算符重载函数,对其结果取反,就能实现“!=”运算符重载函数了
图示:
---------------------------------------------------------------------------------------------
operator>函数 -- “>”运算符重载函数
- 先比较年份,“年大就大”;
如果年份相等,“月大就大”;
如果年份相等,月份也相等,“天大就大”;
大就返回true
- 除此以外,就是两日期小于等于的情况了,返回false
图示:
---------------------------------------------------------------------------------------------
operator>=函数 -- “>=”运算符重载函数
- 复用“>”和“==”运算符重载函数即可实现">="运算符重载函数
图示:
---------------------------------------------------------------------------------------------
operator<函数 -- “<”运算符重载函数
- 复用“>=”运算符重载函数即可实现"<"运算符重载函数
图示:
---------------------------------------------------------------------------------------------
operator<=函数 -- “<=”运算符重载函数
- 复用“>”运算符重载函数即可实现"<="运算符重载函数
图示:
---------------------------------------------------------------------------------------------
operator+=函数 -- “+=”运算符重载函数(日期+=整型)
- 我们执行“+=”操作时,有时右值可能是一个负数,实际执行时就是减法了,
所以需要进行特殊处理
- 如果右值为正数,则将右值(天数)加到日期(左值)上,
再按需进行进位操作图示:
---------------------------------------------------------------------------------------------
operator+函数 -- “+”运算符重载函数(日期+整型)
- 和“+=”不同,“+”不会改变左值,
所以为了保证左值不变,可以对左值进行拷贝,拷贝出一个tmp对象,
对tmp复用“+=”并返回即可实现“+”图示:
测试:
---------------------------------------------------------------------------------------------
先实现“+=”再复用实现“+” 还是 先实现“+”再复用实现“+=”
- 上面我们是先实现了“+=”,再复用“+=”实现了“+”,
先实现的“+=”中不需要进行对象的拷贝;
而复用“+=”实现的“+”中需要进行两次对象的拷贝:
一次是拷贝生成tmp对象,还有一次是传值返回需要拷贝一次,
所以总共需要拷贝两次对象
- 而如果是先实现“+”,再复用“+”实现“+=”的话,
先实现的“+”中需要进行两次对象的拷贝:
一次是拷贝生成tmp对象,还有一次是传值返回需要拷贝一次,
而复用“+”实现的“+=”中需要进行三次对象的拷贝:
复用的“+”需要进行两次拷贝,“+”完后还要进行“=”拷贝一次
所以总共需要拷贝三次对象
所以最好还是先实现“+=”,再复用“+=”实现“+”比较好,
减少对象的拷贝次数提高效率|
---------------------------------------------------------------------------------------------
operator-=函数 -- “-=”运算符重载函数(日期-=整型)
- 我们执行“-=”操作时,有时右值可能是一个负数,实际执行时就是加法了,
所以需要进行特殊处理
- 如果右值为正数,则将右值(天数)减到日期(左值)上,
再按需进行借位操作图示:
---------------------------------------------------------------------------------------------
operator-函数 -- “-”运算符重载函数(日期-整型)
- 和“-=”不同,“-”不会改变左值,
所以为了保证左值不变,可以对左值进行拷贝,拷贝出一个tmp对象,
对tmp复用“-=”并返回即可实现“-”图示:
图示:
(“-100天” / “-10000天”)
---------------------------------------------------------------------------------------------
operator-函数 -- “-”运算符重载函数(日期-日期)
- 先假设左值日期为较大日期max,右值日期为较小日期min,
此时两日期相减的天数会是正的,定义flag变量为1(正数)
(参数给得没问题,左大右小,减出来应该是一个正数)
- 如果我们假设错了,则将较大的右值日期赋给max,将较小的左值日期赋给min,
按照“-”减号的逻辑在假设的这种情况下,最终返回的天数会是负的,
将flag变量变为-1(负数)
(参数可能给反了,左小右大,减出来应该是一个负数)
- 我们不使用传统的“日期-日期”方法,因为有太多情况要处理,
通过前两个操作,max是两日期中的较大日期,min是两日期中的较小日期,
使用while循环,循环一次就++min,直到min==max为止,
此时循环次数就是两日期相差的天数
(关于日期类的“++”运算符重载函数后面会实现)
- 最终返回 循环日期*flag 即可
图示:
测试:
---------------------------------------------------------------------------------------------
operator++函数 -- “++”运算符重载函数(日期前置++)
- “前置++” -- 先+1再使用(逻辑上)
直接复用“+=”实现“++”,再返回“++”后的日期(实现上)图示:
测试:
---------------------------------------------------------------------------------------------
operator++函数 -- “++”运算符重载函数(日期后置++)
- 因为“++”运算符有“前置++”和“后置++”,所以需要分别重载实现,重载两次,
但是要让“前置++”和“后置++”构成重载函数,
需要函数名相同,参数不同,但不管是哪种“++”,
都只有一个操作数,隐藏的this指针就够用了,
所以为了区分“前置++”和“后置++”,“后置++”的参数会多一个int(必须是int类型),
也可以相应的给个实参,如:int i,
但是实现过程不会使用到i,所以一般直接写int即可
- “后置++” -- 先使用再+1(逻辑上)
在+1前先拷贝当前日期tmp,再对日期+1,最后返回的是+1前日期tmp(实现上)
- “前置++”和“后置++”对比:
“前置++”的效率是比较高的,
因为“后置++”为了实现“后置”需要进行两次拷贝,
所以自定义类型中使用“++”的话,最好还是使用“前置++”比较好效率高图示:
测试:
---------------------------------------------------------------------------------------------
operator--函数 -- “--”运算符重载函数(日期前置--)
- “前置--” -- 先-1再使用(逻辑上)
直接复用“-=”实现“--”,再返回“--”后的日期(实现上)图示:
---------------------------------------------------------------------------------------------
operator--函数 -- “--”运算符重载函数(日期后置--)
- “后置--” -- 先使用再-1(逻辑上)
在-1前先拷贝当前日期tmp,再对日期-1,最后返回的是-1前日期tmp(实现上)图示:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
日期类完善后代码
Date.h -- 日期类头文件:
#pragma once //包含头文件: #include <iostream> #include <assert.h> //完全展开std命名空间: using namespace std; //日期类: class Date { public: //全缺省构造函数: Date(int year = 1, int month = 1, int day = 1); //打印日期函数: void Print(); //“辅助”完成“+、+=”运算符重载的函数: /* * 因为考虑到各月份日期可能不一样, * 二月还需考虑闰年还是平年, * 所以可以单独写一个函数处理月份的情况: */ int GetMonthDay(int year, int month); //“==”运算符重载函数: //判断对象是否相等的函数(以日期类为例): bool operator==(const Date& y); //等于 //“!=”运算符重载函数: bool operator!=(const Date& y); //不等于 //“>”运算符重载函数: //写一个函数判断自定义类型大小(以日期类对象为例): bool operator>(const Date& y); //大于 //“>=”运算符重载函数: bool operator>=(const Date& y); //大于等于 //“<”运算符重载函数: bool operator<(const Date& y); //小于 //“<=”运算符重载函数: bool operator<=(const Date& y); //小于等于 //“+=”运算符重载函数: Date& operator+=(int day); // “+=” 才会改变d1对象 //“+”运算符重载函数: Date operator+(int day); // “+” 不会改变d1对象 //“-=”运算符重载函数: Date& operator-=(int day); // “-=” 会改变d1对象 //“-”运算符重载函数 -- “日期 - 整型”: Date operator-(int day); // “-” 不会改变d1对象 //“-”运算符重载函数 -- “日期-日期”: int operator-(const Date& d); //“++”运算符重载函数 -- “前置++”: Date& operator++(); //“++”运算符重载函数 -- “后置++”: Date operator++(int); //“--”运算符重载函数 -- “前置--”: Date& operator--(); //“--”运算符重载函数 -- “后置--”: Date operator--(int); //private: int _year; int _month; int _day; };
Date.cpp -- 日期类成员函数实现文件:
#define _CRT_SECURE_NO_WARNINGS 1 /* * 类的成员函数,声明和定义分离的好处: * * 将成员函数的声明写在头文件中, * 方便我们查看这个类中有哪些成员函数, * 具体实现再到成员函数实现文件进行查看 */ //包含日期类头文件: #include "Date.h" //全缺省构造函数: Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; //初始化时先考虑日期是否合理: if ( _year < 1 || _month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) /* * 日期不合理的情况: * 年份/月份/日期 < 1 或者 * 月份 > 12 或者 日期大于当前月份天数 *(当前月份通过GetMonthDat函数获得) */ { //通过断言报错: assert(false); } } /* * 注:这里的缺省参数只能在声明或实现中给, * 如果声明和实现中都给了缺省参数的话, * 编译器不知道要用哪边的缺省参数, * 两边的缺省参数还可能不一样,所以这里是 * 在声明中给了缺省参数,实现时并没有给 */ //打印日期函数: void Date::Print() { cout << _year << "/" << _month << "/" << _day << endl; } //“辅助”完成“+、+=”运算符重载的函数: /* * 因为考虑到各月份日期可能不一样, * 二月还需考虑闰年还是平年, * 所以可以单独写一个函数处理月份的情况: */ int Date::GetMonthDay(int year, int month) { //assert断言防止year或month传错: assert(year >= 1 && month >= 1 && month <= 12); int monthArray[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; //返回闰年二月的29天 } //把数组下标当作月份,返回对应月份: return monthArray[month]; } //“==”运算符重载函数: //判断对象是否相等的函数(以日期类为例): bool Date::operator==(const Date& y) //等于 /* * (注:)在类中的话,隐藏的this指针是第一个参数, * == 有两个操作数,this指针 和 y 就是两个操作数, *(运算符重载,这个运算符操作数和要和函数的参数匹配) */ { //比较两日期类对象是否相等只要判断两者年月日是否相等即可: return _year == y._year && _month == y._month && _day == y._day; /* * 成员变量虽然是私有的, * 但在类中通过this指针访问私有成员变量是可以的 */ } //“!=”运算符重载函数: bool Date::operator!=(const Date& y) //不等于 { //直接返回判断结果: return !(*this == y); /* * *this即左值对象,y即右值对象 * 再通过operator==重载函数判断是否相等 */ } //“>”运算符重载函数: //写一个函数判断自定义类型大小(以日期类对象为例): bool Date::operator>(const Date& y) //大于 /* * > 同样有两个操作数,隐藏的this指针是第一个参数 */ { //“年大就大”: if (_year > y._year) { return true; } //"年相等,月大就大": else if (_year == y._year && _month > y._month) { return true; } //"年相等,月相等,天大就大": else if (_year == y._year && _month == y._month && _day > y._day) { return true; } //上面已经写出了所有大于的情况,执行到这说明是小于等于: return false; //返回false } //“>=”运算符重载函数: bool Date::operator>=(const Date& y) //大于等于 { //直接复用“>”和“==”运算符重载函数即可: return *this > y || *this == y; /* * *this即左值对象,y即右值对象 * 通过复用“>”和“==”运算符重载函数判断并返回结果即可, * “>=” --> “>” 或 “==” */ } //“<”运算符重载函数: bool Date::operator<(const Date& y) //小于 { //直接复用“>=”运算符重载函数即可: return !(*this >= y); /* * *this即左值对象,y即右值对象 * 通过复用“>=”运算符重载函数即可, * 如果不是“>=”,那就是“<”了 */ } //“<=”运算符重载函数: bool Date::operator<=(const Date& y) //小于等于 { //直接复用“>”运算符重载函数即可: return !(*this > y); /* * *this即左值对象,y即右值对象 * 通过复用“>”运算符重载函数即可, * 如果不是“>”,那就是“<=”了 */ } /* * 对于这类用于判断的运算符, * 只要实现了“>”(或“<”)和 “=”, * 其它的判断运算符就只要通过复用这两个 * 运算符即可实现了(逻辑取反) */ //“+=”运算符重载函数: Date& Date::operator+=(int day) // += 才会改变d1对象 //为了防止拷贝返回,使用引用返回 { /* * 可能有坏蛋右值传了个负数过来, * 如:d1 += -100; * 这时就要进行特殊处理了: */ if (day < 0) //如果右值是一个负数: { //使用“-=”运算符重载函数处理该情况: return *this -= (-day); /* * *this = *this - (-day) * (-day)负负得正,让日期减去day(变正), * 返回日期减去day后的日期 */ } //思路:当月天数满了进位到月,月满了则进位到年: _day += day; //先将要加的天数加到当前的天数上 // this.day = this.day + (day) //开始进位(如果需要的话): while (_day > GetMonthDay(_year, _month)) /* * 先通过GetMonthDay函数获得当月的天数, * 再比较相加后的天数是否超过了当月的天数, * 只要超过了则进行进位,进位到月: * (while循环到没超过为止) */ { //天数减去一轮当前月天数: _day -= GetMonthDay(_year, _month); //减去的一轮当前月天数进位到月中: ++_month; //如果当前月进位后超过了12个月: if (_month == 13) { //将一轮月份进位到年: ++_year; //将月份重置为1月: _month = 1; } } return *this; } //“+”运算符重载函数: Date Date::operator+(int day) { Date tmp(*this); /* * 为了实现加法,加了后不改变d1对象(+=才会改变) * 先通过*this(即d1)拷贝出一个tmp, * 对tmp进行加法操作就不会改变d1对象了 */ /* * 复用 “+=运算符重载” ,只要 += 到d1的拷贝tmp上即可, * 就不会改变到d1对象,通过tmp返回d1的加法结果: */ tmp += day; //通过tmp返回d1的加法结果: return tmp; /* * 这里tmp是d1的拷贝,出了函数就销毁了, * 所以需要传值返回拷贝一份回主函数 */ } “+”运算符重载函数: d1 + 100(整数) ,计算d1日期的100天后的日期: //Date Date::operator+(int day) // “+” 不会改变d1对象 // //为了防止拷贝返回,使用引用返回 //{ // Date tmp(*this); // /* // * 为了实现加法,加了后不改变d1对象(+=才会改变) // * 先通过*this(即d1)拷贝出一个tmp, // * 对tmp进行加法操作就不会改变d1对象了 // */ // // //思路:当月天数满了进位到月,月满了则进位到年: // // tmp._day += day; //先将要加的天数加到当前的天数上 // // //开始进位(如果需要的话): // while (tmp._day > GetMonthDay(tmp._year, tmp._month)) // /* // * 先通过GetMonthDay函数获得当月的天数, // * 再比较相加后的天数是否超过了当月的天数, // * 只要超过了则进行进位,进位到月: // * (while循环到没超过为止) // */ // { // //天数减去一轮当前月天数: // tmp._day -= GetMonthDay(tmp._year, tmp._month); // // //减去的一轮当前月天数进位到月中: // ++tmp._month; // // //如果当前月进位后超过了12个月: // if (tmp._month == 13) // { // //将一轮月份进位到年: // ++tmp._year; // // //将月份重置为1月: // tmp._month = 1; // } // } // //return tmp; ///* //* 这里tmp是d1的拷贝,出了函数就销毁了, //* 所以需要传值返回拷贝一份回主函数 //*/ //} “+=”运算符重载函数: //Date& Date::operator+=(int day) //{ // //复用“+”运算符重载函数: // *this = *this + day; // /* // * 这里的*this(左值对象)和day(右值对象), // * 都是已经存在的对象,把已经存在的对象赋值给 // * 另一个已经存在的对象上,需要使用到赋值运算符重载函数 // * // * 使用“+”运算符重载函数将day日期加到*this日期上, // * 再使用赋值运算符重载函数把加上的日期赋值给*this, // * 完成“+”运算符重载函数的复用 // */ // // //返回“+=”结果: // return *this; //} /* * 一: * 如果先实现了“+”运算符重载函数, * 可以复用“+”运算符重载函数实现“+=”运算符重载函数 * * 二: * 如果先实现了“+=”运算符重载函数, * 那就可以复用“+=”运算符重载函数实现“+”运算符重载函数 * * 第一种方法的“+”需要进行两次对象拷贝, * “+=”通过复用“+”实现,需要三次对象拷贝, * 所以该方法总共需要五次拷贝 * * 第二种方法的“+=”不需要进行对象拷贝, * “+”中需要两次对象拷贝,该方法总共需要两次拷贝 * * 所以还是通过第二种方法实现“+=”和“+”比较好, * 而且实现加法最好使用“+=”,而不是“+”, * 因为“+=”不需要进行拷贝,“+”需要拷贝两次对象 */ //“-=”运算符重载函数: Date& Date::operator-=(int day) { /* * 可能有坏蛋右值传了个负数过来, * 如:d1 -= -100; * 这时就要进行特殊处理了: */ if (day < 0) //如果右值是一个负数: { //使用“+=”运算符重载函数处理该情况: return *this += (-day); /* * *this = *this + (-day) * (-day)负负得正,让日期加上day(变正), * 返回日期加上day后的日期 */ } //先将要减的日期day减到对象的_day变量上: _day -= day; //注:这里是内置类型(int)的“-=” /* * 减去day后日期不能小于等于0, *(日期不能为负数,也不能有0日) * 需要跟上个月份借天数: */ while (_day <= 0) { --_month; //月份-1,跟上个月借天数 //如果月份被"减没了"(月份为0): if (_month == 0) { //再跟上一年借12各月份: --_year; //年份-1 //月份重置为12个月: _month = 12; } //将跟当月借的天数加到_day中: _day += GetMonthDay(_year, _month); //循环到_day天数>0为止 } //返回左值对象减去一个天数后的日期: return *this; //即d1(左值对象) } //“-”运算符重载函数 -- “日期-整型”: Date Date::operator-(int day) { //“-”不会改变对象,所以先拷贝一个左值对象出来: Date tmp(*this); /* * *this即左值对象, * 通过拷贝构造函数对其拷贝出一个tmp对象出来 */ //复用上面的“-=”运算符重载函数实现“-”: tmp -= day; /* * “-=”改变的是*this的拷贝tmp, * 所以不会影响到*this */ //返回日期对象减去day天后的日期: return tmp; } //“-”运算符重载函数 -- “日期-日期”: int Date::operator-(const Date & d) { //假设左值日期为大值: Date max = *this; //存放左值对象 //假设右值日期为小值: Date min = d; //存放右值对象 //“如果max要减去min”,相差天数可能为正可能为负: int flag = 1; //flag为1即为正,flag为-1即为负 //如果我们假设的“左大右小”错了: if (*this < d) { //那就让max存储d(大值), //min存储*this(小值): max = d; //存储大值d min = *this; //存储小值*this //那么此时max减去min的话,相差天数应是负的: flag = -1; } //定义一个变量n: int n = 0; //存储max和min相差的天数 while (min != max) //当min还小于max时: { //min++,min加一天: ++min; //n存储的相差天数+1: ++n; /* * 当min加到和max相等时, * n就是max和min相差的天数, * 巧用“++”运算符重载函数就能计算出相差天数, * 而不是“年-年,月-月,天-天”的方法, * 不需要分析大量的情况,逻辑简单, * 日期相差比较大的时候,可能执行会比较“笨”, * 但这对计算机来说不算什么 */ } return n * flag; /* * max - min: * 假设我们一开始假设是对的:max > min * 那么 n*flag = 相差天数(正)*1 ,返回正日期 * 如果我们假设错了:max < min * 那么 n*flag = 相差天数(正)*-1 ,返回负日期 *(返回负日期是因为使用函数时参数给错了,左小右大) *(正常应该是左大右小,这时按照了“-”减号的逻辑实现的) */ } //“++”运算符重载函数 -- “前置++”: Date& Date::operator++() //“++”对于日期类的意义:计算明天的日期 { //直接复用“+=”实现“++”: *this += 1; //“++”是会改变左值的,所以使用“+=” //返回“明天的日期”: return *this; } //“++”运算符重载函数 -- “后置++”: Date Date::operator++(int) { //在当前日期加上一天前,先保留当前日期: Date tmp(*this); //拷贝当前日期: //对当前日期+1天: *this += 1; //但返回的是+1天前的日期: return tmp; //使用的是当前日期,实际日期已+1天 /* * 因为“++”运算符有“前置++”和“后置++”, * 所以需要分别重载实现,重载两次, * 但是要让“前置++”和“后置++”构成重载函数, * 需要函数名相同,参数不同,但不管是哪种“++”, * 都只有一个操作数,隐藏的this指针就够用了, * 所以为了区分“前置++”和“后置++”, * “后置++”的参数会多一个int(必须是int), * 也可以相应的给个实参,如:int i, * 但是实现过程不会使用到i,所以一般直接写int即可 */ /* * “前置++”和“后置++”对比: * “前置++”的效率是比较高的, * 因为“后置++”为了实现“后置”需要进行两次拷贝, * 所以自定义类型中使用“++”的话, * 最好还是使用“前置++”比较好效率高 */ } //“--”运算符重载函数 -- “前置--”: Date& Date::operator--() //“--”对于日期类的意义:计算前天的日期 { //直接复用“-=”实现“--”: *this -= 1; //“--”是会改变左值的,所以使用“-=” //返回“前天的日期”: return *this; } //“--”运算符重载函数 -- “后置--”: Date Date::operator--(int) { //在当前日期减去一天前,先保留当前日期: Date tmp(*this); //拷贝当前日期: //对当前日期-1天: *this -= 1; //但返回的是-1天前的日期: return tmp; //使用的是当前日期,实际日期已-1天 }
Test.cpp -- 测试文件:
//包含日期类头文件: #include "Date.h" //优化全缺省构造函数: void TestDate1() { Date d1; //打印d1日期: d1.Print(); //全缺省构造函数的完善: //初始化的日期可能不合法(月份): Date d2(2023, 13, 1); d2.Print(); //初始化的日期可能不合法(日期): Date d3(2010, 2, 29); //2月可能没有29天 d3.Print(); } //测试 “-” 和 “+”(日期-+整型): void TestDate2() { //创建一个日期类对象:d1 Date d1(2023, 10, 24); d1.Print(); //测试“-”运算符重载函数: Date ret1 = d1 - 100; //隔几个月 ret1.Print(); //打印结果 Date ret2 = d1 - 10000; //跨越闰年 ret2.Print(); //打印结果 //测试“+”运算符重载函数: Date ret3 = d1 + 100; //隔几个月 ret3.Print(); //打印结果 Date ret4 = d1 + 10000; //跨越闰年 ret4.Print(); //打印结果 } //测试“前置++”和“后置++”: void TestDate3() { //创建一个日期类对象:d1 Date d1(2023, 10, 24); d1.Print(); ++d1; //d1.operator++() -- “前置++” d1.Print(); //2023/10/25 Date d2 = d1++; //d1.operator++(int) -- “后置++” d2.Print(); //先取到d1:2023/10/25,再++ d1.Print(); //2023/10/26 } //测试“日期-日期”运算符重载函数: void TestDate4() { //创建一个日期类对象:d1 Date d1(2023, 10, 24); d1.Print(); //创建一个日期类对象:d2 Date d2(2024, 2, 10); d2.Print(); //打印两日期相差的日期: cout << (d2 - d1) << endl; cout << (d1 - d2) << endl; } //测试“+=”的特殊情况: void TestDate5() { Date d1(2023, 10, 24); d1 += -100; d1.Print(); } int main() { //TestDate1(); //TestDate2(); //TestDate3(); TestDate4(); //TestDate5(); return 0; }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
二 . 流运算符重载函数
“<<”流运算符重载函数
简单介绍<<流插入运算符:
- << -- 流插入运算符(输出流运算符),默认只支持内置类型的调用,
自定义类型默认不能使用流插入运算符,所以需要自定义该运算符的重载函数:operator<<()函数
<<运算符有两个操作数:
左操作数:cout,即ostream类(输出流)对象;
右操作数:这里以日期类为例,这里是接收一个日期类对象,接收后打印该日期而在C++中,流运算符是可以自动分辨右操作数的类型的,
我们所说的“自动分辨”其实是通过函数重载实现的,
数据类型不同调用的重载函数不同(参数匹配不同)
流本质是为了解决:自定义类型的输入和输出问题,
C语言中 printf 和 scanf 无法解决自定义类型的输入输出问题,
(使用时需要使用%指定类型,如:%d,只能指定内置类型)
所以通过 面向对象+运算符重载 来解决该问题
---------------------------------------------------------------------------------------------
实现“<<”流运算符重载函数会遇到的问题:
"<<"左右操作数问题:
- 在实现运算符重载函数时,如果该运算符有两个操作数,
函数的第一个参数会是左操作数,而函数的第二个参数会是右操作数,
一般运算符对左右操作数的顺序没有要求,但"<<"这个运算符有点例外
- 一般使用时是这样的:cout << d1;
可以看到左操作数会是cout这个输出流对象,右操作数是日期类对象
- 如果我们将“<<”运算符重载函数实现在成员函数中的话,
成员函数默认会有一个隐藏的this指针作为函数的第一个参数,且存储的是d1对象,
那么第二个参数就会是输出流对象cout,
那么左操作数就会是d1对象,右操作数就会是cout,那么实际调用时就会是这样:
d1 << cout;
有点违反常理:cout“流入”d1中,可读性差图示:
类中私有成员变量使用问题:
- 所以为了不受制于隐藏的this指针,我们需要将"<<"流运算符重载函数实现在全局中,
但在全局进行实现的话,就无法访问类中的私有成员变量了
- 为了在全局也能访问类中的私有成员变量,需要将该函数设置成友元函数。
friend(友元函数关键字):友元函数,可以访问类中的私有成员变量
(之后会跟细致地讲解友元函数)
---------------------------------------------------------------------------------------------
operator<<函数 -- “<<”流运算符重载函数
- 该函数需要声明在全局中,不受制于隐藏的this指针,
同时为了能够调用私有成员变量,需要将其设置为友元函数
- "<<"还需要支持连续打印,如:cout << d1 << d2 << endl;
该语句执行时是从左往右执行的,先执行cout << d1,再返回cout,
返回的cout再和后面的d2形成:cout << d2,打印d2后再返回cout,
再和后面的endl形成:cout << endl,进行换行
注意:
endl就是‘\n’,是char类型的,所以执行 cout << endl 时,
"<<"调用的是库中char类型的"<<"流运算符重载函数,而不是日期类的
- 日期类是自定义类型,但日期类中的成员变量年、月、日不是,而是内置类型,
所以对这些内置类型正常调用<<打印即可实现日期类对象的打印图示:
“>>”流运算符重载函数
operator>>函数 -- “>>”流运算符重载函数
- 有了前面“<<”流插入运算符(输出流运算符)重载函数的实现,
">>"输入流运算符重载函数也是同理
- 日期类是自定义类型,但日期类中的成员变量年、月、日不是,而是内置类型,
所以对这些内置类型正常调用>>输入即可实现日期类对象的输入图示:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
三 . const成员
回顾 -- const:
const是一个关键字,被const修饰的变量不允许被改变,为静态变量,
当const修饰一个指针时:
当const在指针变量类型的右边时,修饰的是指针本身(指针常量);
当const在指针变量类型的左边时,修饰的是指针所指向的内容(常量指针)
const成员函数
- 成员函数中隐藏的this指针的类型是(以日期类为例):
Date* const (this)
- 如果我们使用const对类对象进行修饰,那么其类型是(以日期类为例):
const Date (d1)
如果该对象调用成员函数的话,隐藏的this指针会获取该对象的地址(&d1),
所以函数中this指针指向的对象类型就是:
const Date* (d1)
- 所以当对象调用成员函数时:
this指针的Date* const (this) 会接收 d1的const Date* (d1),
此时this指针是指针常量(const在类型右边),d1是常量指针(const在类型左边)
即 d1常量指针 传到 隐藏this指针常量 后,其权限被放大了,编译器是不允许的
(d1中内容本来是不能改变的,传到this指针后可以改变了)
- 权限只能是平移或缩小,唯独不能放大,为了让const对象也可以调用成员函数,
需要对成员函数中隐藏的this指针也进行const修饰,使其类型变成:
const Date* const (this)
这样当其接收const对象时,就是 const Date* const (this) 接收 const Date* (d1),
是权限的平移,是被允许的,
当接收非const对象时,就是 const Date* const (this) 接收 Date* (d1) ,
是权限的缩小,也是被允许的
- 需要使用const修饰的成员函数:
这里加的const是加到隐藏this指针上的,成员函数才有隐藏this指针,
被const修饰的对象是静态的,内容无法被修改,
所以当成员函数中隐藏的this指针所指向的对象在函数实现过程中不需要被改变时,
该成员函数就适合使用const对其进行修饰图示:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
四 . “&”取地址运算符重载
“&”取地址运算符也是可以进行重载的,
上面我们了解了const成员函数,这里的“&”运算符重载函数也需要考虑这个问题
“&”运算符重载函数:
- “&”取地址运算符就是获取对象的地址并返回,
对于自定义类型对象,就是返回该对象的地址
- 但为了应对const对象,需要实现两个版本(以日期类为例):
Date* const (this) 版本 和 const Date* const (this) 版本
- “&”运算符重载函数的非const版本和const版本都是默认成员函数,
如果我们不显式定义,编译器也会默认生成这两个运算符重载函数
- 虽然可以显式实现,但显式实现也没什么可以操作的,
和编译器默认生成的功能接近,都是直接返回对象地址,
所以这两个重载函数一般不会显式定义(对一般类来讲)图示: