【C++】日期类@类和对象(中)—— const成员函数

简介: 类和对象

@TOC

0. 引

继C++入门、类和对象上篇中篇后,我们把这些学到的知识都用起来,来写一个日期类。这不禁让我想起了闷热又潮湿的夏季小学期java的第一个作业,写一个万年历,那时我写的真是一塌糊涂啊哈哈。下面我贴出头文件,各位自己写。所有要注意的小点我都写出来了,文章尽量呈现一套自然而然理所当然的思路。开始吧!

#include<iostream>
using namespace std;

class Date
{
public:
    //构造函数
    Date::Date(int year = 0, int month = 1, int day = 1);
    //析构函数、拷贝构造、赋值重载都不用写
    //Date(const Date& d)
    //{
    //    cout << "Date(const Date& d)" << endl;
    //}
    void Print();

    int getMonthDay(int year, int month);
    //运算符重载系列
    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);

    // d1 += 100;
    Date& operator+=(int day);
    // d1 + 100;
    Date operator+(int day);
    // d1 -= 100;
    Date& operator-=(int day);
    // d1 - 100;
    Date operator-(int day);
    
    // 前置++
    Date& operator++();
    // 后置++
    Date operator++(int);
    // 前置--
    Date& operator--();
    //后置 --
    Date operator--(int);

    // 日期 - 日期
    int operator-(const Date& d);
    // 今天是星期几?
    void PrintWeekday();
private:
    int _year;
    int _month;
    int _day;
};

1. 基本接口

1.1 默认成员函数

1.1.1 构造函数

通过对于构造函数、析构函数、拷贝构造这一系列函数特征的学习,我们知道对于日期类,我们并不需要写析构、拷贝构造一类,用默认的就行。只需要自己实现构造函数

  • [ ] 缺省参数不能在声明和定义中同时出现,一般写在声明中
Date::Date(int year, int month, int day )
{
    _year = year;
    _month = month;
    _day = day;

    if (year<0
        ||(month<0 || month >12)
        ||(day <0 || day >getMonthDay(year,month)))
    {
        cout << "非法日期:>";
        Print(); //在类里,可以直接访问类函数
    }
}

这就还需要考虑非法日期的输入:为了判断天数是否合理,我们引入了下一个接口,来获取某年某月的天数 ——

1.1.2 拷贝构造函数

可以不写。但后面我为了验证拷贝构造函数的调用,在里面瞎写了一句打印。

1.2 获取某年某月天数

这里采用了比较巧妙的用数组存储天数,第几月就对应着相应天数。

  • [ ] 对于天数,不会修改,置为静态数组。
  • [ ] 对闰年的处理,先来判断month == 2
int Date::getMonthDay(int year, int month)
{
    //静态数组
    static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day = monthDayArray[month];
    //闰年:四年一闰、百年不闰、四百年再闰
    if (month == 2
        && ((year%4==0 && year%100!=0)||( year%400 == 0)))
    {
        day += 1;
    }
    return day;
}

2. 一系列运算符重载函数

2.1 += 、+、++

2.2.1 日期 += 天数 = ?

2022.1.21 + 100是几号?这个功能的实现,乍一想好像挺复杂,还要考虑闰年,不同月的天数。但顺着我们正常的计算思维,列写几个例子,其实很简单。:white_check_mark:它其实就是一个不断进位的过程,天满了往月进;月满了往年进、月归1。

<img src=" title="">

那就写个循环直至合法 ——

Date& Date::operator+=(int day)
{
    _day += day;
    while (_day > getMonthDay(_year, _month))
    {
        _day -= getMonthDay(_year, _month);
        _month++;

        if (_month == 13)
        {
            _year += 1;
            _month = 1;
        }
    }
    return *this;//d1没销毁,可以传引用返回
}
  • [ ] 注意,这里我们实现的是+=,而不是 +,在这里我们改变了d1,回想,我们之前多次强调过
    int i = 0;
    i + 100;

i的值并不会改变。那怎么实现运算符+的重载呢?我们就弄一个临时对象来 ——

2.2.2 日期 + 天数

  • [ ] 拷贝构造一个临时对象,以避免对d的改变
  • [ ] 可以巧妙复用+=接口
// d1 + 100; 不能改变d1
Date Date::operator+(int day)
{
    Date ret(*this);//拷贝构造一个
    ret += 100; // ret.operator+=(&ret, 100);
    return ret;
}

2.2.3 前置++、后置++

在写++的运算符重载函数时,发现这两个函数啊,函数名相同。为了区分前置++和后置++,我们增加一个参数占位,它们俩就构成了函数重载。

实现:前置++,与后置++,区别就在于返回值的不同。

  • [ ] 注意:究竟传引用返回(能不能?),还是传值返回 —— 不是背出来一开始就写上的。而是根据你实现的的不同逻辑,看你返回的对象,出了作用域被没被销毁,分析出来加上的,当然熟练了就直接写了。

:strawberry:前置++

// 先++后使用
Date& Date::operator++()
{
    *this += 1;
    return *this;
}

:strawberry:后置++

// 先使用后++
Date Date::operator++(int)
{
    Date ret(*this);
    *this += 1;
    return ret;
}

  • [ ] 如何调用?

编译器会把它们转化为 ——

    Date d1(2022, 1, 22);
    ++d1; //d1.operator(&d1);
    d1++; //d1.operator(&d1, 1); 

后置++,这个整数只起到占位作用,没有实在意义,所以传什么随意。

  • [ ] 对于整型,前置++后置++区别不大(不涉及使用返回值时),但是对于自定义类型,建议使用前置++。这是因为前置++,我们可以传引用返回;后置++,拷贝构造临时对象加上传值返回,要拷贝构造两次(调试/打印可测,我都测了哈),效率低下。

所以你看,前置++也保持了它原生的样子。

2.2 -=、-、--

有了2.1节“加”的一系列铺垫,关于“减”那是不在话下。

2.2.1 日期 -= 天数 = 日期

怎么减呢?写过“加”了,那思路就顺下来。(在我测试的时候,把几个易错点全测到了,或许女生真的适合去做测试?哈哈,值得表扬的是通过调试都改过来了,也侧面说明我踩雷踩得相当准,我都写在注意里了)

<img src=" title="">

注意

  • [ ] 天数 <= 0都不合法
  • [ ] 注意我们借到的天数是上一个月的天数(这与“加”不同,“加”获取的是本月天数)。
  • [ ] 到达边界时,我们是先把月置12,再对天数处理(这与”加“也不同,这里是先处理月份,后处理天,光我说你可能晕了,自己想想者的逻辑很简单)。
Date& Date::operator-=(int day)
{
    while (_day <= 0)
    {
        _month--;
        if (_month == 0)
        {
            _year--;
            _month = 12;
        }
        _day += getMonthDay(_year, _month);

    }
    return *this;
}

在这里要考虑,万一我输入的day是负数?

我们需要把它单拎出来处理一下,-= day 等价于+= (-day)。同样的刚刚我们实现的+=运算符重载函数也需要同样处理,代码如下 ——

// +=
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 == 13)
        {
            _year += 1;
            _month = 1;
        }
    }
    return *this;//d1没销毁,可以传引用返回
}
//-=
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;
}

2.2.2 日期 - 天数

复用嗷!

Date Date::operator-(int day)
{
    Date ret(*this);
    ret -= day;
    return ret;
}

2.2.3 前置--、后置--

注意事项同++

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

//后置 --
Date Date::operator--(int)
{
    Date ret(*this);
    *this -= 1;
    return *this;
}

2.3 一堆比较 >、>=、<、<=、==、!=

事实上,在我们实现了>==,就可以根据这些符号简单的关系,疯狂复用完成其他所有接口。

相似的逻辑,很自然的就能想到能不能复用。来!

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

来,开始疯狂复用吧!基本数学知识哈哈

// >=
bool Date::operator>=(const Date& d)
{
    return *this > d || *this == d;
}
// <
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);
}

3. 日期 - 日期

这乍一想是挺复杂,又可能是闰年,跨几个月也不定。其实可以暴力求解,找那个较小的日期,加加加到较大的日期。

这里我计算一个,从今天到小边冲实习生那天还有多少天吧!诶,有高考倒计时的感觉了。来!

// 日期 - 日期
// offerDay - today => offerDay.operator(&offerday, today);
int Date::operator-(const Date& d)
{
    // 假设
    Date min = d;//早
    Date max = *this;//晚
    int flag = 1;
    if (*this < d)
    {
        min = *this; //早
        max = d; // 晚
        flag = -1;
    }

    int count = 0;
    while (min < max)
    {
        min++;
        count++;
    }
    return count*flag;
}

<img src=" title="">

4. 星期几?

void Date::PrintWeekday()
{
    Date start(1900, 1, 1); //查询得星期一
    int count = *this - start;
    cout << "星期" << ((count % 7) + 1) << endl;
}

5. const成员

引入 ——

<img src=" title="">

于是C++增加了const成员函数,实际修饰this指针保护this指向的内容不被修改,即不能对任何类成员进行修改。

class Date
{
public:
    void Display() const
    {
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }

private:
    int _year; 
    int _month; 
    int _day; 
};

int main()
{
    Date d1;
    d1.Display(); //权限缩小

    const Date d2;
    d2.Display(); //权限不变
    return 0;
}

总结:成员函数加const是好的,建议能加上的都加上,这样普通对象和const对象都能调用。

但是如果要修改成员变量的成员函数就不可以加。比如日期类中的+=++

6. 取地址及const取地址操作符重载

这两个运算符重载意义不大。

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况才需要重载,比如想让别人获取到指定的内容!

class Date
{
public:
    Date* operator&()
    {
        return this;
    }

    const Date* operator&()const
    {
        return this;
    }
private:
    int _year; // 年
    int _month; // 月
    int _day; // 日
};

附录:

测试文件随写随测

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include"Date.h"

int Date::getMonthDay(int year, int month)
{
    //静态数组
    static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day = monthDayArray[month];
    //闰年:四年一闰、百年不闰、四百年再闰
    if (month == 2
        && ((year%4==0 && year%100!=0)||( year%400 == 0)))
    {
        day += 1;
    }
    return day;
}

//缺省参数不能在声明和定义中同时出现
Date::Date(int year, int month, int day )
{
    _year = year;
    _month = month;
    _day = day;

    if (year<0
        ||(month<0 || month >12)
        ||(day <0 || day >getMonthDay(year,month)))
    {
        cout << "非法日期:>";
        Print(); //在类里,可以直接访问类函数
    }
}

void Date::Print()
{
    cout << _year << "-" << _month << "-" << _day << endl; 
}

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 == 13)
        {
            _year += 1;
            _month = 1;
        }
    }
    return *this;//d1没销毁,可以传引用返回
}

// d1 + 100; 不能改变d1
Date Date::operator+(int day)
{
    Date ret(*this);//拷贝构造一个
    ret += 100; // ret.operator+=(&ret, 100);
    return ret;
}

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

//后置++
Date Date::operator++(int)
{
    Date ret(*this);
    *this += 1;
    return ret;
}



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

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

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

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);
}


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;
}

Date Date::operator-(int day)
{
    Date ret(*this);
    ret -= day;
    return ret;
}

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

//后置 --
Date Date::operator--(int)
{
    Date ret(*this);
    *this -= 1;
    return *this;
}

// 日期 - 日期
// offerDay - today => offerDay.operator(&offerday, today);
int Date::operator-(const Date& d)
{
    // 假设
    Date min = d;//早
    Date max = *this;//晚
    int flag = 1;
    if (*this < d)
    {
        min = *this; //早
        max = d; // 晚
        flag = -1;
    }

    int count = 0;
    while (min < max)
    {
        min++;
        count++;
    }
    return count*flag;
}

void Date::PrintWeekday()
{
    Date start(1900, 1, 1); //查询得星期一
    int count = *this - start;
    cout << "星期" << ((count % 7) + 1) << endl;
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "Date.h"

void test1()
{
    Date d1(2002, 3, 7);
    d1.Print();

    Date d2(2022, 2, 29);
}

//测试+、+=、++
void test2()
{
    Date d1(2022, 1, 16);
    /*Date ret = d1 + 100;
    ret.Print();*/

    //Date d2(2022, 1, 16);
    //d2 += 100;
    //d2.Print();

    //++d1;
    d1++;
}

//测试这一堆运算符重载函数
void test3()
{
    
    Date d1(2002, 3, 7);
    Date d2(2002, 2, 19); //missing lmyy
    Date d3(2002, 3, 7);

    cout << (d1 == d3) << endl;
    cout << (d1 >= d2) << endl;
    cout << (d1 < d3) << endl;
    cout << (d1 <= d2) << endl;
    cout << (d1 != d3) << endl;

}

// 测试-,-=,--
void test4()
{
    Date d1(2022, 1, 10);
    Date d2(2022, 2, 19);

    Date ret2 = d2 - 60;
    ret2.Print();

    d1 -= 10;
    d1.Print();

    /*--d2;
    d2--;*/
}

//测试日期 - 日期,星期几
void test5()
{
    Date today(2022,1,23);
    Date offerDay(2022, 9, 1);
    cout << (offerDay - today) << endl;
    today.PrintWeekday();
}

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

热门文章

最新文章