【C++】类和对象
【C++】类和对象
文章目录
- 【C++】类和对象
- 一、类的定义
- 1.1 分类
- 1.2 访问限定符
- 1.3 类的作用域
- 1.4 类的实例化
- 二、封装
- 三、this指针
- 四、默认成员函数
- 4.1 构造函数
- 4.2 析构函数
- 4.3 拷贝构造
- 4.4.0 运算符重载
- 4.4.1 赋值重载
- 4.4 取地址重载
- 4.5 const取地址操作符重载
- 五、const成员
- 六、初始化
- 6.1 构造函数赋值
- 6.2 初始化列表
- 6.3 explicit关键字
- 七、 static成员
- 八、友元
- 8.1 友元函数
- 8.2 友元类
- 九、内部类
- 补充:匿名对象
- 补充:编译器优化
- 练习示例:日期类
- 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看;
- 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识;
- 文章多为学习笔记,以综述学习的重点为主,可能有一些细节没有提及或把握不到位,感谢理解;
一、类的定义
class className {// 类体:由成员函数和成员变量组成
};//注意分号
- class为定义类的关键字
- ClassName为类的名字
- {}中为类的主体
- 注意类定义结束时后面分号。
- 类中的元素称为类的成员:类中的数据称为类的属性或者成员变量;
- 类中的函数称为类的方法或者成员函数。
1.1 分类
-
声明和定义都在类体中,如果直接在类体定义中,编译器会将类内函数作为内联函数。因此短小函数,适合作为内联函数,适合在类内定义
struct Student{void Set(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gender, gender);_age = age;}void Print(){cout << _name << " " << _gender << " " << _age << endl;}char _name[20];char _gender[3];int _age; };
-
声明和定义分离:声明放在头文件中,定义放在源文件中
//.h #include<iostream> using namespace std; struct Student{void Set(const char* name, const char* gender, int age);void Print();char _name[20];char _gender[3];int _age; };
注意加上
Student::
,表示为Student类中的成员函数//.cpp #include"test.h" void Student::Set(const char* name, const char* gender, int age){strcpy(_name, name);strcpy(_gender, gender);_age = age; } void Student::Print(){cout << _name << " " << _gender << " " << _age << endl; }
1.2 访问限定符
包括三种:public(公有)、protected(保护)、private(私有)
说明:
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public
- 实际使用上,不期望使用默认,而是自己约束
struct
和class
的区别:
- C++需要兼容C语言,所以C++中struct可以当成结构体去使用。
- 另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
1.3 类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 ::
作用域解析符指明成员属于哪个类域。
1.4 类的实例化
声明:类的声明只是限定了类的成员变量和成员函数,相当于一个模型,并没有分配实际内存空间来存储
实例化:用类创建对象的过程,实例化出的对象是需要占用内存空间的
存储分布:成员变量存储在对象中,而成员函数不是,放在了代码段中。因为每个对象的成员变量是不一样的,需要独立存储,而成员函数是公共的,如果放在对象中就会浪费内存。
注意:如果实例化的类仅有成员函数或是一个空类,实例化大小就是1B,用于占位,不存储有效数据,标识对象被实例化定义出来了。
二、封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,对于外部用户内部细节是透明的。
三、this指针
作用:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
显示使用
注意
-
this指针的类型:类类型* const 或者 类类型* (这里没太弄清楚,应该是和成员函数的类型相关)
-
只能在“成员函数”的内部使用
-
存在于this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针。一般情况存在于栈中,而对于vs编译器通过
ecx
寄存器自动传递,不需要用户传递。 -
编译器工作:增加一个隐藏的指针参数、用this指针访问成员变量
void Init(int year, int month, int day){_year = year;_month = month;_day = day; } void Init(int year, int month, int day)const {_year = year;_month = month;_day = day; }
void Init(Date* this,int year, int month, int day){this->_year = year;this->_month = month;this->_day = day; } void Init(Date* const this,int year, int month, int day){this->_year = year;this->_month = month;this->_day = day; }
-
空指针问题:
Date* ptr = nullptr;
,若将类的指针变量赋予一个空指针,可能会出现程序崩溃的问题。问题的发生在于编译器是否对于空指针有必要进行解引用。从以下代码进行分析,对于
ptr->func();
与(*ptr).func();
两句代码,是直接访问进程地址空间中代码段的数据,因此不需要在栈中进行解引用,故而可以正常运行;对于ptr->Init(2022, 2, 2);
,需要访问类的成员函数,需要对nullptr
进行解引用,而无法对空指针进行解引用,因此程序会崩溃。class Date{ public:// 定义void Init(int year, int month, int day){cout << this << endl;this->_year = year;this->_month = month;this->_day = day;}void func(){cout << this << endl;cout << "func()" << endl;} //private:int _year; // 声明int _month;int _day; };int main(){Date d;d.Init(2022, 2, 2);Date* ptr = nullptr;//ptr->Init(2022, 2, 2); // 运行崩溃ptr->func(); // 正常运行,(*ptr).func(); // 正常运行,return 0; }
可以从汇编的角度去理解,更为清晰。对应操作为,传递一个this指针,然后对于call的调用,是直接访问Date的函数func,而不需要进行解引用。
ptr->func(); // 正常运行, 00ED585A 8B 4D E0 mov ecx,dword ptr [ptr] 00ED585D E8 E5 BB FF FF call Date::func (0ED1447h) (*ptr).func(); // 正常运行, 00ED5862 8B 4D E0 mov ecx,dword ptr [ptr] 00ED5865 E8 DD BB FF FF call Date::func (0ED1447h)
四、默认成员函数
六个默认成员函数
- 初始化和销毁:构造函数和析构函数
- 拷贝复制:拷贝构造和赋值重载(复制重载)
- 取地址重载与const取地址操作符重载
4.1 构造函数
定义:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
使用:
class Date{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
默认构造:
-
编译器会自动生成一个默认构造函数,但当实现任一个构造函数后,编译器将不会自动生成一个默认构造函数
-
默认生成构造函数,内置类型成员不会被处理,自定义类型成员会调用相应的构造函数
-
如此规则是存在缺陷的,因此在C++11中,可以在声明位置给予缺省值
class Date{ public:Date(){} private:int _year = 2023;int _month = 2;int _day = 1; };
注意:
-
函数名与类名相同
-
没有返回值
-
在对象实例化时,编译器自动调用构造函数
-
构造函数可以重载,可以有多个构造函数。
-
一般条件下可以提供缺省参数,调用时要注意构造函数调用不明确的问题。
-
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(默认构造函数:不传参就可以调用的函数,建议每个类都提供一个)
class Date{ public:Date(int year = 2023, int month = 2, int day = 1){_year = year;_month = month;_day = day;} private:int _year;int _month;int _day; };
4.2 析构函数
定义:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。 析构函数名是在类名前加上字符 ~
。
使用:
class Date{
public:Date(){cout << "Date()" << endl;}~Date() {cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
默认析构函数:
- 编译器会自动生成一个默认析构函数,但当实现任一个析构函数后,编译器将不会自动生成一个默认析构函数
- 默认生成析构函数,内置类型成员不会被处理,自定义类型成员会调用相应的析构函数
注意:
- 无参数无返回值
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 析构顺序类似于栈,后定义的先析构
4.3 拷贝构造
定义:只有单个形参,该形参是对本类类型对象的引用(常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
原因:cpp中内置类型编译器会直接拷贝,而自定义类型需要调用拷贝构造。自定义类型无法承担直接拷贝的责任,比如对于一块开辟了的堆区空间,如果拷贝后,两个实例退出作用域,会析构两次,从而发生错误。
使用:如果自己实现了析构函数释放空间(或者说是实现了资源管理),就需要实现拷贝构造
Date(const Date& date) {_year = date._year;_month = date._month;_day = date._day;;
}
Date d2(d1);
Date d3 = d1;
默认生成的拷贝构造:默认拷贝无论对于内置类型是浅拷贝,自定义类型会调用它的拷贝函数,而有时这种拷贝方式可能会造成一些错误,需要重新定义拷贝函数。
注意:
-
无穷递归问题:拷贝构造函数不可以是传值传参
Date(Date date) {_year = date._year;_month = date._month;_day = date._day;; }
原因:
Date(Date date)
传值传参需要拷贝出一个临时变量,而cpp自定义类型需要调用拷贝构造,从而会调用Date(Date date)
,此时成了一个无穷递归的问题。因此必须要引用传参来打破这个无穷递归问题。 -
拷贝方向:最好加上
const
,这样就不会出现反方向拷贝了 -
区分
Date(const Date& date)
与Date(const Date* date)
:前者为一个拷贝构造函数,而后者为一个构造函数
4.4.0 运算符重载
作用:增强程序的可读性
使用:
返回值类型 operator操作符(参数列表){//……
}
注意:
-
运算符重载和函数重载:
函数重载:支持函数名相同,参数不同的函数可以同时使用
运算符重载:自定义类型的对象可以使用运算符
-
若果是两个操作数,则参数列表:
(左操作数, 右操作数)
-
不能通过连接其他符号来创建新的操作符
-
重载操作符必须有一个类类型或者枚举类型的操作数
-
用于内置类型的操作符,其含义不能改变
-
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的,操作符有一个默认的形参this,限定为第一个形参
-
.*
、::
、sizeof
、?:
、.
以上5个运算符不能重载 -
后置
++
与前置++
的区别在于,后置需要传递一个int
参数使二者区分
4.4.1 赋值重载
classType& operator=(const classType& className){if(this == &className){return *this;}//……return *this;
}
注意:
- 参数类型:包含隐含的
this
指针,另外一个参数最好使用引用,从而减少拷贝带来的开销 - 返回值:使用引用返回,可以连续调用赋值函数
- 赋值对象为自己:检查是否出现给自己赋值的情况,使用判断逻辑判断,减少拷贝开销
- return:返回
*this
- 区分拷贝构造和赋值:
classType className1 = className2
为拷贝构造,赋值重载是针对两个已经实例化的对象,拷贝构造是初始化实例的过程。
默认赋值函数:完成对象按字节序的值拷贝
4.4 取地址重载
以Date类示例来编写,实际上没有太多的价值
Date* operator&(){return this ;}
4.5 const取地址操作符重载
以Date类示例来编写,实际上没有太多的价值
const Date* operator&()const{return this ;}
五、const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。在内部不改变成员变量的成员函数时,最好加上const
。
void Print() const{cout << "Print()const" << endl;
}
//编译器处理后
void Print(const Date* this) {cout << "Print()const" << endl;
}
实例:如果调用d2.Print()
传递的this
指针不是const
,将会导致权限扩大的问题,编译器将会报错,故需要在Print()
设置为const
成员函数
class Date{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;}void Print() const{cout << "Print() const" << endl;}
private:int _year; int _month; int _day;
};
int main(){Date d1(2023, 1, 1);d1.Print();//Print()const Date d2(2023, 1, 1);d2.Print();//Print() const
}
六、初始化
6.1 构造函数赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
Date::Date(int year, int month, int day) {if ((month <= 0 || month > 12) || (day < 0 || day > GetMonthDay(year, month))) {cout << "日期非法" << endl;}_year = year;_month = month;_day = day;
}
注:
- 类的初始化可以分为整体的初始化和各个成员变量的初始化
- 构造函数调用不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,因为初始化只能初始化一次,而构造函数体内可以多次赋值。
6.2 初始化列表
作用:由于const
或者引用必须要在定义出初始化需要给每一个成员变量找一个定义变量的位置。同时调用构造函数时对自定义类型只会调用其默认构造函数。
使用:在构造函数中,冒号开始,逗号分割
class Date{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
注意:
- 哪个对象调用构造函数,初始化列表是所有成员变量定义的位置
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 不管是否显示定义初始化列表,编译器会对每个变量通过初始化列表定义就行初始化。如果未显示定义,则可能会是随机值或者是缺省值
- 三类成员必须在初始化列表初始化:
const
、引用、缺少默认构造函数的对象 - 建议:所有的成员能在初始化列表初始化就在初始化列表初始化
- 初始化列表的初始顺序:不是初始化列表出现顺序,而是声明顺序
- 对于构造函数来说,先走初始化列表,再走函数体类,因为初始化列表是成员的定义
6.3 explicit关键字
作用:对于单个参数的构造函数,还具有类型转换的作用,explicit修饰构造函数,将会禁止单参构造函数的隐式转换。
实例:
class Date {
public://Date(int year)// : _year(year)//{}explicit Date(int year): _year(year){}
private:int _year;
};
补充:多参数的c++构造函数的隐式类型转换
class Date {
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};int main() {Date d1 = { 1 , 1, 1 };d1.Print();
}
七、 static成员
作用:声明为static的类成员称为类的静态成员:
- 用static修饰的成员变量,称之为静态成员变量
- 用static修饰的成员函数,称之为静态成员函数。
使用:
-
静态的成员变量一定要在类内声明,类外进行初始化
class A { private:static int count; // 声明 };int A::count = 0; // 定义初始化
-
静态成员变量访问
-
类静态成员,可用
类名::静态成员
或者对象.静态成员
来访问,但是静态成员和类的普通成员一样,也有public
、protected
、private
访问级别。 -
类内函数
class A { private:static int count; public:int GetCount() {return count;} };
-
静态成员函数
-
-
静态成员函数:没有传递this指针,无需对对象进行解引用,可以不初始化实例进行调用。同时,静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
class A { private:static int count; public:static int GetCount() {return count;} };int A::count = 0;int main() {A a1;cout << a1.GetCount() << endl;cout << A::GetCount() << endl;A *a2 = nullptr;cout << a2->GetCount() << endl; }
注:
- 静态成员为所有类对象所共享,不属于某个具体的实例
- 静态成员变量必须在类外定义,定义时不添加static关键字
八、友元
作用:提供了一种突破封装的方式,有时提供了便利
分类:友元函数和友元
注意:友元会增加耦合度,破坏了封装,所以友元不宜多用
8.1 友元函数
作用:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend
关键字。
注:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰,因为
const
修饰的是*this
,只有非静态的成员函数才能用const
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
实例:
class Date {
public://友元friend ostream& operator<<(ostream& out, const Date& date);friend istream& operator>>(istream& in, Date& date);//.....
private:int _year;int _month;int _day;
};inline ostream& operator<<(ostream& out, const Date& date) {out << date._year << "/" << date._month << "/" << date._day;return out;
}inline istream& operator>>(istream& in, Date& date) {in >> date._year >> date._month >> date._day;return in;
}
8.2 友元类
作用:友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
注意:
- 友元关系是单向的,不具有交换性
- 友元关系不能传递
- 友元关系不能继承
实例:
class Time{//友元类friend class Date;
public:Time(int hour, int minute, int second): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};class Date{
public:Date(int year = 2024, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTime(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};
九、内部类
定义:如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。
特点:
- 内部类是独立的类,不属于外部类,不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。内部类可以定义在外部类的public、protected、private都是可以的,访问时也需要考虑到访问级别。
- 内部类只受外部类域的限制。
- 内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元。
- 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
sizeof(外部类)
,和内部类没有任何关系。
实例:
class Time{
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}// 内部类class Date {public:Date(int year = 2024, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void Print(const Time& t) {cout << _year << "/" << _month << "/" << _day << " " << t._hour << ":" << t._minute << ":" << t._second << " id:" << _creator << endl;}private:int _year;int _month;int _day;};
private:int _hour;int _minute;int _second;static string _creator;
};
string Time::_creator = "Jerry";int main() {Time::Date d1;d1.Print(Time());
}
补充:匿名对象
作用:有时需要对类内函数进行调用时,如果重新定义初始化会显得麻烦,因此引用了匿名对象,匿名对象生命周期只在代码的一行,较为方便。
private:int _a;
public:A(int a):_a(a){}
};class Solution {
public:int my_Solution() {cout << "my_Solution" << endl;return 1;}~Solution(){cout << "~Solution()" << endl;}
};// 使用实例
A f(){int ret = Solution().my_Solution();return A(ret);
}int main(){Solution s;// 匿名对象Solution();cout << s.my_Solution() << endl;// 使用实例cout << Solution().my_Solution() << endl;f();
}
补充:编译器优化
总结:
- 接收返回值对象,尽量用拷贝构造的方式接收,不要赋值接收
- 函数中返回对象时,尽量返回匿名对象
- 函数传参:能使用引用传参就使用引用传参
练习示例:日期类
//Date.ch
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;class Date {
public://友元friend ostream& operator<<(ostream& out, const Date& date);friend istream& operator>>(istream& in, Date& date);int GetMonthDay(int year, int month);//知识点://1.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个//2.一般条件下可以提供缺省参数,调用时要注意构造函数调用不明确的问题//3.缺省参数不能在函数声明和定义中同时出现。若声明和定义进行分离,需要使用缺省参数,必须在声明中给出。Date(int year = 2024, int month = 1, int day = 14);// 析构函数~Date();// 拷贝构造//易错点:拷贝构造函数不可以是传值传参;最好加上`const`,这样就不会出现反方向拷贝了Date(const Date& date);void Print();//运算符重载bool operator==(const Date& date);bool operator!=(const Date& date);bool operator>(const Date& date);bool operator<(const Date& date);bool operator>=(const Date& date);bool operator<=(const Date& date);Date& operator+=(const int day);Date operator+(const int day);Date& operator-=(const int day);//知识点:函数重载Date operator-(int day);int operator-(const Date& date);//自增 自减Date& operator++();// 后置,需要设置一个整型参数Date operator++(int);Date& operator--();// 后置,需要设置一个整型参数Date operator--(int);private:int _year;int _month;int _day;
}; 流插入
//ostream& operator<<(ostream& out, const Date& date);
流提取
//istream& operator>>(istream& in, Date& date);inline ostream& operator<<(ostream& out, const Date& date) {out << date._year << "/" << date._month << "/" << date._day;return out;
}inline istream& operator>>(istream& in, Date& date) {in >> date._year >> date._month >> date._day;return in;
}
//Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"int Date::GetMonthDay(int year, int month){assert(!(month <= 0 || month > 12));int monthArray[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {monthArray[2] = 29;}return monthArray[month];
}Date::Date(int year, int month, int day) {if ((month <= 0 || month > 12) || (day < 0 || day > GetMonthDay(year, month))) {cout << "日期非法" << endl;}_year = year;_month = month;_day = day;//cout << "Date(int year, int month, int day)" << endl;
}Date::~Date() {//cout << "~Date()" << endl;
}Date::Date(const Date& date) {_year = date._year;_month = date._month;_day = date._day;//cout << "Date(const Date& date) " << endl;
}void Date::Print() {cout << _year << "/" << _month << "/" << _day << endl;
}bool Date::operator==(const Date& date) {if (_year == date._year && _month == date._month && _day == date._day) {return true;}else return false;
}bool Date::operator!=(const Date& date) {return !(*this == date);
}bool Date::operator<(const Date& date) {if (_year < date._year) {return 1;}else if (_year == date._year && _month < date._month) {return 1;}else if (_year == date._year && _month == date._month && _day < date._day) {return 1;}return 0;
}
bool Date::operator<=(const Date& date) {return (*this < date) || (*this == date);
}
bool Date::operator>(const Date& date) {return !(*this <= date);
}
bool Date::operator>=(const Date& date) {return (*this < date);
}Date& Date::operator+=(const int day) {if (day < 0) {*this -= (-day);return *this;}else {_day += day;while (_day > GetMonthDay(_year, _month)) {_day -= GetMonthDay(_year, _month);_month++;if (_month == 13) {_year++;_month = 1;}}return *this;}
}
// 函数复用
Date Date::operator+(const int day) {Date temp = *this;temp += day;return temp;
}Date& Date::operator-=(const int day) {if (day < 0) {*this += -day;return *this;}_day -= day;while (_day <= 0) {//借上个月 需要对月先处理_month--;if (_month == 0) {_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day) {Date temp = *this;temp -= day;return temp;
}int Date::operator-(const Date& date) {Date max = *this;Date min = date;int flag = 1;if (*this < date) {max = date;min = *this;flag = -1;}int gap = 0;while (min != max) {++min;++gap;}return gap * flag;
}Date& Date::operator++() {*this += 1;return *this;
}Date Date::operator++(int) {Date temp = *this;*this += 1;return temp;
} Date& Date::operator--() {*this -= 1;return *this;
}
// 后置,需要设置一个整型参数
Date Date::operator--(int) {Date temp = *this;*this -= 1;return temp;
}