紧接上回,这次我们把Date类完善一下

运算符重载之++/--

前置++/--

计算当前日期的后一天/前一天

#pragma once
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int y = 2025, int m = 9, int d = 11) 
    {
        _year = y, _month = m, _day = d; 
    }
    void Print()
    {
        printf("%d-%02d-%02d\n", _year, _month, _day);
    }
    
// 下面我们来实现这两个函数。注意这是前置++与前置--
    Date& operator++ ();
    Date& operator-- ();

private:
// ++和--会频繁求当前月的天数
    int get_day_of_month(int year, int m)
    {
        static int d[13] = {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if(m != 2) return d[m];
        if(_year % 4 == 0 && _year % 100 != 0 || _year % 400 == 0)
            return d[2] + 1;
        else return d[2];
    }
    int _year;
    int _month;
    int _day;
};

代码实现:

Date& Date::operator++ ()
{
    _day++;
    if(_day > get_day_of_month(_year, _month))
    {
        _day = 1;
        _month++;
        if(_month == 13)
        {
            _month = 1;
            _year++;
        }
    }
    return *this;
}

Date& Date::operator-- ()
{
    _day--;
    if(_day == 0)
    {
        _month--;
        if(_month == 0)
        {
            _month = 12;
            _year--;
        }
        _day = get_day_of_month(_year, _month);
    }
    return *this;
}

测试一下:

int main()
{
    Date d1(2025, 2, 10);
    Date d2(2025, 3, 1);
    while(d1 != d2)
    {
        d1.Print();
        ++d1;
    }
    return 0;
}

后置++/--

为了跟前置++/--区分开,C++规定后置++/--参数要多一个int

Date operator++ (int);
Date operator-- (int);

代码实现:

// 注意后置++/--的返回值
Date Date::operator++ (int)
{
    Date t = *this;
    _day++;
    if(_day > get_day_of_month(_year, _month))
    {
        _day = 1;
        _month++;
        if(_month == 13)
        {
            _month = 1;
            _year++;
        }
    }
    return t;
}

Date Date::operator-- (int)
{
    Date t = *this;
    _day--;
    if(_day == 0)
    {
        _month--;
        if(_month == 0)
        {
            _month = 12;
            _year--;
        }
        _day = get_day_of_month(_year, _month);
    }
    return t;
}

测试:

int main()
{
    Date d(2025, 9, 12);
    Date d1 = d++;
    d.Print();
    d1.Print();
    return 0;
}

运行结果
在这里插入图片描述

运算符重载之+=/-=

有了前两个代码我们就可以求距离该日期n天后(或n天前)的日期

Date& operator+= (int n);
Date& operator-= (int n);

代码实现:

Date& Date::operator+= (int n)
{
    while(n--) (*this) ++;
    return *this;
}
Date& Date::operator-= (int n)
{
    while(n--) (*this) --;
    return *this;
}

测试,结果可以对比 在线日期计算器

int main()
{
    Date d1(2025, 9, 12);
    d1 += 333330;
    d1.Print();
    return 0;
}

运算符重载之+/-

日期 + 天数n: n天后的日期
日期 + 日期:无意义
日期 - 天数n: n天前的日期
日期 - 日期:两个日期间的天数

需要实现的相关函数如下:

Date operator+(int n);
Date operator-(int n);
int operator- (const Date& d);

代码实现

Date Date::operator+ (int n)
{
    Date t = *this;
    t += n;
    return t;
}

Date Date::operator-(int n)
{
    Date t = *this;
    t -= n;
    return t;
}

int Date::operator- (const Date& d)
{
    // 大 - 小:天数为正;反之为负
    int flag = 1;
    Date begin = d;
    Date end = *this;
    if(*this < d) 
    {
        flag = -1;
        begin = *this, end = d;
    }
    int cnt = 0;
    while(begin != end)
    {
        cnt++;
        begin++;
    }
    return cnt * flag;
}

测试:

int main()
{
    Date d1(1999, 1, 1);
    Date d2(2025, 9, 12);
    cout << (d1 - d2) << endl;
    cout << (d2 - d1) << endl;
    
    Date d3(2125, 9, 12);
    cout << (d3 - d2) <<endl; // 人生不过三万天 ~ 
    return 0;
}

运算符重载之<< / >>

int main()
{
    Date d(2025, 2, 28);
    d.Print(); // 每次打印都要调用Print,能否像下面那样打印日期呢?
    // cout << d;
    return 0;
}

我们先来理一下为什么cout可以自动识别类型
cout其实是ostream类的一个对象,库里面已经写好了相关的运算符重载
在这里插入图片描述

转念一想在类里面写出了下面的代码

// 补充:cout只能传引用,ostream的拷贝构造被禁用了。也不要加const
 void operator<< (ostream& out) 
 {
     out << _year << '-' << _month << '-' << _day;
 }

转到main函数测试

int main()
{
    Date d(2025, 2, 28);
    cout << d; 
    return 0;
}

运行一下,呕吼,编译报错。
为什么呢?
如果显示调用的话,应该这样写:d.operator<<(cout); 由于运算符重载中,参数顺序和操作数顺序是一致的,而我把顺序搞反了。正确写法如下:

int main()
{
    Date d(2025, 2, 28);
    d << cout; // d.operator<< (cout);
    return 0;
}

改完后成功运行,但是不太符合习惯。如何让ostream做第一个参数,Date做第二个参数呢?我们要在类外面写运算符重载

// 保持参数类型的顺序为ostream,Date
ostream& operator<< (ostream& out, Date& d)
{
    out << d._year << '-' << d._month << '-' << d._day;
    return out;
}
// 细心的同志可能发现我把返回值改为了ostream&,这是为了支持连续输出,类似上篇内容的赋值重载

但是又存在一个问题,Date类里的成员变量访问权限都是private,类外面根本就无法访问!如何解决?

  1. 像Java那样提供getset函数
  2. 友元,在Date类里面加入下面代码(放在public或者private区域都行)
friend ostream& operator<< (ostream& out, Date& d);

friend修饰的函数可以访问类里面private的成员变量成员函数。(friend修饰的函数放在类里面不存在隐含的this

同理也可以写出流提取重载:

类外面:
istream& operator>> (istream& in, Date& d)
{
    in >> d._year >> d._month >> d._day;
    return in;
}

注意还要在类里面加上声明:
friend istream& operator>> (istream& in, Date& d);

const成员

先看下面的代码:

class Date
{
public:
// ... 省略别的代码 
    void Print()
    {
        printf("%d-%02d-%02d\n", _year, _month, _day);
    }
// ...省略成员变量
}
int main()
{
    const Date d(2025, 2, 28); //对象被const修饰
    d.Print();
    return 0;
}

会编译报错,原因是权限放大
this指针的类型是Date* const,而d调用Print()时会把d的地址传给this,即Date* const this = &d,然而&d的类型是const Date*
补充const修饰指针的一些知识:

  • const*之前,表示不可修改指向的内容,但可以修改指向
  • const*之后,表示不可修改指向,但可以修改指向的内容

这时候我们其实要修改this的类型。C++规定了如下写法:

class Date
{
public:
// ... 省略别的代码 

// 在函数后面加上const
// 这样this的类型就由原来的Date* const 变成了 const Date* const,就不涉及权限放大了
    void Print() const 
    {
        printf("%d-%02d-%02d\n", _year, _month, _day);
    }
// ...省略成员变量
}
int main()
{
    const Date d(2025, 2, 28);
    d.Print();	//const成员可以调用

    Date d1(2027, 9, 12);
    d1.Print();	//非const成员也可以调用。权限可以缩小
    return 0;
}

至此,Date类大致搞定了,但是还存在一些潜在的bug
例如本篇开头的构造函数:

 Date(int y = 2025, int m = 9, int d = 11) 
 {
     _year = y, _month = m, _day = d; 
 }

万一有人写了这样的代码:

Date d(2000, 999, 999);

程序就会出一系列的bug一行代码写业务,百行代码防刁民。 为此我们添加额外判断:

Date(int y = 2025, int m = 9, int d = 11) 
{
    if(y <= 0 || m <= 0 || m > 12 || d <= 0 || d > get_day_of_month(y, m))
    {
        puts("日期非法");
        exit(-1);
    }
    _year = y, _month = m, _day = d; 
}

与此相关的还有operator+= / -=

Date& Date::operator+= (int n)
{
    while(n--)
        (*this) ++;
    return *this;
}
Date& Date::operator-= (int n)
{
    while(n--)
        (*this) --;
    return *this;
}

如果有人写成如下代码,就会死循环

Date d;
d -= -1000;

改完后的代码

Date& Date::operator+= (int n)
{
    if(n >= 0)
    {
        while(n--)
            (*this) ++;
    }
    else 
        (*this) -= -n;
    return *this;
}
Date& Date::operator-= (int n)
{
    if(n >= 0)
    {
        while(n--)
            (*this) --;
    }
    else 
        (*this) += -n;
    return *this;
}

文章最后贴上完整日期类代码以及相关练习题
日期类代码
日期类练习题

再谈构造函数

先看段代码:

#include<iostream>
using namespace std;

// A类无法修改内部成员
class A
{
public:
    A(int x)
    {
        _a = x;
    }
private:
    int _a;
};

class B
{
public:

private:
    A _aa; 
    int _b;
};

int main()
{
    B b;
    return 0;
}

代码编译错误。编译器会生成B的构造函数,会调用A的默认构造初始化_aa,但A不提供默认构造,这就很难受。想着给B写一个构造:

class B
{
public:
    B()
    {
        // _aa.A(1); // 不可显式调用构造,但是不写这一行也会编译报错
        _b = 0;
    }
private:
    A _aa; 
    int _b;
};

为了解决上面那种情况,就要用初始化列表了

初始化列表

用法如下:

class A
{
public:
    A(int x)
    {
        _a = x;
    }
private:
    int _a;
};

class B
{
public:
    B()
        :_aa(1) // 调用A(int x)初始化_aa
        , _b(5) // 用5初始化_b
    {}
private:
    A _aa; 
    int _b;
};

初始化列表在函数声明跟函数体之间,以冒号开始,用逗号分割,每个成员变量用其后面括号内的值初始化

一些相关性质:
① 初始化列表本质上可以理解为每个对象中的成员定义的地方
② 必须在初始化列表进行初始化的类型:1.const类型,2.引用类型,3.没有默认构造的自定义类型

class T
{
    int _t;
public:
    T(int x)
    {
        _t = x;
    }
};

class A
{
    const int _a; 
    int& _ref;
    T _tt;
public:
    A(int& rr)
        :_a(1)		//这三者必须在初始化列表初始化
        ,_ref(rr)
        ,_tt(2)
    {
        _ref = rr; // 函数体内并非初始化,而是赋值
    }
};

③ 初始化列表不管你写不写,每个成员变量都会按在类里面声明的顺序依次走一遍。 自定义类型调用其默认构造;内置类型不做处理(如果给了缺省值,初始化列表就会用缺省值初始化。缺省值其实是给初始化列表用的)

小练习:

class A
{
    int _a2;
    int _a1;
public:
    A(int a)
        :_a1(a)
        ,_a2(_a1)
        {}
    void Print()
    {
        cout << _a1 << ' ' << _a2 << endl;
    }
};
int main()
{
    int n;
    A a(1);
    a.Print();
    return 0;
}
上述的代码会输出 1 1 吗?

虽然初始化列表的顺序是_a1, _a2,但成员变量在类里面声明的顺序为_a2, _a1。代码会按照声明的顺序先初始化_a2,再初始化_a1。所以输出结果是:1 随机值

隐式类型转换

内置类型的隐式类型转换

int main()
{
    int a = 3.3; // a是int,3.3是double。编译器会先把3.3转换成int放在临时变量中,然后再拷贝给a
    /*代码等价于:
    const int tmp = 3.3;
    int a = tmp;
    */

    // int& a1 = 3.3; // 临时变量具有常性,会权限放大
    const int& a1 = 3.3;
    return 0;
}

单参数隐式类型转换

#include<iostream>
using namespace std;

class A
{
    int _a;
public:
    A(int x)
        :_a(x)
    {
        cout << "A(int x)" << endl;
    }

    A (const A& a)
        :_a(a._a)
    {
        cout << "A (const A& a)" << endl;
    }
};

int main()
{
    A a1(1);    // 构造
    A a2 = a1;  // 拷贝构造

    A a3 = 1;   // 这里是隐式类型转换。内置类型转自定义类型
    //原理:编译器会调用A的单参数构造,用1构造出临时对象,然后在拷贝给a3。等价于下面的代码
    /*
    const A tmp(1);
    A a3 = tmp;
    */
    return 0;
}

Dev-C++运行结果如下:
在这里插入图片描述
但是你们的编译器可能只会输出A(int x),原因是编译器进行了拷贝省略优化(连续的构造+拷贝构造 -> 优化为直接构造)。
这里建议用老版编译器观察,并加入命令-fno-elide-constructors以关闭优化。
Dev-C++中,工具 -> 编译选项
在这里插入图片描述

了解了底层原理之后,再看一段代码

class A
{
    int _a;
public:
    A(int x)
        :_a(x)
    {
        cout << "A(int x)" << endl;
    }

    A (const A& a)
        :_a(a._a)
    {
        cout << "A (const A& a)" << endl;
    }
};

int main()
{
    // int& x = 3.3; 	// 编译错误
    const int& x = 3.3;	// 3.3转换成int会产生临时变量,临时变量具有常性

	////与上面类似:
    // A& a = 1;	// 编译错误
    const A& a = 1;	// 1转换成对象会产生临时对象,临时对象具有常性
    return 0;
}

多参数隐式类型转换

class A
{
    int _a;
    int _a1;
    int _a2;
public:
    A(int x)
        :_a(x)
    {
        cout << "A(int x)" << endl;
    }

    A(int a1, int a2)
        :_a(0)
        ,_a1(a1)
        ,_a2(a2)
    {
        cout << "A(int a1, int a2)" << endl;
    }

    A (const A& a)
        :_a(a._a)
    {
        cout << "A (const A& a)" << endl;
    }
};

int main()
{
    A a(1); 	// 调用单参数构造
    A a1 = 1; 	// 隐式类型转换
    
    A aa(1, 2);		// 调用多参数构造
    A aa1 = {1, 2}; // 隐式类型转换,这个C++11以后支持
    // A a = (1, 2); 后面的会识别成逗号表达式,等价于 A a = 2;
    return 0;
}

explicit关键字

若想关闭隐式类型转换,就在函数前面加上explicit

class A
{
    int _a;
    int _a1;
    int _a2;
public:
// 禁止隐式类型转换
    explicit A(int x)
        :_a(x)
    {
        cout << "A(int x)" << endl;
    }
    
// 禁止隐式类型转换
    explicit A(int a1, int a2)
        :_a(0)
        ,_a1(a1)
        ,_a2(a2)
    {
        cout << "A(int a1, int a2)" << endl;
    }
};

int main()
{
    A a1 = 1; // err
    A aa1 = {1, 2}; // err
    return 0;
}

static 成员

静态成员为所有类、类的对象所共享,不属于某个具体的对象,存放在静态区

静态成员变量

class A
{
    int _a1;
    int _a2;
    static int _s; // 被static修饰的成员变量就是静态成员变量
};

int main()
{
    cout << sizeof(A) << endl; // 8
    A a1;
    cout << sizeof(a1) << endl; // 8
    // 静态成员变量不在对象中,在静态区
    return 0;
}

静态成员变量需要在类外初始化

class A
{
    int _a1 = 1;        
    int _a2 = 2; 
public:       
    static int _s;      // 静态成员变量声明
};

// 静态成员的定义
int A::_s = 1;

int main()
{
    A a;
    cout << a._s << endl;   // 用类的对象访问
    cout << A::_s << endl;  // 用类访问
    // 静态成员变量一般设为公有
    return 0;
}

但如果将A类的_s访问权限改为私有:

class A
{
    int _a1 = 1;        
    int _a2 = 2; 
    static int _s;      // 静态成员变量声明
};

// 静态成员的定义
int A::_s = 1; // 这里仍然可以定义

int main()
{
    A a;
    // cout << a._s << endl;   // err
    // cout << A::_s << endl;  // err
    return 0;
}

虽然_s是私有,但静态成员的定义不受访问控制限制(访问静态成员还是受限制的)

注意:静态成员变量不可用缺省值初始化

class A
{
    int _a1 = 1;
    int _a2 = 2;
    static int _s = 3; // err 
    // 缺省值本质是给初始化列表使用的,_s不会走初始化列表
};

静态成员函数

访问经验成员变量还可以提供静态成员函数

class A
{
    int _a1 = 1;        
    int _a2 = 2;        
    static int _s;      // 静态成员变量声明
public:
    static int Get_s()
    {
        return _s;
    }
    // 不过感觉有点脱裤子放屁——多此一举。还不如直接把_s设为public呢
};

// 静态成员变量的定义
int A::_s = 0;

int main()
{
    A a;
    cout << A::Get_s() << endl;  
    cout << a.Get_s() << endl;
    return 0;
}

注意静态成员函数没有隐含的this,无法访问非静态成员变量。

class A
{
    int _a1 = 1;        
    int _a2 = 2;        
    static int _s;      // 静态成员变量声明
public:
    static int Get_s()
    {
        cout << _a1 << ' ' << _a2 << endl; // err 编译报错
        return _s;
    }
};

// 静态成员变量的定义
int A::_s = 0;

int main()
{
    return 0;
}

友元

友元函数

上文运算符重载<< / >>已经略微介绍了,再回顾一下

class Date
{
public:
    Date(int y = 2025, int m = 9, int d = 11) 
    { _year = y, _month = m, _day = d; }
    
    void Print() const
    { printf("%d-%02d-%02d\n", _year, _month, _day); }

private:
// Date类把此函数当作朋友。也可以说此函数是Date类的朋友
    friend ostream& operator<< (ostream& out, Date& d);
    
    int _year;
    int _month;
    int _day;
};

// Date类把此函数当作朋友,此函数就可以访问Date类型的对象的私有成员
ostream& operator<< (ostream& out, Date& d)
{
// _year, _month, _day都是私有,但此函数是Date类的friend,可以访问Date的私有成员
    out << d._year << '-' << d._month << '-' << d._day;
    return out;
}

友元函数是定义在类外面普通函数,在类内部加friend声明

友元类

class A
{
    int _a = 1000;
public:
    void Print() { cout << _a << endl; }
    friend class B; // A 把 B 当朋友
};

class B
{
    int _b;
public:
    void func(A _A)
    {
        cout << _A._a << endl; // A类里的_a是私有
        // A 把 B 当朋友,B里面的成员函数都可以是A类的友元函数,都可以访问A类型对象的私有成员
    }
};

int main()
{
    A a;
    B b;
    b.func(a);
    return 0;
}

注意:

① 友元关系是单向的

class A
{
    int _a;
public:
    void Print() { cout << _a << endl; }
    void funcA(B b)
    {
    	// B类 没有说明与 A类的关系,换句话说 B没把A当朋友
        cout << b._b << endl; // 所以A类中无法访问B类对象的私有成员,编译报错
    }
    friend class B; // A把 B当朋友
};

class B
{
    int _b;
    A _A;
public:
    void func()
    {
        _A._a = 1; // B 是 A 的朋友,B里面的成员函数都可以是A类的友元函数,都可以访问A类的私有成员
    }
};

int main()
{
    return 0;
}

② 友元关系不可传递

A把B当朋友,B把C当朋友,不能推出A把C当朋友

class A
{
    int _a = 10;
    friend class B; // A把 B当朋友
};

class B
{
    int _b = 11;
    friend class C; // B把 C当朋友
};

class C
{
    int _c = 12;
public:
    void func(B b, A a)
    {
        cout << b._b << endl;
        // cout << a._a << endl; // err 无法访问
    }
};

int main()
{
    A a;
    B b;

    C c;
    c.func(b, a);
    return 0;
}

③友元这种关系尽量少用,一定程度上破坏了封装

内部类

如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。内部类是一个独立的类,仅受外部类的类域限制。内部类天生是外部类的友元。

class A
{
    int _a;
public:
    class B // B天生就是A的友元。相当于在A里面加上friend class B
    {
        int _b;
    public:
        B(int x = 1)
            :_b(x)
        {}
    };
};

int main()
{
    cout << sizeof(A) << endl;
    A a;
    A::B b; // 用B类创建对象
    return 0;
}

匿名对象

class A
{
    int _a;
public:
    A(int x = 1)
        :_a(x)
    { cout << "A()" << endl; }
    
    ~A() { cout << "~A()" << endl; }
};

int main()
{
    A a1(1);    // 对象名字是a1
    A a2(2);    // 对象名字是a2

    A(3);   // 没有名字,称为匿名对象。其生命周期只在当前这一行

    A a3;
    return 0;
}

运行结果
在这里插入图片描述

class A
{
    int _a;
public:
    A(int x = 1)
        :_a(x)
    { cout << "A()" << endl; }
    
    ~A() { cout << "~A()" << endl; }
    
    void Print() { cout << _a << endl; }
};

// 匿名对象的特殊之处
int main()
{
    // A& a = A(3);		// 引用匿名对象需要加const,具体原理在C++11的右值中讲
    const A& a = A(3); // 会把匿名对象的生命周期变成a的生命周期

    A(3).Print();   // 虽然引用需要加const,但匿名对象可以调用非const成员函数
    return 0;
}

运行结果
在这里插入图片描述

对比不加引用的结果

// ... 其他代码同上
int main()
{
    A(3);	// 不加引用,声明周期只在当前这一行
    A(3).Print();
    return 0;
}

在这里插入图片描述
再看个代码回顾一下之前的内容

class A
{
    int _a;
public:
    A(int x = 1)
        :_a(x)
    {
        cout << "A()" << endl;
    }
    A(const A& a)
    {
        cout << "A(const A& a)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void Print()
    {
        cout << _a << endl;
    }
};

void func(const A& a)
{
    ;
}

int main()
{
    A a;
    func(a);    // 普通对象传参
    func(A(2)); // 匿名对象传参
    func(2);    // 隐式类型转换
    return 0;
}

至此,类和对象已经入门

Date类完整代码

Date.h:

#pragma once
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int y = 2025, int m = 9, int d = 11) 
    {
        if(y <= 0 || m <= 0 || m > 12 || d <= 0 || d > get_day_of_month(y, m))
        {
            puts("日期非法");
            exit(-1);
        }
        _year = y, _month = m, _day = d; 
    }
    void Print() const
    { printf("%d-%02d-%02d\n", _year, _month, _day); }
    
    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;

    Date& operator++ ();
    Date& operator-- ();
    Date operator++ (int);
    Date operator-- (int);

    Date& operator+= (int n);
    Date& operator-= (int n);

    Date operator+(int n) const;
    Date operator-(int n) const;
    int operator- (const Date& d) const;

private:

//friend 修饰的函数没有隐含的this
    friend ostream& operator<< (ostream& out, Date& d);
    friend istream& operator>> (istream& in, Date& d);

    int get_day_of_month(int year, int m)
    {
        static int d[13] = {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if(m != 2) return d[m];
        // 特判闰年
        if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
            return d[2] + 1;
        else return d[2];
    }
    int _year;
    int _month;
    int _day;
};

Date.cpp

#include"Date.h"

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

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

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

Date& Date::operator++ ()
{
    _day++;
    if(_day > get_day_of_month(_year, _month))
    {
        _day = 1;
        _month++;
        if(_month == 13)
        {
            _month = 1;
            _year++;
        }
    }
    return *this;
}

Date& Date::operator-- ()
{
    _day--;
    if(_day == 0)
    {
        _month--;
        if(_month == 0)
        {
            _month = 12;
            _year--;
        }
        _day = get_day_of_month(_year, _month);
    }
    return *this;
}

Date Date::operator++ (int)
{
    Date t = *this;
    _day++;
    if(_day > get_day_of_month(_year, _month))
    {
        _day = 1;
        _month++;
        if(_month == 13)
        {
            _month = 1;
            _year++;
        }
    }
    return t;
}

Date Date::operator-- (int)
{
    Date t = *this;
    _day--;
    if(_day == 0)
    {
        _month--;
        if(_month == 0)
        {
            _month = 12;
            _year--;
        }
        _day = get_day_of_month(_year, _month);
    }
    return t;
}


Date& Date::operator+= (int n)
{
    if(n >= 0)
    {
        while(n--)
            (*this) ++;
    }
    else 
        (*this) -= -n;
    return *this;
}
Date& Date::operator-= (int n)
{
    if(n >= 0)
    {
        while(n--)
            (*this) --;
    }
    else 
        (*this) += -n;
    return *this;
}

Date Date::operator+ (int n) const
{
    Date t = *this;
    t += n;
    return t;
}

Date Date::operator-(int n) const
{
    Date t = *this;
    t -= n;
    return t;
}

int Date::operator- (const Date& d) const
{
    // 大 - 小:天数为正;反之为负
    int flag = 1;
    Date begin = d;
    Date end = *this;
    if(*this < d) 
    {
        flag = -1;
        begin = *this, end = d;
    }
    int cnt = 0;
    while(begin != end)
    {
        cnt++;
        begin++;
    }
    return cnt * flag;
}

ostream& operator<< (ostream& out, Date& d)
{
    out << d._year << '-' << d._month << '-' << d._day;
    return out;
}

istream& operator>> (istream& in, Date& d)
{
    in >> d._year >> d._month >> d._day;

    // 额外检查日期是否合法
    if (d._year <= 0 || d._month <= 0 || d._month > 12 || 
    	d._day <= 0 || d._day > d.get_day_of_month(d._year, d._month))
    {
        puts("输入日期非法");
        exit(-1);
    }
    return in;
}

Date类练习题

更多推荐