C++类和对象(二)

一.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

在这里插入图片描述

二. 构造函数

构造函数的概念

构造函数:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

例如,以下日期类中的成员函数Date就是一个构造函数。当你用该日期类创建一个对象时,编译器会自动调用该构造函数对新创建的变量进行初始化。

class Date
{
public:
 Date(int year = 1, int month = 1, int day = 1) //构造函数 变量初始化为1
 {
	cout<<"Date()"<<endl;
 }
 void Print()
 {
 cout << _year << "-" << _month << "-" << _day << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d1;  //对象实例化时自动调用构造函数
 d1.Print;  //结果 1 1 1
 return 0;
}

构造函数的特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

一、构造函数的函数名与类名相同

二、构造函数无返回值
 这里所说的构造函数无返回值是真的无返回值,而不是说返回值为void。

三、对象实例化时编译器自动调用对应的构造函数
 当你用类创建一个对象时,编译器会自动调用该类的构造函数对新创建的变量进行初始化。

四、构造函数支持重载
 这意味着你可以有多种初始化对象的方式,编译器会根据你所传递的参数去调用对应的构造函数。

五、 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

六、无参的构造函数、全缺省的构造函数以及我们不写编译器自动生成的构造函数都称为默认构造函数,并且默认构造函数只能有一个

既然编译器能够提供默认构造函数,为什么还要自己去实现?

例如以下代码中的d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用? 所以我们要去自己实现一个构造函数,达到想要的结果。

class Time
{
public:
 Time()
 {
 cout << "Time()" << endl;
 _hour = 0;
 _minute = 0;
 _second = 0;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

编译器自动生成的构造函数机制:
 1、编译器自动生成的构造函数对内置类型不做处理。
 2、对于自定义类型,编译器会再去调用它们自己的默认构造函数。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。此时也可以用编译器提供的默认的无参构造函数

class Time
{
public:
 Time()
 {
 cout << "Time()" << endl;
 _hour = 0;
 _minute = 0;
 _second = 0;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;   //内置类型给缺省值
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

总结一下:虽然在我们不写的情况下,编译器会自动生成构造函数,但是编译器自动生成的构造函数可能达不到我们想要的效果,所以大多数情况下都需要我们自己写构造函数。

三. 析构函数

析构函数的概念

与构造函数功能相反,析构函数不是完成对对象本身的销毁(main函数栈帧销毁而销毁),局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数的特性

一. 析构函数名是在类名前加上字符 ~。

class Time
{
public:
 ~Time()
 {
 cout << "~Time()" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

二. 无参数无返回值类型。
析构函数所谓的无返回值也是真的无返回值,而不是返回值为void。

三. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

四. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

五.编译器自动生成的析构函数机制:
 1、编译器自动生成的析构函数对内置类型不做处理。
 2、对于自定义类型,编译器会再去调用它们自己的默认析构函数。

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
总结:有动态开辟之类这些资源申请的情况时,一定要自己实现析构,防止内存泄漏

六、先构造的后析构,后构造的先析构
因为对象是定义在函数中的,函数调用会建立栈帧,栈帧中的对象构造和析构也要符合先进后出的原则。
在这里插入图片描述
结论:

**1. 对于构造的顺序:全局对象先于局部对象进行构造
局部对象按照出现的顺序进行构造,无论是否为static,构造不受static影响,从上而下构造。
**
2. 对于析构顺序:1. 先局部对象(后定义的先析构),2. 再局部静态对象(后定义的先析构),3. 最后全局变量,无论静态与否,都是后定义的先析构。

四. 拷贝构造函数

拷贝构造函数的概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

拷贝构造函数的特性

拷贝构造函数也是特殊的成员函数,其特征如下:

一. 拷贝构造函数是构造函数的一个重载形式。

注意:因为拷贝构造函数的函数名与类名一致,所以当自己为了解决深拷贝的问题,而自己手动实现了一个拷贝构造函数后,一定也要提供构造函数,因为拷贝构造也是构造函数的重载形式,编译器就不会再提供默认的无参构造函数了。

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0, 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;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 5, 31);
	Date d2(d1); // 用已存在的对象d1创建对象d2

	return 0;
}

二、拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
在这里插入图片描述

因为调用拷贝构造函数,先将d1传给date,传值传参又是将d1拷贝构造给date,又调用拷贝构造,如此无穷递归下去。
在这里插入图片描述
所以,用引用接收,此时date就是d1的别名,this指向的就是d2,就可以解决问题了,最好还要加上const,避免昏头写反了,自己反而被随机值拷贝了。

三. 若未显式定义,编译器会生成默认的拷贝构造函数

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 0, 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(2021, 5, 30);
	Date d2(d1); // 用已存在的对象d1创建对象d2
	d1.Print();
	d2.Print();
	return 0;
}

结果我们没有提供拷贝构造函数,也拷贝成功了,原因就在于编译器自动生成了一个默认的拷贝构造函数。
在这里插入图片描述
编译器自动生成的拷贝构造函数机制:
 1、编译器自动生成的拷贝构造函数对内置类型会完成浅拷贝(值拷贝)。
 2、对于自定义类型,编译器会再去调用它们自己的默认拷贝构造函数。

四. 编译器自动生成的拷贝构造函数不能进行深拷贝

类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
比如stack栈

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 10)
 {
 _array = (DataType*)malloc(capacity * sizeof(DataType));
 if (nullptr == _array)
 {
 perror("malloc申请空间失败");
 return;
 }
 _size = 0;
 _capacity = capacity;
 }
 void Push(const DataType& data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = nullptr;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType *_array;
 size_t _size;
 size_t _capacity;
};
int main()
{
 Stack s1;
 s1.Push(1);
 s1.Push(2);
 s1.Push(3);
 s1.Push(4);
 Stack s2(s1);
 return 0;
}

在这里插入图片描述
在这里插入图片描述
总结:存在资源申请时一定要进行深拷贝,否则会和拷贝构造出来的对象共用同一块空间,析构多次释放,程序崩溃。

五. 赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,为了让自定义类型能够像内置类型一样可以直接使用运算符进行操作

运算符重载函数也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)
注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  3. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    4.** .* :: sizeof ?: . **注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

这里以重载==为例:

class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
   {
        _year = year;
        _month = month;
        _day = day;
   }    
//private:
 int _year;
 int _month;
 int _day;
};
// 全局的operator==
bool operator==(const Date& d1, const Date& d2)  //全局函数,运算符重载
{
    return d1._year == d2._year
   && d1._month == d2._month
        && d1._day == d2._day;
}
void Test ()
{
    Date d1(2018, 9, 26);
    Date d2(2018, 9, 27);
    cout<<(d1 == d2)<<endl;
}

这里会发现运算符重载成全局的,就无法访问类的私有成员,就需要成员变量是公有的,或者提供一堆get和set方法,那么问题来了,封装性如何保证?

  1. 这里其实可以用我们后面学习的友元解决,声明为友元函数后全局函数也可以使用类的私有成员变量。
  2. 声明为或者干脆重载成成员函数。
    真正常用最好解决办法就是重载成成员函数。
class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
   {
        _year = year;
        _month = month;
        _day = day;
   }
    
    // bool operator==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
   bool operator==(const Date& d2)  //重载成类的成员函数
 {
        return _year == d2._year;
            && _month == d2._month
            && _day == d2._day;
 }
private:
 int _year;
 int _month;
 int _day;
};

赋值运算符重载

一. 赋值运算符重载格式

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)// 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)// 赋值运算符重载函数
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void Print()// 打印函数
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

一. 参数类型:const T&,传递引用可以提高传参效率
赋值运算符重载函数的第一个形参默认是this指针,第二个形参是我们赋值运算符的右操作数。由于是自定义类型传参,我们若是使用传值传参,会额外调用一次拷贝构造函数,所以函数的第二个参数最好使用引用传参,提高效率。
其次,第二个参数,即赋值运算符的右操作数,我们在函数体内不会对其进行修改,所以最好加上const进行修饰。

二. 检测是否自己给自己赋值
若是出现d1 = d1,我们不必进行赋值操作,因为自己赋值给自己是没有必要进行的。所以在进行赋值操作前可以先判断是否是给自己赋值,避免不必要的赋值操作。

三. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
实际上,我们若是只以d2 = d1这种方式使用赋值运算符,赋值运算符重载函数就没必要有返回值,因为在函数体内已经通过this指针对d2进行了修改。但是为了支持连续赋值,即d3 = d2 = d1,我们就需要为函数设置一个返回值了,而且很明显,返回值应该是赋值运算符的左操作数,即this指针指向的对象。

四. 返回*this :要复合连续赋值的含义
赋值操作进行完毕时,我们应该返回赋值运算符的左操作数,而在函数体内我们只能通过this指针访问到左操作数,所以要返回左操作数就只能返回*this。

五、一个类如果没有显示定义赋值运算符重载,编译器也会自动生成一个,完成对象按字节序的值拷贝
没错,赋值运算符重载编译器也可以自动生成,并且也是支持连续赋值的。但是编译器自动生成的赋值运算符重载完成的是对象按字节序的值拷贝,例如d2 = d1,编译器会将d1所占内存空间的值完完全全地拷贝到d2的内存空间中去,类似于memcpy。
 对于日期类,编译器自动生成的赋值运算符重载函数就可以满足我们的需求,我们可以不用自己写。但是这也不意味着所有的类都不用我们自己写赋值运算符重载函数,当遇到一些特殊的类,我们还是得自己动手写赋值运算符函数的。

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理即深拷贝时则必须要手动实现。

注意:

Date d1(2021, 6, 1);  //构造d1
	Date d2(d1);  //将d1拷贝构造给d2
	Date d3 = d1;  //将d1拷贝构造给d3
	Date d4;
	d4 = d1;  //将d1赋值给d4,因为d4已经是存在的对象

拷贝构造函数:用一个已经存在的对象去构造初始化另一个即将创建的对象

赋值运算符重载函数:在两个对象都已经存在的情况下,将一个对象赋值给另一个对象

六. 赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

六. const成员

const修饰类的成员函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
例如,我们可以对类成员函数中的打印函数进行const修饰,避免在函数体内不小心修改了对象:

	void Print()const// cosnt修饰的打印函数
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
  4. 非const成员函数内可以调用其它的const成员函数吗?
    解析:
     1.非const成员函数,即成员函数的this指针没有被const所修饰,我们传入一个被const修饰的对象,用没有被const修饰的this指针进行接收,属于权限的放大,函数调用失败。
     2.const成员函数,即成员函数的this指针被const所修饰,我们传入一个没有被const修饰的对象,用被const修饰的this指针进行接收,属于权限的缩小,函数调用成功。
     3.在一个被const所修饰的成员函数中调用其他没有被const所修饰的成员函数,也就是将一个被const修饰的this指针的值赋值给一个没有被const修饰的this指针,属于权限的放大,函数调用失败。
     4.在一个没有被const所修饰的成员函数中调用其他被const所修饰的成员函数,也就是将一个没有被const修饰的this指针的值赋值给一个被const修饰的this指针,属于权限的缩小,函数调用成功。

    总结:权限可以放大或者平移,不能缩小

七. 取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,而且使用编译器默认生成的就可以了。

class Date
{ 
public :
 Date* operator&()
 {
 return this ;
 }
 const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

八. 日期类的实现

为了巩固六大默认成员函数的理解,实现日期类,加深理解,以下是日期类的声明

class Date
{
public:
	//得到该月的最大天数
	int GetMonthDay(int year, int month);
	//打印函数
	void Print() const;
	//构造函数
	Date(int year = 1, int month = 1, int day = 1);
	//日期大小关系比较
	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);
	//日期+天数
	Date operator+(int day) const;
	//日期-=天数
	Date& operator+=(int day);
	//日期-天数
	Date operator+(int day) const;
	//前置++
	Date& operator++();
	//后置++
	Date operator++(int);
	//前置--
	Date& operator--();
	//后置--
	Date operator--(int);
	//日期-日期 
	int operator-(const Date& d) const;
	//这里不涉及资源释放,申请,所以不需要深拷贝和手动清理,析构,拷贝构造,赋值运算符重载不用实现
private:
	int _year;
	int _month;
	int _day;
};

构造函数

进入函数体,只有日期合法,才会构造。

//得到该年该月的天数
int Date::GetMonthDay(int year, int month)
{
	assert(year >= 1 && month >= 1 && month <= 12);
static int MonthDay[13] = { -1, 31,28,31,30,31,30,31,31,30,31,30,31 };  //static优化 多次调用只初始化一次
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	return 29;  //如果想要获取二月份的天数时需判断是否闰年,是闰年直接返回29天不能对static数组改变,
//生命周期在全局其它月份直接取 
return MonthDay[month];
}
//构造函数
Date::Date(int year, int month, int day)
{
	if (year < 1 || month < 1 || month>12 || day<1 || _day>GetMonthDay(year, month))  //对传入参数判断合法才继续构造
	{
		cout << "非法日期!" << endl;
		cout << year << "年" << month << "月" << day << "日" << endl;
	}
	else
	{
		_year = year;
		_month = month;
		_day = day;
	}
}

打印函数

//打印函数
void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

日期 += 天数

对于+=运算符,我们先将需要加的天数加到日上面,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。
调整日期的思路:
 1.若日已满,则日减去当前月的天数,月加一。
 2.若月已满,则将年加一,月置为1。
反复执行1和2,直到日期合法为止。


//日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;  //当+=一个负值,就复用-=
	}
	else
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))  //大于该月的天数就往下一个月走,走到不大于该月最大天数为止
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;  //月加到13 年+1
				_month = 1;
			}
		}
	}
	return *this;  //对自身的修改返回自身引用
}

日期 + 天数

+运算符的重载,我们可以复用上面已经实现的+=运算符的重载函数。但是要注意:虽然我们返回的是加了之后的值,但是对象本身的值并没有改变。就像a = b + 1,b + 1的返回值是b + 1,但是b的值并没有改变。所以我们还可以用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

//日期+天数
Date Date::operator+(int day) const
{
	Date tmp(*this);  //拷贝构造自身给tmp
	tmp += day;  //复用+=
	return tmp;
}

注意:+=运算符的重载函数采用的是引用返回,因为出了函数作用域,this指针指向的对象没有被销毁。但+运算符的重载函数的返回值只能是传值返回,因为出了函数作用域,对象tmp就被销毁了,不能使用引用返回。

日期 -= 天数

对于-=运算符,我们先用日减去需要减的天数,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。
调整日期的思路:
 1.若日为负数,则月减一。
 2.若月为0,则年减一,月置为12。
 3.日加上当前月的天数。
重复至步骤日>0结束

//日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)  //如果-=一个负值 复用+=
	{
		*this += -day;
	}
	else
	{
		_day -= day;
		while (_day <= 0)
		{
			_month--;
			if (_month == 0)  //减到0月往前推一年
			{
				_year--;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);  
		}
	}
	return *this;
}

日期 - 天数

和+运算符的重载类似,我们可以复用上面已经实现的-=运算符的重载函数,而且最好用const对该函数进行修饰,防止函数内部改变了this指针指向的对象

//日期-天数
Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;  //保存一份复用-=
	return tmp;
}

注意:-=运算符的重载函数采用的是引用返回,但-运算符的重载函数的返回值只能是传值返回,也是由于-运算符重载函数中的tmp对象出了函数作用域被销毁了,所以不能使用引用返回。

前置 ++

复用+=

//前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

后置 ++

由于前置++和后置++的运算符均为++,为了区分它们的运算符重载,我们给后置++的运算符重载的参数加上一个int型参数,使用后置++时不需要给这个int参数传入实参,因为这里int参数的作用只是为了跟前置++构成重载。

//后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

注意:后置++也是需要返回加了之前的值,只能先用对象tmp保存之前的值,然后再然对象加一,最后返回tmp对象。由于tmp对象出了该函数作用域就被销毁了,所以后置++只能使用传值返回,而前置++可以使用引用返回。

前置 –

复用-=

//前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

后置–

和后置++一样,不想再描述了

//后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

日期类的大小关系比较

注意:进行日期的大小比较,我们并不会改变传入对象的值,所以这6个运算符重载函数都应该被const所修饰。
只需要实现==和>,即可其它复用。

>运算符的重载

>运算符的重载很简单,先判断年是否大于,再判断月是否大于,最后判断日是否大于,这其中有一者为真则函数返回true,否则返回false。

bool Date::operator>(const Date& d) const
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month > d._month)
		{
			return true;
		}
		else if (_month == d._month)
		{
			if (_day > d._day)
			{
				return true;
			}
		}
	}
	return false;
}
==运算符的重载

只有年月日都相等才相等

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 || (*this) == d;
}
<运算符的重载

<是大于等于的反面

bool Date::operator<(const Date& d) const
{
	return !((*this) >= d);
}
<=运算符的重载

大于的反面

bool Date::operator<=(const Date& d) const
{
	return !((*this) > d);
}
!=运算符的重载

==的反面

bool Date::operator!=(const Date& d) const
{
	return !((*this) == d);
}

日期 - 日期

日期 - 日期,即计算传入的两个日期相差的天数。我们只需要让较小的日期的天数一直加一,直到最后和较大的日期相等即可,这个过程中较小日期所加的总天数便是这两个日期之间差值的绝对值。若是第一个日期大于第二个日期,则返回这个差值的正值,若第一个日期小于第二个日期,则返回这个差值的负值。

//日期-日期 
int Date::operator-(const Date& d) const
{
	int flag = 1;  //假设左边大于右边,返回一个正值
	Date max = *this;
	Date min = d;
	if (max < min)
	{
		flag = -1;  //假设错误更改,返回一个负值
		max = d;
		min = *this;
	}
	int n = 0;
	while (min != max)  //自增到相等,n就是差值
	{
		++min;
		++n;
	}
	return n * flag;  //返回乘积flag=1 为正否则为负
}

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

维生素C++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值