首页 > 编程语言 >c++类和对象(3):默认成员函数(下)

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

时间:2024-09-14 17:49:43浏览次数:3  
标签:函数 int 默认 month 运算符 c++ year Date day

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; // ⽇

};

 

标签:函数,int,默认,month,运算符,c++,year,Date,day
From: https://blog.csdn.net/wangchen_0/article/details/142030401

相关文章

  • 南沙C++noip老师解一本通题: 1360:奇怪的电梯(lift)
    ​【题目描述】大楼的每一层楼都可以停电梯,而且第i层楼(1≤i≤N)上有一个数字Ki(0≤=Ki≤=N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:33125代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4......
  • 【关于c++模版类的报错问题】
    关于c++模版类的报错问题模版类的定义使用类模板模版类的定义通常在实现一个类时,会在.h头文件中声明函数,在.cpp文件中实现该函数。然而如果是模版类的话情况则会稍有不同。这是一个名为debug.h的头文件,里面包含了一个名为A的模版类类A的实现在debug.hpp中,模板类的......
  • 天梯赛(常用STL函数)+ 常见算法
    0.(森森美图)判断一个点x3,y3在一条直线(由x1,y1和x2,y2组成)的哪一边若(y2-y3)/(x2-x3)-(y1-y3)/(x1-x3)>0逆时针方向否则顺时针方向1.vectorvector<node>ve;//定义ve.insert(ve.begin()+i,k);//中间插入ve.insert(ve.begin()+i,num,key);ve.erase(ve.begin()+i);//删......
  • 高等数学 2.2 函数的求导法则
    目录1、常数和基本初等函数的导数公式2、函数的和、差、积、商的求导法则3、反函数的求导法则4、复合函数的求导法则1、常数和基本初等函数的导数公式公式公式(1)\((C)'=0\)(2)\((x^{\mu})'=\mux^{\mu-1}\)(3)\((\sinx)'=\cosx\)(4)\((\cosx)'=-\sinx\)......
  • C++STL~~stack&queue
    文章目录stack&queue的概念stack&queue的使用stack&queue的练习总结stack&queue的概念stack(栈)概念和特点栈是一种后进先出(LastInFirstOut,LIFO)的数据结构。就像一叠盘子,最后放上去的盘子最先被拿走。queue(队列)概念和特点队列是一种先进先出(FirstInFirstO......
  • codesys将自定义的功能块或者函数保存到本地库
    将通过ST代码实现的自定义功能保存到codesys的本地库,其他project可以直接实现调用。提高灵活性和效率。1、创建库工程 这里可能会提示涉及个别库没有安装或版本更新,根据提示安装对应库或更新即可。2、添加功能块和函数3、编写功能块和函数的参数定义及逻辑实现    ......
  • Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数
    title:NuxtKit自动导入功能:高效管理你的模块和组合式函数date:2024/9/14updated:2024/9/14author:cmdragonexcerpt:通过使用NuxtKit的自动导入功能,您可以更高效地管理和使用公共函数、组合式函数和VueAPI。无论是单个导入、目录导入还是从第三方模块导入,您都可......
  • C++——类与对象(三)
    目录引言类型转换1.类型转换2.explicit关键字static成员1.static静态成员变量2.static静态成员函数3.访问静态成员变量的方法3.1 静态成员变量为公有3.2静态成员变量为私有友元1.友元函数2.友元类内部类匿名对象对象拷贝时的编译器优化1.类型转换2.传值......
  • 《 C++ 修炼全景指南:九 》打破编程瓶颈!掌握二叉搜索树的高效实现与技巧
    摘要本文详细探讨了二叉搜索树(BinarySearchTree,BST)的核心概念和技术细节,包括插入、查找、删除、遍历等基本操作,并结合实际代码演示了如何实现这些功能。文章深入分析了二叉搜索树的性能优势及其时间复杂度,同时介绍了前驱、后继的查找方法等高级功能。通过自定义实现的......
  • C++实现线程池
    目录一.什么是线程池二.为什么要用线程池三.如何实现线程池这篇文章简单讨论下线程池。一.什么是线程池线程池简单来时就是维护了一组线程的池子,这组线程执行一些相似任务。是一种线程的使用方式。二.为什么要用线程池有的时候系统需要处理大量相似任务,频繁创建销......