日期类完善

目录

日期类:

运算符重载:

​编辑

 赋值重载:

拷贝构造和赋值重载的区别:

 实现赋值重载:

划分成员函数:

日期类的声明和定义分离

日期类-=:

 日期类-

 前置后置++


日期类:

写一个简单的日期类:

#pragma once;
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;
};
#include
using namespace std;
#include"Date.h"

void TestDate()
{
	Date d1;
	Date d2(2022, 9, 18);
	Date d3(2022, 2, 30);
	Date d4(2022, 2, 49);
	d3.Print();
	d4.Print();
}
int main()
{
	TestDate();
	return 0;
}

我们发现,日期d3和d4都不合法,我们进行运行:

日期类完善_第1张图片

也没有提示我们输入日期错误。

 我们可以对我们的构造函数进行优化:

Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		if (!(year >= 1 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year, month)))
		{
			cout << "非法日期" << endl;
		}
	}

日期类的拷贝构造:

对于日期类,我们不需要写对应的拷贝构造,因为对于内置类型,编译器会默认执行值拷贝:

void TestDate()
{
	Date d1;
	Date d2(2022, 9, 18);
	/*Date d3(2022, 2, 30);
	Date d4(2022, 2, 49);
	d3.Print();
	d4.Print();*/
	Date d5(d2);
	d5.Print();
}
int main()
{
	TestDate();
	return 0;
}

运算符重载:

日期类完善_第2张图片

 赋值重载:

void TestDate()
{
	Date d1;
	Date d2(2022, 9, 18);
	/*Date d3(2022, 2, 30);
	Date d4(2022, 2, 49);
	d3.Print();
	d4.Print();*/
	/*Date d5(d2);
	d5.Print();*/
	Date d5;
	d5 = d2;
}

日期类完善_第3张图片

拷贝构造和赋值重载的区别:

拷贝构造:一个对象拷贝初始化另一个要创建的对象。

赋值重载:已经存在的两个对象之间的拷贝。 

日期类完善_第4张图片

 实现赋值重载:

赋值重载可以传值传参吗?

可以,不会发生无穷递归,发生无穷递归的条件是: 拷贝构造时,使用传值传参,传值传参本身就是拷贝构造,拷贝构造又需要传值传参,无限循环。

所以这里可以传值传参。

但是传值传参还需要调用拷贝构造,有些麻烦,我们最好还是使用传地址传参。

void operator=(const Date&d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
void TestDate()
{
	Date d1(2022, 9, 10);
	Date d2;
	d2 = d1;
}
int main()
{
	TestDate();
	return 0;
}

我们进行测试:

日期类完善_第5张图片

赋值成功。

赋值重载写的不够全面,我们知道整型可以连等:

int main()
{
	/*TestDate();*/
	int i, j;
	i = j = 1;
	return 0;
}

我们这里是不能连等的:

void TestDate()
{
	Date d1(2022, 9, 10);
	Date d2, d3;
	d2.Print();
	d3=d2 = d1;
	d2.Print();
}
int main()
{
	TestDate();
	/*int i, j;
	i = j = 1;*/
	return 0;
}

日期类完善_第6张图片

原因如下:

 

 

这里我们把d1赋值给d2之后,返回值应该是d2,但是我们实现的函数是没有返回值的,所以我们需要完善返回值:

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

传值传参是拷贝构造,传值返回也是拷贝构造,所以我们这里可以用传引用返回:

而且this指向的对象出了作用域依旧存在,我们可以用引用返回。

能不能不返回*this,返回d呢?

答:不可以

Date& operator=(const Date&d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return d;
	}

1:因为d是只读的,传引用返回后的类型变成可读可写的了,属于权限的放大。

2:赋值的返回值是左操作数,不能返回右操作数。

划分成员函数:

日期类完善_第7张图片

赋值运算符的重载对于内置类型完成值拷贝,所以对于日期类,我们可以不定义赋值重载

 日期类完善_第8张图片

我们没有定义赋值重载函数,依旧可以完成日期类对象的赋值。

大部分类的默认赋值重载都不需要我们自定义,但是有些需要,例如栈:

#pragma once;
class Stack
{
public:
	Stack(int capacity=4 )
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
		cout << "Stack构造函数()" << endl;
	}
	Stack(const Stack&st)
	{
		_a=(int*)malloc(sizeof(int)*st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int)*st._top);
		_top = st._top;
		_capacity = st._capacity;
	}
	void Push(int x)
	{
		_a[_top++] = x;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
		cout << "Stack析构函数()" << endl; 
	}
private:
	int*_a;
	int _capacity;
	int _top;
};

我们没有实现赋值重载,看看能否使用赋值重载

void StackTest()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);

	Stack st2;
	st2.Push(10);
	st2.Push(20);
	st2.Push(30);
	st2.Push(40);
	st1 = st2;
}
int main()
{
	StackTest();
	/*int i, j;
	i = j = 1;*/
	return 0;
}

日期类完善_第9张图片

运行之后,直接报错。

 日期类完善_第10张图片

我们进行值拷贝:

把st2._top赋值给st1._top,把st2._capacity赋值给st1._capacity。

再让st1._a指向st2._a的位置。

日期类完善_第11张图片

后创建st2的先析构,析构之后st2所指向的空间的数据释放了,这时候st1再进行析构就导致了越界访问。

并且原本属于st1的指向的数据的地址也找不到了,造成了内存泄漏。

日期类完善_第12张图片

所以对于栈类,我们要自己实现赋值重载:

 日期类完善_第13张图片

我们先释放掉st1所指向的空间的数据,然后新创建一个空间,拷贝st2的数据,让st1指向该空间:

 日期类完善_第14张图片

Stack& operator=(const Stack&st)
	{
		free(_a);
		_a = (int*)malloc(sizeof(int)*st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int)*st._top);
		_top = st._top;
		_capacity = st._capacity;
		return *this;
	}

 日期类完善_第15张图片

完成了深拷贝。 

还有一种情况:

用自己去赋值自己。

 日期类完善_第16张图片

我们进行运行,发现数据出现了错误。

 因为日期类完善_第17张图片

 我们可以先提前判断:

Stack& operator=(const Stack&st)
	{
		if (&st != this)
		{
			free(_a);
			_a = (int*)malloc(sizeof(int)*st._capacity);
			if (_a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			memcpy(_a, st._a, sizeof(int)*st._top);
			_top = st._top;
			_capacity = st._capacity;
		}
		return *this;
	}

对于一些有资源释放的类,我们也可以不自己写:

class MyQueue
{
public:
	void push(int x)
	{
		_pushST.Push(x);
	}
private:
	Stack _pushST;
	Stack _popST;
	size_t size = 0;
};

对于内置类型完成值拷贝,对于自定义类型,调用其默认赋值重载。

void MyQueueTest()
{
	MyQueue s1;
	s1.push(10);
	MyQueue s2;
	s2 = s1;
}
int main()
{
	MyQueueTest();
	/*int i, j;
	i = j = 1;*/
	return 0;
}

日期类完善_第18张图片

完成了深拷贝。 

日期类的声明和定义分离

对于比较小并且频繁调用的函数,这类函数一般是内联函数,内联函数只能在类里面定义,不能声明和定义分离。

对于行数不多并且调用不频繁的我们可以声明和定义分离。

日期类-=:

Date&Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
#include"Date.h"
void TestDate1()
{
	Date d1(2022, 10, 19);
	d1 -= 1000;
	d1.Print();
}
int main()
{
	TestDate1();
	return 0;
}

 日期类-

Date Date::operator-(int day)
{
	Date ret(*this);
	ret -=day;
	return ret;
}
#include"Date.h"
void TestDate1()
{
	Date d1(2022, 10, 19);
	Date d2 = d1 - 10000;
	d2.Print();
}
int main()
{
	TestDate1();
	return 0;
}

假如我们要减去一个负数呢?

#include"Date.h"
void TestDate1()
{
	Date d1(2022, 10, 19);
	
	d1 -= -10000;
	d1.Print();
}
int main()
{
	TestDate1();
	return 0;
}

 前置后置++

日期类完善_第19张图片

 

我们在定义的时候如何区分前置和后置++?

 日期类完善_第20张图片

Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date ret = *this;
	*this + 1;
	return ret;
}

 

 

你可能感兴趣的:(c++,算法,数据结构)