仿佛_一念成佛 发表于 2024-3-22 07:48

【数据结构】C++实现日期基本类,重载运算符,友元..【第一篇】

本帖最后由 仿佛_一念成佛 于 2024-3-22 08:16 编辑

# C++实现日期基本类,重载运算符,友元..【第一篇】

最近刚好写完了关于c++的日期类实现。特地来分享给大家

话不多说,直接上示例

首先我要实现三个构造函数,一个拷贝函数和另外一个析构函数来构成最基本的日期类。在此之前我需要在我的类中声明private变量。
## 声明private变量
`int dia //表示天数`
`int mes // 表示月份`,
`int anyo // 表示年份`
和`char *mensaje // 表示一个字符串数组`

## 声明默认函数

它们分别是
`TCalendario(); // 默认构造函数,用来初始话日期为1/1/1900和mensaje为nullptr`

`TCalendario(int dia, int mes, int anyo, const char *mens); // 用来初始化日期为参数`

`TCalendario(const TCalendario &); // 深拷贝`

`~TCalendario(); //析构函数,用来释放内存`

哦对了,类名为Tcalendario,相信聪明的你一定已经看出来了。

## 声明重载运算符

`TCalendario &operator=(const TCalendario &); // 赋值运算符重载`

`TCalendario operator+(const int); // +号运算符重载`

`TCalendario operator-(const int); // -号运算符重载`
`TCalendario operator++(int); //后置++运算符重载`

`TCalendario &operator++(); // 前置++运算符重载`

`TCalendario operator--(int); // 后置--运算符重载`
`TCalendario &operator--(); // 前置--运算符重载`

`bool operator==(const TCalendario &) const; // 等号运算符重载`

`bool operator!=(const TCalendario &) const; //不等于号运算符重载`

`bool operator>(const TCalendario &) const; // 大于号运算符重载`

`bool operator<(const TCalendario &) const; //小于号运算符重载`

## 一些其他函数的实现

`bool ModFecha(int dia, int mes, int anyo); // 用来修改日期,True为修改成功,False为修改失败`

`bool EsVacio() const; // 用来判断当前对象是否具有初始化的值`

`int Dia() const; // 返回对象的当前天数`

`int Mes() const;// 返回对象的当前月份`

`int Anyo() const;// 返回对象的当前年份`

`char *Mensaje() const // 返回对象的当前数组字符`


另外我们可以再加两个函数来判断**日期是否正确**OR**闰年**
`int get_month_day(int month, int year) const;`

`bool check_days_correct(int day, int month, int year) const;`

---

目前为止,我们已经声明了所有需要的函数,现在要对它们一一进行实现。声明函数放在.h文件中,实现函数放在.cpp文件中。

---


## 实现默认构造析构函数

### # 默认构造函数

`TCalendario::TCalendario() : dia(1), mes(1), anyo(1900), mensaje(nullptr) {} // 使用初始化列表来进行初始化`

### #默认构造参数函数

```
TCalendario::TCalendario(int dia, int mes, int anyo, const char *mens)
{
    if (check_days_correct(dia, mes, anyo)) // 首先需要判断日期是否正确,如为True,进行初始化
    {
      this->dia = dia;
      this->mes = mes;
      this->anyo = anyo;
      if (mens != nullptr) // 此处需要注意,我们正在使用一个指针来对char进行操作,需要先进行判断,然后动态分配char所需要的内存大小
      {
            // dynamic alloc memory to char
            size_t t_strlen = strlen(mens);
            this->mensaje = new char;
            memset(this->mensaje, 0, t_strlen);
            strcpy(this->mensaje, mens);
      }
      else
      {
            this->mensaje = nullptr;
      }
    }

    else
    {
      // 日期错误,初始化为默认值
      this->dia = 1;
      this->mes = 1;
      this->anyo = 1900;
      this->mensaje = nullptr;
    }
}

```

### 日期判断函数

这一部分相信大家都看得懂就不打注释了

```

bool TCalendario::check_days_correct(int day, int month, int year) const
{
    int dia_ = get_month_day(month, year);
    if (day > dia_ || month > 12 || year < 1900 || day < 1 || month < 1)
    {
      return false;
    }
    else
    {
      return true;
    }
}
int TCalendario::get_month_day(int month, int year) const
{
    static int day[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) // check if year is bisiestor
    {
      if (month == 2)
      {
            return 29;
      }
    }
    return day;
}

```
### 析构函数(释放内存)

```
TCalendario::~TCalendario()
{
    // 在这里进行释放内存操作,重新将变量设置为默认
    this->anyo = 1900;
    this->mes = 1;
    this->dia = 1;
    if (mensaje != nullptr) // 此处需要特别注意,如果不加上这个空指针判断会造成释放空指针错误,从而导致程序崩溃。因为是一个ptr,所以要用delete[]
    {
      delete[] mensaje;
      mensaje = nullptr;
    }
    else
    {
      mensaje = nullptr;
    }
}

```

### 深拷贝

这里没有什么好说的,需要注意的是深拷贝带有const关键字。其他的都上默认构造参数函数一样

```
TCalendario::TCalendario(const TCalendario &t)
{
    if (check_days_correct(t.dia, t.mes, t.anyo))
    {
      // deep copy all value
      // cout << "deep copy" << endl;
      this->dia = t.dia;
      this->mes = t.mes;
      this->anyo = t.anyo;
      // // deep copy char
      if (t.mensaje != nullptr)
      {
            size_t t_strlen = strlen(t.mensaje);
            this->mensaje = new char;
            memset(this->mensaje, 0, t_strlen);
            strcpy(this->mensaje, t.mensaje);
      }
      else
      {
            this->mensaje = nullptr;
      }
    }
    else
    {
      this->dia = 1;
      this->mes = 1;
      this->anyo = 1900;
      this->mensaje = nullptr;
    }
}

```
## 实现重载运算符

### #= 重载
```
TCalendario &TCalendario::operator=(const TCalendario &t)
{

    if (this == &t)
    {
      /* 如果出现当对象1赋值给对象1的情况下直接返回对象1,无需继续进行后续操作因为为同一个对象*/
      return *this;
    }
    /*在进行赋值前,需要对左值进行内存释放,不然会造成segmentation fault。在这里使用了析构函数来进行释放*/
    (*this).~TCalendario();

    /*下面的没什么可以讲的,都是一样的步骤,赋值,分配内存,然后结束*/
    if (check_days_correct(t.dia, t.mes, t.anyo))
    {
      this->dia = t.dia;
      this->mes = t.mes;
      this->anyo = t.anyo;
      // // deep copy char
      if (t.mensaje != nullptr)
      {
            size_t t_strlen = strlen(t.mensaje);
            this->mensaje = new char;
            memset(this->mensaje, 0, t_strlen);
            strcpy(this->mensaje, t.mensaje);
      }
      else
      {
            this->mensaje = nullptr;
      }
    }
    else
    {
      this->dia = 1;
      this->mes = 1;
      this->anyo = 1900;
      this->mensaje = nullptr;
    }
    return *this;
}
```

### + 重载

这里需要特别注意,这里没有使用引用进行返回,而是创建了一个临时对象

```
TCalendario TCalendario::operator+(const int value)
{
    // 创建临时对象
    TCalendario temp(*this);
    if (value < 0) // 当传入的参数小于0无需进行任何操作
    {
      return temp;
    }
    // 这里进行了+操作,首先对临时对象的dia变量进行了相加,然后进入一个while循环。后面就是进行加月份或者年份操作当当前日期超出的时候
    temp.dia += value;
    while (temp.dia > get_month_day(temp.mes, temp.anyo))
    {
      temp.dia -= get_month_day(temp.mes, temp.anyo);
      ++temp.mes;
      if (temp.mes > 12)
      {
            temp.mes = 1;
            ++temp.anyo;
      }
    }
    return temp;
}
```
### - 重载

和上面的+一样,不过逻辑需要变动一下,直接看代码吧

```
TCalendario TCalendario::operator-(const int value)
{
    TCalendario temp(*this);
    if (value < 0)
    {
      return temp;
    }
    temp.dia -= value;
    while (temp.dia <= 0)
    {
      --temp.mes;
      if (temp.anyo <= 1900) // when data is inferior than 1900 reinicialice to init value and break loop
      {
            temp.~TCalendario();
            break;
      }
      if (temp.mes == 0)
      {
            temp.mes = 12;
            --temp.anyo;
      }
      temp.dia += get_month_day(temp.mes, temp.anyo);
    }
    return temp;
}

```

### ++ 后置重载

和上方的一样,不过这里是后置++,有一个占位符int来表示后置
```
TCalendario TCalendario::operator++(int)
{
    TCalendario temp = *this;

    ++dia;
    while (dia > get_month_day(mes, anyo))
    {
      dia -= get_month_day(mes, anyo);
      ++mes;
      if (mes > 12)
      {
            mes = 1;
            ++anyo;
      }
    }

    return temp;
}
```

### ++ 前置重载

此处开始变了,返回的不再是临时对象而是引用。逻辑还是一样的,只不过返回对象变了
```

TCalendario &TCalendario::operator++()
{
    ++this->dia;
    while (this->dia > get_month_day(this->mes, this->anyo))
    {
      this->dia -= get_month_day(this->mes, this->anyo);
      ++this->mes;
      if (this->mes > 12)
      {
            this->mes = 1;
            ++this->anyo;
      }
    }
    return *this;
}
```
### -- 后置重载

返回临时对象。当超出初始范围时重新赋值。其他逻辑不变

```
TCalendario TCalendario::operator--(int)
{
    if (this->dia == 1 && this->mes == 1 && this->anyo == 1900)
    {
      this->~TCalendario();
      return *this;
    }

    TCalendario temp(*this);
    --dia;
    while (dia <= 0)
    {
      --mes;
      if (anyo <= 1900) // when data is inferior than 1900 reinicialice to init value and break loop
      {
            this->~TCalendario();
            break;
      }
      if (mes == 0)
      {
            mes = 12;
            --anyo;
      }
      dia += get_month_day(mes, anyo);
    }
    return temp;
}
```
### -- 前置重载

返回引用,逻辑不变

```
TCalendario &TCalendario::operator--()
{
    if (this->dia == 1 && this->mes == 1 && this->anyo == 1900)
    {
      this->~TCalendario();
      return *this;
    }

    --this->dia;
    while (this->dia <= 0)
    {
      --this->mes;
      if (this->anyo <= 1900) // when data is inferior than 1900 reinicialice to init value and break loop
      {
            this->~TCalendario();
            break;
      }
      if (this->mes == 0)
      {
            this->mes = 12;
            --this->anyo;
      }
      this->dia += get_month_day(this->mes, this->anyo);
    }
    return *this;
}
```

### == 重载

返回为布尔值。此函数功能为用来比较两个对象是否是同一个值。

在这里有一个坑,当时写的时候花了有些时间才发现。

当对象中的mensaje为nullptr的时候,我一开始用的是strcmp来比较两个是否相等。那么大家肯定知道,我一个空指针和一个字符比较那么肯定会报错。所以这里我使用了一个string对象来初始化一个空的字符串然后去进行compare函数比较,这样子就没有什么问题了
```
bool TCalendario::operator==(const TCalendario &t) const
{
    string this_mensaje;
    string t_mensaje;
    if(this->mensaje == nullptr)
    {
      this_mensaje = "";
    }
    else
    {
      this_mensaje = this->mensaje;
    }

    if(t.mensaje == nullptr)
    {
      t_mensaje = "";
    }
    else
    {
      t_mensaje = t.mensaje;
    }
    return ((this->dia == t.dia) && (this->mes == t.mes) && (this->anyo == t.anyo) && (this_mensaje.compare(t_mensaje) == 0));
}
```

### != 重载

和==一样,不过是相反的条件
```
bool TCalendario::operator!=(const TCalendario &t) const
{
    string this_mensaje;
    string t_mensaje;
    if(this->mensaje == nullptr)
    {
      this_mensaje = "";
    }
    else
    {
      this_mensaje = this->mensaje;
    }

    if(t.mensaje == nullptr)
    {
      t_mensaje = "";
    }
    else
    {
      t_mensaje = t.mensaje;
    }
   
    return ((this->dia != t.dia) || (this->mes != t.mes) || (this->anyo != t.anyo) || (this_mensaje.compare(this_mensaje) != 0));

}
```

### > 重载
此处有两个条件来判断对象1是否大于对象2
    1. 日期大于
    2. 日期等于但是mensaje大于


```
bool TCalendario::operator>(const TCalendario &t) const
{
    // (Criterio 1) : la fecha de T1 es posterior a la de T2(independientemente del mensaje).
    // (Criterio 2) : la fecha de T1 es igual a la de T2, y el mensaje de T1 es mayor(en comparación de cadenas) al mensaje de T2.Si la fecha de T1 es igual a la de T2 y el mensaje de T1 e igual al de T2, entonces tanto operator> como operator<devuelven FALSE.
    string this_mensaje;
    string t_mensaje;
    if(this->mensaje == nullptr)
    {
      this_mensaje = "";
    }
    else if(t.mensaje == nullptr)
    {
      t_mensaje = "";
    }
    else
    {
      this_mensaje = this->mensaje;
      t_mensaje = t.mensaje;
    }
    bool crt_1 = (this->anyo > t.anyo) || (this->anyo == t.anyo && this->mes > t.mes) || (this->anyo == t.anyo && this->mes == t.mes && this->dia > t.dia);

    // crt_2 have more priority than crt_1
    bool crt_2 = ((this->anyo == t.anyo && this->mes == t.mes && this->dia == t.dia) && this_mensaje.compare(t_mensaje) > 0);

    return crt_1 || crt_2;
}

```

### < 重载

相同的逻辑不同的条件

```

bool TCalendario::operator<(const TCalendario &t) const
{
    string this_mensaje;
    string t_mensaje;
    if (this->mensaje == nullptr)
    {
      this_mensaje = "";
    }
    else if (t.mensaje == nullptr)
    {
      t_mensaje = "";
    }
    else
    {
      this_mensaje = this->mensaje;
      t_mensaje = t.mensaje;
    }
    bool crt_1 = (this->anyo < t.anyo) || (this->anyo == t.anyo && this->mes < t.mes) || (this->anyo == t.anyo && this->mes == t.mes && this->dia < t.dia);

    // crt_2 have more priority than crt_1
    bool crt_2 = ((this->anyo == t.anyo && this->mes == t.mes && this->dia == t.dia) && this_mensaje.compare(t_mensaje) < 0);

    return crt_1 || crt_2;
}

```

那么到这里已经全部实现了个别运算符重载和默认函数的实现。接下来实现一些其他函数or方法也可以这么说


## 其他函数

### 修改日期


返回True 修改成功。反之失败

```

bool TCalendario::ModFecha(int dia, int mes, int anyo)
{
    /*Check de Numero de días correcto, número de mes correct y Control de años bisiestos*/

    if (dia > 31 || dia < 1 || mes < 1 || mes > 12 || anyo < 1900)
    {
      return false;
    }

    int dia_ = get_month_day(mes, anyo);
    if (dia <= dia_)
    {
      this->dia = dia;
      this->mes = mes;
      this->anyo = anyo;
      return true;
    }
    return false;
}

```

### 修改mensaje

返回True 修改成功。反之失败

```

bool TCalendario::ModMensaje(const char *c)
{
    /*mensaje is null we need to allocated memory to it*/
    if (c == nullptr)
    {
      this->mensaje = nullptr;
      return true;
    }

    if (this->mensaje == nullptr)
    {
      // alloc new memory
      size_t t_strlen = strlen(c);
      this->mensaje = new char;
      memset(this->mensaje, 0, t_strlen);
      strcpy(this->mensaje, c);
      return true;
    }
    /*delete old char memory and alloc new memory*/
    else
    {
      delete[] this->mensaje;
      size_t t_strlen = strlen(c);
      this->mensaje = new char;
      memset(this->mensaje, 0, t_strlen);
      strcpy(this->mensaje, c);
      return true;
    }
    /*unusual condicional return*/
    return false;
}

```

### 判断当前对象日期是否为默认值


根据声明的函数来实现。很简单的逻辑

```

bool TCalendario::EsVacio() const
{
    /* devuelve TRUE si el calendario tiene fecha 1/1/1900 y el mensaje es NULL */
    if ((this->anyo == 1900 && this->dia == 1 && this->mes == 1) && (this->mensaje == nullptr))
    {
      return true;
    }
    return false;
}

```

接下来是几个返回天数,月份,年份,信息的函数

### 天数

```

int TCalendario::Dia() const
{
    return this->dia;
}

```

### 月份

```
int TCalendario::Mes() const
{
    return this->mes;
}
```

### 年份

```
int TCalendario::Anyo() const
{
    return this->anyo;
}
```

### 信息

唯一需要注意的就是这个了,不好好判断会有空指针错误。然后程序就崩了

```
char *TCalendario::Mensaje() const
{
   
    if (this->mensaje == nullptr)
    {
      return nullptr;
    }
    else
    {
      return this->mensaje;
    }
}

```

## 友元 【<<重载】

终于到最后了,友元还是蛮简单的实现方法,和cout输出一样,只是它换位另外一种输出方式了。

### 声明

一般我放在类的最上方,要求为public,不能为private

```
friend std::ostream &operator<<(std::ostream &, const TCalendario &);

```

### 实现

跟cout差不多,不过要带两个参数,一个为输出流对象,另外一个为const对象。内部实现就是进行了一些格式化的输出,没什么可以讲的

```
ostream &operator<<(ostream &cout, const TCalendario &t)
{
    if (t.dia < 10)
    {
      cout << "0";
    }
    cout << t.dia << "/";
    if (t.mes < 10)
    {
      cout << "0";
    }
    cout << t.mes << "/" << t.anyo << " ";

    if (t.mensaje == nullptr)
    {
      cout << "\"\"";
    }
    else
    {
      cout << "\""<< t.mensaje << "\"";
    }

    return cout;
}

```
## 结束

到这里第一部分就写完了,这个作业在写的过程中还是花费了一些时间的。不过好在最后还是完成了。难度为c++初学者中等。

后面我将实现日期类的Vector类,自己手搓一个仿vector的方法出来,实现了resize()。

那么大家下次再见!

爱飞的猫 发表于 2024-3-22 08:06

仿佛_一念成佛 发表于 2024-3-22 08:03
我从vscode直接复制的排版,改了好几次了不知道为什么改不回来

因为两边的 Markdown 渲染实现不一样,所以会造成这种情况。

你文中的 ``` 可能没被正确识别出来,被合并到之前的列表里面了。在这行之前加一个空行可能会改善这个情况。

爱飞的猫 发表于 2024-3-22 08:00

排版乱了哦!代码块建议前后空一行避免识别错误。

此外,自 C++20 开始 [`<chrono>`](https://zh.cppreference.com/w/cpp/chrono#.E6.97.A5.E5.8E.86) 自带了相关的实现,可以参考实现/使用。

caojian1624 发表于 2024-3-22 07:57

谢谢分享

仿佛_一念成佛 发表于 2024-3-22 08:03

爱飞的猫 发表于 2024-3-22 08:00
排版乱了哦!代码块建议前后空一行避免识别错误。

此外,自 C++20 开始 [``](https://zh.cppreferen ...

我从vscode直接复制的排版,改了好几次了不知道为什么改不回来

仿佛_一念成佛 发表于 2024-3-22 08:12

爱飞的猫 发表于 2024-3-22 08:06
因为两边的 Markdown 渲染实现不一样,所以会造成这种情况。

你文中的 ``` 可能没被正确识别出来,被 ...

从<重载往后面的就开始奇怪了,明明都和前面的是一样的格式哇。这咋识别不出来呢

仿佛_一念成佛 发表于 2024-3-22 08:17

爱飞的猫 发表于 2024-3-22 08:06
因为两边的 Markdown 渲染实现不一样,所以会造成这种情况。

你文中的 ``` 可能没被正确识别出来,被 ...

找到那个列表了。感谢!

zsj118106 发表于 2024-3-22 08:27

学习了,大佬牛比

wuaipojie92 发表于 2024-3-22 08:41

水到渠成 发表于 2024-3-22 08:55

学习一下
页: [1] 2
查看完整版本: 【数据结构】C++实现日期基本类,重载运算符,友元..【第一篇】