cpp03:小项目Da
//对于日期类,构造需要自己实现,否则没办法初始化。析构和拷贝构造,赋值重载不需要,因为没有资源释放。
作业:
class Date
{//返回值是什么 就看表达式的结果是什么// d1 + 100; 日期+天数Date operator+(int day) const;// d1 - 100; 日期-天数Date operator-(int day) const;// d1 - d2; 天数-天数=中间差了多少天int operator-(const Date& d) const;private:int _year;int _month;int _day;
}//函数重载:函数名相同,参数不同
//运算符重载:重新定义运算符的行为
//二者没有直接关系
-------------------------------------------------------------------------------------------------------------------------------
1)声明和定义分离:
一旦把成员函数定义写到类外,就必须加类域限定符 类名::
,否则编译器当成普通全局函数处理,链接时会报 “未定义引用”。
Data.h//
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1);void Print();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);------------------------------------------------------------------------------//定义在类(class/struct都是定义类,只是默认限定符不同)里面的函数,默认是inline。int GetMonthDay(int year, int month) {//[13] 不是笔误,而是故意多开一个元素,把 “第 0 个月” 当成哨兵位(dummy entry),这样数组下标 直接对应真实月份,使用时不用再写 month-1,代码更直观,也省去一次减法运算。//把数组定义成静态的,这样每次调用这个函数的时候就不用重新在栈帧种创建数组,而是直接放在静态区,出了函数也不会销毁。assert(month > 0 && month < 13);static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//接下来的问题就是2月的问题了,判断闰年时候单独改成29.if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) )return 29;return monthDayArray[month];}
------------------------------------------------------------------------------Date operator+(int day);Date& operator+=(int day);Date operator-(int day);Date& operator-=(int day);private:int _year;int _month;int _day;
};
---------------------------------------------------------------------------------------------------------------------------------
2)日期+天数:满了需要进位,且不止进位一次可能(天满->进位月->月满->进位年)。
问题:要确定每个月的天数。int GetMonthDay(int year, int month)
Data.c//
#include "Date.h"
// 必须写 Date:: 指明这是 Date 类的作用域
Date::Date(int year, int month, int day)
{ _year=year;_month=month;_day=day;
}void Date::Print()
{cout << _year << "_" << _month << "_" << _day << endl;
}----------------------------------------------------------------------//出了作用域*this还在,所以可以用Date& 。
//d1+=100 , 实现+=,可以改变自己(d1)。
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;
}
//this指针指向Date的地址,解引用后也就是Date本身,返回其别名。----------------------------------------------------------------------//d1+100 , 实现+,不可以改变自己(d1)。
Date Date::operator+(int day)
{Date tmp = *this; //+不能改变自己,所以得拷贝一份出来,整个的+,+到tmp上面去。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; //tmp:局部对象,出来就销毁了,所以只能用传值返回
}----------------------------------------------------------------------
//+的写法2:用+复用+=。
Date Date::operator+(int day)
{Date tmp = *this;tmp += day; //拷贝的对象 直接调用上面写的成员函数的+=return tmp;
}
int main()
{Date d1(2024, 7, 12); //实例化对象d1Date d2 = d1 + 100; // 等价于 Date d2(d1 + 100);//d1 + 100是调用算符重载函数的写法(step1);//Date d2 = xxx/Date d2(xxx) (step2):调用拷贝构造d1.Print(); // 打印原始 d1d2.Print(); // 打印 d1+100 后的新日期d1 += 100; // 将 d1 自身再加 100 天d1.Print(); // 打印修改后的 d1return 0;
}
------------------------------------------------------------------------------------------------------------------------------
3)日期 - 天数:借位逻辑。(借的是上一个月的) 
//先写 -=
Date& Date::operator-=(int day)
{_day -= day;while (_day <= 0) //不合法,需要借位(月)减出来是负数 / 0 :借位。(比如说7月借6月的,而6月是30天){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month); //把借位的天数加上}return *this;
}//前置--和后置--此处没有区别,只对返回值有区别
//再写-,此处仿造上面,用-复用-=。
Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}
void TestDate2()
{Date d1(2024, 7, 12);d1 -= 200;
//d1 -= 200; 这条语句会被编译器翻译成 d1.operator-=(200);
//因此唯一被调用的函数就是类成员运算符重载:Date& Date::operator-=(int day);d1.Print();
}int main()
{TestDate2();return 0;
}
-------------------------------------------------------------------------------------------------------------------------------
//-=复用-:
Date Date::operator-(int day)
{Date tmp = *this; // 1. 拷贝一份tmp._day -= day; // 2. 先直接把天数减掉// 3. 若天数减到 ≤0,向前借月/年while (tmp._day <= 0){--tmp._month;if (tmp._month == 0) // 跨年了{tmp._month = 12;--tmp._year;}tmp._day += GetMonthDay(tmp._year, tmp._month);}return tmp; // 4. 返回减完后的新日期
}
// d1 -= 100Date& Date::operator-=(int day)
{/*Date tmp = *this - day; //*this:d1*this = tmp;*/ // *this = *this - day; // return *this; //
}
-------------------------------------------------------------------------------------------------------------------------------
4)日期大小的比较:
<的正确写法:
//d1<d2:左操作数是this,右操作数是参数.
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)return _day < d._day;}return false;
}
>的写法1:
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)return _day > d._day;}return false;
}
通用写法:(复用上面写过的函数)
bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
// d1 <= d2
bool 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);
}
5)日期大小的++:
//二元:一左一右
//一元:
d1++; // 后置++
Date operator++(int); ++d1; // 前置++
Date& operator++(); //这两个函数不能同时存在,因为无法区分。针对这个问题,cpp提出一种规定:(如下)
//建议多用前置,因为前置会返回++后的值,函数就可以传引用返回。
//由于CPP的规定,所以代码写成:
// d1++;
d1.operator++(0); // 后置++,编译器传0作哑参
Date operator++(int); // 后置自增原型// ++d1;
d1.operator++(); // 前置++
Date operator++(); // 前置自增原型
// d1++;
// d1.operator++(0);
Date Date::operator++(int) // 后置++
{Date tmp = *this; // 拷贝一份给tmp,保存原值 拷贝1*this += 1; // *this 复用Date类的+=return tmp; // 返回原值 传值返回,拷贝2
}
// ++d1;
// d1.operator++();
Date& Date::operator++() // 前置++
{*this += 1; // 自身先加1return *this; // 返回加1后的自身引用
}
前置++先加后用,返回自身引用;后置++先用后加,返回原值副本。
void TestDate2()
{/*Date d1(2024, 7, 13);d1 -= 30000;d1.Print();*/Date d1(2024, 7, 13);Date ret1 = d1++; // 后置++ret1.Print(); // 打印原值d1.Print(); // 打印加1后的值Date d2(2024, 7, 13);Date ret2 = ++d2; // 前置++ret2.Print(); // 打印加1后的值d2.Print(); // 打印加1后的值
}
6)日期做差:
// d1 - d2
int Date::operator-(const Date& d)
{int flag = 1;Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}
void TestDate4()
{Date d1(2024, 7, 12);Date d2(2024, 9, 1);cout << (d1 - d2) << endl;
}int main()
{TestDate4();return 0;
}
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;if (!CheckDate()) // 假设 CheckDate() 返回 true 表示合法{cout << "非法日期:" << endl;Print(); // 打印出出错日期}
}
实现方式2:输入d1 d2来实现作差(scanf)
scanf printf直接适用的是内置类型(%d int这种),没办法很好支持自定义类型(类,结构体),只能拆分。
cin(ostream类型的对象),cout(istream类型的对象)直接适用内置类型(%d int这种),因为库里面已经重载好了,能自动识别类型是源自于函数重载从而直接输出;对于自定义类型的支持,需要重载函数。
//cin cout 输出内置类型:
void TestDate4()
{int i=0;int d1=0;std::cout << i << d; //本质是转为上述重载好的函数调用,然后返回iostream类型
}
//cin cout输出自定义类型,需要重载函数。重载后遇到这个运算符会自动去调用。
void TestDate4()
{Date d1, d2;cin >> d1 >> d2; cout << d1 << d2;
}
// d1 - d2
int operator-(const Date& d);void operator<<(ostream& out);private:
int year;
int month;
int day;
}
1)Date::operator<<(ostream& out)作为成员函数:
则只能写成控制台流向日期类。
void Date::operator<<(ostream& out)
{out << _year << "年" << _month << "月" << _day << "日" << endl;
}
void TestDate5()
{Date d1, d2;// cin >> d1 >> d2;// cout << d1 << d2;cout << d1; // 实际调用 d1.operator<<(cout);
}int main()
{TestDate5();return 0;
}
写到这里发现报错了:日期类 流向 控制台
报错原因:对于二元运算符,左操作数对应第一个参数,右操作数对应第二个参数,此处对不上。
虽然是双目对双目,但是有一个是成员函数,有一个是隐含的this,this把第一个位置占了,所以左操作数必须是日期类,右操作数必须是out。
--------------------------------------------------------------------------------------------------------------------------------
修正:(改成控制台流向日期类)
void TestDate5()
{Date d1, d2;// 两种等价写法,都是调用成员函数 operator<<(ostream&)d1 << cout; // 语法倒装,实际含义:d1.operator<<(cout)d1.operator<<(cout); // 显式写成成员函数调用
}
此时可以成功匹配,也就是对于void Date::函数来说,双目的第一个参数是this指针(指向日期类),第二个是out。这里的 out
就是 成员函数 operator<<
的形参名,类型为 std::ostream&
,表示“往哪里输出”,它对应调用时传进来的实参。
------------------------------------------------------------------------------------------------------------------------------
2)Date::operator<<(ostream& out)作为全局函数:
此时可以写成日期类 流向 控制台。
//不是成员函数,去掉Date::
//小问题,访问不了私有
void operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}//解决方案1:
{
...//private: 放成公有
int year;
int month;
int day;
}
void TestDate5()
{Date d1, d2;//cin >> d1 >> d2;//cout << d1 << d2;cout << d1;operator<<(cout, d1); //改成全局的才行//T//倒反天罡//d1 << cout;//d1.operator<<(cout);
}