date类成员变量
date 类的成员变量为私有访问属性的三个整型变量,分别代表年、月、日,即_year、_month、_day,全部属于内置类型。
class date
{
public:
//类函数声明/定义部分
//成员变量一般需要保护起来
private:
int _year;
int _month;
int _day;
};date类默认成员函数
对于日期类而言,它的三个成员变量均不涉及动态资源的申请,因此,可以不用为其写析构函数,而且它的三个成员变量也都不涉及指针,因此拷贝构造函数和赋值运算符重载函数也不需要我们自己写,因为编译器默认生成的浅拷贝对它而言就是够用的了。最后两个取地址重载函数对于绝大多数类而言一般都不需要我们自己实现。
date类构造函数
在类的 public 部分声明构造函数 date (int year = 1, int month = 1, int day = 1);,构造函数名字和类名 date 保持一致,构造函数没有返回值。这里给三个形参全部设置缺省值,年、月、日默认都是 1。缺省参数规定写在类内的声明位置,类外定义的时候不能重复书写缺省值,重复写会编译报错。有了全缺省构造,我们既可以不传参数写 date d1;,使用默认的 1‑1‑1 创建对象;也可以传入实参 date d2 (2025,10,1);,使用自定义的年月日创建对象。类的私有部分定义三个整型成员变量_year、_month、_day,用来存储对象的年、月、日,外部不能直接访问这三个成员。
date::date (int year, int month, int day) 是构造函数在类外面的定义,date:: 是类域限定符,告诉编译器这个函数属于 date 类。函数接收传入的 year、month、day,首先做日期合法性判断,判断条件分为三部分,月份要大于 0 并且小于 13,日期要大于 0,同时日期不能大于当前年月对应的当月总天数,这里调用 getmonthday 函数获取当月天数,该函数需要提前实现,要处理闰年二月的特殊情况。
如果条件成立,代表传入的是合法日期,就把形参 year、month、day 赋值给对象的私有成员_year、_month、_day,完成对象成员的初始化;如果条件不成立,属于非法日期,就打印 “非法日期” 提示,执行 assert (false),断言在调试模式会直接终止程序,防止生成一个年月日无效的 date 对象。
这里要注意一个细节,代码是先校验参数,校验通过之后才给成员变量赋值。假如传入非法年月日,_year、_month、_day 不会被赋值,由于它们是内置 int 类型,对象内存里会保留随机垃圾值。这是这段代码的一处小缺陷,对象被创建出来但是成员是随机值,实际工程中一般会先赋值再校验,或者校验失败要给成员赋予安全默认值。
class date
{
public:
//date类构造函数声明(一般函数缺省值是在声明给,定义时不能再提供缺省值)
date(int year = 1, int month = 1, int day = 1);
private:
int _year;
int _month;
int _day;
};
date::date(int year, int month, int day)
{
//防止构造非法日期
if (month > 0 && month < 13 && day>0 && day <= getmonthday(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}date类成员函数
date类日期比较大小函数
判断日期类变量大于
函数后面的 const 修饰代表这个成员函数不会修改当前对象的成员变量,const 类型的 date 对象也可以调用这个函数,参数使用 const date& x,以引用方式接收对象,避免传值带来的拷贝开销,同时保护传入对象不会被修改。
函数的比较逻辑按照先比年份、再比月份、最后比天数的顺序执行。首先判断当前对象的_year 是否大于 x 的_year,如果年份更大,直接返回 true,代表当前日期更大。如果年份相等,就继续判断月份,当前对象_month 大于 x._month 的话,返回 true。如果年份和月份全部相等,就比较天数,当前对象的_day 大于 x._day,返回 true。上面三个条件只要有一个成立,就说明当前日期大于 x。如果三层条件都不满足,说明当前日期小于或者等于 x,函数最后返回 false。
举个例子,比如当前对象是 2025‑10‑1,传入 x 是 2025‑9‑30,年份相同,当前月份更大,函数返回 true。如果当前对象是 2024‑5‑1,x 是 2025‑3‑1,当前年份更小,前面条件全部不命中,走到最后返回 false。写完 operator > 之后,其他比较运算符就可以复用这个函数,不用重复写全套年月日判断逻辑。
bool date::operator>(const date& x) const
{
if (_year > x._year)
{
return true;
}
else if (_year == x._year && _month > x._month)
{
return true;
}
else if (_year == x._year && _month == x._month && _day > x._day)
{
return true;
}
return false;
}判断日期类变量等于
函数参数是 const date& x,使用引用传参可以避免对象拷贝,const 保证传入的对象在函数内部不会被修改。函数末尾的 const 修饰隐含的 this 指针,表示该函数不会修改当前对象的成员,const 修饰的 date 对象也可以正常调用这个比较函数。
函数使用 return 直接返回表达式结果,判断逻辑要求年、月、日三者必须同时全部相等。&& 是逻辑与运算符,只有_year 等于 x._year,并且_month 等于 x._month,并且_day 等于 x._day,整个表达式结果才为 true,代表两个日期完全相同。
如果月份不一样,即便年和日相同,表达式也会返回 false。这个写好的相等判断函数还可以给不等于运算符复用,直接对 operator== 的返回结果取反即可,不用再重新写一套年月日判断。
bool date::operator==(const date& x)const
{
return _year == x._year && _month == x._month && _day == x._day;
}判断日期类变量不等于
函数的参数是 const date& x,引用传参避免发生对象拷贝,const 保障传入的对象不会被修改。函数尾部带上 const,代表函数不会修改当前对象的成员,const 修饰的日期对象也能够调用这个函数。
写法复用已经完成的相等判断逻辑,减少重复代码,也能保证判断标准统一,不会出现相等和不等两套判断逻辑不一致的 bug。
bool date::operator!=(const date& x)const
{
return !(operator==(x));
}判断日期类变量大于等于
参数使用 const date& x,引用传参避免对象拷贝,同时保护传入对象不被修改,函数末尾的 const 表示不会修改当前对象,const 修饰的日期对象也可以调用该函数。
函数内部直接复用已经实现好的大于运算符 operator > 和等于运算符 operator==。*this 代表调用这个函数的当前对象,|| 是逻辑或运算符,只要两个条件里面有一个成立,整体结果就为 true。如果当前日期大于 x,或者当前日期等于 x,就返回 true,代表满足大于等于;既不大于也不等于的时候就返回 false。
这样复用已有接口可以减少重复代码,保证各个比较运算符的判断规则保持一致。
bool date::operator>=(const date& x)const
{
return ( * this > x || *this == x );
}判断日期类变量小于
参数使用 const date& x,引用传参避免对象拷贝,同时保护传入的对象不会被修改,函数末尾的 const 说明该函数不会修改当前对象的成员,const 修饰的日期对象也可以调用这个函数。
函数内部直接复用已经写好的大于等于运算符 operator>=。this 代表调用函数的当前对象,!对 this>=x 的运算结果取反。如果当前对象大于等于 x,取反之后得到 false,说明当前日期并不小于 x。如果当前对象不满足大于等于 x,取反得到 true,就代表当前日期小于 x。
这种复用已有函数的写法可以减少重复代码,也可以让所有比较运算符的判断标准保持统一,避免出现逻辑不一致的问题。
bool date::operator<(const date& x)const
{
return !(*this>=x);
}判断日期类变量小于等于
参数使用 const date& x,引用传参避免对象拷贝,同时保护传入对象不会被修改,函数末尾的 const 表示不会修改当前对象,被 const 修饰的日期对象也可以调用该函数。
直接复用已经实现好的大于运算符 operator>。this 代表调用这个函数的当前对象,! 是逻辑非运算符,对this > x 的结果进行取反。如果当前日期大于 x,*this>x 结果为 true,取反之后返回 false,说明不满足小于等于。如果当前日期不大于 x,也就是小于或者等于 x,*this>x 结果为 false,取反之后返回 true。
复用已经写好的运算符能够减少重复代码,保证全部比较运算符的判断标准统一。
bool date::operator<=(const date& x)const
{
return !(*this>x);
}
date类加减天数函数
获取月份天数函数
函数内部定义了 static 修饰的整型数组 monthdays,数组下标从 0 到 12,下标 0 位置的数据没有实际用处,下标 1 到 12 依次对应十二个月的天数,平年二月默认存 28。数组前面加 static,数组就存放在静态数据区,函数多次调用的时候不会重复创建数组,程序运行期间数组只初始化一次,以此提升执行效率。
如果当前是 2 月份,并且满足闰年的判定条件,普通闰年能被 4 整除但是不能被 100 整除,世纪闰年可以被 400 整除,满足条件就返回 29 天。不是闰年的二月,或者是其他月份,直接从 monthdays 数组取出对应下标的天数返回。把二月的判断放在前面,大部分月份不会进入这个 if 分支,可以减少判断开销。
要注意这个函数没有做月份合法性校验,调用这个函数的时候,传入的 month 必须是 1 到 12 之间的数值,否则数组下标越界会出现问题。闰年的判断只针对二月,其余月份的天数直接读取数组,不受闰年影响。
int date::getmonthday(int year, int month)
{
static int monthdays[13] = { 0,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 monthdays[month];
}
}日期加等天数
给当前日期对象增加指定天数,会直接修改调用函数本身的对象,返回 date & 引用类型,目的是支持连续的复合赋值操作。
函数首先判断传入的 day 是否小于 0,如果 day 是负数,就复用已经写好的减等于运算符,把 - day 传入,相当于执行日期减去正数,直接 return *this -= -day,处理传入负数天数的场景。
如果 day 是正数,先把 day 直接加到当前对象的_day 上。之后进入 while 循环,循环的条件是当前的_day 大于当月的总天数,通过 getmonthday 获取当月的天数。当日期超过本月最大天数,就用_day 减去本月的天数,月份加一。月份增加之后再判断,如果月份大于 12,说明需要跨年,年份加一,月份减去 12 变回 1。循环会一直重复这个进位逻辑,直到_day 不再大于当月天数,日期就调整为合法的新日期。
最后返回 * this,也就是当前对象的引用,支持像 d1 += 10 += 20 这样连续调用。
这个函数会直接修改原对象,和 operator + 不一样,operator + 会生成临时对象,不会改动原来的日期。
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 > 12)
{
_year++;
_month -= 12;
}
}
return *this;
}日期加天数
日期类普通加号运算符的重载函数,作用是实现日期加上天数,这个函数不会修改调用函数的原日期对象,所以函数尾部加了 const 修饰。
返回值类型是 date,不是引用,因为函数里面创建了局部临时对象 tmp,局部对象出函数作用域就会销毁,不能用引用返回。
函数内部先用 date tmp (this),调用拷贝构造,把当前对象this 拷贝一份给临时对象 tmp,tmp 就是原日期的副本。接着调用 tmp += day,借助已经实现好的 += 运算符给临时对象增加天数,这一步只会修改 tmp,原来的对象不会发生任何变化。最后把修改完成的临时对象 tmp 返回出去。
使用的时候比如 date d2 = d1 + 100,d1 本身保持不变,计算出来的新日期存到 d2 里面。这里要区分开 operator + 和 operator+=,+= 是直接修改自身,+ 是生成一份新对象做运算,原对象保持不变。因为返回的是局部临时对象,不能返回引用,只能以值的方式返回。
date date::operator+(int day)const
{
date tmp(*this);
tmp += day;
return tmp;
}日期类变量前置++
date 类前置 ++ 运算符的重载函数,对应 ++d 这种写法,作用是把当前日期向后增加一天。返回值是 date & 引用类型,可以返回当前对象本身。
函数里面直接复用已经写好的 += 运算符,执行this += 1,也就是给当前对象的日期加上一天,这一步会直接修改调用该函数的对象。运算完成之后返回this,也就是返回自身对象的引用。
前置 ++ 不需要创建临时对象,直接修改原对象并且返回自身引用,所以效率相对更高,因此自定义类型一般优先推荐使用前置 ++。返回引用也支持连续运算,比如 ++++d 这样的写法。
注意这个函数没有写 const,因为它会修改对象的成员变量,const 修饰的日期对象不能调用这个函数。
date& date::operator++()
{
*this += 1;
return *this;
}日期类变量后置++
date 类后置 ++ 运算符的重载函数,对应 d++ 这种写法,功能是日期向后增加一天,但是返回自增操作之前的旧值。
函数参数里面写了 int,这里只是一个占位参数,没有写参数名字,这个参数不会在函数内部被使用,c++ 靠这个额外的 int 形参和前置 ++ 构成函数重载,用来区分前置 ++ 和后置 ++。
函数首先用 date tmp (this) 拷贝构造一个临时对象 tmp,把当前对象还没有自增之前的日期保存下来。接着执行this += 1,调用 += 运算符,把调用函数的原对象日期加一天,原对象本身会被修改。最后返回临时对象 tmp,tmp 里面存放的是自增之前的旧日期。
返回类型是 date,不是引用,因为 tmp 是函数内部的局部临时对象,函数结束之后 tmp 就会销毁,不能返回局部对象的引用,只能做值返回。后置 ++ 需要创建临时对象保存旧数据,相比前置 ++ 会多一份拷贝,所以自定义类型优先使用前置 ++。
这个函数会修改对象成员,所以末尾不能加 const。
date date::operator++(int)
{
date tmp(*this);
*this += 1;
return tmp;
}日期减等天数
日期类减等于运算符的重载函数,用来实现当前日期减去指定天数,函数会直接修改调用该函数的对象,返回 date & 引用是为了支持连续复合赋值。
首先判断传入的 day 是否小于 0,如果 day 是负数,就复用已经实现的 += 运算符,把 - day 当做正数,相当于日期加上正数天数,直接返回 * this += -day,处理传入负天数的情况。
如果 day 是正数,先把 day 直接从当前对象的_day 上面减掉。进入 while 循环,循环条件是_day 小于等于 0,代表日期不够,需要向上一个月借天数。首先把月份减一,如果月份减完之后小于等于 0,就代表需要跨年,年份减一,月份加上 12 回到 12 月。处理完年月之后,调用 getmonthday 拿到刚刚跳转过去的上一个月的总天数,加到_day 上面。循环会不断执行借位逻辑,直到_day 大于 0,得到合法的日期。
处理完成之后返回 * this,也就是当前对象的引用,支持连续调用
函数会直接改动原对象,和 operator - 区分开,operator‑会生成临时对象,不会修改原本的日期。函数内部会修改成员变量,所以不能加 const 修饰。
date& date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month <= 0)
{
_year--;
_month += 12;
}
_day += getmonthday(_year, _month);
}
return *this;
}日期减天数
日期类减号运算符重载函数,实现日期减去天数的功能,函数末尾的 const 代表不会修改调用这个函数的原对象。
返回类型是 date,不能返回引用,因为 tmp 是函数内部创建的局部对象,函数执行结束后局部对象会销毁,返回引用会产生野引用。函数内部先用 date tmp (*this) 调用拷贝构造函数,复制一份当前对象保存到临时对象 tmp 中。接着调用已经写好的减等于运算符,对 tmp 减去 day 天,修改的只有 tmp,原来的日期对象不会发生变化。最后把 tmp 以值拷贝的方式返回。
使用的时候写 date d2 = d1 - 20,d1 本身不会改变,计算得到的新日期存放在 d2。和 operator-= 对比,operator-= 直接修改原对象,而 operator - 会拷贝一份对象做运算,保留原始数据不变。
date date::operator-(int day)const
{
date tmp(*this);
tmp -= day;
return tmp;
}日期类变量前置--
date 类前置--运算符的重载函数,对应 --d 这种写法,作用是把当前日期向前减少一天。返回值是 date & 引用,返回对象自身。
函数内部复用已经实现好的减等于运算符,执行this -= 1,直接修改调用函数的原对象,让日期减去一天。运算完成之后返回this,也就是当前对象的引用。
前置 -- 不需要创建临时对象,直接修改原对象,效率更高,也支持连续运算,比如 ----d。因为函数会修改对象的成员变量,所以不能在函数末尾添加 const 修饰。
date& date::operator--()
{
*this -= 1;
return *this;
}日期类变量后置--
date 类后置--运算符的重载函数,对应 d-- 这种写法,作用是将日期向前减少一天,但是返回减减操作之前的旧日期。
函数参数里的 int 是占位参数,不需要写参数名,仅仅用来和前置 -- 构成函数重载,区分前置和后置版本。函数内部首先用 date tmp (this) 拷贝构造临时对象 tmp,保存当前对象还没有自减之前的日期。接着执行this -= 1,调用已经实现好的减等于运算符,修改原对象,让原日期减去一天。最后返回临时对象 tmp,tmp 里面存放的是自减之前的旧日期。
返回类型是 date,不能返回引用,因为 tmp 是函数内部的局部对象,函数结束之后 tmp 就会销毁,返回引用会出现野引用问题。后置 -- 需要创建临时对象保存旧值,相比前置 -- 会多一次对象拷贝,运行效率更低。
函数会修改对象的成员,所以不能在函数末尾加 const。
date date::operator--(int)
{
date tmp(*this);
*this -= 1;
return tmp;
}日期减日期
日期类相减运算符重载函数,用来计算两个日期之间相差的天数,也就是当前日期减去传入的日期对象 x,函数末尾带 const,代表不会修改当前对象。函数返回 int 类型,结果可以是正数、负数或者 0,正数代表当前日期比 x 晚,负数代表当前日期比 x 早。
函数首先定义变量 count 用来记录相差的天数,再拷贝一份 x 保存到临时对象 tmp。接着判断当前日期 * this 是否小于 tmp,如果当前日期更早,就进入第一个循环。只要两个日期不相等,count 就减一,tmp 执行后置减减,也就是 tmp 向前走一天,直到 tmp 和当前日期相等,最后返回 count,得到负数的天数差。如果当前日期大于或者等于 x,就进入第二个循环,只要两个日期不相等,count 加一,tmp 执行后置加加,tmp 向后增加一天,直到两个日期相同,返回 count,得到正数的天数差。
这个实现依靠循环一天一天推移日期来统计天数,逻辑简单易懂,但是时间效率不高,两个日期相隔很远的时候循环次数会很多。函数不会修改原对象和传入的 x 对象,只操作临时拷贝的 tmp。
//日期减日期
int date::operator-(const date& x)const
{
int count = 0;
date tmp(x);
if (*this < tmp)
{
while (*this != tmp)
{
count--;
tmp--;
}
return count;
}
else
{
while (*this != tmp)
{
count++;
tmp++;
}
return count;
}
}到此这篇关于c++ date 类从零实现:日期校验、运算符重载与日期计算的文章就介绍到这了,更多相关c++ date类实现内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论