C++的类与对象(五):赋值运算符重载与日期类的实现

目录

比较两个日期对象

运算符重载

赋值运算符重载

连续赋值

日期类的实现

Date.h文件

Date.cpp文件

Test.cpp文件

const成员 

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


比较两个日期对象

问题描述:内置类型可直接用运算符比较,自定义类型的对象是多个内置类型的集合编译器不支持

解决办法: 自定义比较函数

#include <iostream>
using namespace std;

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

//年份是否相等
bool TimeCompare1(const Time&x,const Time& y)
{
	return x._year == y._year
		&& x._month == y._month
		&& x._day == y._day;
}

//左年份是否小于右年份
bool TimeCompare2(const Time& x, const Time& y)
{
	if (x._year < y._year)
	{
		return true;
	}
	else if (x._year == y._year)
	{
		if (x._month < y._month)
		{
			return true;
		}
		else if (x._month == y._month)
		{
			return x._day < y._day;
		}
	}
	return false;
}

int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);
	cout << TimeCompare1(t1, t2) << endl;
	cout << TimeCompare2(t1, t2) << endl;
	return 0;
}

        代码的编写者知道TimeCompare1和TimeCompare2两个函数的区别,但是对于其他人来说只能大概的知道它们是两个比较函数?对此C++提出了运算符重载的概念帮助帮助我们更好的了解这些比较函数的大致功能

运算符重载

格式:返回类型 operator 操作符(参数列表)

作用:不仅解决了函数名不规范的问题增强代码的可读性,而且还使自定义类型也可以用运算符

注意事项:

1、operator关键字+运算符做函数名

#include <iostream>
using namespace std;

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

bool operator==(const Time&x,const Time& y)
{
	return x._year == y._year
		&& x._month == y._month
		&& x._day == y._day;
}

bool operator<(const Time& x, const Time& y)
//bool TimeCompare2(const Time& x, const Time& y)
{
	if (x._year < y._year)
	{
		return true;
	}
	else if (x._year == y._year)
	{
		if (x._month < y._month)
		{
			return true;
		}
		else if (x._month == y._month)
		{
			return x._day < y._day;
		}
	}
	return false;
}


int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);
	cout << operator==(t1, t2) << endl;
	cout << operator<(t1, t2) << endl;
	return 0;
}

同样的对于cout部分也可以写成:

int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);
    cout << (t1 == t2) << endl; //等价于cout << (operator==(t1,t2)) << endl;
    cout << (t1 < t2) << endl;
    return 0;
}

可以写成(t1 == t2)的原因是编译器帮我们做了一些处理,但是需要注意的是,在上述函数中我们将成员变量时的访问权限变为了public,但是实际情况中成员变量的访问权限是private,为此我们可以将定义的函数直接写入类中来解决这一问题:

但是当我们将运算符重载函数原封不动的移入类中时,会发现系统提示函数的参数太多了,这是因为除了x和y两个参数外还有一个隐藏的this指针,为此我们可以继续做出以下改变:

#include <iostream>
using namespace std;

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

	bool operator==(const Time& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}

	bool operator<( const Time& y)
		//bool TimeCompare2(const Time& x, const Time& y)
	{
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		return false;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);

	cout << t1.operator==(t2) << endl; //仍然可以写成cout << (t1 == t2) << endl;
	cout << t1.operator<(t2) << endl;  //仍然可以写成cout << (t1 < t2) << endl;
	return 0;
}

我们删除了参数const Time&x,这是因为我们放弃了将两个对象的成员变量放在一个比较函数进行比较的方法,而是调用t1类中的运算符重载函数,并将t1的this指针和t2的地址传递过去

#include <iostream>
using namespace std;

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

	void Print()
	{
		cout << this << endl;
	}

	bool operator==(const Time& y)
	{
		cout << this << endl;
		return _year == y._year    //等价于this->_year == y._year
			&& _month == y._month
			&& _day == y._day;
	}

	bool operator<( const Time& y)
	{
		cout << this << endl;
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		return false;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Time t1(2024, 1, 28);
	t1.Print();
	Time t2(2024, 2, 27);
	cout << t1.operator==(t2) << endl;
	cout << t1.operator<(t2) << endl;
	return 0;
}

2、不是所有运算符都需要(支持) 运算符重载

3、函数重载的意义是可以存在同名但是其它内容不同的重名函数,运算符重载的意义是可以让两个自定义类型的对象进行比较

4、operato后的操作符必须是C/C++语法中存在的

operator@ //wrong

5、重载操作符必须有一个自定义类型的参数(不能去重载运算符改变内置类型的行为)

6、用于内置类型的运算符,其含义不能改变(是大于号但是重载时按小于的逻辑实现)

7、运算符重载函数作为类的成员函数重载时,其形参看起来要比操作数数目少1(因为成员函数的第一个参数为隐藏的this指针) 

8、“.*”、“::”、“sizeof”、“?:”、“.”这五个运算符不能重载

.*:调用成员函数的指针时会使用

typedef重命名函数指针时,新名字要写在括号内:typedef void(*xinmingzi) ()

赋值运算符重载

拷贝构造:同类型的一个存在的对象初始化要创建的对象

赋值重载:两个已经存在的同类型的对象,一个拷贝赋值给另一个

#include <iostream>
using namespace std;

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

	bool operator==(const Time& y)
	{
		cout << this << endl;
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}

	bool operator<(const Time& y)
	{
		cout << this << endl;
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		return false;
	}

	void  operator=(const Time& y)
	{
		_year = y._year;
		_month = y._month;
		_day = y._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "=" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);
	t1 = t2;
	t1.Print();
	t2.Print();
	return 0;
}

连续赋值

一般情况下的赋值运算符可以进行连续赋值:

//连续赋值
int i,j;
i = j = 10

过程描述:10赋值给j,表达式j = 10的返回值是j,然后返回值j作为表达式i = j的右操作数赋值给i

 所以,赋值重载应该也应该可以进行连续赋值,但是会报错:

#include <iostream>
using namespace std;

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

	bool operator==(const Time& y)
	{
		cout << this << endl;
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}

	bool operator<( const Time& y)
	{
		cout << this << endl;
		if (_year < y._year)
		{
			return true;
		}
		else if (_year == y._year)
		{
			if (_month < y._month)
			{
				return true;
			}
			else if (_month == y._month)
			{
				return _day < y._day;
			}
		}
		return false;
	}

	void  operator=(const Time& y)
	{
		_year = y._year;
		_month = y._month;
		_day = y._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "=" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Time t1(2024, 1, 28);
	Time t2(2024, 2, 27);
	Time t3(t1);
	t1 = t2 = t3;
	t1.Print();
	t2.Print();
	return 0;
}

这是因为t2 = t3的表达式没有返回值,实际上这里应该有一个返回值t2(跟普通的连续赋值一样) ,然后返回值t2应该作为t1 = t2这个表达式的右操作数,最后将t2赋值给t1,所以赋值重载函数应该有一个返回值,返回的应该是Time类的对象(有可能是t2也有可能是t1)

Time& operator=(const Time& y)
{
	_year = y._year;
	_month = y._month;
	_day = y._day;
	return *this;
}

注意事项:

1、返回值不是this而是*this,因为this是指针而我们想要返回的是该指针指向的对象

2、返回值类型应该是Time&而不是Time,因为返回类型是Time的话就是传值返回,传值返回的是*this(某个对象)的拷贝,这表示还要去调用拷贝构造函数,会造成无限递归

3、为了防止不必要自我的赋值,在赋值开始时会进行检查

Time& operator=(const Time& y)
{
    if(this != &y) //这里的&是取地址,判断this指向的地址和对象y的地址是否相同
    {
	    _year = y._year;
    	_month = y._month;
    	_day = y._day;
    }
   	return *this;
}

4、 没有显示定义赋值重载函数,编译器生成一个默认赋值重载函数,进行浅拷贝(内置类型的成员变量直接赋值,自定义类型的成员变量需要调用对应类的赋值重载函数)如果类中未涉及资源管理(开辟空间)赋值重载函数是否实现都可以,一旦涉及到资源管理必须实现赋值重载函数

5、C++规定赋值重载不能重载全局(大于小于可以重载成全局的,因为它们不是默认赋值重载函数)

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

6、拷贝构造是同一类型存在的对象初始化要创建的对象、赋值是已经存在的两个对象,一个拷贝赋值给另一个

日期类的实现

Date.h文件

#pragma once 
#include <iostream>
#include <assert.h>
using namespace std;

//声明日期类Date
class Date
{

public:
	//全缺省构造函数,为程序提供默认的缺省值
	Date(int year = 1, int month = 1, int day = 1);

	//运算符重载函数,更好的反应出函数的所提供的服务
	//日期大小的逻辑判断
	bool operator>(const Date& d);//返回值为bool型,因为我们只需要知道到底是大于还是小于即可
	bool operator<(const Date& d);//采用传引用传参,传入的是右操作数的别名,且加了const修饰后该别名只有读权限,对别名的修改不会影响全局
	bool operator==(const Date& d);
	bool operator>=(const Date& d);
	bool operator<=(const Date& d);
	bool operator!=(const Date& d);


	//日期增加和减少的运算
	Date& operator+=(int d);//返回值类型是Date和Date&均可但是是Date类型时会发生数据拷贝,造成资源浪费
	Date operator+(int d);//+=采用Date&做返回值类型是为了效率,+采用Date做返回值类型是因为防止野引用
	Date operator-(int d);
	Date& operator-=(int d);

	//日期++和--的运算(需要做特殊处理,区分前置和后置++、--),特殊处理都是为了解决语法逻辑不自洽,Date&是因为前置++在函数中进行+1后直接返回即可(相当于走个过场)用引用做返回值类型可以减少拷贝,Date是因为后置++返回的是原对象的拷贝虽然原对象也进行了++操作但是不能让它作为返回值,如果也是Date&由于tmp在函数结束时就会销毁会造成野引用的情况
	Date& operator++();//++d1
	Date operator++(int);//d1++,为了跟前置++区分,需要强行增加一个int形参,从而构成operator++()和operator++(int)之间的函数重载
	Date& operator--();//--d1
	Date operator--(int);//d1--

	//日期减日期(运算符重载函数同时也可以构成函数重载,这里的int operator-和Date operator-构成函数重载)
	int operator-(const Date& d);//返回的是两个日期之间的差值,同时不能修改传入的日期


	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	//根据月份获取本月天数(本质是inline)
	//即使没有显式使用 inline 关键字,类中定义的函数也可能被视为内联函数。
	//在C++中,将函数定义放在类声明内部(通常是在类定义头文件中)的成员函数会被隐式视为内联函数。
	//当你把一个成员函数的定义直接放在类声明里时,编译器会自动将其视为内联函数。
	// 这种情况下,编译器会尝试优化这些成员方法以减少调用开销,并且不需要额外的 inline 关键字来指示。
	int GetMonthDay(int year,int month)	//省去了if...else的判断,直接将月份写在数组中,同时为了能够使输入的月份能与数组中该月份的天数对应,创建了一个13个元素大小的数组,数组首元素为0,当传入月份为1时monthDays[1]刚好是31天
	{
		assert(month > 0 && month < 13);//月份的大小要在合理的范围内
		static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//该函数会被频繁调用,所以为了避免每次调用函数的时候都会创建数组,应该在静态区开辟数组(生命周期延长至全局)
		if (month == 2 &&((year % 4 == 0) && year % 100 != 0) || (year % 400 == 0) )//只有month==2时才需要进行模运算,逻辑短路
		{
			return 29;
		}
		return monthDays[month];//返回当前月对应的天数
	} 

	//检查流提取时的无效日期:流提取时输入2024 2 30可是二月根本没有三十天,但是如果没有检查函数的话编译器不认为这是错的,所以需要在人为初始化对象的时候(全缺省参数)以及流提取时进行无效日期的判断
	bool CheckInvalid();//看起来没参数,但是类中的成员函数有一个隐藏的this指针参数

	//友元声明(朋友声明)
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
	//友元声明允许一个函数或类访问另一个类的私有成员。
	//通过使用友元声明,可以授权指定的外部函数或类来访问另一个类的私有成员,而无需将这些外部函数或类作为该类的成员函数
	//友元声明通常在被授权访问私有成员的外部实体(如全局函数、其他类)中进行。下面是一些关于友元声明使用方式和语法示例:
	//注意事项:
	//友元关系不具备传递性。
	//类之间互为友好时,它们各自可见对方所有非私密信息。
	//应慎用友好机制以避免破坏封装性原则。

private:
	int _year;
	int _month;
	int _day;
};

//流插入运算重载,目的是为了让除了内置类型的对象能用流插入运算符,自定义类型的对象也可以用流插入
//流插入运算符属于iostraem类中的ostream对象
//将该运算符重载函数放在类外是因为<<是双目运算符,在类中该函数的形式operator<<(ostream& out),而类中的成员函数有一个默认的this指针作为第一个形参,所以实际上的形式是operator<<(this,ostream& out),而cout<<d1,很明显调用时运算符左右两侧的类型是相反的,那么就需要写成d1 << cout 的形式,但是这就表示控制台流入到日期类中(不会报错,但是倒反天罡)
//根本原因:operator<<作为成员函数重载,this指针必须占据第一个参数,Date必须是左操作数
//因此为了让ostream作为第一个参数必须让该函数是全局函数
//但是此时就无法直接访问private的成员变量了,
ostream& operator<<(ostream& out, const Date& d);//全局函数不能放在.h中否则容易出现重定义(两个.cpp文件都会包含该函数.h)
//ostream&是为了满足多次流插入的需求写的,如果只进行一次流插入可以写成void



//道理基本相同,流提取运算符属于iostream类中的istraem对象
istream& operator>>(istream& in, Date& d);//这里不加const的原因是,流提取的值要放在日期类中这个日期类碧血得是可以被读写的,加了const就不能更改日期类


Date.cpp文件

#include "Date.h"

//以下的所有函数都位于类域中,这就导致以往的声明与定义分离时在.cpp文件中直接写函数名即可,
// 但是位于特定类域中的函数只有在指明函数所在的类域时才能使用,故在函数名前加上了域作用限定符

//全缺省的默认构造函数(确定默认值,函数声明和定义分离时缺省值未避免歧义只能出现一次,最好是在声明中给与)
Date::Date(int year, int month, int day)//全缺省的默认构造函数不需要返回值
{
	_year = year;
	_month = month;
	_day = day;

	if (!CheckInvalid())//人为初始化的时候(上来就定义d2(2024,2,30))也得检查
	{
		cout << "构造日期非法" << endl;
	}
}

//小于运算
bool Date::operator<(const Date& y)
{
	if (_year < y._year)
	{
		return true;
	}
	else if (_year == y._year)
	{
		if (_month < y._month)
		{
			return true;
		}
		else if (_month == y._month)
		{
			return _day < y._day;
		}
	}
	return false;
}

//小于等于
bool Date::operator<=(const Date& y)
{
	return *this < y || *this == y;//this是指向的是对象d1空间的指针,对this的解引用可以获得d1对象的值
}
//涉及对象判断、加减的运算符在运算时都会去调用我们自定义的运算符重载函数
//如果判断d1<=d2,在进入该运算符重载函数时,会先调用小于运算符重载函数的情况,虽然看起来只是一个<,实际上却是(*this).operator<(y),可以写成一个简单的<是因为编译器替我们做了处理
//判断*this是否小于y,如果小于就返回真否则为假,由于总的目的是判断d1是否小于等于d2,所以满足小于或等于一个条件即可返回真

//大于
bool Date::operator>(const Date& y)
{
	return !(*this <= y);//*this<=y的结果如果为真,即d1<=d2为真,再加上一个逻辑取反运算符!,就可以在逻辑上表明d1是不大于d2的,合理的运用逻辑取反运算符可以省略很多代码
}//判断小于等于就会调用operator<=,进而调用operator<和operator==

//大于等于
bool Date::operator>=(const Date& y)
{
	return !(*this < y);//原理同上
}

//等于
bool Date::operator==(const Date& y)
{
	return _year == y._year
		&& _month == y._month
		&& _day == y._day;
}

//不等于
bool Date::operator!=(const Date& y)
{
	return !(*this == y);//原理同上
}

//日期加天数(如果将该函数作为operator+,那么该函数就会导致d2 = d1 + 20得到d2时,d1的值也会发生改变,毕竟是this->_day = day,所以为了避免对d1的修改应该传入一个d1的拷贝代替d1进行运算)
Date& Date::operator+=(int day)
{//此时该函数传入的this指针是tmp对象的地址
	_day += day;//_day = 29+20 = 49
	//cout<<"还没进来" << endl;
	//t天大于当前月应该的天数,天数就应该减去当前月对应的天数,同时将月数++,进行下一个月的判断
	while (_day > GetMonthDay(_year, _month))//此时调用GetMonthDay函数,49>(该函数的返回值是)
	{
		//cout<<"进来了" << endl;
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)//如果天数大到把12个月份的所有天数都减完,此时的_month的值应该就是13
		{
			++_year;//_month=13证明_day都比365或366都大,此时年份+1
			_month = 1;//重现将月份置为1,再次开始循环判断
		}
	}
	return *this;//返回类型为tmp对象的别名,而不是tmp对象的值当函数返回一个对象(而不是引用)时,会调用拷贝构造函数来创建一个新的对象,并将其作为函数的返回值。这意味着会发生一次数据复制
}//返回引用允许直接操作原始对象而不是在内存中创建副本。这样可以避免额外开销和数据复制

//日期加天数
Date Date::operator+(int day)//这里的返回值类型是Date,函数结束后tmp被销毁,如果使用Date&作为返回值,返回给d2的是tmp的别名,但是tmp所在空间已经被销毁里面的值也不保证有效了,而Date的话就可以将tmp的值放入临时变量中最后交给d2
{
	Date tmp = *this;//拷贝构造,等价于Date tmp(*this)
	tmp += day;//tmp = tmp + day,此时应该调用operator+=,且此时传入的day也是原来我们提供的数字
	return tmp;//为了防止d1+20对d1本身也产生了影响所以这里要用一个Date类型对象来做d1对象的拷贝,对tmp的修改无法影响d1
}


//日期减天数
Date Date::operator-(int day)//Date防止野引用
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}


//日期减天数
Date& Date::operator-=(int day)//Date&增加效率
{
	_day -= day;//用this->_day减去传递的天数
	while (_day <= 0)//如果天数大于_day,_day的结果就是负数,那么就将月份向前移动一月,月份减一,如果天数小于_day不进入循环直接将减去天数后的日期返回即可
	{
		--_month;
		if (_month == 0)//如果月份减一变为了零证明今年天数不够了,需要将年份向前移动一年,年份减一,同时将月份变为12月份
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);//_day在原有的为负数的基础上加上当前年当前月份的天数,如果结果大于等于零那么证明日期减天数的结果的确是该年该月
	}

	return *this;
}

//前置++,编译器在调用时会自动识别++d1->d.operator++()
Date& Date::operator++()
{
	*this += 1;//调用operator+=,1表示天数(++日期嘛肯定是从天数上开始加的)
	return *this;//前置++:先++后使用,直接返回自己加一的结果即可
}

//后置++,编译器在调用时会自动识别++d1->d.operator++(0),括号里的可以是0、1等整型常量只是起到标识作用
Date Date::operator++(int)
{
	Date tmp = *this;//拷贝构造,tmp是d1的拷贝
	*this += 1;//调用operator+=,1表示天数(++日期嘛肯定是从天数上开始加的)
	return tmp;//后置++:先使用后++,要先返回使用前的结果,然后再++,tmp中保存的就是++前的结果
}

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

}

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

//日期减日期(假设法)
int Date::operator-(const Date& d)//这里的d是对象d2,而不是具体的天数
{
		int flag = 1;//标志,假设d1>d2,同时也可以用来表示运算结果的正负
		Date max = *this;//拷贝构造,将d1拷贝给max对象
		Date min = d;//拷贝构造将d2,将d2拷贝给min对象,因为我们默认传入的左操作数比右操作数小,所以直接进行了拷贝

		//但是有可能我们将大小搞反了,所以还需要进行判断与修正
		if (*this < d)//如果d1<d2那么就将flag设置为-1,同时交换对象的值
		{
			int flag = -1;
			max = d;
			min = *this;
		}//反正到这一步max对象肯定是大日期对象的拷贝,min肯定是小日期对象的拷贝

		int n = 0;
		while (min != max)//operator!=的效率会比operator<的效率高,反正最后的循环终止条件是min==max,多次掉用更麻烦的operator<达到==还不如选择多次调用简单的operator!=达到==
		{
			++min;//直到min的日期加到和max的日期相等时才会跳出循环
			++n;//min的日期每++一次,n也跟着++
		}
		return n * flag;//如果flag最后为正,则d1 - d2时,d1>d2,结果肯定也为正,如果最后flag为负,则d1 - d2时,d1<d2,结果肯定为负
}


ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;//自定义类型的本质还是内置类型,所以可以用operator<<重载读取自定义类型中的内置类型
	return out;//最后返回out的别名即可
}

istream& operator>>(istream& in,Date& d)
{
	while (1)
	{
		cout << "依次输入年月日:>";
		in >> d._year >> d._month >> d._day;

		if (!d.CheckInvalid())//检查输入日期是否有效
		{
			cout << "输入日期无效" << endl;
		}
		else
		{
			break;
		}
	}

	return in;
}

bool Date::CheckInvalid()
{
	if (this->_year <= 0
		|| this->_month < 1 || this->_month >12
		|| this->_day < 1 || this->_day > GetMonthDay(this->_year, this->_month))//防止遗忘this指针这里就直接加上了
	{
		return false;
	}

	else
	{
		true;
	}
}


Test.cpp文件

#include "Date.h"

int main()
{
	Date d1(2024, 2, 30);
	Date d2(2024, 8, 1);
	cout << d1 - d2 << endl;
	Date d3;
	operator<<(cout, d1);
	cout << d1;
	cout << d1 << d2;//<<运算符是左结合,所以先进行的是cout<<d1,然后cout<<d1结束后的返回值作为下一次流插入的左操作数“返回值 << d2”
	cin >> d3;
	cout << d3;

	return 0;
}

注意事项:拷贝构造、赋值和析构函数都不需要写

  • 拷贝构造:不需要用同类对象初始化创建对象,日期对象的值我们已经提供了
  • 赋值:不需要把一个对象赋值给另一个对象,日期对象的值我们已经提供了
  • 析构:没有空间的开辟不需要写,编译器默认的就行  

const成员 

基本概念:const修饰的成员函数叫做const成员函数

格式:成员函数()const

注意事项:const修饰成员函数,本质上是在修饰成员函数隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改

        Date d1(2024,1,31)被const修饰,所以调用Print函数时,&d1指向的内容应该是const Date*即可读不可写,但是在Print函数中this指针指向的却是可读可写的,这是权限的放大

 

结论:

  1. 成员函数如果是一个对成员变量只进行读访问的函数,建议加const,这样const对象和非const对象都可以使用
  2. 成员函数如果是一个对成员变量要进行读写访问的函数,不能加const,否则不能修改成员变量
  3. const对象不可以调用非const成员函数,权限放大
  4. 非const对象可以调用const成员函数,权限缩小
  5. const成员函数内不可以调用其它的非const成员函数,权限放大
  6. 非const成员函数内可以调用其它的conts成员函数,权限缩小

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

作用:返回普通对象和const对象的地址(取地址运算符&的重载)

注意事项:

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

2、这两个运算符一般不需要重载,使用编译器生成的默认地址的地址重载即可

3、被用户自定义的默认成员函数不会由编译器提供

#include <iostream>
using namespace std;
 
class Date
{ 
public :
 //允许普通对象拿到地址
 Date* operator&()
 {
 return this ;
 }
 
 //允许const对象拿到地址
 const Date* operator&()const
 {
 return this ;
 }

private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

int main()
{
    Date aa1;
    const Date aa2;
    cout << &aa1 <<endl;
    cout << &aa2 <<endl;
    return 0;
}

3、只有特殊情况下才需要重载(比如想让别人获取到指定的内容)

只允许const对象拿到地址,普通对象拿到空:

#include <iostream>
using namespace std;
 
class Date
{ 
public :
 //普通对象拿到空地址
 Date* operator&()
 {
 return nullptr ;
 }
 
 //const对象拿到真地址(用编译器提供的)
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

int main()
{
    Date aa1;
    const Date aa2;
    cout << &aa1 <<endl;
    cout << &aa2 <<endl;
    return 0;
}

 const对象拿到假地址,普通对象拿到真地址:

#include <iostream>
using namespace std;
 
class Date
{ 
public :
 //普通对象拿到真地址
 Date* operator&()
 {
 return this ;
 }
 
 //const对象拿到假地址
 const Date* operator&()const
 {
 return (const Date*)0x00eeffee;//十六进制强转为const Date*地址
 }

private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

int main()
{
    Date aa1;
    const Date aa2;
    cout << &aa1 <<endl;
    cout << &aa2 <<endl;
    return 0;
}

~over~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/446841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《日期类》的模拟实现

目录 前言&#xff1a; 头文件类与函数的定义Date.h 实现函数的Date.cpp 测试Test.cpp 运行结果&#xff1a; 前言&#xff1a; 我们在前面的两章初步学习认识了《类与对象》的概念&#xff0c;接下来我们将实现一个日期类&#xff0c;是我们的知识储备更加牢固。 头文件…

角蜥优化算法 (Horned Lizard Optimization Algorithm ,HLOA)求解无人机路径优化

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径,使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一,它可以通过算法和模型来确定无人机的航迹,以避开障碍物、优化飞行时间和节省能量消耗。 二、算法介…

【JAVA】CSS3:3D、过渡、动画、布局、伸缩盒

1 3D变换 1.1 3D空间与景深 /* 开启3D空间,父元素必须开启 */transform-style: preserve-3d;/* 设置景深&#xff08;你与z0平面的距离 */perspective:50px; 1.2 透视点位置 透视点位置&#xff1a;观察者位置 /* 100px越大&#xff0c;越感觉自己边向右走并看&#xff0c;…

K8S之实现业务的蓝绿部署

如何实现蓝绿部署 什么是蓝绿部署&#xff1f;蓝绿部署的优势和缺点优点缺点 通过k8s实现线上业务的蓝绿部署 什么是蓝绿部署&#xff1f; 部署两套系统&#xff1a;一套是正在提供服务系统&#xff0c;标记为 “绿色” &#xff1b;另一套是准备发布的系统&#xff0c;标记为…

LInux系统架构----Apache与Nginx动静分离

LInux系统架构----Apache与Nginx动静分离 一.动静分离概述 Nginx的静态处理能力比较强&#xff0c;但是动态处理能力不足&#xff0c;因此在企业中常采用动静分离技术在LNMP架构中&#xff0c;静态页面交给Nginx处理&#xff0c;动态页面交给PHP-FPM模块处理。在动静分离技术…

【软件测试面试】银行项目测试面试题+答案(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试题&#xff1…

HTTP/2的三大改进:头部压缩、多路复用和服务器推送

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CSS 居中对齐 (水平居中 )

水平居中 1.文本居中对齐 内联元素&#xff08;给容器添加样式&#xff09; 限制条件&#xff1a;仅用于内联元素 display:inline 和 display: inline-block; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…

[c++] 查表 —— 策略模式和职责链模式的核心

查表法在工厂模式、策略模式以及职责链模式中都有使用。以工厂模式为例&#xff0c;表中存储的数据&#xff0c;key 是商品的类型&#xff0c;value 是生产这个商品的工厂。在生产商品的时候&#xff0c;直接根据商品类型从表中获得商品对应的工厂&#xff0c;然后通过工厂生产…

多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测

多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测 目录 多维时序 | Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现BiTCN双向时间卷积神经网络多变量时间序列预测&#xff08;完整…

HTML概念

文章目录 1. HTML 概念1.1. 简介1.2. 思想1.3. 特点1.4. 语法1.4.1. 标签1.4.2. 属性1.4.3. 标签体1.4.4. 注释 2. HTML 实体2.1. 练习 3. HTML 结构3.1. <!DOCTYPE html>声明3.2. html根标签 4. 补充4.1. 管理文件4.2. 配置 VsCode4.2. 配置 VsCode 1. HTML 概念 1.1. 简…

QT画图功能

QT画图功能 每个QWidget都自带的功能&#xff0c;继承了QPainteDevice都可以使用QPainter来进行绘图。 画图需要调用paintEvent绘制事件&#xff0c;paintEvent事件时QWidget类自带的事件。 重写paintEvent事件。&#xff08;重写事件&#xff1a;如果父类有某个方法&#xff…

路由器动态路由配置

本博客为观看湖科大的教书匠系列计算机网络视频的学习笔记。 静态路由选择动态路由选择采用人工配置的方式给路由器添加网络路由、默认路由和特定主机路由等路由条目。路由器通过路由选择协议自动获取路由信息。静态路由选择简单、开销小&#xff0c;但不能及时适应网络状态(流…

【SOFARPC】SOFA入门实战

背景 由于最近交付项目&#xff0c;甲方使用了SOFA这套体系&#xff0c;之前虽然有过一些了解&#xff0c;但是真正实战还是没有那么熟悉&#xff0c;因此搭建一个实战的demo&#xff0c;熟悉一下相关内容。 SOFA SIMALE DEMO 项目搭建 项目目录结构 如上图所示&#xff0…

基于电鳗觅食优化算法(Electric eel foraging optimization,EEFO)的无人机三维路径规划(提供MATLAB代码)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

数据结构小记【Python/C++版】——BST树篇

一&#xff0c;基础概念 BST树&#xff0c;英文全称:Binary Search Tree&#xff0c;被称为二叉查找树或二叉搜索树。 如果一个二叉查找树非空&#xff0c;那么它具有如下性质&#xff1a; 1.左子树上所有节点的值小于根节点的值&#xff0c;节点上的值沿着边的方向递减。 2…

Python+Django+Html网页前后端指纹信息识别

程序示例精选 PythonDjangoHtml网页前后端指纹信息识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjangoHtml网页前后端指纹信息识别》编写代码&#xff0c;代码整洁&#xff0…

【Spring Boot系列】快速上手 Spring Boot

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【大厂AI课学习笔记NO.75】人工智能产业的就业岗位分布

见上图&#xff0c;这是详细的人工智能产业的就业岗位分布情况。 就业领域包括物联网、智能芯片、机器学习、深度学习、计算机视觉CV、自然语言处理NLP、智慧语音、机器人、知识图谱等领域。 人工智能作为当今科技革命与产业变革的重要驱动力量&#xff0c;其就业岗位分布广泛…

【开源】SpringBoot框架开发软件学院思政案例库系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理员2.2 普通教师 三、系统展示四、核心代码4.1 查询思政案例4.2 审核思政案例4.3 查询思政课程4.4 思政案例点赞4.5 新增思政案例评语 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的软件学…