类和对象(中)默认成员函数

继上篇我们初步认识了c++中的类和对象,这篇文章我们就来深入探讨一下c++在类于对象的基础上做出了什么改变。

类与对象上

这里也给大家放一下链接,以便回顾重温。

目录

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

1.构造函数定义及其特征

1.1构造函数的使用

2.析构函数

3.拷贝构造函数

4.赋值运算符重载

4.1运算符重载

4.2 赋值运算符重载

4.3 前置++和后置++重载

5.const成员

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

7.日期类的实现

7.1流插入 <<  和 流提取 >>

7.2日期类代码


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

如果一个类中什么成员都没有,简称为空类。但是事实如此吗?并不是,类有自己的成员。

默认成员函数就是用户没有显示实现 , 编译器会自动生成的成员函数称为默认成员函数 。 一个类 , 在不写的情况下 , 编译器会默认生成以下的 6 个 默认成员函数 , 需要注意的是这 6个中最重要的是前 4 个 , 最后两个取地址重载不重要 , 稍微了解就好 。 其次  C++11  以后还会增加两个默认成员函数 , 移动构造 和 移动赋值

1.构造函数定义及其特征

构造函数是特殊的成员函数 需要注意的是 构造函数虽然名称为 构造 , 但 是构造函数的主要任务并不是开辟空间,创造对象(我们常使用的局部对象是栈帧创建时 , 空间就开好了) , 而是对象实例化时 初始化对象

构造函数的本质是要替代我们以前 Stack 和 Date 类中写的 Init 函数的功能 , 构造函数自动调用的特点就完美的替代了 Init 。

其特征如下: 
1. 函数名与类名相同。
2. 无返回值。 
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载

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


6.无参构造函数 , 全缺省构造函数 , 在不写构造函数时编译器默认生成的构造函数都叫做默认构造函数 。 但是这三个函数有且只有一个存在 , 不能同时存在 。无参构造函数和全缺省构造函数虽然构成函数重载 , 但是调用时会存在歧义 。 

默认构造函数并不只有编译器默认生成的那个叫默认构造 , 实际上无参构造函数 , 全缺省构造函数也是默认构造函数 ,也就是说不传实参就可以调用的构造就叫默认构造 。


7.不写构造函数时 , 编译器默认生成的构造 , 对内置类型成员变量的初始化没有要求 , 也就是是否初始化不确定,看编译器 。 对于自定义类型成员变量 ,要求调用这个成员变量的默认构造函数初始化 。如果这个成员变量 ,没有默认构造函数 ,那么就会报错 , 需要初始化这个成员变量 , 需要使用初始化列表才能解决 , 初始化列表是什么,我们后续在讲。

1.1构造函数的使用

假如我们这里来定义一个日期类,同时使用构造函数。

#include <iostream>
using namespace std;
 
class Data
{
public:
	Data()
	{ }
	Data(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	};
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	
	int _year=1;
	int _month=1;
	int _day=1;
};
 
int main()
{
	Data d1;
	Data d2(2023, 1, 1);
	d1.Print();
	d2.Print();
	return 0;
}

这里就是一个无参的构造函数:

Data();

相反的,这里是一个带参的构造函数:

Data(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	};

他们的调用各自如何呢:

Data d1;
Data d2(2023, 1, 1);

那我们现在就来看看,构造函数的赋值。

如果类里面没有显示的定义构造函数,那么 C++编译器就会自动生成一个无参的默认构造函数,一旦用户显示的定义,那么编译器将不再生成。所以的话,如果我们不写构造函数的话那也是可以的,但是初始化就可能会麻烦一点。

在创建对象的时候,编译器通过调用构造函数,给每一个对象中的各个成员给一个合适的初始值。有两种方法,一种是初始化列表,另一种就是函数体内赋值,函数体内赋值不讲了,这里讲初始化列表。

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

那么,什么时候用初始化链表呢?

以下三种成员变量必须用列表来进行初始化:
1、引用成员变量
2、const成员变量

3、自定义类型成员(且该类没有默认构造函数时)

这里注意,每一个成员变量在初始化列表中只能出现一次,也就是初始化只能有一次。

我们下面来一一举例看一下:

class A
{
public:
	A(int a)
		:_a(a)
	{
	}
private:
	int _a;
};
class B
{
public:
	B(int a, int ref)
		:_aobj(a)
		, _ref(ref)
		, _n(10)
		, _x()   //不写的话,缺省值就有用,如果显示的给了值,那么缺省值就没有用了
	{
	}
private:
	//声明
	A _aobj; // 没有默认构造函数,不传参和全缺省,不单单指编译器自动生成的
	//必须在定义的时候进行初始化
	int& _ref; // 引用
	const int _n; // const
	int _x = 1;//这里的1是缺省值,缺省值是给初始化列表的
};

还有一个问题,成员声明顺序的事,我们定义成员数据的时候究竟是按什么来赋值的?

实际初始化顺序由成员在类中的声明顺序决定,而非初始化列表中的顺序。

如果 _aobj、_ref、_n、_x 的声明顺序与初始化列表不一致,可能导致未定义行为(尤其是依赖其他成员初始化时)。

所以日常使用需要保持初始化列表顺序与成员声明顺序一致。

最后我们再来思考一下,什么情况下我们需要使用构造函数?

2.析构函数

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

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

举个栗子,比如局部对象是存在栈帧的 , 函数结束栈帧销毁 , 它就释放了 , 不需要我们管 , C++规定对象再销毁时会自动调用析构函数 , 完成对对象中的资源清理释放工作 。

析构函数的功能类比我们之前Stack 实现的Destory 的功能 , 而像Date 没有 Destory , 其实就是没有资源需要释放 , 所以严格说Date日期类 , 是不需要析构函数的 。

析构函数的特性:

1.析构函数名是在类名前加上字符~
2.无参数无返回值 。 (这里与构造类似 , 也不需要加void)
3.一个类只能有一个析构函数 。 若未显式定义,系统会自动生成默认的析构函数 。

注意,析构函数不能被重载。
4. 对象生命周期结束时,C++编译系统系统会自动调用析构函数。
5. 与构造函数类似,我们不写构造函数时,编译器会自动生成析构函数,但是对内置类型成员不做处理,自定类型成员会调用他的析构函数。

(因为程序结束操作系统会自动回收这些内置类型成员变量的空间的)类对象在实例化之后,对象的变量也是开辟在栈等空间上的,这些空间都是操作系统开辟的,回收也是由操作系统回收。
仍需注意的是显示写析构函数时 ,对于自定义类型成员也会调用自定义类型中的析构函数 , 也就是说自定义类型成员无论什么情况都会自动调用析构函数 。
如果类中没有申请资源时,析构函数可以不写 ,直接使用编译器生成的默认析构函数 ,如 Date类 ; 如果是默认生成的析构就可以用 ,也就不需要显示写析构函数 , 如MyQueue ; 但是有资源申请时 , 一定要自己写析构 , 否则会造成资源泄漏 , 如Stack 。

这就是著名的RAII(资源获取即初始化)原则

在构造函数中获取资源,在析构函数中释放资源。 
         一个局部域的多个对象 , C++规定后定义的先析构 。

3.拷贝构造函数

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?这就是拷贝构造函数。

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

拷贝构造的特征:

1.拷贝构造函数是构造函数的一个重载形式,一般得加const,以免拷贝错了

什么意思呢?我们之前在学引用的时候已经注意到,引用是被引用对象的别名,本质上是同种东西

这里就涉及到一个权限的放大的问题。

举个例子 : 如果我给了一个张三的蓝本 , 给你去造一个张三出来 , 但是因为某一个不小心的错误 , 把张三 变成了 李四了 , 给我造了个李四出来 , 还把我原先给的张三的蓝图 改成了 李四的蓝图 , 这就和本意不符合了。

所以我们就可以得出,为什么加const:

避免意外修改原对象(拷贝时不应改变源对象)。
允许拷贝 const 对象(提高灵活性)。
符合语义(拷贝构造函数应该是
“只读”的)。

2.拷贝构造函数的参数只有1个,且必须是该类类型对象的引用(指针也行,但c++规定是引用),如果用传值传参,这个时候,拷贝构造函数的形参是接受来自实参的值传递,又相当于调用了一次新的拷贝构造函数,如此,无穷反复,编译器会直接报错。

这就要我们来看看,拷贝构造函数到底干了什么,动了谁的蛋糕。

传值返回会产生一个临时对象调用拷贝构造 

传值引用返回,返回的是返回对象的别名(引用) , 没有产生拷贝!

但是如果返回对象是一个当前函数局部域的局部对象 ,函数结束就销毁了 ,那么使用引用返回是有问题的,这时的引用相当于野引用,类似一个野指针一样 。

传引用返回可以减少拷贝 , 但是一定要确保返回对象 , 在当前函数结束后还在 ,才能用 引用返回 。

class MyClass {
public:
    MyClass(MyClass other) {  // ❌ 错误!传值方式会导致递归
        this->data = other.data;
    }
};
MyClass obj1;
MyClass obj2(obj1);  // 调用拷贝构造函数

发生了什么?

1.obj2(obj1) 调用拷贝构造函数 MyClass(MyClass other)。
2.由于 other 是传值(而不是引用),所以编译器需要先复制 obj1 来构造 other。
3.为了复制 obj1,又需要调用 MyClass(MyClass other),这时又需要再复制 obj1 来构造新的 other。
所以这个逻辑会无限循环下去,直到栈溢出。

注意 : 拷贝构造的第一个参数必须是类类型对象的引用,可以再后面加参数,但此时的参数必须是缺省的!

 
	Date(const Date& d,int x= 1)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序
完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

那么我们来看,什么是浅拷贝?什么是深拷贝?

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

那么,什么时候用浅拷贝,什么时候用深拷贝呢?

在c++中如果用值拷贝自定义类型,会出事,假如是栈(数据结构),那么用传值调用,由于析构函数在对象生命周期结束后,会自动调用,这个时候,形参和实参都会对空间进行一次释放。相当于一块空间被连续释放了两次。

因此对于涉及资源申请(例如空间开辟)的类,我们应当自己写拷贝构造函数,对于不涉及的,可以让编译器自己生成。(注意,还有一种类型,就是我们进行嵌套,比如用两个栈实现队列,这个时候队列的拷贝构造我们没必要写,因为对于自定义类型,会调用它的拷贝构造,所以我们只需要把栈的拷贝构造写好就行)

对于拷贝构造一般用在:用已经存在的同类的对象初始化或拷贝。

函数参数为类类型对象或返回值为类类型对象。

下面给大家一段代码演示一下在vs2022里面,构造函数的调用

int main() {
    Date d1(2022, 1, 13);    // 调用构造函数创建d1
    Test(d1);                // Test函数以值方式传递,传参时调用拷贝构造函数创建d
    return 0;
}

Date Test(Date d) {        // 函数以值方式返回,返回时使用temp拷贝构造临时对象用来返回
    Date temp(d);          // 调用拷贝构造函数创建temp
    return temp;           // 返回temp
}

4.赋值运算符重载

4.1运算符重载

C++为了增强代码的可读性引入了运算符重载.
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

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

bool operator> (NN x, NN y)
{
	if (x._c > y._c)return true;
	else return false;
}
 
int main()
{
	NN B1, B2;
	cout << (B1 > B2);
//这个等同于cout<<(operator>(B1,B2));
	return 0;
}

为什么要有运算符重载呢?因为我们有了类,这时候可能会需要比较,但问题是,编译器也不知道我们比较的规则等,所以编译器对于自定义类型,干脆是不提供比较方法,要我们自己想办法,对于内置类型,编译器最开始就知道怎么比,所以有运算符直接提供给我们用。

这些运算符可以直接转换为机器能识别的指令。

但是,这样我们就忽略了封装了,如果不采用全局的operator,我们可以封装成成员函数。因为 operator> 直接访问了 NN 类的私有成员 _c,而通常情况下,类的私有成员(private)不应该被外部函数直接访问。

class NN
{
public:
	bool operator> (NN y)//可以是引用也可以传值
	{
		if (_c > y._c)return true;
		else return false;
	}
   //对于成员函数来说,可以随意调用同类的成员,不受限制
//对于成员函数来说,一直有个默认的this指针,作为第一参数,
//所以只需要一个参数即可.
private:
	int _c = 1;
	int _k;
};
 
 
 
 
int main()
{
	NN B1, B2;
	cout << (B1 > B2);
    //B1>B2在编译器视角下,是NN::operator>(&B1,B2);
	return 0;
}

4.2 赋值运算符重载

赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义

#include<iostream>
using namespace std;
 
class NN
{
public:
	NN(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
 
	NN& operator=(const NN& a)
	{
		if (this != &a)
		{
			_year = a._year;
			_month = a._month;
			_day = a._day;
		}
		return *this;
	}
 
	
	void print()
	{
		cout << _year << '/' << _month << '/' << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
 
 
 
 
int main()
{
	NN d1(2024, 1, 4);
	NN d2 =d1 + 100;
	 d2.print();
	 d1.print();
	return 0;
}

1.赋值运算符只能重载成类的成员函数,不能重载成全局函数,因为编译器会自动生产一个赋值运算符的重载在类里面(如果你不显示定义),这样全局和类里面会冲突,所以不能重载全局。

2.编译器默认生成的赋值运算符,跟拷贝构造很像,对内置类型的变量,直接采用逐字节值覆盖,自定义类型采用该类型的赋值运算符。赋值运算符重载是一个默认成员函数 , 用于完成两个  已经存在  的对象直接拷贝赋值 , 这里要注意跟拷贝构造区分 , 拷贝构造用于一个对象拷贝初始化给另一个对象 。 

3.没有显式实现时 , 编译器会自动生成一个默认赋值运算符重载 , 默认运算符重载行为跟默认构造函数类似 , 对内置类型成员变量会完成 浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造 。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

4.3 前置++和后置++重载

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // 前置++:返回+1之后的结果
 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
 Date& operator++()
 {
 _day += 1;
 return *this;
 }
 // 后置++:
 // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递
 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
 //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
 Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d;
 Date d1(2022, 1, 13);
 d = d1++;    // d: 2022,1,13   d1:2022,1,14
 d = ++d1;    // d: 2022,1,15   d1:2022,1,15
 return 0;
}

5.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,const 修饰成员函数放在成员函数参数后面 。实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

class Date
{
public:
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 void Print()
 {
 cout << "Print()" << endl;
 cout << "year:" << _year << endl;
 cout << "month:" << _month << endl;
 cout << "day:" << _day << endl << endl;
 }
 void Print() const
 {
 cout << "Print()const" << endl;
 cout << "year:" << _year << endl;
 cout << "month:" << _month << endl;
 cout << "day:" << _day << endl << endl;
 }
private:
 int _year; // 年
 int _month; // 月
 int _day; // 日
};
void Test()
{
 Date d1(2022,1,13);
 d1.Print();
 const Date d2(2022,1,13);
 d2.Print();
}

我们来看这段代码思考几个问题:

1. const对象可以调用非const成员函数吗?

❌ 不可以!const 对象保证其成员变量不会被修改,而非 const 成员函数可能修改成员变量。这里还是涉及到一个可更改的权限的问题。

2. 非const对象可以调用const成员函数吗?

 ✅ 可以!const 成员函数承诺不会修改对象状态,对非 const 对象是安全的。
3. const成员函数可以调用其它的非const成员函数吗? 

不可以!同1
4. 非const成员函数内可以调用其它的const成员函数吗?

可以!这是一种常见做法,例如在非 const 函数中复用 const 函数的逻辑

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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

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

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

7.日期类的实现

7.1流插入 <<  和 流提取 >>

注意,我们打印自定义类型不能直接用cout,为什么(我们之前知道cout和cin是自动识别类型,这是因为c++自己写了内置类型的函数重载,从而识别)cout是ostream类里面的,cin是在istream类里面的。如果我们要用cout和cin输入输出指定的自定义类型,我们要运算符重载。

通常我们要输出自定义类型有两种方法:

1.全局重载,需要用到友元函数,下一篇会讲。

class Date {
    int year, month, day;
public:
    Date(int y, int m, int d) : year(y), month(m), day(d) {}
    friend ostream& operator<<(ostream& os, const Date& dt); // 声明友元函数
};

// 实现 `<<` 运算符
ostream& operator<<(ostream& os, const Date& dt) {
    os << dt.year << "-" << dt.month << "-" << dt.day;
    return os; // 返回流以便链式调用(如 `cout << d1 << d2;`)
}

int main() {
    Date d1(2023, 1, 1);
    cout << d1; // ✅ 正确,输出:2023-1-1
    return 0;
}

2.函数重载,调用比较奇怪。cout在右边

class Date {
    int year, month, day;
public:
    Date(int y, int m, int d) : year(y), month(m), day(d) {}
    ostream& operator<<(ostream& os) { // ❌ 不推荐,因为调用方式奇怪
        os << year << "-" << month << "-" << day;
        return os;
    }
};

int main() {
    Date d1(2023, 1, 1);
    d1 << cout; // ❌ 语法奇怪,不符合常规 `cout << d1;` 的习惯
    return 0;
}

但是这里我们也可以让>>和<<重载.

class Date {
    int year, month, day;
public:
    Date() = default; // 默认构造函数(cin 需要)
    friend istream& operator>>(istream& is, Date& dt); // 声明友元函数
};

// 实现 `>>` 运算符
istream& operator>>(istream& is, Date& dt) {
    is >> dt.year >> dt.month >> dt.day;
    return is; // 返回流以便链式调用(如 `cin >> d1 >> d2;`)
}

int main() {
    Date d1;
    cin >> d1; // ✅ 正确,输入:2023 1 1
    cout << d1; // 输出:2023-1-1
    return 0;
}

7.2日期类代码

date.h

#pragma once
#include<iostream>
using namespace std;
 
class NN
{
public:
	NN(int year=1, int month=1, int day=1);
	int getday(int year, int month1);
 
	NN& operator+= (int day);
	NN operator+ (int day);
	NN& operator=(const NN& a);
	NN& operator-= (int day);
	NN operator- (int day);
	NN& operator++();
	NN operator++(int);
	NN& operator--();
	NN operator--(int);
 
	bool operator==(const NN& a);
	bool operator>(const NN& a);
	bool operator<(const NN& a);
	bool operator>=(const NN& a);
	bool operator<=(const NN& a);
	bool operator!=(const NN& a);
	int operator-(const NN& a);
	void print();
private:
	int _year;
	int _month;
	int _day;
};

date.cpp

#include"date.h"
 
NN::NN(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day; 
	if (_year < 1 || _month>12 || _day < 1 || _day>getday(_year, _month))
	{
		this->print();
		cout << "日期非法" << endl;
	}
}
 
int NN::getday(int year, int month1)
{
	int month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month1 == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
	{
		return 29;
	}
	return month[month1];
}
NN& NN::operator+= (int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}
	_day += day;
	while (_day > getday(_year, _month))
	{
		_day -= getday(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	//* this = *this + day;
	return *this;
}
NN NN::operator+ (int day)
{
	NN T(*this);
	T += day;
	//T._day += day;
	//while (T._day > getday(T._year, T._month))
	//{
	//	T._day -= getday(T._year, T._month);
	//	T._month++;
	//	if (T._month > 12)
	//	{
	//		T._year++;
	//		T._month = 1;
	//	}
	//}
	return T;
}
NN& NN::operator=(const NN& a)
{
	if (this != &a)
	{
		_year = a._year;
		_month = a._month;
		_day = a._day;
	}
	return *this;
}
 
NN& NN::operator++()
{
	*this += 1;
	return *this;
}
NN NN::operator++(int)
{
	NN tmp(*this);
	*this += 1;
	return tmp;
}
 
bool NN::operator==(const NN& a)
{
	if (_year == a._year && _month == a._month && _day == a._day)
	{
		return true;
	}
	else
	{
		return false;
	}
}
bool NN::operator>(const NN& a)
{
	if (_year > a._year)
	{
		return true;
	}
	else if (_year == a._year && _month > a._month)
	{
		return true;
	}
	else if (_year == a._year && _month == a._month && _day > a._day)
	{
		return true;
	}
	else return false;
}
void NN::print()
{
	cout << _year << '/' << _month << '/' << _day;
}
 
bool NN::operator>=(const NN& a)
{
	return *this > a || *this == a;
}
bool NN::operator<(const NN& a)
{
	return !(*this >= a);
}
 
bool NN::operator<=(const NN& a)
{
	return !(*this > a);
}
bool NN::operator!=(const NN& a)
{
	return !(*this == a);
}
 
 
NN& NN::operator-= (int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += getday(_year, _month);
	}
	return *this;
}
NN NN::operator- (int day)
{
	NN a(*this);
	a -= day;
	return a;
}
 
int NN::operator-(const NN& a)
{
	int flag = 1;
	NN max(*this);
	NN min(a);
	if (*this < a)
	{
		max = a;
		min = *this;
		flag = -1;
	}
	int d = 0;
	while (min != max)
	{
		++min;
		d++;
	}
	return d * flag;
 
}
 
NN& NN::operator--()
{
	*this -= 1;
	return *this;
}
NN NN::operator--(int)
{
	NN tmp(*this);
	*this -= 1;
	return tmp;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值