c++实现类
Date类的实现-->(里面涉及类,this指针,引用,复用,运算符重载,友元函数,)
Date类的实现
本章节我们将根据前面所学过的知识,综合运用来完成一个日期类代码的实现,里面的知识点也能够让我们更好的掌握所学的内容。(所采用的编译环境为vs2019)我们采用类分离来进行定义,并且实现上面的功能。
我们规范的建立3个文件 test.cpp Date.c Date.h
首先我们知道定义一个类需要使用class(struct也可以)关键字,然后里面需要定义成员变量与成员函数,对于成员变量我们一般将它设计位私有,对于成员函数我们设计为公有。
于是我们就有了类的定义声明
class Date
{
public:
//构造函数 (全缺省)
Date(int year = 2024, int month = 9, int day = 20);
//析构函数
/*~Date()
{
}
//拷贝构造
Date(const Date& d)//必须给引用,如果是用const Date d 那么会引发无穷递归
{
_year = d._year;
_month = d._month;
_day = d._day;
}*/
//赋值运算符重载
/*Date operator=(const Date& dd)
{
}*/
//默认成员函数&
Date* operator&()
{
return this;
}
int GetMonthDay(int year, int month);
// == > >= < <=
bool operator==(Date& dd); 是否想等
bool operator!=(Date& dd);
bool operator>(Date& dd);
bool operator>=(Date& dd);
bool operator<(Date& dd);
bool operator<=(Date& dd);
//Date + - day
Date& operator+=(int day);
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
//Date-Date
int operator-(Date& dd);
/*Date operator-=(Date& dd);*/
//前置++
Date& operator++();
//后置++
Date operator++(int);
//前置--
Date operator--();
//后置--
Date operator--(int);
//cout<< dd
//友元函数
friend ostream& operator<<(ostream& out, Date& p);
friend istream& operator>>(istream& in, Date& p);
private:
//成员变量
int _year;
int _month;
int _day;
};
运算符重载要写那些就得我们来判断那些运算符是有意义的。
1.0首先我们来看构造函数的代码定义(Date.cpp)中
Date::Date(int year , int month , int day )
{
if (year < 1 || month>12 || month < 1 || day>31)
{
assert(false); //非法年月日直接报错 assert的用法;
}
_year = year;
_month = month;
_day = day;
}
因为我们的类声明和定义是分开的,所以我们需要给函数名前面加上Date::(相当与告诉编译器该函数是属于Date类的成员函数),此外我们还需要注意当全缺省的参数声明与定义分离的时候,我们只需要在声明的地方写缺省值就行。
对于Date类来说我们知道,成员变量都是内置类型的,我们的6个成员函数中就只需要写构造函数,因为析沟函数不需要释放Date类资源,等函数结束对象也就销毁了,而拷贝构造/赋值运算符重载我们不写编译器会生成默认的拷贝构造/赋值运算符重载完成值拷贝。还有其它两个成员函数一般都会生成默认的成员函数,我们一般不需要来进行实现。
1.1获得具体月的天数
int Date::GetMonthDay(int year, int month)
{
if (year < 1 || month < 1 || month>12)
{
//暴力报错 //非法的月和年
assert(false);
}
//给一个数组用来存放每个月的天数
int MonthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//最好先判断月是否为2月,能提高一点点效率,因为只有2月才会多一天。
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
//如果是2月且是闰年那么就返回29天
return 29;
}
return MonthArray[month];
}
1.2:判断两个日期是否相等与不相等
bool Date::operator==(Date& dd)
{
//年月日相等返回真
return _year == dd._year && _month == dd._month && _day == dd._day;
}
//我们复用上面写过的运算符重载 ==
bool Date::operator!=(Date& dd)
{
return !(*this == dd);
}
1.3判断d1与d2的关系(包括> >= < <=)
//关于形参是否需要加引用,我们只需要看出了作用域该对象是否还存在,如果出了这个作用域还存在,那么我们就可以使用引用来做形参
bool Date::operator>(Date& dd)
{
//年大则大
if (_year > dd._year)
{
return true;
}
//年等月大则大
else if (_year == dd._year && _month > dd._month)
{
return true;
}
//年等月等日大则大
else if (_year == dd._year && _month == dd._month && _day > dd._day)
{
return true;
}
//其他情况均为d1<d2
return false;
}
//>= 我们只需要复用 > == 运算符重载
bool Date::operator>=(Date& dd)
{
return ((*this > dd) || (*this == dd));
}
//>=取反
bool Date::operator<(Date& dd)
{
return !(*this >= dd);
}
// >的取反
bool Date::operator<=(Date& dd)
{
return !(*this > dd);
}
上面就是我们需要比较两个日期的大小的运算符了,对于自定义类型如果我们要使用运算符,我们都需要自己去成员函数中写(因为编译器不了解我们自定义该怎么比较)。
1.4+=day 与 +day的实现(一个日期过了day天之后的日期)
Date& Date::operator+=(int day)
{
if (day < 0)
{
//如果要+1个负数,就相当于-=1个正数
day = -day;
return *this -= day;
}
_day = _day + day;
while (_day > GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
} this--->Date* const this
return *this;//*this指的就是d1
}
//对于+一个天数我们需要创建一个零时变量来作为返回值,因为与i+10类似 i不变
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
+=的实现逻辑如下图
在这里我们也可以先实现+,+=来复用+。但是这两种有效率的快慢问题。
由上图可知,对于同样调用 d1+100与d1+=100来说左边总共只调用了2次拷贝构造,右边调了5次拷贝构造,所以左边的效率大于右边的效率。
1.5 - 与 -=
Date& Date::operator-=(int day)
{
if (day < 0)
{
day = -day;
return *this += day;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
该代码的实现与+的想法类似,先--month,然后再用负的天数加上该月的天数循环走下去。
1.6前置++与后置++ (天数+1,前置++,先++再用,后置++先用后++)
前置--与后置--
对于前置++,c++默认为无形参的那个,后置++为有整形参数的那个。前置--与后置--类似的道理
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this+=1;
return tmp;
}
//前置--
Date Date::operator--()
{
*this -= 1;
return *this;
}
//后置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
1.7日期-日期
int Date::operator-(Date& dd)
{
int flag = 1;
int n = 0;
Date max = (*this);
Date min = dd;
if (max < min)
{
max = dd;
min = *this;
flag = -1;
}
while (min!=max)
{
++min;
++n;
}
return flag * n;
}
我们先假设左边的日期大于右边的日期,在判断是否真的左边大于右边,如果大于flag不变,如果小于flag=-1,由于我们前面已经实现了!=,前置++运算符重载,所以这里我们就可以复用,
只要(min!=max) ++min ,并统计次数,这样我们就能够得到两个日期相差多少天了。
1.8流插入与流提取操作符
首先我们得知道 cout是ostream的流对象,cin是istream的流对象。
并且流运算操作符的本质是为了解决自定义类型成员的输入与输出的比如 cout<<d1. cin>>d1.
实现这两个操作符,我们需要知道定义为类成员函数是不行的,因为在类中第一个参数编译器默认给的是对象的指针this,而我们使用cout<<d1,的时候第一个参数必须是cout,也就是流对象。
所以我们需要将流运算符定义在全局中。
并且为了能够访问类内部中的成员,我们需要定义友元函数,这样就可以使用类内部的成员变量了。
实现如下:
写完这些内容我们的日期类实现也差不多完成了。