c++类和对象(3):默认成员函数(下)

news2024/9/20 11:55:22

1.拷贝构造函数

如果⼀个构造函数的第⼀个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数

c++规定:类类型的传值传参必须用拷贝构造

1.1拷贝构造函数的特点

1.拷贝构造函数是构造函数的⼀个重载

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//同名函数,形参不同,构成重载
    //参数是自身类类型的引用
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

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

2.拷贝构造函数的第⼀个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。 拷贝构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引用,后面的参数必须有缺省值。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//对类类型对象的引用
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    Date d1(2024,6,6);
    Date d2(d1);
    return 0;
}
第⼀个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为编译器会不断地调用Date函数

3. C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返 回都会调用拷贝构造完成。
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, 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;
	}
    //不是拷贝构造,就是一个普通构造
	Date(Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}


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

Date F1()
{
	Date ret;
	//..
	return ret;
}

int main()
{
	Date d1(2024, 2, 3);
	// C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,
	// 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉
	//都是拷贝构造
	Date d2(d1);
	Date d3 = d1;
	Date d4(F1());
	Date d5 = F1();

	return 0;
}

4. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成 员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构 造。
#include<iostream>
using namespace std;

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

	//Date(Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}

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


int main()
{
	Date d1(2024,2,3);
	// C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,
	// 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉
	Date d2(d1);


	return 0;
}
将显示的拷贝构造函数注释之后我们可以看到,d2依旧完成了拷贝。编译器自动生成了拷贝构造函数,调用自定义类型Date的拷贝构造函数,完成对内置类型int的成员变量的拷贝。
5.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型
Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显示实现
MyQueue的拷贝构造。这里还有⼀个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就
需要显示写拷贝构造,否则就不需要。
#include<iostream>
using namespace std;

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		// 需要对_a指向资源创建同样⼤的资源再拷⻉值
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:
private:
	Stack pushst;
	Stack popst;
};
int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	// Stack不显⽰实现拷⻉构造,⽤⾃动⽣成的拷⻉构造完成浅拷⻉
	// 会导致st1和st2⾥⾯的_a指针指向同⼀块资源,析构时会析构两次,程序崩溃
	Stack st2 = st1;
	MyQueue mq1;
	// MyQueue⾃动⽣成的拷⻉构造,会⾃动调⽤Stack拷⻉构造完成pushst/popst
	// 的拷⻉,只要Stack拷⻉构造⾃⼰实现了深拷⻉,他就没问题
	MyQueue mq2 = mq1;
	return 0;
}

6。传值返回会产生⼀个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),好处是没有产生拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于⼀个野引用,类似⼀个野指针⼀样。传引用返回可以减少拷贝,但是⼀定要确保返回对象,在当前函数结束后还在,才能用引用返回。

 

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		// 需要对_a指向资源创建同样⼤的资源再拷⻉值
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	STDataType Top()
	{
		return _a[_top - 1];
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:
private:
	Stack pushst;
	Stack popst;
};
Stack& Func()
{
	Stack st;
	st.Push(1);
	st.Push(2);
	st.Push(3);

	return st;
}

int main()
{
	Stack ret = Func();
	cout << ret.Top() << endl;

	return 0;
}

上面代码会程序崩溃。因为当函数Func结束时会调用Stack的析构函数,会把局部变量st销毁。

 

当我们把用static修饰st,使其存储到静态区,不会因函数结束而被销毁时,代码正常运行:

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		// 需要对_a指向资源创建同样⼤的资源再拷⻉值
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	STDataType Top()
	{
		return _a[_top - 1];
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public:
private:
	Stack pushst;
	Stack popst;
};
Stack& Func()
{
	static Stack st;
	st.Push(1);
	st.Push(2);
	st.Push(3);

	return st;
}


int main()
{
	Stack ret = Func();
	cout << ret.Top() << endl;

	return 0;
}

 

2.赋值运算符重载 

赋值运算符重载是c++的一种操作,它允许程序员为自定义类型重新定义赋值运算符(=)的行为。

2.1运算符重载

运算符重载是c++的一种强大的特性,它允许程序员为自定义类型定义已有的运算符行为。

(1)运算符重载是具有特殊名字的函数,它的名字是由operator后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
 
(2)重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。⼀元运算符有⼀个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, 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;
};
//除了注释private的三种方法
//1.提供对应的getxxx函数
//2.友元
//3.重载成为成员函数


bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

bool operator<(const Date& d1, const Date& d2)
{
	if (d1._year < d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year 
		&& d1._month < d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year 
		&& d1._month == d2._month
		&& d1._day < d2._day)
	{
		return true;
	}

	return false;
}

int main()
{
	Date d1(2024, 6, 6);
	Date d2(2024, 8, 8);
	// 运算符重载函数可以显⽰调⽤
	bool ret1 = operator==(d1, d2);
	
	// 编译器会转换成调用对应的
	// 运算符重载函数 operator==(d1, d2);
	//operator==(d1, d2)与d1 == d2;效果相同
	bool ret2 = d1 == d2;
	bool ret3 = d1 < d2;

	//内置类型调用简单
	int i = 1, j = 2;
	bool ret4 = i < j;

	cout << ret1 << endl;
	cout << ret2 << endl;
	cout << ret3 << endl;
	cout << ret4 << endl;
	return 0;
}
(3)如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个
(4)运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
(5)不能通过连接语法中没有的符号来创建新的操作符:比如operator@。
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	bool operator<(const Date& d)
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year
			&& _month < d._month)
		{
			return true;
		}
		else if (_year == d._year
			&& _month == d._month
			&& _day < d._day)
		{
			return true;
		}

		return false;
	}

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

int main()
{
	Date d1(2024, 6, 6);
	Date d2(2024, 8, 8);

	bool ret1 = d1.operator==(d2);
	bool ret2 = d1.operator<(d2);

	cout << ret1 << endl;
	cout << ret2 << endl;

	return 0;
}

(6).*    ::   sizeof   ?:   .这5个运算符不能重载。

(7)重载操作符至少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,内置类型入int,double等,不能把两个整数相加的“+”运算符重载为两个整数相减的操作

//错误示范
int operator+( int x, int y)
{
	return x - y;
}
(8)⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator*就没有意义。对于operator-可能是计算两个日期之间的天数差,二operator+则没有明确的、普遍接受的作用。
 
(9)重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
    //前置++,返回++后的
	Date& operator++()
	{
		cout << "前置++" << endl;
		// *this就是d1
		//*this += 1;
		return *this;
	}
    //后置++,返回++前的
	Date  operator++(int)
	{
		cout << "后置++" << endl;
		Date tmp;

		//Date tmp(*this);
		//*this += 1;

		return tmp;
	}

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

int main()
{
	Date d1(2024, 9, 9);

	//编译器会将其转换成d1.operator();
	d1++;
	//编译器会将其转换成d1.operator(0);
	++d1;

	return 0;
}

(10)在 C++中,重载 << (左移运算符)和 >> (右移运算符)可以实现对自定义类型的输入输出操作, 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第二个形参位置当类类型对象
一、重载 << 运算符
 
 
#include <iostream>
using namespace std;

//1. 作用:通常用于将自定义类型的对象输出到标准输出流(如 cout )或其他输出流对象。
//2. 语法:
ostream& operator<<(ostream& os, const YourClass& obj);
//其中 ostream& 是返回类型,表示可以进行链式输出。 
//os 是输出流对象, 
//const YourClass& obj 是要输出的自定义类型对象。
class Point
{
public:
	int _x, _y;
	Point(int x, int y)
	{
		_x = x;
		_y = y;
	}
};
//重载为全局函数
ostream& operator<<(ostream& os, const Point& p)
{
	os << "(" << p._x << "," << p._y << ")" << endl;
	return os;
}

int main()
{
	Point p(3, 4);

	cout << p << endl;
	return 0;
}



 
二、重载 >> 运算符
 

#include <iostream>
using namespace std;

//1. 作用:用于从输入流(如 cin )读取数据并存储到自定义类型的对象中。
//2. 语法:
//istream & operator>>(istream & is, YourClass & obj);
//istream& 是返回类型, is 是输入流对象, 
// YourClass& obj 是要接收输入数据的自定义类型对象。
class Point
{
public:
	int _x, _y;
	Point(int x = 0, int y = 0)
	{
		_x = x;
		_y = y;
	}
};

istream& operator>>(istream& is, Point& p)
{
	is >> p._x >> p._y;
	return is;
}

ostream& operator<<(ostream& os, const Point& p)
{
	os << "(" << p._x << "," << p._y << ")" << endl;
	return os;
}

int main()
{
	Point p;
	cout << "输入x和y的值: ";
	cin >> p;
	cout << "Point: " << p << endl;

	return 0;
}


 
通过重载 << 和 >> 运算符,可以使自定义类型的对象像内置类型一样方便地进行输入输出操作。

2.2 赋值运算符重载

赋值运算符重载是⼀个默认成员函数,用于为自定义类型的对象提供自定义的赋值行为,可以完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个已经存在对象拷贝初始化给另⼀个要创建的对象。
赋值运算符重载的特点:
(1) 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝
(2)有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续值场景。
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, 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;
	}
	//传引用返回减少拷贝
	//d2 = d3 
	//d2.operator= d3
	Date& operator=(const Date& d)
	{
		// 检查⾃⼰给⾃⼰赋值的情况		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		// d2 = d3表达式的返回对象应该为d1,也就是*this
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 9, 9);
	//拷贝构造用于一个已经存在的对象拷贝初始化给一个要创建的对象
	Date d2(d1);

	//赋值运算符重载用于两个已经存在的对象的直接拷贝赋值
	Date d3(2024, 6, 6);
	d2 = d3;
	return 0;
}

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

(4)像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是 内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部 主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载, 也不需要我们显示实现MyQueue的赋值运算符重载。这⾥还有⼀个小技巧,如果⼀个类显示实现 了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。

将赋值运算符注释后,对于Date类成员变量,编译器会自动生成可完成所需要拷贝到赋值运算符,是否显示的写出来没有影响。

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, 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;
	}
	//传引用返回减少拷贝
	//d2 = d3 
	//d2.operator= d3
	//Date& operator=(const Date& d)
	//{
	//	// 检查⾃⼰给⾃⼰赋值的情况		if (this != &d)
	//	{
	//		_year = d._year;
	//		_month = d._month;
	//		_day = d._day;
	//	}
	//	// d2 = d3表达式的返回对象应该为d1,也就是*this
	//	return *this;
	//}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 9, 9);
	//拷贝构造用于一个已经存在的对象拷贝初始化给一个要创建的对象
	Date d2(d1);

	//赋值运算符重载用于两个已经存在的对象的直接拷贝赋值
	Date d3(2024, 6, 6);
	d2 = d3;
	return 0;
}

显示的写出来的结果:

注释之后的结果:

 

 

2.3日期类实现

Date.h:

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

class Date
{
	// 友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();

	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month <= 13);

		static int MonthDayArr[] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };

		if (month == 2 && (year % 4 == 0 & year % 100 != 0) || (year % 400 == 0))
		{
			return 29;
		}
		else
		{
			return MonthDayArr[month];
		}
	}

	bool CheckDate();

	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);

	// d1 += 天数
	Date& operator+=(int day);
	Date operator+(int day);
	// d1 -= 天数
	Date& operator-=(int day);
	Date operator-(int day);
	// d1 - d2
	int operator-(const Date& d);

	// ++d1 -> d1.operator++()
	Date& operator++();
	// 为了区分,构成重载,给后置++,强⾏增加了⼀个int形参
	// 这个参数仅仅是为了跟前置++构成重载区分
	// d1++ -> d1.operator++(0)
	Date operator++(int);

	Date& operator--();
	Date operator--(int);
	// 流插⼊
	// 不建议,因为Date* this占据了⼀个参数位置,使⽤d<<cout不符合习惯
	//void operator<<(ostream& out);

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

// 重载
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

text.cpp:

#include "Date.h"

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!d.CheckDate())
	{
		cout << "日期非法" << endl;
	}
}

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

bool Date::CheckDate()
{
	if (_month < 1 || _month>12 
		|| _day<1 || _day>GetMonthDay(_year, _day))
	{
		return false;
	}
	else
	{
		return true;
	}

}

bool Date::operator<(const Date& d)
{
	if (_year < d._year)
	{
		return true;
	}
	else if (_year == d._year 
		&& _month < d._month)
	{
		return true;
	}
	else if (_year == d._year
		&& _month == d._month
		&& _day < d._day)
	{
		return true;
	}
	return false;
}
//d1 <= d2
//*this是d1,d是d2
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

bool Date::operator>(const Date& d)
{
	return !(*this <= d);
}
bool Date::operator>=(const Date& d)
{
	return *this > d || *this == d;
}
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}
//d1+=100
//改变了d1的值
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)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
//d1+100
//没有改变d1的值
Date Date::operator+(int day)
{
	//拷贝构造,将d1的值拷贝给tmp
	Date tmp = *this;

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

int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	while (*this < d) 
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		min++;
		n++;
	}
	return n * flag;
}

//++d1
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
//d1++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
//--d1
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
//d1--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:>";
	in >> d._year >> d._month >> d._day;
	if (!d.CheckDate())
	{
		cout << "日期非法" << endl;
	}
	return in;
}

text.cpp:

#include "Date.h"

int main()
{
	Date d1(2024, 9, 9);
	Date d2(2024, 6, 6);

	d1 += 100;
	d1.Print();//2024-12-18
	d1 + 100;
	d1.Print();//2024-12-18
	Date ret1 = d1 + 100;
	ret1.Print();//2025-3-28

	d2 -= 200;
	d2.Print();//2023-11-19
	d2 - 100;
	d2.Print();//2023-11-19
	Date ret2 = d2 - 100;
	ret2.Print();//2023-8-10

	cout << d1 - d2 << endl;//395

	++d1;
	d1.Print();
	Date ret3 = d1++;
	ret3.Print();
	d1.Print();

	cout << d1 << d2;
	cin >> d1 >> d2;
	cout << d1 << d2 << endl;
	return 0;
}

 3.取地址运算符重载

3.1const成员函数

(1)将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
 
(2)const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。 const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this (不能改变this的指向,可以改变this所执行的内容) 变为 const Date* const this
(3)const成员函数可以与非const的成员函数进行重载。如果一个类同时具有const和非const版本的成员函数,那么根据对象的const性质,编译器会自动选择合适的版本进行调用。建议不修改成员变量的成员函数后都加上。
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// void Print(const Date* const this) const
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 这⾥⾮const对象也可以调⽤const成员函数
	// 这是⼀种权限的缩⼩
	Date d1(2024, 9, 9);
	d1.Print();
    //const的修饰使得d2的内容不能被改变
	const Date d2(2024, 6, 6);
	d2.Print();
	return 0;
}

3.2取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非⼀些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现⼀份,胡乱返回⼀个地址。
class Date
{
public:
	//显示的写了,便用写了的,编译器不会再自动生成
	Date* operator&()
	{
		return this;
		//return nullptr
	}

	const Date* operator&()const
	{
		return this;
		//return nullptr
	}
private:
	int _year; // 年
	int _month; // ⽉
	int _day; // ⽇

};

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot:Web开发(基于SpringBoot使用MyBatis-Plus+JSP开发)

目录 前期准备 构建项目&#xff08;IDEA2023.1.2&#xff0c;JDK21&#xff0c;SpringBoot3.3.3&#xff09; 添加启动器 Model准备 这里我们利用MybatisX插件生成我们所需要的实体类、数据访问层以及服务层 注意选择MyBatis-Plus3以及Lombok 然后再在service接口中定义…

Leetcode 每日一题:Course Schedule II

写在前面&#xff1a; 今天我们继续来看一道经典的图论问题&#xff0c;而这个问题可以说是跟我们一众学生的生活息息相关啊&#xff01;我们每年都有很多需要完成的必修指标&#xff0c;每一个必修指标可能会有一个或多个先修要求&#xff0c;而我们需要决定是否能将这些课全…

kAFL部署、使用与原理分析

文章目录 前言1、概述1.1、工作原理1.2、工作流程1.2.1、部署kAFL1.2.2、准备工作1.2.2.1、准备主机代理内核1.2.2.2、准备待Fuzz目标1.2.2.3、配置待Fuzz目标1.2.2.4、配置kAFL组件 1.2.3、Fuzz测试1.2.3.1、获取配置信息1.2.3.2、准备工作目录1.2.3.3、复制种子文件1.2.3.4、…

大顶堆+动态规划+二分

前言&#xff1a;我们这一题需要分类讨论 对于我们左边和右边的我们需要预处理 有点类似反悔堆的做法&#xff0c;得出i之前取出 m 个元素代价最小&#xff0c;并且这个代价一定是递减的&#xff08;可以推导一下&#xff09; 题目地址 #include<bits/stdc.h> using name…

Docker 华为云镜像加速器配置

​​ 操作说明 1. 安装/升级容器引擎客户端 推荐安装1.11.2以上版本的容器引擎客户端 2. 加速器地址 访问华为云容器镜像服务&#xff1a;https://console.huaweicloud.com/swr/ 获取加速器地址 https://xxxxxxxxx.mirror.swr.myhuaweicloud.com3. 配置镜像加速器 针对…

c语言快递小项目

struct pack_head{ int fd;//通信的文件描述符 unsigned char type; //消息类型 unsigned char usertype; //用户类型&#xff1a;1&#xff1a;用户 2&#xff1a;快递员 char name[32]; //用户名 char paaswd[32]; //密码 char buf[32]; //调试…

抗金属RFID标签如何提升资产管理效率

在资产管理中&#xff0c;金属表面的设备和资产对传统RFID标签来说是一大挑战。为了解决这一问题&#xff0c;企业开始广泛采用抗金属RFID标签&#xff0c;以确保在金属环境下也能高效地进行资产跟踪与管理。 抗金属RFID标签的应用场景 抗金属RFID标签是一种专门设计用于金属…

如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)

一、简述 在我们编写控制台应用程序时&#xff0c;默认情况下程序的打印输出会在控制台窗口中进行显示&#xff0c;我们在写服务功能时在窗口中会不断打印消息输出&#xff0c;这个时候如果使用鼠标点击了控制台窗口&#xff0c;会阻塞程序的继续运行&#xff0c;导致我们的程…

【Unity】在Unity 3D中使用Spine开发2D动画

文章目录 内容概括前言下载安装 Spine Pro导入Unity插件Spine动画导入Unity使用展现动画效果展现 内容概括 本文主要讲解 Spine Pro 免&#xff08;破&#xff09;费&#xff08;解&#xff09;版的安装&#xff0c;以及如何将动画导入到Unity中使用。 前言 通常要用 Spine …

Hadoop林子雨安装

文章目录 hadoop安装教程注意事项&#xff1a; hadoop安装教程 链接: 安装教程 注意事项&#xff1a; 可以先安装ububtu增强功能&#xff0c;完成共享粘贴板和共享文件夹 ubuntu增强功能 2.这里就可以使用共享文件夹 或者在虚拟机浏览器&#xff0c;用 微信文件传输助手 传文…

医学数据分析实训 项目二 数据预处理预备知识(数据标准化处理,数据离差标准化处理,数据二值化处理,独热编码处理,数据PCA降维处理)

文章目录 数据预处理预备知识任务一 数据标准化处理1. 数据准备2. 数据标准化 任务二 数据离差标准化处理任务三 数据二值化处理任务五 独热编码处理对数据进行“离散化处理”&#xff08;装箱&#xff09;将已经装箱的数据进行OneHotEncoder独热编码 任务六 数据PCA降维处理1.…

ingress对外服务

目录 ingress概念 安装ingress ingress-nginx暴露服务的方式 1. DeploymentLoadBalncer 2. DaemonSetHostNetworknodeSelector ​编辑 3.deploymentnodePort ​编辑 ingress-nginx的deploymentnodePorthttps部署 1.创建ssl的证书 2.ingressnginx ingress-nginx的权…

最强AI照片说话Windows一体包下载地址,口型合成音频驱动图片,免安装,下载即用

照片数字一键整合包&#xff1a;点击下载 一键安装包&#xff0c;简单一键启动&#xff0c;即刻使用&#xff0c;秒级体验。 目前效果最好的音频驱动图片说话的软件&#xff0c;比sadtalker、MuseTalk更清晰&#xff0c;效果更好&#xff0c;可以作为DID heygen的开源平替。原…

Vue安装及环境配置【图解版】

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 Facts speak louder than words&#xff01; 目录 一.node.js的安装…

C语言算法

大纲 算法复杂度 排序算法 经典算法

如何使用ssm实现物流配送人员车辆调度管理系统的设计与实现+vue

TOC ssm618物流配送人员车辆调度管理系统的设计与实现vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思…

react和vue区别以及为什么会说react适合大型项目

都说react适合做大型项目&#xff0c;但是什么是大型项目呢。 什么是大型项目 这个所谓的大项目应该是指 多部门&#xff0c;多项目协作。而并不是页面量和工作日&#xff0c;对于大公司&#xff0c;协作所带来的成本&#xff0c;效率问题才是问题 为什么会说react要更适合大型…

开源waf牛了个b之长亭雷池

雷池官网&#xff1a; https://waf-ce.chaitin.cn/

IVF 视频文件格式

IVF IVF有两种定义&#xff0c;一种是 Intel创建&#xff0c;用于封装其Indeo编解码器。Indeo是一系列视频编解码器&#xff0c;由英特尔在1990年代开发&#xff0c;主要用于视频游戏和早期的互联网视频流&#xff1b;Indeo编解码器以其高压缩率和良好的视频质量而闻名&#x…

三好夫人 | 茶香月饼甜  浓情赏月圆

在这个金风送爽、丹桂飘香的中秋佳节&#xff0c;家家户户都沉浸在团圆与温馨的氛围之中。月饼&#xff0c;作为中秋的传统美食&#xff0c;承载着无数人对家的思念与美好祝愿。而今&#xff0c;当传统遇见创新&#xff0c;“三好夫人”——一个以男士滋补茶闻名遐迩的品牌&…