C++学习笔记(六)——日期类的实现

简介: C++学习笔记(六)——日期类的实现

前言


在前面的章节中,已经初步理解类和对象的基本思想,也知道了类和对象的一些特性。而前面一直贯穿着我们学习类和对象的例子“日期类(Date)”一直只是定义了它的属性,而这一章我们就要运用前面的知识去完善这一个日期类的实现!

日期类的大致功能概况


基本成员函数:

  构造函数,拷贝构造函数,析构函数,赋值运算符重载,操作符重载

日期类功能函数:

  • 计算一个日期加上多少天数后的日期
  • 把该日期改为加上指定数目的天数后的日期
  • 一个日期减上多少天数后的日期
  • 把该日期改为减去指定数目的天数后的日期
  • 该日期加1(前置++)(后置++)
  • 该日期减1(前置--)(后置--)
  • 计算某日期到未来某日期间隔的天数

具体代码实现


日期类中所包含的成员函数和成员变量:

class Date
{
public:
  // 构造函数
  Date(int year = 0, int month = 1, int day = 1);
  // 打印函数
  void Print() const;
  // 日期+=天数
  Date& operator+=(int day);
  // 日期+天数
  Date operator+(int day) const;
  // 日期-=天数
  Date& operator-=(int day);
  // 日期-天数
  Date operator-(int day) const;
  // 前置++
  Date& operator++();
  // 后置++
  Date operator++(int);
  // 前置--
  Date& operator--();
  // 后置--
  Date operator--(int);
  // 日期的大小关系比较
  bool operator>(const Date& d) const;
  bool operator>=(const Date& d) const;
  bool operator<(const Date& d) const;
  bool operator<=(const Date& d) const;
  bool operator==(const Date& d) const;
  bool operator!=(const Date& d) const;
  // 日期-日期
  int operator-(const Date& d) const;
  // 析构,拷贝构造,赋值重载可以不写,使用默认生成的即可
private:
  int _year;
  int _month;
  int _day;
};

构造函数


 进入构造函数体,首先需要检查日期的合法性,只有当日期合法时,才能进行后续的构造操作。

// 获取某年某月的天数
inline int GetMonthDay(int year, int month)
{
  // 数组存储平年每个月的天数
  static int dayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  int day = dayArray[month];
  if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
  {
    //闰年2月的天数
    day = 29;
  }
  return day;
}
// 构造函数
Date::Date(int year, int month, int day)
{
  // 检查日期的合法性
  if (year >= 0
  && month >= 1 && month <= 12
  && day >= 1 && day <= GetMonthDay(year, month))
  {
    _year = year;
    _month = month;
    _day = day;
  }
  else
  {
    // 严格来说抛异常更好
    cout << "非法日期" << endl;
    cout << year << "年" << month << "月" << day << "日" << endl;
  }
}

GetMonthDay函数中的三个细节:

 1.该函数可能被多次调用,所以我们最好将其设置为内联函数。

 2.函数中存储每月天数的数组最好是用static修饰,存储在静态区,避免每次调用该函数都需要重新开辟数组。

 3.逻辑与应该先判断month == 2是否为真,因为当不是2月的时候我们不必判断是不是闰年。

注意:当函数声明和定义分开时,在声明时注明缺省参数,定义时不标出缺省参数。

打印函数


这个不用说了,相当简单。

// 打印函数
void Date::Print() const
{
  cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

日期+=天数


 对于+=运算符,我们先将需要加的天数加到日上面,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。

调整日期的思路:

 1.若日已满,则日减去当前月的天数,月加一。

 2.若月已满,则将年加一,月置为1。

反复执行1和2,直到日期合法为止。

// 日期+=天数
Date& Date::operator+=(int day)
{
  if (day<0)
  {
    // 复用operator-=
    *this -= -day;
  }
  else
  {
    _day += day;
    // 日期不合法,通过不断调整,直到最后日期合法为止
    while (_day > GetMonthDay(_year, _month))
    {
      _day -= GetMonthDay(_year, _month);
      _month++;
      if (_month > 12)
      {
        _year++;
        _month = 1;
      }
    }
  }
  return *this;
}

注:当需要加的天数为负数时,转而调用-=运算符重载函数。

日期+天数


+运算符的重载,我们可以复用上面已经实现的+=运算符的重载函数。但是要注意:虽然我们返回的是加了之后的值,但是对象本身的值并没有改变。就像a = b + 1,b + 1的返回值是b + 1,但是b的值并没有改变。所以我们还可以用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 日期+天数
Date Date::operator+(int day) const
{
  Date tmp(*this);// 拷贝构造tmp,用于返回
  // 复用operator+=
  tmp += day;
  return tmp;
}

注意:+=运算符的重载函数采用的是引用返回,因为出了函数作用域,this指针指向的对象没有被销毁。但+运算符的重载函数的返回值只能是传值返回,因为出了函数作用域,对象tmp就被销毁了,不能使用引用返回。

日期-=天数


对于-=运算符,我们先用日减去需要减的天数,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。

调整日期的思路:

 1.若日为负数,则月减一。

 2.若月为0,则年减一,月置为12。

 3.日加上当前月的天数。

反复执行1、2和3,直到日期合法为止。

// 日期-=天数
Date& Date::operator-=(int day)
{
  if (day < 0)
  {
    // 复用operator+=
    *this += -day;
  }
  else
  {
    _day -= day;
    // 日期不合法,通过不断调整,直到最后日期合法为止
    while (_day <= 0)
    {
      _month--;
      if (_month == 0)
      {
        _year--;
        _month = 12;
      }
      _day += GetMonthDay(_year, _month);
    }
  }
  return *this;
}

注:当需要减的天数为负数时,转而调用+=运算符重载函数。

日期-天数


和+运算符的重载类似,我们可以复用上面已经实现的-=运算符的重载函数,而且最好用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 日期-天数
Date Date::operator-(int day) const
{
  Date tmp(*this);// 拷贝构造tmp,用于返回
  // 复用operator-=
  tmp -= day;
  return tmp;
}

注意:-=运算符的重载函数采用的是引用返回,但-运算符的重载函数的返回值只能是传值返回,也是由于-运算符重载函数中的tmp对象出了函数作用域被销毁了,所以不能使用引用返回。

前置++


前置++,我们可以复用+=运算符的重载函数。

// 前置++
Date& Date::operator++()
{
  // 复用operator+=
  *this += 1;
  return *this;
}

后置++


由于前置++和后置++的运算符均为++,为了区分它们的运算符重载,我们给后置++的运算符重载的参数加上一个int型参数,使用后置++时不需要给这个int参数传入实参,因为这里int参数的作用只是为了跟前置++构成重载。

// 后置++
Date Date::operator++(int)
{
  Date tmp(*this);// 拷贝构造tmp,用于返回
  // 复用operator+=
  *this += 1;
  return tmp;
}

前置--


前置–,我们也是可以复用前面的-=运算符的重载函数。

// 前置--
Date& Date::operator--()
{
  // 复用operator-=
  *this -= 1;
  return *this;
}

后置--


 后置–需要注意的事项和后置++是一样的,我这里就不过多阐述了。

// 后置--
Date Date::operator--(int)
{
  Date tmp(*this);// 拷贝构造tmp,用于返回
  // 复用operator-=
  *this -= 1;
  return tmp;
}

日期类的大小关系比较


日期类的大小关系比较需要重载的运算符看起来有6个,实际上我们只用实现>和=就可以了,然后其他的通过复用这两个就可以实现。

注意:进行日期的大小比较,我们并不会改变传入对象的值,所以这6个运算符重载函数都应该被const所修饰。

>运算符的重载


 >运算符的重载很简单,先判断年是否大于,再判断月是否大于,最后判断日是否大于,这其中有一者为真则函数返回true,否则返回false。

bool Date::operator>(const Date& d) const
{
  if (_year > d._year)
  {
    return true;
  }
  else if (_year == d._year)
  {
    if (_month > d._month)
    {
      return true;
    }
    else if (_month == d._month)
    {
      if (_day > d._day)
      {
        return true;
      }
    }
  }
  return false;
}

==运算符的重载


 ==运算符的重载也是很简单,年月日均相等,则为真。

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

>=运算符的重载


 >=,即大于或者等于,满足其中之一即可。

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

<运算符的重载


 <,大于等于的反面即是小于。

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

<=运算符的重载


 <=,大于的返回即是小于等于。

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

!=运算符的重载


 !=,等于的反面即是不等于。

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

日期 - 日期


 日期 - 日期,即计算传入的两个日期相差的天数。我们只需要让较小的日期的天数一直加一,直到最后和较大的日期相等即可,这个过程中较小日期所加的总天数便是这两个日期之间差值的绝对值。若是第一个日期大于第二个日期,则返回这个差值的正值,若第一个日期小于第二个日期,则返回这个差值的负值。

// 日期-日期
int Date::operator-(const Date& d) const
{
  Date max = *this;// 假设第一个日期较大
  Date min = d;// 假设第二个日期较小
  int flag = 1;// 此时结果应该为正值
  if (*this < d)
  {
    // 假设错误,更正
    max = d;
    min = *this;
    flag = -1;// 此时结果应该为负值
  }
  int n = 0;// 记录所加的总天数
  while (min != max)
  {
    min++;// 较小的日期++
    n++;// 总天数++
  }
  return n*flag;
}
相关文章
|
19天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
29 2
|
26天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
62 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
68 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
77 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
30 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
26 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
25 1
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
2月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)