C++入门(类和对象之实现日期类)

时间:2022-05-12 01:27:38

日期类的实现

1、构造函数

//在类里声明,类外实现
Date::Date(int year, int month, int day)
{
	if (year >= 1 &&
		month <= 12 && month >= 1 &&
		day >= 1 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
	}
}

2、拷贝构造

//代码小于5行,直接在类里面实现
Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

3、判断天数函数

int Date::GetMonthDay(int year, int month)
{
	assert(year >= 0 && month > 0 && month < 13);//断言

	const static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	//用static修饰数组,将数组放在静态区,不用每次执行判断函数都要开辟这块数组空间,提高效率
	//再加一个const,让别人不能修改这个数组里的数据
	if (month == 2 && isLeapYear(year))
	{
		return 29;
	}
	else
	{
		return monthDayArray[month];
	}
}

4、运算符重载

4.1、加法

// d1 + 100 --> d1.operator+(day);
Date Date::operator+(int day)
{
	Date ret(*this);//拷贝构造一个和d1一样的对象,这样就不会改变d1的值

	ret._day += day;
	while (ret._day > GetMonthDay(ret._year, ret._month))//天数超过该月天数就进1
	{
		ret._day -= GetMonthDay(ret._year, ret._month);//减去该月天数
		ret._month++;
		if (ret._month == 13)//如果月超过12,年就进1
		{
			++ret._year;
			ret._month = 1;
		}
	}

	return ret;//返回我们拷贝构造之后的对象
}

4.2、赋值运算符重载

Date& operator=(const Date& d)
	{
		if (this != &d)//判断是不是自己给自己赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

4.3、加等

// d1 += 100
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)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
	//由于出了加等函数的作用域,*this还没有被销毁,所以我们可以用传引用返回
}

由于加法和加等的逻辑相同,所以我们可以复用加等的代码来实现加法

Date Date::operator+(int day)
{
	Date ret(*this);
	ret += day;

	return ret;
}

4.4、减等、减法

// d1 -= 100
Date& Date::operator-=(int day)
{
	if (day < 0)//如果减等数是负数
		return *this += -day;//等于调用加等函数

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

由于减等和减法的逻辑相同,所以我们可以复用减等的代码来实现减法

Date Date::operator-(int day)
{
	Date ret = *this;
	ret -= day;
	return ret;
}

4.5、前置++和后置++

  • 由于前置++和后置++函数都是operator++()
  • 所以C++规定,利用函数重载规则,不加参数的operator++()是前置++
  • 加参数的operator++(int i)是后置++
//前置++代码
// ++d1
	Date& operator++()      // 前置
	{
		*this += 1;
		return *this;//出了作用域*this还在,所以可以用传引用返回
	}

后置++

//后置++代码
// d1++
	Date operator++(int) //这里不具体接收参数的原因:传参只是为了让编译器判断是不是后置++
	{
		Date tmp(*this);
		*this += 1;
		return tmp;//出了作用域tmp就被销毁了,所以只能用传值返回,传值返回就需要拷贝
	}

4.6、小于和小于等于

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

等于运算符重载

// d1 == d2
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
  • 实现了小于和等于函数之后,其他的比较大小的运算符都可以用小于函数和等于函数进行复用
  • 由于代码复用,所以其他函数的代码行数都比较少,可以直接写在类里面,变成内联(inline)函数
// inline不支持声明和定义分别放到.h 和.cpp
	// 所以成员函数中要成为inline最好直接在类里面定义
	// 类里面定义默认就是inline
	bool operator>(const Date& d)
	{
		return !(*this <= d);
	}

	bool operator>=(const Date& d)
	{
		return !(*this < d);
	}

	bool operator!=(const Date& d)
	{
		return  !(*this == d);
	}

	// d1 <= d2
	bool operator<=(const Date& d)
	{
		return *this < d || *this == d;
	}

4.7、日期 减去 日期函数

// d1 - d2
int Date::operator-(const Date& d)
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		min = *this;
		max = d;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++n;
		++min;
	}

	return n * flag;
}

4.8、流插入<< 和 流提取>> 运算符重载

  • 要想进行流提取和流插入的运算符重载,首先我们就要知道首先,cout是iostream中定义的ostream类的对象
  • <<能用在cout上是因为,在ostream类里对<<进行了函数重载,把内置类型都重载了一遍
  • cout << 1相当于执行函数cout.operator<<(1)
    cout << "this"相当于执行函数cout.operator<<(“this”)
  • 如果在程序中希望对<<运算符进行重载,使其能够输出自定义的数据,如何实现呢?
    由于ostream类型已经在iostream中实现,所以不能作为ostream类的成员函数重载,只能作为全局函数或友元函数重载。
  • 流插入重载
//流插入重载
std::ostream& operator<<(std::ostream& out, const Date& d)
{
    out << d._year << "_" << d._month << "_" << d.day << endl;
    return out;
}
  • 流提取重载
std::istream& operator>>(std::istream& in, Date& d)
//流提取不加const的原因:流插入就是把从流里面提取到的值写入到d里
//如果用了const就只能读不能写
{
    in >> d._year >> d._month >> d.day;
    return in;
}
  • 注意
  • 写这两个重载函数的时候要在类里面写友元函数来声明,否则访问不到私有的成员变量
  • 友元函数表示让编译器知道这个函数是类的朋友,可以访问类的私有成员变量
friend std::ostream& operator<<(std::ostream out,const Date& d);//流插入
friend std::istream& operator>>(std::istream out,Date& d);//流提取

小技巧

1、如何判断该传值返回还是传引用返回

  • 自定义类型的传值返回需要调用拷贝构造,所以为了提升代码效率,我们有时使用传引用返回
  • 判断返回的对象除了函数作用域之后还存不存在,如果存在,就用传引用返回,如果不存在,就用传值返回
  • 如果实在判断不出来就使用传值返回,传值返回一定是对的,当不一定是高效的

2、const修饰成员函数

  1. 建议成员函数中不修改成员变量的成员函数,都可以加上const
  2. 这样普通对象和const对象都可以调用
  3. 如果声明和定义分离,声明和定义都要加const

3、关于const修饰成员函数的几个小问题

  1. const对象可以调用非const成员函数吗?(不能,这是权限放大)
  2. 非const对象可以调用const成员函数吗?(可以,这是权限缩小)
  3. const成员函数内可以调用其他非const成员函数吗?(不能,这是权限放大)
  4. 非const成员函数内可以调用其他const成员函数吗?(可以,这是权限缩小)
//void Print(const Date* const this)
void Print() const//相当于上面那行代码
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}