【C++】类和对象(下篇)

类和对象

  • 一、初始化列表
    • [1. 构造函数体赋值](#1. 构造函数体赋值)
    • [2. 初始化列表](#2. 初始化列表)
  • [二、explicit 关键字](#二、explicit 关键字)
    • [1. 单参数构造函数的隐式类型转换](#1. 单参数构造函数的隐式类型转换)
    • [2. explicit 关键字](#2. explicit 关键字)
    • [3. 多参数的隐式类型转换(C++11)](#3. 多参数的隐式类型转换(C++11))
  • [三、static 成员](#三、static 成员)
  • 四、友元
    • [1. 友元函数](#1. 友元函数)
    • [2. 友元类](#2. 友元类)
  • 五、内部类
  • 六、匿名对象
  • 七、日期类

在前两期的 类和对象(上篇)类和对象(中篇) 我们学习了有关类和对象的大部分知识,这一篇我将会带大家完善这方面的有关知识,并完成我们日期类的完整实现。

一、初始化列表

1. 构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

		class Date
		{
		public:
			Date(int year, int month, int day)
			{
				_year = year;
				_month = month;
				_day = day;
			}
		
		private:
			int _year;
			int _month;
			int _day;
		};	

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化 ,构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

2. 初始化列表

所以我们引入一个概念:初始化列表初始化列表是每个对象的成员定义的地方,不管我们写不写,每个成员都要走初始化列表。

其用法是,以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 "成员变量" 后面跟一个放在括号中的初始值或表达式。

例如以下日期类:

		// 初始化列表
		class Date
		{
		public:
			Date(int year, int month, int day)
				: _year(year)
				, _month(month)
				, _day(day)
			{}
		
		private:
			int _year;
			int _month;
			int _day;
		};

使用初始化列表需要注意的:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。

  2. C++11 支持在成员声明处给缺省值,这个缺省值也会给初始化列表,如果初始化列表没有显示给值,就用这个缺省值;如果显示给值了,就不用这个缺省值。

     	// 初始化列表
     	class Date
     	{
     	public:
     		Date(int year , int month , int day)
     			: _year(year)
     			, _month(month)
     			,_day(day)
     		{}
     	
     		void Print()
     		{
     			cout << _year << "年" << _month << "月" << _day << "日" << endl;
     			cout << _x << endl;
     		}
     	
     	private:
     		int _year ;
     		int _month;
     		int _day ;
     	
     		int _x = 10;
     	};
    

例如以上代码,_x 没有显式给值,但是它在声明处给了缺省值,这个缺省值最终也会给初始化列表定义 _x。

  1. 类中包含以下成员,必须放在初始化列表位置进行初始化:
  • 引用成员变量
  • const 成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

例如以下这段代码:

		class A
		{
		public:
			A(int a)
				:_a(a)
			{}
		private:
			int _a;
		};
		
		class Date
		{
		public:
			
			Date(int year, int month, int day, int& i)
				: _year(year)
				, _month(month)
				,_aa(1)
				,_refi(i)
			{
				// 赋值,并不是初始化
				_day = day;
			}
		
		private:
			// 每个成员声明
			int _year;  
			int _month;
			int _day;
			
		
			// 必须定义时初始化
			const int _x = 10; //const成员变量;此处是给缺省值,也可以在初始化列表中初始化
			int& _refi; 	   // 引用成员变量
			A _aa;	           //自定义成员变量(没有默认构造)
		};
		
		int main()
		{
			int n = 0;
			Date d1(2023, 7, 28, n);
		
			return 0;
		}

const 成员变量必须定义时初始化是因为一旦初始化就不能被修改;引用成员变量 也一样,引用成员变量是一个变量的别名,需要定义的时候就初始化;因为 _aa 是个自定义成员变量,而且它没有默认的构造函数(因为它的构造函数中没有给缺省值,所以无法调到),所以也要在定义的时候初始化;

所以以上三种类型必须在定义的时候初始化,而初始化列表就是每个成员定义的地方,所以我们要在初始化列表给它们初始化的值,也可以在声明处给缺省值(C++11支持),例如以上代码中 const 成员变量 _x 就是给了缺省值,但缺省值最终也会给初始化列表初始化。

  1. 能用初始化列表就用初始化初始化列,有些场景还是需要初始化列表和函数体混着用。

例如 Stack 类,以下场景:

		typedef int DataType;
		
		class Stack
		{
		public:
			Stack(size_t capacity = 4)
				:_array((DataType*)malloc(sizeof(DataType)* capacity))
				, _size(0)
				, _capacity(capacity)
			{
				if (_array == NULL)
				{
					perror("malloc申请空间失败!!!");
					return;
				}
		
				memset(_array, 0, sizeof(DataType) * _capacity);
			}
		
		private:
			DataType* _array;
			int _size;
			int _capacity;
		};
  1. 对于自定义类型成员变量,一定会先使用初始化列表初始化。

例如以下代码,时间类显式写了构造函数并用初始化列表初始化;

		class Time
		{
		public:
			Time(int hour = 0)
				:_hour(hour)
			{}
		
		private:
			int _hour;
		};
		
		class Date
		{
		public:
			Date(int day)
				:_day(day)
			{}
		
		private:
			int _day;
			Time _t;
		};
		
		int main()
		{
			Date d(1);
		}

我们在日期类中声明时间类的自定义类型,但是不在日期类的初始化列表初始化它,我们观察 _t 对象中的成员变量的值被初始化为什么:

通过调试窗口可以观察到,它会调它的构造函数并走它的初始化列表,并使用缺省值 0 初始化;

那么我们在日期类的初始化列表给它初始化呢?我们也不妨试试,结果如下:

如上图,我们在日期类的初始化列表中给它初始化,它还是会走时间类的初始化列表,但是没有用缺省值进行初始化,而是用我们给定的值进行初始化。

  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

例如以下的日期类,我们观察 _a1 和 _a2 的结果会是什么呢?

		class A
		{
		public:
			A(int a)
				:_a1(a)
				, _a2(_a1)
			{}
		
		private:
			int _a2;
			int _a1;
		};

		int main() 
		{
			A aa(1);
		}

我们对它进行打印观察:

结果是 1随机值 ,就是因为初始化列表是按照声明的顺序进行初始化,先对 _a2 进行初始化,此时 _a1 还没有被初始化,所以用 _a1_a2 进行初始化是随机值;然后再对 _a1 进行初始化,此时 _a1 被初始化为 1.

二、explicit 关键字

1. 单参数构造函数的隐式类型转换

构造函数不仅可以构造与初始化对象,对于单个参数 或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。

我们在以前也学过隐式类型转换,例如一个 int 类型的值赋给 double 类型,中间会发生隐式类型转换;同样道理,对象的构造函数也会完成隐式类型的转换。

单个参数构造函数的隐式类型转换,例如以下 A 类:

		class A
		{
		public:
				A(int i)
				:_a(i)
			{
				cout << "A(int i)" << endl;
			}
		
			A(const A& aa)
				:_a(aa._a)
			{
				cout << "A(const A& aa)" << endl;
			}
		
			~A()
			{
				cout << "~A()" << endl;
			}
		
		private:
			int _a;
		};

我们可以对对象进行这样的实例化:A aa1(1); 这是我们已经知道的,但是我们还可以这样对对象实例化:A aa2 = 2; ,这就是单参数构造函数的隐式类型转换。

对于我们的理解,A aa2 = 2; 应该是用 2 调用 A 构造函数生成一个临时对象,再用这个对象去拷贝构造 aa2,但是编译器会优化,优化用 2 直接构造对象 aa2 ,例如以下代码,我们对对象实例化观察对象调用了哪些函数:

		int main()
		{
			A aa1(1);
			cout << "-----------------------------------" << endl;
		
			A aa2 = 2;
			cout << "-----------------------------------" << endl;
		
			return 0;
		}

结果如下图:

我们观察可以看出,它们两个是等价的,所以说明了编译器对 aa2 对象的实例化进行了优化。

对于除第一个参数无默认值其余均有默认值,例如以下日期类:

		class Date
		{
		public:
		
			// 虽然有多个参数,但是创建对象时后两个参数可以不传递
			Date(int year, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
			{
				cout << "Date(int year, int month = 1, int day = 1)" << endl;
			}
			
			Date& operator=(const Date& d)
			{
				cout << "Date& operator=(const Date& d)" << endl;
		
				if (this != &d)
				{
					_year = d._year;
					_month = d._month;
					_day = d._day;
				}
				return *this;
			}
		
		private:
			int _year;
			int _month;
			int _day;
		};

我们对对象进行这样的实例化:Date d1(2022); 这对我们来说也没问题,但是我们这样 d1 = 2023; 呢,编译器又会做什么优化处理呢?我们执行观察一下:

我们可以观察到,在 d1 = 2023; 的时候,我们用一个整型变量给日期类型对象赋值, 实际编译器背后会用 2023 构造一个无名对象,最后用无名对象给 d1 对象进行赋值,这也是编译器的单参数构造函数的隐式类型转换。

2. explicit 关键字

对于上述代码可读性不是很好,所以C++中可以用 explicit 修饰构造函数,将会禁止构造函数的隐式转换。

例如上述的日期类中,我们在构造函数前用 explicit 关键字修饰,那么d1 = 2023; 这段代码就不会发生单参数构造函数的隐式类型转换,例如:

			explicit Date(int year, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
			{}

编译器会报以下错误:

同理,我们对上面的 A 类的构造函数也用 explicit 关键字修饰,如:

			explicit A(int i)
				:_a(i)
			{
				cout << "explicit A(int i)" << endl;
			}

我们对对象进行 A aa2 = 2; 的实例化时,编译器也会报类似的错误:

因为 explicit 修饰构造函数,禁止了单参构造函数类型转换的作用。

3. 多参数的隐式类型转换(C++11)

在 C++11 中,C++11 支持多参数的隐式类型转换,例如以下的 B 类:

		class B
		{
		
		public:
			B(int b1, int b2)
				:_b1(b1)
				, _b2(b2)
			{
				cout << "B(int b1, int b2)" << endl;
			}
		
			B(const B& b)
				:_b1(b._b1)
				,_b2(b._b2)
			{
				cout << "B(const B& b)" << endl;
			}
		
		private:
			int _b1;
			int _b2;
		};

我们有以下的实例化对象:

		int main()
		{
			B bb1(1, 1);
			B bb2 = { 2, 2 };
			const B& ref2 = { 3,3 };
		
			return 0;
		}

其中 B bb1(1, 1); 是正常的实例化对象,没有进行隐式类型的转换;而 B bb2 = { 2, 2 };const B& ref2 = { 3,3 }; 则进行了多参数的隐式类型转换,我们执行程序观察结果:

如上图,三个实例化都是只是调用了构造函数,说明编译器对其进行了优化。

当我们对构造函数加上 explicit 关键字后,编译器就无法对 B bb2 = { 2, 2 };const B& ref2 = { 3,3 }; 进行多参数的隐式类型转换了。

三、static 成员

概念:static 修饰类的成员变量,称之为静态成员变量 ;用 static 修饰的成员函数,称之为静态成员函数静态成员变量 一定要在类外进行初始化。

例如我们需要统计一个类中创建出了多少对象,并计算正在使用的还有多少个对象;我们可能会想到以下的思路:

		// 累积创建了多少个对象  
		int n = 0;
		
		// 正在使用的还有多少个对象
		int m = 0;
		
		class A
		{
		public:
			A()
			{	
				++n;
				++m;
			}
		
			A(const A& t)
			{
				++n;
				++m;
			}
		
			~A() 
			{
				--m;
			}
		private:
		};
		
		// A& Func(A& aa)
		A Func(A aa)
		{
			return aa;
		}

我们创建了两个全局变量,m 和 n 分别统计正在使用的还有多少个对象和累积创建了多少个对象 ;并在构造函数、拷贝构造函数和析构函数体内做相应统计;

这种方法虽然能统计出来,但是 m 和 n 是全局变量,说明我们可以随意修改数据,变得不安全,所以这种方法不好。

所以就有静态成员变量这个概念,我们在类中定义静态成员变量,静态成员变量属于所有类的对象,属于整个类。 例如我们将上面的 m 和 n 声明为静态成员变量:

		class A
		{
		public:
			A()
			{
				++n;
				++m;
			}
		
			A(const A& t)
			{
				++n;
				++m;
			}
		
			~A()
			{
				--m;
			}
		
			// 静态成员函数的特点:没有this指针
			static int GetM()
			{
				// x++; // 不能访问非静态,因为没有this
				return m;
			}
			
			static int GetN()
			{
				return n;
			}
		
		private:
			// 静态成员变量属于所有 A 类的对象,属于整个类
			// 声明
			
			// 累积创建了多少个对象  
			static int n;
			// 正在使用的还有多少个对象
			static int m;
		
			int _x = 0;
		};
		// 定义
		int A::n = 0;
		int A::m = 0;

如上代码,静态成员的变量需要在类外定义 ;我们在类中也定义了静态成员函数 ,静态成员函数的特点是没有 this 指针,所以它不能访问非静态成员变量 ,假设我们声明了一个 _x 成员变量,GetM 函数是无法访问 _x 的;但是它可以访问静态成员的变量;例如如果我们想要拿到 m 和 n 的值,可以通过函数 GetMGetN 拿到,而这两个静态成员函数都是返回静态成员变量。

那么我们为什么不将这两个函数直接定义成成员函数呢?因为我们需要统计的是累计创建了多少对象,而需要访问成员函数就必须得实例化一个对象出来,假如我们没有实例化对象,就不能得到 m 和 n 的值;而定义成静态成员函数只需要指定类域即可得到 m 和 n 的值,例如以下代码:

		int main()
		{
			// 匿名对象
			A();
			A();
			
			// error
			//cout << aa1.GetM() << ' ' << aa1.GetN() << endl;

			// 可以访问
			cout << A::GetM() << ' ' << A::GetN() << endl;
			
			// 实例化对象
			A aa1;
			
			cout << aa1.GetM() << ' ' << aa1.GetN() << endl;
			cout << A::GetM() << ' ' << A::GetN() << endl;

			return 0;
		}

以上代码中,我们实例化了两个匿名对象(下面的内容会讲到匿名对象),匿名对象也会调用构造函数,但是我们如果不将 GetMGetN 函数定义成静态成员函数,就无法得到 m 和 n 的值,因为没有实例化的对象,匿名对象的生命周期只有一行;只有我们实例化 aa1 对象才可以得到 mn 的值,但是这样又调用了一次构造函数,并不是我们想要的结果。

四、友元

1. 友元函数

我们在运算符重载中,还有两个运算符没有重载:流插入和流提取。

假设我们在类内部实现流插入和流提取运算符重载:

			// 流插入重载
			void operator<<(ostream& out)
			{
				out << _year << '.' << _month << '.' << _day << endl;
			}
		
		
		
			// 流提取重载
			void operator>>(istream& in)
			{
				in >> _year >> _month >> _day;
			}

这时候我们要注意,this 指针抢占了成员函数的第一个参数的位置,导致 cout 参数变成在第二个参数位置,参数的顺序不一样,所以我们在使用中应该是 d << cout , 虽然可以使用,但是不符合我们使用的习惯和价值。

所以要将 operator<< 重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。

我们先看如何使用:

		class Date
		{
			// 友元函数
			friend ostream& operator<<(ostream& out, const Date& d);
			friend istream& operator>>(istream& in, Date& d);
			
		public:
			//日期类的构造函数
			Date(int year = 2023, int month = 7, int day = 29)
				:_year(year)
				, _month(month)
				, _day(day)
			{}
		
		private:
			int _year;
			int _month;
			int _day;
		};
		
		
		// 流插入重载
		inline ostream& operator<<(ostream& out, const Date& d)
		{
			cout << d._year << '.' << d._month << '.' << d._day << endl;
			return out;
		}
		
		// 流提取重载
		inline istream& operator>>(istream& in, Date& d)
		{
			in >> d._year >> d._month >> d._day;
		
			return in;
		}

如上代码,流插入和流提取重载是放在全局域中,此时我们需要在类的内部声明友元函数,可以在任意位置,此处我们在最上面声明两个重载的友元,此时两个重载函数就可以正常访问类的成员变量,从而不受访问限定符的影响,cout 的参数又可以在第一个形参位置,这才是符合我们的要求。这里使用 ostream&istream& 类型返回是为了支持连续插入和提取。

最后总结,友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字。

2. 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

例如,这里有个时间类,在时间类中声明日期类为友元类:

		class Time
		{
			// 声明日期类为时间类的友元类,则在日期类中就直接访问 Time 类中的私有成员变量
			friend class Date;
		
		public:
			Time(int hour = 0, int minute = 0, int second = 0)
				: _hour(hour)
				, _minute(minute)
				, _second(second)
			{}
		
		private:
			int _hour;
			int _minute;
			int _second;
		};

在日期类中定义时间类的自定义类型,访问时间类的成员变量:

		class Date
		{
		public:
			Date(int year = 2023, int month = 7, int day = 29)
				: _year(year)
				, _month(month)
				, _day(day)
			{}
		
			void SetTimeOfDate(int hour, int minute, int second)
			{
				// 直接访问时间类私有的成员变量
				_t._hour = hour;
				_t._minute = minute;
				_t._second = second;
			}
		
		private:
			int _year;
			int _month;
			int _day;
		
			Time _t;
		};

上述代码是没有问题的,在日期类中可以直接访问时间类的成员变量,因为日期类是时间类的友元 ,但是在时间类中却无法访问日期类的成员变量,因为时间类不是日期类的友元

最后对友元进行总结,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

五、内部类

概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。 内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意: 内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

例如以下两个类,A类和B类,B类是A类的内部类:

		class A
		{
		public:
		
			class B
			{
			public:
				void FuncB()
				{
					A aa;
					aa._a = 1;
				}
		
			private:
				int _b;
			};
		
		
			void PrintA()
			{
				cout << _a << endl;
			}
		
		private:
			int _a;
		};

A类受B类域和访问限定符的限制,B类可以访问A类的成员变量,A类却不能访问B类的成员变量;其实他们是两个独立的类;内部类默认就是外部类的友元类

如果计算上面代码中A类的大小,会是多少呢?

		int main()
		{
			cout << sizeof(A) << endl;
		
			// 实例化 aa	
			A aa;
			
			// 实例化 bb1
			A::B bb1;
		
			return 0;
		}

结果如下,虽然B类是A类的内部类,但是实际上它们是两个独立的类,所以没有计算B类的大小,A类的大小只包括成员变量 _a 的大小;

六、匿名对象

以前我们定义的对象大多数都是有名对象,有名对象的特点是生命周期在当前局部域 ;而匿名对象的特点是生命周期只在定义的那一行。

例如有一个A类,匿名对象的生命周期只有这一行,下一行它就会自动调用析构函数:

			// 有名对象
			A aa(6);
		
			// 匿名对象
			A(7);

但是我们不可以这样定义对象:

		 A aa1();

因为编译器无法识别这是一个函数声明,还是对象定义;

七、日期类

下面我们用前面所学的知识完善我们的日期类,我们将声明和定义分开写在两个文件中,声明写在 .h 文件,定义写在 .cpp 文件中:

声明:

		#pragma once
		#include <iostream>
		#include <assert.h>
		using namespace std;
		
		class Date
		{
			// 友元函数
			friend ostream& operator<<(ostream& out, const Date& d);
			friend istream& operator>>(istream& in, Date& d);
		public:
		
			// 获取月份的天数
			int GetMonthDay(int year, int month)
			{
				static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
				int day = days[month];
		
				// 二月并且是闰年
				if (month == 2
					&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
				{
					day += 1;
				}
		
				return day;
			}
		
			//检查日期是否合法
			bool CheakDate()
			{
				if (_year >= 1
					&& _month > 0 && _month < 13
					&& _day > 0 && _day <= GetMonthDay(_year, _month))
				{
					return true;
				}
				return false;
			}
		
			//日期类的构造函数
			Date(int year = 2023, int month = 7, int day = 29)
				:_year(year)
				, _month(month)
				, _day(day)
			{
				assert(CheakDate());
			}
		
			void Print() const;
		
			//运算符重载
			bool operator==(const Date& d) const;
			bool operator!=(const Date& d) const;
			bool operator>(const Date& d) const;
			bool operator>=(const Date& d) const;
			bool operator<(const Date& d) const;
			bool operator<=(const Date& d) const;
		
		
			Date operator+(int day) const;
			Date& operator+=(int day);
		
			Date operator-(int day) const;
			Date& operator-=(int day);
		
			Date& operator++(); //前置
			Date operator++(int); //后置
		
			Date& operator--(); //前置
			Date operator--(int); //后置
		
			int operator-(const Date& d) const;
		
		private:
			int _year;
			int _month;
			int _day;
		};
		
		
		// 流插入重载
		inline ostream& operator<<(ostream& out, const Date& d)
		{
			cout << d._year << '.' << d._month << '.' << d._day << endl;
			return out;
		}
		
		
		// 流提取重载
		inline istream& operator>>(istream& in, Date& d)
		{
			in >> d._year >> d._month >> d._day;
			assert(d.CheakDate());
		
			return in;
		}

函数的实现:

		#include "ClassAndObj_3.h"
		
		
		//打印日期
		void Date::Print() const
		{
			cout << _year << '.' << _month << '.' << _day << endl;
		}
		
		
		// == 运算符重载
		bool Date::operator==(const Date& d) const
		{
			return _year == d._year
				&& _month == d._month
				&& _day == d._day;
		}
		
		
		
		// != 运算符重载
		bool Date::operator!=(const Date& d) const
		{
			return !(*this == d);
		}
		
		
		
		// > 运算符重载
		bool Date::operator>(const Date& d) const
		{
			if (_year > d._year
				|| (_year == d._year && _month > d._month)
				|| (_year == d._year && _month == d._month && _day > d._day))
			{
				return true;
			}
		
			return false;
		}
		
		
		// >= 运算符重载
		bool Date::operator>=(const Date& d) const
		{
			return (*this > d) || (*this == d);
		}
		
		
		
		// < 运算符重载
		bool Date::operator<(const Date& d) const
		{
			return !(*this >= d);
		}
		
		
		
		// <= 运算符重载
		bool Date::operator<=(const Date& d) const
		{
			return (*this < d) || (*this == d);
		}
		
		
		
		// + 运算符重载
		Date Date::operator+(int day) const
		{
			Date ret(*this);
		
			ret += day;
		
			return ret;
		}
		
		
		
		// += 运算符重载
		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 == 13)
				{
					_year++;
					_month = 1;
				}
			}
		
			return *this;
		}
		
		
		
		// - 运算符重载
		Date Date::operator-(int day) const
		{
			Date ret(*this);
		
			ret -= day;
		
			return ret;
		}
		
		
		
		// -= 运算符重载
		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(_month, _year);
			}
		
			return *this;
		}
		
		
		
		
		// 前置++ 重载
		Date& Date::operator++()
		{
			return *this += 1;
		}
		
		
		
		// 后置++ 重载
		Date Date::operator++(int)
		{
			Date ret(*this);
			*this += 1;
			return ret;
		}
		
		
		
		// 前置-- 重载
		Date& Date::operator--()
		{
			return *this -= 1;
		}
		
		
		
		// 后置-- 重载
		Date Date::operator--(int)
		{
			Date ret(*this);
			*this -= 1;
			return ret;
		}
		
		
		
		//计算两个日期之间相差的天数
		int Date::operator-(const Date& d) const
		{
			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;
		}

如上我们的日期类就基本完善了。这也意味着我们的类和对象的知识也就学完啦,感觉有帮助的小伙伴点个赞吧~

相关推荐
荒古前3 分钟前
龟兔赛跑 PTA
c语言·算法
Colinnian6 分钟前
Codeforces Round 994 (Div. 2)-D题
算法·动态规划
Algorithm15767 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
用户00993831430112 分钟前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明16 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
დ旧言~22 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
Monly2122 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu23 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202124 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频