类和对象 (中)
感谢各位大佬的观摩和支持!!!
文章目录
-
1. 类的默认成员函数
2. 构造函数
3. 析构函数
4. 拷贝构造函数
5. 赋值运算符重载
5.1 运算符重载
5.2 赋值运算符重载
5.3 日期类实现
1. 类的默认成员函数
默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们在不写的情况下编译器会默认生成6种默认成员函数,需要注意的是重要的是前4个默认成员函数,后面的两个是去地址重载不重要,我们稍微了解一下即可。然后就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值。默认成员函数很重要但也很复杂,我们可以从两个方面进行学习:
1. 我们不显示写时,编译器默认生成的函数行为是什么,是否满足我们的需求
2. 编译器默认生成的函数不满足我们的需求,我们需要总结实现
6个默认成员函数:
接下来我会重点讲解前4个默认成员函数!!!
2. 构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名字叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化后初始化对象。构造函数的本质是要替代我们以前Stack或者Date类中的初始化函数Init,构造函数自动调用的特点就完美的替代了Init函数
构造函数的特点:
1. 函数名与类名相同
2. 没有返回值(返回值不需要给,也不需要加void,这是C++的规定)
3. 对象实例化时系统会自动调用对应的构造函数
4. 构造函数可以重载
5. 如果类中没有显示定义构造函数,则C++编译器就会自动生成一个无参的默认构造函数,一旦用户显示定义了编译器就不再生成
6. 无参构造函数、全缺省函数和编译器默认生成的构造函数都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省函数虽然可以构成函数重载,但是调用会存在歧义。总结一下,就是不传实参就可以调用的构造称为默认构造
7. 我们不写,编译器默认生成的构造函数,对于类的内置类型成员变量的初始化没有要求,也就是说是否初始化时不确定的,看编译器。对于自定义类型成员变量,这里要求调用这个自定义类型成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要使用初始化列表才能解决
8. C++把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型,比如int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型
代码示例1:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;class Date
{
public:// 默认构造函数不能同时存在多个 只能存在一个// 无参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}// 全缺省构造函数/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1; //使用无参构造函数或全缺省构造函数Date d2(2025,9,5); // 使用带参的构造函数// 对于无参的构造函数 对象后面不用跟括号// Date d1();d1.Print();d2.Print();return 0;
}
代码示例2:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:// 默认构造函数--全缺省函数/*Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr){perror("malloc failure");exit(1);}_capacity = n;_top = 0;}*/// .......// 编译器生成的默认构造函数 内置类型(基本类型)可能不做处理 但是我们把它看作不处理// 这个不是默认构造函数 会报错Stack(int n){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr){perror("malloc failure");exit(1);}_capacity = n;_top = 0;}private:STDataType* _a;size_t _top;size_t _capacity;
};class Queue
{
public:// 编译器会默认生成Queue的构造函数调用了Stack的构造,完成了两个成员的初始化
private:// 注意Stack必须要有默认构造函数才可以正常运行 默认构造有:无参默认构造 全缺省默认构造 编译器自动生成默认构造Stack _pushst;Stack _popst;
};int main()
{Queue q;return 0;
}
3. 析构函数
析构函数和构造函数功能是相反的,析构函数不是完成对对象本身的销毁,比如说局部对象是存在栈帧的,函数结束时栈帧销毁,它就释放了,不需要我们管,C++规定对象在自动销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack里写的Destroy函数的功能,像Date没有Destroy,其实就是没有资源需要释放,所有严格来说Date时不需要析构函数的
析构函数的特点:
1. 析构函数名时在类的名字前加上一个字符~
2. 无参数无返回值(这里和构造函数类似,也不需要加void)
3. 一个类只能有一个析构函数,如果没有显示定义,系统会自动生成默认的析构函数
4. 对象生命周期结束时,系统会自动调用析构函数
5. 跟构造函数类似,我们不显示写时编译器会自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用它的析构函数
6. 需要注意的是我们显示写析构函数,对于自定义类型成员它会调用它的析构函数,也就是说自定义类型成员无论什么情况都会自动调用析构函数
7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构函数就可以用,也不需要显示写析构函数,如MyQueue;但是有资源申请时,一定要自己写析构函数,否则会造成资源泄露,如Stack
8. 一个局部域的多个对象,C++规定后定义的先析构
代码示例1:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:// 默认构造函数--全缺省类型Stack(int n = 4){_arr = (STDataType*)malloc(sizeof(STDataType) * n);if (_arr == nullptr){perror("malloc failure");exit(1);}_capacity = n;_top = 0;}// 析构函数--有资源需要清理--malloc~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = nullptr;_top = _capacity = 0;}private:STDataType* _arr;size_t _top;size_t _capacity;
};// 两个栈实现一个队列
class MyQueue
{// 自定义类型Stack 它会调用Stack的析构函数Stack _pushst;Stack _popst;
};int main()
{Stack st;MyQueue q;return 0;
}
代码示例2:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>typedef char STDataType;
class Stack
{
public:// 默认构造函数--全缺省类型Stack(int n = 4){_arr = (STDataType*)malloc(sizeof(STDataType) * n);if (_arr == nullptr){perror("malloc failure");exit(1);}_capacity = n;_top = 0;}// 析构函数--有资源需要清理--malloc~Stack(){cout << "~Stack()" << endl;free(_arr);_arr = nullptr;_top = _capacity = 0;}void Push(STDataType x){if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)malloc(sizeof(STDataType)* newcapacity);if (tmp == nullptr){perror("malloc failure");exit(1);}_arr = tmp;_capacity = newcapacity;}_arr[_top++] = x;}bool Empty(){return _top == 0;}void Pop(){assert(!Empty());_top--;}STDataType Top(){assert(!Empty());return _arr[_top - 1];}private:STDataType* _arr;size_t _top;size_t _capacity;
};// 使用类Stack实现括号匹配问题bool isvalue(const char* s)
{// 实例化对象st 这时候st会调用构造函数Stack st;while (*s) // 把左括号全部放入栈中{if (*s == '(' || *s == '[' || *s == '{'){st.Push(*s);}else // 取到了右括号{if (st.Empty())// 先判空 如果栈为空 说明没有左括号 返回false{return false;}STDataType top = st.Top();// 取栈顶进行匹配st.Pop();if (*s == ')' && top != '(' || *s == ']' && top != '[' || *s == '}' && top != '{'){return false;}}s++;}// 跳出循环 说明字符串已经来到了'\0' 此时要判断栈是否为空// 注意执行到这里 完成st.Empty之后会调用析构函数清理资源return st.Empty();
}int main()
{/*Stack st;MyQueue q;*/cout << isvalue("[()][]") << endl;cout << isvalue("[(])[]") << endl;return 0;
}
4. 拷贝构造函数
如果构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值 ,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数
拷贝构造的特点:
1. 拷贝构造函数是构造函数的一个重载
2. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器会直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值
3. C++规定自定义类型对象进行拷贝行为必须调用拷贝构造函数,所以这里自定义类型传值和传值返回都会调用拷贝构造完成
4. 如果没有显式定义拷贝构造,编译器回自动生成拷贝构造函数。自动生成的拷贝构造对于内置类型成员变量会完成值拷贝(浅拷贝,一个字节一个字节的拷贝),对于自定义类型成员变量它会调用它自己的拷贝构造
5. 像Date这样的类成员变量都是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝(浅拷贝),所有不需要我们显示实现拷贝构造。但是像Stack这样的类,虽然也是内置类型,但是_a指向了资源,编译器自动生成的拷贝构造函数完成的拷贝是浅拷贝,这不符合我们的要求,所有这里需要我们自己实现拷贝构造函数,即深拷贝(对指向的资源也进行拷贝),像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显示实现。对于MyQueue的拷贝构造,如果一个类显示实现了析构函数并释放资源,那么他就需要显示写出拷贝构造,否则就不需要
6. 传值返回会产生一个临时对象调用拷贝构造,传引用返回,返回的是返回对象的别名,没有产生拷贝。但是如果返回对象是一个当前函数的局部域的局部对象,函数结束时内存就还给操作系统了,即销毁了,这时使用传引用返回是有问题的,这时引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是要确保返回对象在当前函数结束时还是存在的,才能用引用返回
代码示例1:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>class Date
{
public:// 默认构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造函数--这里不显示写出来也可以 编译器默认生成的拷贝构造时浅拷贝够用Date(const Date& d) // 注意这里是传引用调用 如果是传值调用 会发生无穷递归拷贝构造函数问题{_year = d._year;_month = d._month;_day = d._day;}// 当然这里使用指针也是可以实现的// 析构函数可写可不写 这里没有需要释放的资源void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};void Func1(Date d)
{cout << &d << endl;d.Print();
}Date& func2()
{Date tmp(2025, 9.7);tmp.Print();return tmp;
}int main()
{// 会调用Date d1(2025,9,7);// 这里调用func1 实参d1传给形参d 这里会发生拷贝构造函数 把d1的值拷贝给d后再进入func1函数中实现Func1(d1);// 这是拷贝构造 通过同类型的对象初始化构造Date d3(d1);// 这种也是拷贝构造 也会调用拷贝构造函数Date d4 = d1;// 调用func2函数 它返回的是类对象tmp的别名 但是出了func2函数后tmp就销毁了// 这里相当于野指针的访问Date ret = func2();ret.Print();return 0;
}
代码示例2:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>typedef int STDataType;
class Stack
{
public:// 默认构造函数--全缺省Stack(int n = 4){_arr = (STDataType*)malloc(sizeof(STDataType) * n);if (_arr == nullptr){perror("malloc failure");return;}_top = 0;_capacity = n;}// 拷贝构造函数--深拷贝Stack(const Stack& st){_arr = (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (_arr == nullptr){perror("malloc failure");return;}memcpy(_arr, st._arr, sizeof(STDataType) * st._top);_top = st._top;_capacity = st._capacity;}void Push(STDataType x){if (_top == _capacity){int newcapacity = 2 * _capacity;STDataType* tmp = (STDataType*)realloc(_arr , sizeof(STDataType) * newcapacity);if (tmp == nullptr){perror("malloc failure");return;}_arr = tmp;_capacity = newcapacity;}_arr[_top++] = x;}// 析构函数--有资源需要释放~Stack(){free(_arr);_arr = nullptr;_top = _capacity = 0;}private:STDataType* _arr;size_t _top;size_t _capacity;
};class MyQueue
{
public:private:Stack _pushst;Stack _popst;
};int main()
{Stack st1;st1.Push(1);st1.Push(2);// 这里是st1拷贝给st2 如果我们不显示写拷贝构造函数 编译器默认生成的拷贝构造函数为浅拷贝 即st1和st2的_arr指向同一块空间// 当析构时会析构两次 导致程序崩溃Stack st2 = st1;MyQueue mq1;// MyQueue 会自动生成拷贝构造函数 会自动调用Stack的拷贝构造函数// 注意拷贝构造需要深拷贝MyQueue mq2 = mq1;return 0;
}
补充:
为什么拷贝构造函数的形参只能是引用而不能是值?!
对于拷贝构造,如果我们使用的形参是该类类型的值,这里就会涉及到无穷递归,因为当我们把类实例化的对象传入形参,这里就要调用拷贝构造函数了,这时候又会形成了一个新的拷贝构造函数,故最后导致无穷递归这一现象
5. 赋值运算符重载
5.1 运算符重载
1. 当我们要实现类类型对象的运算时,C++规定我们可以使用运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应的运算符重载,如果没有对应的运算符重载,那么编译器就会报错
2. 运算符重载时具有特殊名字的函数,它的名字由operator和后面要定义的运算符构成。和其他函数一样,它也具有返回类型和参数列表以及函数体
3. 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数
4. 如果一个重载运算符函数是成员函数,那么它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个
5. 以上5个运算符不能重载(需要注意并且记住):[.* 、:: 、sizeof 、?: 、.]
6. 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,比如:int operator+(int x , int y) ,我们只能设置类实例化对象之间的运算,而不能去改变已经规定好的内置类型的运算规则
7. 一个类需要重载哪些运算符,我们是要考虑它是否是有意义的,比如Date类重载operator-就有意义,但是重载operator+就没有意义
8. 重载++运算符时,我们在C中知道有前置++和后置++,在运算符重载中它们都叫operator
++,为了区分是前置还是后置,C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,以便区分,当然这里的参数int传多大都无所谓,只是为了区分
9. 重载<<和>>时,需要重载为全局函数,因为如果是重载为成员函数时,this指针默认占去了第一个形参的位置,第一个形参对应着左侧运算对象,那么调用时就变成了 对象<<cout,这不符合我们的习惯和可读性,因此我们应该重载为全局函数,把ostream/istream放在第一个形参的位置,第二个形参位置放类类型对象
代码示例(.*运算符示例)1:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>class A
{
public:void func(){cout << "A::func()" << endl;}
};// 成员函数指针类型
typedef void(A::* PF)();int main()
{// C++规定成员函数要加&才能取到函数地址PF pf = &A::func;// 类实例化对象A obj;// 对象调用成员函数指针时 要使用 .* 运算符(obj.*pf)();return 0;
}
代码示例2:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>class Date
{
public:// 默认构造函数--全缺省Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//private:int _year;int _month;int _day;
};// 用const修饰 防止权限放大问题
// 用引用传参 减少拷贝构造 提高效率
bool operator==(const Date& d1, const Date& d2)
{// 这里是在全局域 如果我们类类型的内置类型是私有的 在这里是访问不了的// 故我们需要使用访问限定符public 允许类外访问return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}int main()
{Date d1(2025, 9, 8);Date d2(2025, 9, 10);// 运算符重载可以显示调用operator==(d1, d2);// 编译器会转换成 operator==(d1,d2)d1 == d2;cout << (d1 == d2) << endl;return 0;
}
代码示例3:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>class Date
{
public://默认构造函数--全缺省Date(int year = 1 , int month = 1 , int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}// 运算符重载--判断是否等于bool operator==(const Date& d){return _year == d._year && _month == d._month && _day == d._day;}// 前置++ 没有形参 并且返回的是引用Date& operator++(){cout << "前置++" << endl;(*this)._day++;return *this;}// 后置++ 为了区分有一个int类型的参数 传不传值都可以 并且只能是返回值Date operator++(int){Date tmp;cout << "后置++" << endl;(*this)._day++;return tmp;}private:int _year;int _month;int _day;
};int main()
{Date d1(2025, 9, 8);Date d2(2025, 9, 9);// 运算符重载函数显示调用// 注意这里隐式传递了d1的地址给this指针d1.operator==(d2);// 编译器会转化成 d1.operator==(d2)d1 == d2;// 编译器会转化成 d1.operator++()++d1;// 编译器会转化为 d1.operator++(int)d1++;return 0;
}
5.2 赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里注意要和拷贝构造函数区分开,拷贝构造针对的是用一个已经存在的对象初始化给另一个即将要创建的对象
赋值运算符重载的特点:
1. 赋值运算符重载其实也是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型引用,否则传值传参会有拷贝
2. 赋值运算符是有返回值的,并且返回值为当前类类型引用,引用返回可以提高效率,有返回值的目的是为了支持连续赋值场景
3. 如果没有显示实现时,编译器会自动生产默认的赋值运算符重载,默认赋值运算符重载行为和默认拷贝构造函数类似,对内置类型成员完成浅拷贝(一个一个字节地拷贝),对自定义类型成员变量会调用它的赋值重载函数
4. 像Date这样的类成员都是内置类型的并且没有指向资源,编译器自动生成的默认赋值运算符重载函数就已经足够使用,所有我们不需要显示地写出赋值重载函数。但是像Stack这样的类,虽然它的类类型成员都是内置类型,但是它有指向资源,如果只是使用编译器默认生成的赋值重载函数是不够的,因为它完成的只是浅拷贝,这不符合我们的要求,故这里我们需要显示地实现赋值重载函数,完成深拷贝。像MyQueue这样的类,它的类类型成员都是自定义类型成员,我们不显示写赋值重载时,编译器默认生成的赋值重载函数会自动地调用Stack的赋值运算符重载函数。
5. 这里有一个小技巧,当一个类显示地写出了析构函数并释放资源,故它就需要我们显示地写出赋值运算符重载,和前面的拷贝构造函数一样,都是需要显示地写出来,否则的话就不需要
代码示例:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>class Date
{
public:// 默认构造函数--全缺省Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造函数Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}// 赋值运算符重载 为了连续赋值返回值为传引用返回 并且减少拷贝// d1 = d2// 对于Date类类型 不写赋值运算符重载也是可以的 编译器自动生成的就够用Date& operator=(const Date& d){// 自己给自己赋值 我们直接跳过 返回自己就好了if (this != &d){_year = d._year;_month = d._month;_day = d._month;}// d1 = d2 的结果应该为d1 所有我们返回d1 即 *thisreturn *this;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2025, 9, 9);// 拷贝构造Date d2(d1);// 拷贝构造--用一个已经存在的对象初始化即将要创建的对象Date d3 = d1;// 赋值运算符重载--两个已经存在的对象进行赋值 结果为d1d1 = d3;return 0;
}
5.3 日期类实现
以我们当前的知识,只能实现日期类的部分功能,剩下的功能留到我们有足够的知识后再进行补充!!!
代码示例:
#define _CRT_SECURE_NO_WARNINGS
#include "Date.h"// 判断日期是否非法bool Date::CheckDate()
{if (_month < 1 || _month > 12)return false;if (_day > GetMonthDay(_year, _month))return false;return true;
}// 全缺省的构造函数Date::Date(int year , int month , int day )
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "非法日期" << endl;}
}// 拷贝构造函数// d2(d1)Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& Date::operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}// 析构函数Date::~Date()
{_year = _month = _day = 1;
}// 打印函数
void Date::Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}// 日期+=天数Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day = day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month > 12){_year++;_month = 1;}}return *this;
}// 日期+天数Date Date::operator+(int day)
{Date tmp(*this);tmp += day;return tmp;
}// 日期-=天数Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}// 日期-天数Date Date::operator-(int day)
{Date tmp(*this);tmp -= day;return tmp;
}// 前置++Date& Date::operator++()
{*this += 1;return *this;
}// 后置++Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}// 前置--Date& Date::operator--()
{*this -= 1;return *this;
}// 后置--Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}// >运算符重载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);
}// <运算符重载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 !(*this > d);
}// !=运算符重载bool Date::operator != (const Date& d)
{return !(*this == d);
}// 日期-日期 返回天数int Date::operator-(const Date& d)
{Date min = *this;Date max = d;int flag = -1;if (*this > d){max = *this;min = d;flag = 1;}int day = 0;while (min != max){++min;++day;}return day * flag;
}// 流插入
ostream& operator<<(ostream& out, const Date d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}// 流提取
istream& operator>>(istream& in, Date& d)
{cout << "请输入年月日";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "非法日期" << endl;}return in;
}