【C++】21年精通C++之类与对象(中)——类的默认成员函数

本文详细介绍了C++中类的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载,以及如何实现一个简单的日期类。重点讨论了这些函数的作用、使用场景以及如何自定义以满足特定需求,特别是涉及到动态内存管理和对象初始化、复制、清理等方面的知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

❤️前言

正文

构造函数

析构函数

拷贝构造函数

赋值运算符重载

普通对象和const对象取地址

自增自减操作符重载

const成员

实现一个简单的日期类

🍀结语


❤️前言

        今天这篇博客的内容主要是关于类和对象中类的6个默认成员函数,希望能对大家有所帮助。

正文

        在正式介绍这些默认的成员函数之前,我们可以先大致了解一下它们的名称和作用:

         这些默认成员函数顾名思义就是每一个类中都默认存在的成员函数,也就是说如果我们没有自己手动去定义这些函数,编译器就会自动生成这些默认成员函数,以此让我们可以对这个类进行一些基本的操作,虽然自动生成的成员函数不一定能满足我们的所有需求。

构造函数

        我们以前用C语言来编写数据结构时,常常需要设置一个初始化这个结构的函数,并且在使用这个结构之前调用这个函数对它进行初始化,而粗心的我们有时可能会忘记调用这个函数,然后直接使用这个结构,此时如果运行这个程序往往会带来未知的错误,而当代码变得越来越长,我们会很难发现这种错误,于是C++为了解决这种问题就想出了构造函数在对象实例化时对类的对象进行初始化的方式。

        也就是说,当我们在对一个类进行实例化对象时,编译器会自动调用一次构造函数,使每个对象中的成员变量都有其合适的初始值,这种初始化方式就是对类的默认初始化,而控制这个过程的构造函数叫做默认构造函数

        那么构造函数有哪些属性呢?

  • 构造函数的功能并不是创建一块空间去实例化对象,而是对这个对象的属性进行初始化。
  • 由于要在外部调用构造函数,我们需要将它声明在public限定符之下。
  • 构造函数的名字与我们定义的类的类名相同。
  • 构造函数没有返回值。
  • 构造函数支持重载。

        下面我们在日期类中定义并使用构造函数,以此演示构造函数的基本使用方法:


class Date
{
public:
    // 全缺省参数的构造函数
    Date(int year = 2023, int month = 4, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    // 这样我们不再需要下面这个函数
	void Set(int year = 2023, int month = 4, int day = 1)
    {
        _year = year;
		_month = month;
		_day = day;
    }
	void Print()
	{
		std::cout << _year << " " << _month << " " << _day << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    // Date d1(); 错误的写法,这样编译器会默认你是在声明一个函数
    Date d1;
    Date d2(2023, 5, 5);
    d1.Print();
    d2.Print();
    return 0;
}

        这段代码的结果是:

         可以发现,我们自己定义的构造函数很好的完成了预想当中的工作,而我们之前也说过,类中的默认成员函数是可以由系统自动合成的,那么我们现在来看看如果不在类中声明构造函数然后直接使用系统合成的默认构造函数会发生什么事。

        现在我们将我们自己设置的构造函数暂时屏蔽掉,会发生什么事呢?

         这里编译器直接报错,说未初始化本地变量,但是我们说过如果类中没有声明任何构造函数时,编译器是会默认生成一个构造函数的,那么为什么会发生变量未初始化的情况呢?

        《C++ Primer》中提到,编译器创建的构造函数又被称为合成的默认构造函数,那么这个“合成”出来的默认构造函数到底会发挥什么作用呢?

        怀着这两个疑问,我们一起学习关于合成的默认构造函数的相关知识。

        我们先定义一个Time类,并向Date类中引入一个Time类的私有成员,同时在Date类的构造函数中不对Time类类型的成员进行处理,现在让我们看看这样做会发生什么事情。

class Time
{
public:
	Time(int hour = 0,int minute = 0,int second = 0)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

        我们先解除对Date类构造函数的屏蔽,并在创建Date类类型变量的地方创建一个断点,并一步步运行调试:

        首先,我们发现这里发生了对构造函数的调用:

         然后,我们发现了神奇的一幕——我们虽然没有在构造函数中对Time类的对象进行初始化,但是编译器却立即“自发地”对Time类的构造函数进行了调用:

         那么我们可以得到一个结论,就是当我们进行类的默认初始化时,类中的自定义成员会自己发生自己的默认初始化,也就是调用它自己的默认构造函数(当然前提是它有合适的默认构造函数)。在此基础上我们可以做一个猜测,那就是编译器自己生成的所谓合成的默认构造函数其实是只会对自定义类型成员变量进行处理,也就是让它自己进行默认初始化(调用它的默认构造函数),但是对于内置类型成员,编译器自行生成的构造函数就显得无能为力了,而事实也正是如此。

        但是C++11中对这种情况进行了一定程度上的优化,我们可以在声明内置类型成员变量时给它们设置初始值,让这些类可以匹配编译器自己生成的默认构造函数,并进行默认初始化。也就是说我们现在先将自己写的构造函数屏蔽,并给所有的内置类型成员初始值,这样当我们使用合成的默认构造函数直接初始化对象时便不再会报错。

        也就是这样:


class Date
{
public:
    // 全缺省参数的构造函数
    // Date(int year = 2023, int month = 4, int day = 1)
	// {
	//	 _year = year;
	// 	 _month = month;
	// 	 _day = day;
	// }
	void Print()
	{
		std::cout << _year << " " << _month << " " << _day << std::endl;
	}
private:
	int _year = 2023;
	int _month = 4;
	int _day = 1;
    Time _time;
};

int main()
{
    // 这样做也是可以通过编译的,且成员变量的值吻合我们给的初始值
    Date d1;
    d1.Print();
    return 0;
}

        虽然这种方式一定程度上确实为我们带来了不少便利,但是我们同时也失去了许多自由和操作空间,毕竟很多情况下我们对于一个类的不同对象里的数据成员是有不同要求的,尤其是当我们在设计需要申请内存资源的类时更是如此,因此我们在大多数情况下最好还是不要偷懒,自己写一个默认构造函数(默认构造函数的要求是在调用时可以不传参数,调用该函数时在我们看来就是只有创建对象的步骤)比较好。

析构函数

        上面我们学习了构造函数的相关内容,当类实例化对象时,调用构造函数对对象进行初始化。

        而在对象的生命周期中,还有一种函数是和初始化函数的性质类似的函数,也就是我们之前常常会写的“销毁”函数,当然这个销毁并不是销毁一个对象本身,而是返还我们在对象中申请的资源(也就是动态内存管理),在C语言中我们常常是和free关键字相联系。

        在C语言阶段,我们有时会忘记掉去调用这种清理资源的函数,就比如说当对象的生命周期结束的情况有很多种时,不仅容易忘记释放资源,还会导致一定程度上的代码冗余。这时C++的语法就创造出一个相对于构造函数的析构函数,可以默认地在对象生命周期结束前调用并清理资源。

        这里再次强调一次析构函数的作用是清理对象中申请的资源,而不是销毁这个对象,每个对象在被销毁时都会自动调用它的析构函数。

        现在我们简单地描述析构函数的特性:

  • 析构函数的函数名是在类名的前面加上一个~。
  • 析构函数无参数无返回类型。
  • 每一个类只能有一个析构函数,如果没有显式地定义,那么编译器会自动生成默认的析构函数。(也就是说,析构函数是不能重载的)
  • 对象的生命周期结束时,C++的编译系统会自动调用析构函数。

        这里以一个栈类来举例析构函数的使用方法:

class Stack{
public:
    //构造函数
    Stack(int max_size = 10) {
        max_size_ = max_size;
        initStack();
    }
    //析构函数
    ~Stack() {
        // 用关键字delete清理资源
        delete data_;
    }
    //是否栈空
    bool isEmpty() {
        return top_ == -1;
    }
    //是否栈满
    bool isFull() {
        return top_ >= max_size_;
    }
    //进栈
    int push(int x) {
        if(isFull())
            return 0;
        else
            data_[++top_] = x;
        return 1;
    }
    //出栈
    int pop(int &x) {
        if(isEmpty())
            return 0;
        else
            x = data_[top_--];
        return 1;
    }
    //清理栈
    void clear(){
        top_ = -1;
    }
private:
    int* data_;//存放栈中的数据
    int max_size_;//栈最大空间
    int top_;//栈顶
    //初始化栈
    void initStack() {
        data_ = new int[max_size_];//使用关键字new申请栈空间
        top_ = -1;//初始化栈顶
    }
};

        对于析构函数,在编译器自动合成析构函数时的情况于构造函数类似,合成的析构函数只能做到调用类中自定义类型成员的析构函数,但是却不能实现对类本身的资源清理,因此大部分情况下我们也还是要按照自己的需求去编写每个类的析构函数,也就是在这个函数中进行动态资源管理。

        换句话说,没有涉及动态内存管理的类可以不写析构函数,直接使用编译器默认生成的析构函数,而需要清理资源的类必须要写析构函数,否则会导致内存泄漏。

拷贝构造函数

        我们说过,构造函数是用于初始化对象的函数,而在初始化对象时,我们常常会使用已经创建好的对象对另一个对象进行初始化,类的默认成员函数中也有这种考虑,也就是拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,满足构造函数的基本特点,除此之外其特征有:

  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
  • 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
  • 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

        这里直接用日期类来举例子:

// 日期类的拷贝构造函数
// 由于没有动态资源管理,所以日期类也可以直接使用编译器默认合成的拷贝构造函数
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

int main()
{
    // 使用我们定义的默认构造函数进行初始化
    Date d1(2023, 5, 7);
    // 使用拷贝构造函数进行初始化
    Date d2(d1);
    // 上面的式子等价于 Date d2 = d1;
    return 0;
}

        我们可以发现,在日期类的使用中不会发生动态内存管理,也就是说我们只需要完成成员变量上的浅拷贝就可以,这时候在日期类的使用中我们就可以直接使用编译器自动合成的拷贝构造函数。

        而如果在一个类的使用中需要进行资源的管理,例如栈类,那么我们在对其对象进行拷贝初始化时就需要进行深拷贝(也就是不只是进行值层面上的拷贝,而是申请出不同的内存空间,并在这块内存中放入和被拷贝的类成员相同的值)。

        总结:我们在设计需要进行深拷贝的类时必须要写拷贝构造函数,因为指针是内置类型,而内置类型在合成的拷贝构造函数中是按照字节序直接拷贝的,那么如果使用合成的拷贝构造函数,两个不同的对象就会使用同一块动态内存。反之,我们自己写深拷贝的拷贝构造函数时就需要申请一块新的空间交给初始化的对象,并将被拷贝的对象的动态内存中的内容拷贝到新的空间中去,这样,我们就很好地满足了深拷贝的要求。

        最后,所有的拷贝初始化类都会调用拷贝构造函数,也就是说拷贝构造函数的使用较为频繁,例如以已存在的对象的信息去创建一个新的对象;用类的对象进行传值传参,这时会拷贝一个和此对象内容一致的临时对象;函数返回值类型为类对象,当函数返回值时也会创建一个与函数内对象内容相同的临时对象。

赋值运算符重载

        在正式学习类的默认成员函数之一——赋值运算符的重载之前,我们先想想之前学过的函数重载,函数重载是将作用相似的同名函数以不同的参数进行区分以达到提高代码可读性的目的。那我们现在引入运算符重载的概念,运算符重载顾名思义就是对语法中已知存在的运算符进行重载,和函数重载类似,运算符重载也是用于达到提高代码可读性的目的。

        本质上说,重载后的“运算符”是一种具有特殊函数名的函数,它也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

        运算符重载需要使用到的关键字是operator,重载运算符的函数名为operator运算符,函数原型为  返回值  operator运算符 (参数列表)

        运算符重载的特征有:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 运算符重载必须至少有一个参数是类相关类型,类类型、类指针、类引用
  • 针对于内置类型的运算符是无法改变其意义的,如内置类型相加不能给它改成相减,也就是说运算符重载肯定是与类及其对象的使用相关的(毕竟仔细想想好像也就是在类这方面使用运算符重载比较有意义)。
  • 运算符重载的函数体最多允许有两个参数,放到运算符中的说法就是最多两个操作数(因此我们可以知道三目操作符是不允许重载的)。
  • 当运算符重载作为类的成员函数时,参数中隐含了一个this指针。
  • .* :: sizeof ?: . 注意有5个运算符不能重载:  .*(点加星号)  ::(作用域限定符) sizeof(空间大小)  ?:(三目操作符) .(成员访问符)。
  • 在调用运算符重载时,按照从左往右的方向传递参数,例 a - b,a是第一个参数,b是第二个参数。

        我们大致了解了运算符重载的特征,那么现在我们以日期类重载==运算符来做例子:

// 为了方便这里暂时不写函数在类中的声明。

// 假如设置成全局函数:
// 这里会发现如果将运算符重载成全局的就需要突破类封装来访问类成员变量
bool operator==(const Date& d1, const Date& d2)
{
    // 这里会报错,因为外部函数无法直接访问类对象的私有成员
     return d1._year == d2._year
         && d1._month == d2._month
         && d1._day == d2._day;
}

// 将==运算符重载作为类的成员函数
// 它的本体是:
// bool Date::operator==(Date* this, const Date& d)
// 需要注意的是,左操作数对应this指向的对象,右操作数对应常引用d
bool Date::operator==(const Date& d)
{
    // 这时重载==属于成员函数,因此可以访问成员变量
     return _year == d._year
         && _month == d._month
         && _day == d._day;
}

        在充分认识了运算符重载的内容后,我们现在开始正式学习类的六大默认成员函数之一的赋值运算符("=")重载。

        赋值运算符重载在类中的函数声明是 Type& operator = (const Type& obj);

        这里依旧以日期类的赋值运算符举例:

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
    // 判断是否对自己赋值
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
    // 返回*this使赋值运算符重载支持连续的赋值
	return *this;
}

        在使用赋值运算符重载时我们比较需要注意的是,由于它是类的默认成员函数,如果未显式定义,系统会自动生成一个按照字节序浅拷贝的赋值运算符重载(这点与拷贝构造函数类似)。

        这样的话如果我们同时在全局定义一个赋值运算符重载,这两个函数就会发生冲突(重载赋值运算符的两个操作数一般来说都是类类型),所以在C++中是规定了不允许在全局定义赋值运算符重载的,也就是说:赋值运算符重载只能是一个类的成员函数,而不能在全局中定义。

        而且根据上面的分析与拷贝构造函数相同,赋值运算符重载在类需要深拷贝时是需要我们自己去编写的,这一点也需要注意。

        了解完赋值运算符重载的相关知识之后,我们继续来讲剩下的两个默认成员函数。

普通对象和const对象取地址

        相对于其它的四个默认成员函数,这两个取地址函数就显得比较没那么有存在感了。如同标题所说,这两个函数主要是用于对类对象进行取地址操作(也就是重载取地址运算符&)。

        那么我们知道类的成员函数的参数中是默认含有this指针的,this指针就代表着不同对象的地址,也就是说,我们只需要在函数体中返回this指针的值就可以了。而编译器默认生成的这两个函数也是可以完成取地址的目的,因此大部分情况下我们是不会考虑这两个函数的,直接使用编译器默认生成的取地址函数即可。

// Date类的这两个函数显式定义可以写作这样:
Date* operator&()
{
     return this;
}
 
const Date* operator&()
{
     return this;
}

自增自减操作符重载

        我们在学习运算符重载的时候肯定会遇到自增自减运算符发生冲突的问题,在一般的运算符重载的函数原型中,我们似乎无法将前置操作和后置操作很好地进行区分。

        于是C++就做了一个规定:只需要让后置操作重载的参数列表中加入一个int类型的参数(不需要标注参数名也不需要手动传参,因为只需要在调用函数时传参用于区分前置后置,而不需要在调用后进行接收和使用,所以编译器会自动完成整个过程),目的是以之区分前置操作和后置操作,但是这是为什么呢?我们知道创造C++的大佬们在这些事上都是很严谨有逻辑的,那么为什么受委屈的是后置操作呢?

        这也很好理解,前置操作的运行逻辑是先改变原对象的值,然后返回改变后的值,这样在实现的过程中我们就不需要创建一个临时的用于存储原值的对象;而后置操作的运行逻辑是返回原值,并让操作数发生改变,也就是需要创建一个存储原值的临时的对象。根据这样的规则,在实现上前者的效率就明显高于后者,所以在对类的自增自减操作中,我们一般更推荐使用前置操作符

        下面我们用日期类来展示自增自减操作符重载的定义:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2)
	{
		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
		{
			monthday[2] += 1;
		}
	}
	return monthday[month];
}

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	int monthday = GetMonthDay(_year, _month);
	while (_day > monthday)
	{
		_day -= monthday;
		if (++_month > 12)
		{
			_month = 1;
			_year++;
		}
		monthday = GetMonthDay(_year, _month);
	}
	return *this;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day < 0)
	{
		if ((--_month) == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

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

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

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

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

const成员

        我们之前写的判断相等运算符重载其实是有一个很大的缺陷的,现在让我们一起来看一段作为例子的代码:

bool Date::operator==(const Date& d)
{
    // 这时重载==属于成员函数,因此可以访问成员变量
     return _year == d._year
         && _month == d._month
         && _day == d._day;
}

int main()
{
	Date d1;
	const Date d2;
	bool jud1 = d1 == d2;
    // 这里第二条判断相等的语句会报错
	bool jud2 = d2 == d1;
	return 0;
}

        上面的代码会出现如下的错误:

         报错显示的信息是没有与操作数相匹配的重载函数,但是我们在上面明显已经定义了一个重载函数了,d1 == d2 的语句也是可以通过的,那么也就是说我们在上面定义的那个重载==是不满足所有的使用需求的,这是为什么呢?

        我们这里先复习一下关于this指针的一些内容,this指针的类型是类类型* const,即在成员函数中,不能给this指针赋值,且this指针本质上是“成员函数”的形参,当对象调用成员函数时,编译器自行将对象地址作为实参传递给this形参,此外,this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

        现在我们来探究原因,其实本质上是我们将const对象d2的地址传给了非const*指针类型的this指针(我们需要的类型是const 类类型* const,也就是这个指针不能改变原对象),这样做就触及了引用和指针方面的一大误区,也就是不能用引用和指针将我们访问对象的权限变大,而只能缩小和平移(我在之前的文章中讲过类似的内容)。因此我们的解决方案就是把this指针的类型变成前面带有const的指针,但是this指针是隐式声明的呀?我们该如何将this指针的类型设置成加上const的类指针呢?

        这里C++采用了一种较为“丑陋”的方式来救场,就是我们可以在函数原型和花括号之间以及函数声明的末尾加上两个const,这样this指针就变成了前面带有const的常性指针(类型为const 类类型* const)。具体改变方式如下:

...
// 在函数的声明处也需要加上const
// ==运算符重载
bool operator==(const Date& d) const;
...

// ==运算符重载
bool Date::operator==(const Date& d) const 
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

        而这时相同的代码就不会发生错误:

         同理可得,所有不会改变this指针所指向对象的成员函数其实都要加上const,这样我们编写的成员函数就会变得更加的完善。

实现一个简单的日期类

        这里给出我自己学习时实现的一个日期类,大家如果有兴趣也可以自己写写练习一下,其中还有很多可优化的地方:

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

	// 获取某年某月的天数
	int GetMonthDay(int year, int month) const;

	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day) const;

	// 日期-天数
	Date operator-(int day) const;

	// 日期-日期 返回天数
	int operator-(const Date& d) const;

	// 日期-=天数
	Date& operator-=(int day);

	// 前置++
	Date& operator++();

	// 后置++
	Date operator++(int);

	// 后置--
	Date operator--(int);

	// 前置--
	Date& operator--();

	// >运算符重载
	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;

private:
    // 可以在这里给出成员变量的初始值
	int _year = 0;
	int _month = 0;
	int _day = 0;
};
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month) const
{
	int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2)
	{
		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
		{
			monthday[2] += 1;
		}
	}
	return monthday[month];
}

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	int monthday = GetMonthDay(_year, _month);
	while (_day > monthday)
	{
		_day -= monthday;
		if (++_month > 12)
		{
			_month = 1;
			_year++;
		}
		monthday = GetMonthDay(_year, _month);
	}
	return *this;
}

// 日期+天数
Date Date::operator+(int day) const
{
	Date newdate = *this;
	newdate += day;
	return newdate;
}

// 日期-天数
Date Date::operator-(int day) const
{
	Date newdate = *this;
	newdate -= day;
	return newdate;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day < 0)
	{
		if ((--_month) == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

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

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

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

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

// >运算符重载
bool Date::operator>(const Date& d) const
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if(_month == d._month && _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
{
	Date big = *this;
	Date small = d;
	int flag = 1;
	int day = 0;
	if (*this == d) return 0;
	if (*this < d)
	{
		small = *this;
		big = d;
		flag = -1;
	}
	if (big._year == small._year)
	{
		if (big._month == small._month)
		{
			day = big._day - small._day;
		}
		else
		{
			for (; big._month > small._month; big._month--)
			{
				big._day += GetMonthDay(big._year, big._month - 1);
			}
			day = big._day - small._day;
		}
	}
	else
	{
		for (; big._year > small._year; big._year--)
		{
			int year = big._year;
			if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
			{
				big._day += 366;
			}
			else
			{
				big._day += 365;
			}
		}
		if (big._month >= small._month)
		{
			for (; big._month > small._month; big._month--)
			{
				big._day += GetMonthDay(big._year, big._month - 1);
			}
		}
		else
		{
			for (; small._month > big._month; big._month++)
			{
				big._day -= GetMonthDay(small._year, big._month);
			}
		}
		day = big._day - small._day;
	}
	return flag*day;
}

🍀结语

        这里就是今天C++中关于类和对象的中篇内容啦!最后,希望正在看这篇文章的你能真切的遨游在知识的海洋中,学有所得,谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

发呆的yui~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值