C++类和对象 练习小项目---日期类的实现.

简介: C++类和对象 练习小项目---日期类的实现.

前言

这是我们需要实现的日期类的接口声明,我们需要的是在Date.cpp文件中实现函数的定义.

class Date
{
public:
  // 获取某年某月的天数
  int GetMonthDay(int year, int month);
  //打印日期类函数
  void Print();
  // 全缺省的构造函数
  Date(int year = 1900, int month = 1, int day = 1);
  // 拷贝构造函数
  Date(const Date& d);
  // 赋值运算符重载
  Date& operator=(const Date& d);
  // 析构函数
  ~Date();
  // 日期+=天数
  Date& operator+=(int day);
  // 日期+天数
  Date operator+(int day);
  // 日期-天数
  Date operator-(int day);
  // 日期-=天数
  Date& operator-=(int day);
  // 前置++
  Date& operator++();
  // 后置++
  Date operator++(int);
  // 后置--
  Date operator--(int);
  // 前置--
  Date& operator--();
  // >运算符重载
  bool operator>(const Date& d);
  // ==运算符重载
  bool operator==(const Date& d);
  // >=运算符重载
  bool operator >= (const Date& d);
  // <运算符重载
  bool operator < (const Date& d);
  // <=运算符重载
  bool operator <= (const Date& d);
  // !=运算符重载
  bool operator != (const Date& d);
  // 日期-日期 返回天数
  int operator-(const Date& d);
private:
  int _year;
  int _month;
  int _day;
};

一、构造函数

1.1 默认构造函数

声明:(在Date类中)

//Date.h
  // 全缺省的构造函数
  Date(int year = 2023, int month = 1, int day = 1);

定义:

//Date.cpp
Date::Date(int year , int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
  //这里也就体现出了,成员变量前面'_'的好处,方便与参数区分
}

这里需要注意的是,缺省参数应该在声明处给出,定义时不能有缺省参数,在C++入门章节牛牛有提到过原理.

1.2 拷贝构造函数

使用场景:

Date d1(2023, 4, 26);
Date d2(d1);//使用已存在的对象去初始化另一个对象,被称为拷贝构造

义:

//Date.cpp
// 拷贝构造函数
Date::Date(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
}

注意使用引用传参.

二、获取天数

放在以前,牛牛实现获取天数的函数可能会用一个很长的Switch case语句,然后返回每一个天数的时间.


如今,牛牛发现,除了闰年时2月是29天以外,其他时候,每个月的时间是不变的,我们可以使用数组将每个月的天数存起来.

int Date::GetMonthDay(int year, int month)
{
  int day[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))//如果是闰年,且是2月
  {
    day[2] = 29;
  }
  return day[month];
}

这里需要注意的是判断条件month == 2要放在前面

((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)放在后面,因为前面的判断条件很好判断,后面的比较复杂,这样可以提高效率.

三、运算符重载

3.1 赋值运算符重载

注意:

参数const Date& d


1.const一方面保证右操作数不会被修改,即防止函数写反了功能.

例如:参照错误写法.

d1=d2,结果是将d2修改成了d1,偷鸡不成蚀把米.


2.Date&传引用就不会调用拷贝构造函数去传参,减少拷贝,提高效率.

//运算符重载
Date& Date::operator=(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
  return *this;
}
//错误写法
Date& Date::operator=(Date& d)
{
  d._year=_year; 
  d._month=_month;
  d._day=_day;
  return *this;
}

3.2 日期+=天数

ef7068a5ae914ce980b69711e73fff3a.png

示例: 2023年7月28日+80天

f9919a78f5ff4785b884a5dd007b6619.png

需要注意的是,如果month往后推一位后为13,则应当将month设置为1,并且year++.


代码实现:

// 日期+=天数
Date& Date:: operator+=(int day)
{
  if (day < 0)//不知道使用者会不会调皮传过来负数
  {
    return *this -= -day;//调用"-="的运算符重载
  }
  _day += day;
  while (_day > GetMonthDay(_year, _month))//如果超过当月天数
  {
    _day -= GetMonthDay(_year, _month);//通过调用GetMonthDay函数获取当月天数
    _month++;
    if (_month > 12)//月数超过12,则开始下一年
    {
      _month = 1;
      _year++;
    }
  }
  return *this;
}

3.3 日期+天数

与日期+=天数不同,日期+天数要求该日期本身没有改变,而是返回日期+天数后的日期


此时,我们需要创建一个临时Date 类ret,将增加的天数与ret进行计算,最后返回ret对象.

// 日期+天数
Date Date:: operator+(int day)
{
  if (day < 0)
  {
    return *this -= -day;//调用"-="的运算符重载
  }
  Date ret;//创建临时对象
  ret._day += day;
  while (ret._day > GetMonthDay(_year, _month))//如果超过当月天数
  {
    ret._day -= GetMonthDay(_year, _month);
    ret._month++;
    if (ret._month > 12)
    {
      ret._month = 1;
      ret._year++;
    }
  }
  return ret;
}

3.4 日期-=天数 和 日期-天数

f3ec1d9246aa47f1b5a7ae41a5f06d1c.png

示例:2023年7月28日-100天


需要注意的是,重点是+上月的天数,而不是本月的天数.

ddffc50839ee46fe97a259e742e765ce.png

 // 日期-=天数
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)
 {
   if (day < 0)
   {
     *this -= day;//调用"-="的运算符重载
   }
   Date ret;
   ret._day -= day;
   while (ret._day <= 0)//如果是负数
   {
     ret._day += GetMonthDay(_year, _month-1);//+上个月的总天数
     ret._month--;
     if (ret._month <=0)
     {
       ret._month = 12;
       ret._year--;
     }
   }
   return ret;
 }

3.5 日期-日期

fc358814e15342a9beeccae4bbdaea5b.png

日期-日期怎么计算?

例如:

2023年7月28号距离2024年1月1号还有几天?

如果对应的年月日进行想减,然后还需要计算是那些年有那些天,月数又有几天,那可就太麻烦了吧.


所以我们直接先判断两个日期的大小,选择用较小的日期,对齐进行++操作,直到与较大的相等,统计++了多少天,这样是不是就很简单了?


步骤:


   1.比较日期大小,选出较小者.

   2.对较小者进行++并统计,直到与较大者相等.

   3.返回统计的天数.

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
  //小的日期一直++,加到和大的日期一样时,加了多少次就差多少天
  Date max = *this;
  Date min = d;
  int flag = 1;
  if (*this < d)//如果是左操作数小,则应当是负数
  {
    max = d;
    min = *this;
    flag = -1;
  }
  int n = 0;
  while (min != max)//用n统计相差多少天
  {
    ++min;
    ++n;
  }
  return n * flag;
}

3.6 前置++与后置++

前置++与后置++实现的时候有一个很尴尬的问题,因为前置++和后置++都是单目运算符,即只有一个操作数,那么为了实现他们两个函数能够重载,则只能在后置++处添加一个int类型的参数.

这个参数用户在使用时不需要传递,编译器会自动传递,本质是为了让前置++和后置++进行函数重载.


前置++是返回+1之后的结果,并且this是指向对象本身的,所以我们可以使用传引用返回,减少拷贝,提高效率.


后置++是返回+1之前的值,并且对象最终还需要被修改,所以我们需要创建一个临时对象用于记录+1前对象的日期大小.除此之外,因为临时变量是在局部定义的,所以我们必须传值返回,不能传引用返回.

// 前置++
 Date& Date:: operator++()
{
   _day +=1;
   while (_day > GetMonthDay(_year, _month))//如果超过当月天数
   {
     _day -= GetMonthDay(_year, _month);//则减去当月的天数
     //月份向后推一个月
     _month++;
     if (_month > 12)
     {
       _month = 1;
       _year++;
     }
   }
   return *this;
}
// 后置++
Date Date::operator++(int)//这个参数为了个与前置++构成函数重载,调用的时候不需要传参.
{
  Date tmp = *this;//要保存++前日期的大小.
  _day += 1;
  while (_day > GetMonthDay(_year, _month))//如果超过当月天数
  {
    _day -= GetMonthDay(_year, _month);
    _month++;
    if (_month > 12)
    {
      _month = 1;
      _year++;
    }
  }
  return tmp;
}

3.7 前置–与后置–

学了前置++与后置++,这里也是类似的,需要注意的是,+上月的天数.

// 前置--
Date& Date::operator--()
{
  _day -= 1;
  while (_day <= 0)//如果是负数
  {
    _day += GetMonthDay(_year, _month - 1);//+上个月的总天数
    _month--;
    if (_month <= 0)
    {
      _month = 12;
      _year--;
    }
  }
  return *this;
}
// 后置--
Date Date::operator--(int)
{
  Date tmp = *this;
  _day -= 1;
  while (_day <= 0)//如果是负数
  {
    _day += GetMonthDay(_year, _month - 1);//+上个月的总天数
    _month--;
    if (_month <= 0)
    {
      _month = 12;
      _year--;
    }
  }
  return tmp;
}

3.8 比较运算符

比较运算符重载我不多介绍了,没有什么难度.


需要学习的是,可以使用已经实现的>和"=="去复用实现剩下的其他运算符

bool Date::operator>(const Date& d)
{
  if (_year > d._year)//如果年大
  {
    return true;
  }
  {
    if (_year == d._year)
    {
      if (_month > d._month)//年相同,月大
      {
        return true;
      }
      else
      {
        if (_day > d._day)//入如果年月相同,日大
        {
          return true;
        }
        return false;//月小,或者日小和相等
      }
    }
    return false;//如果年小
  }
}
// ==运算符重载
bool Date::operator==(const Date& d)
{
  if (_year == d._year&&
    _month== d._month&&
    _day==d._day
    )
  {
    return true;
  }
  return false;
}
// >=运算符重载
bool Date::operator >= (const Date& d)
{
  if (*this > d|| *this == d)
  {
    return true;
  }
  return false;
}
// <运算符重载
bool Date::operator < (const Date& d)
{
  return !(*this >= d);
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
  return !(*this > d);
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
  return !(*this == d);
}

日期类就简单实现到这里了,友友们下次见,总代码,可以去我的资源处下载源代码哦.

目录
相关文章
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
190 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
281 0
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
325 12
|
9月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
9月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
189 16
|
10月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
9月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
9月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
9月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
491 6