C++类和对象(二)

简介: 这节才是类和对象的精华,其中有非常多的细节要去进行处理,虽然这块骨头比较难啃,但是还是要硬着头皮去搞懂,迈过类和对象这一关,就为后面的C++学习打下了非常坚实的基础。迈不过,可以说后面的C++基本没得玩了。

@TOC

C++类和对象(二)

这节才是类和对象的精华,其中有非常多的细节要去进行处理,虽然这块骨头比较难啃,但是还是要硬着头皮去搞懂,迈过类和对象这一关,就为后面的C++学习打下了非常坚实的基础。迈不过,可以说后面的C++基本没得玩了。

类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类

class Date {};

但是空类真的是什么都没有吗?答案其实是否定的,一个类在什么都不写时,默认会生成6个默认成员函数。

image-20230427165645566

构造函数:

概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

那么为什么要有默认成员函数呢?

假设有一个日期类,我们创建一个日期类的对象,第一步必须是进行初始化,后面才可以使用,否则在很多情况下会出错。但是初始化函数每次都需要写,而且每次创建一个对象想要去使用第一步都得初始化,第一是比较麻烦,第二很容易忘记,所以为了解决这样的难题,类中引入了构造函数的概念。

class Date
{
public:
    void Init(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    d1.Init(2023, 4, 27);
    //...使用
    Date d2;
    d2.Init(2022, 2, 22);
    //...
    return 0;
}

C语言中这种方式使用起来既麻烦又容易忘记。下面看构造函数怎么使用。

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:

  1. 函数名和类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  6. 编译器默认生成的成员函数只对自定义类型初始化,内置类型成员不作处理,C++11中规定,内置类型成员变量在声明时可以给缺省值。
  7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数

这几个特征必须要理解透彻,一个都不能少,才可以说是理解了构造函数。

首先函数名和类名相同,无返回值,编译器自动调用这些都是规定,主要是将下面几个理解并加以验证。

实践验证

  • 无参构造函数

我们先写一个无参构造函数看一下是否会自动调用:

class Date
{
public:
    Date()
    {
        cout << "hello structure funtion!!!" << endl;
    }
private:
    int _year;
    int _month;
    int day;
};
int main()
{
    Date d1;
    return 0;
}

image-20230427173406330

可以看到,答案是肯定的,确实是自动调用。所以我们就可以在这个函数里面进行初始化的操作。

  • 编译器自动生成的无参默认构造函数

如果类中我们没有定义显式构造函数,那么编译器会自动生成一个无参的默认构造函数。那么该函数又有什么不同呢?我们可以在一个类中同时定义内置类型成员和自定义类型成员,看一下此函数的作用。

struct Stack
{
    int* _a;
    int _top;
    int _capacity;
};
class Date
{
public:
    void Print()
    {
        cout << _year << ' ' << _month << ' ' << _day << endl;
        cout << _s1._a << ' ' << _s1._capacity << ' ' << _s1._top << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    Stack _s1;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}

image-20230427180130647

看到这个结果我相信你可能很迷惑,这个编译器默认生成的函数似乎没有什么作用,它也没有完成初始化的工作嘛。

但是事实真的是这样吗?当然不是,否则还生成这个函数做什么。

C++规定:对于内置类型成员变量不做初始化,对于自定义类型成员一定要做初始化。

为什么我们看到的是这个现象呢?答案当然是编译器的问题,不同的编译器,不同的版本实验起来现象可能不同,下面是我的编译器当前版本:

image-20230427180644222

我们编译器实验起来是这样的情况,当然大家也可以动手去尝试一下。

  • C++补丁的作用

正是因为默认成员函数不做初始化这一条规定,埋下了很多的坑,所以后来C++11中又打了补丁来解决这一问题。

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给缺省值。

所以在仅有内置类型成员的情况下,自己实现一个无参构造函数和直接给缺省值效果是一样的。

第一种情况:

class Stack
{
    int* _a = nullptr;
    int _top = 0;
    int _capacity = 4;
};
//等价于
class Stack
{
public:
    Stack()
    {
        _a = nullptr;
        _top = 0;
        _capacity = 4;
    }
private:
    int* _a;
    int _top;
    int _capacity;
};

第二种情况:

在全部都是自定义类型成员的情况下,也不需要我们自己写构造函数。

  • 带参的构造函数

除了上述两种特殊情况,在绝大多数情况下,都是需要我们自己写的,我们通常要写的是带参的构造函数,而且最好给缺省值

class TreeNode
{
public:
    TreeNode(int val = 0)
    {
        _left = nullptr;
        _right = nullptr;
        _val = val;
    }
private:
    TreeNode* _left;
    TreeNode* _right;
    int _val;
};
int main()
{
    //创建一个树节点,很灵活的创建
    TreeNode tr(5);
    TreeNode tr(6);
    TreeNode tr(7);
    TreeNode tr(8);
    return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

析构函数

概念

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

特性

析构函数也是特殊的成员函数,特征:

  1. 析构函数是类名前加字符 ~
  2. 无参数,无返回值类型
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,编译器自动调用析构函数
typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 3)
    {
        _a = (DataType*)malloc(sizeof(DataType) * capacity);
        if (NULL == _a)
        {
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = capacity;
        _top = 0;
    }
    void Push(DataType data)
    {
        //CheckCapacity()
        _a[_top++] = data;
    }
    ~Stack()
    {
        cout << "you can see me!" << endl;
        if (_a)
        {
            free(_a);
            _a = NULL;
            _capacity = 0;
            _top = 0;
        }
    }
private:
    int* _a;
    int _top;
    int _capacity;
};
int main()
{
    Stack s1;
    return 0;
}

析构函数比起构造就简单的多了,大体上可以分为三种情况:

  1. 有自定义类型成员,进行了动态申请资源,需要析构函数来进行资源的清理
  2. 全是内置类型成员,没有动态申请的资源,不需要析构函数
  3. 需要释放资源的成员都是自定义类型成员,本身就能够释放资源,不需要析构函数

例如以下三种情况:

//有自定义类型成员,进行了动态申请资源,需要析构函数来进行资源的清理
class Stack
{
private:
    int* _a;
    int _top;
    int _capacity;
};
//全是内置类型成员,没有动态申请的资源,不需要析构函数
class Date
{
    int _year;
    int _month;
    int _day;
};
//需要释放资源的成员都是自定义类型成员,本身就能够释放资源,不需要析构函数
class Queue
{
private:
    Stack pushst;
    Stack popst;
};

当然了,不是只有这三种情况,只有理解了析构函数的使用场景,灵活使用不是难事。

拷贝构造函数

概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。注意参数要用const修饰,否则会引发一些不必要的麻烦

设计拷贝构造函数的原因就是我们有时会有场景,需要创建一个和已存在对象一模一样的对象。但是对象通常内部数据比较复杂,所以有些细节需要特别注意,所以不能像内置类型那样简单的浅拷贝。

特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。ps:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
  4. 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

image-20230503110900886

  • 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的
class Stack
{
private:
    int* _a;
    int _top;
    int _capacity;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    Stack st1;
};
int main()
{
    Date d1;
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
    Date d2(d1);
    return 0;
}
  • 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝

如果是使用默认生成的拷贝构造函数则仅仅是浅拷贝,此时如果类中申请了资源,并且将指针或者引用直接浅拷贝过去,在最终析构函数时,就会将申请资源的空间释放两次,此时就会出问题。

typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 10)
    {
        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array)
        {
            perror("malloc申请空间失败");
            return;
        }
        //注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;
        //一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
        _size = 0;
        _capacity = capacity;
    }
    void Push(const DataType& data)
    {
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }
    ~Stack()
    {
        if (_array)
        {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }
private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};
int main()
{
    Stack s1;
    s1.Push(1);
    s1.Push(2);
    s1.Push(3);
    s1.Push(4);
    Stack s2(s1);
    return 0;
}

运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@

  2. 重载操作符必须有一个类类型参数,用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

  3. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

  4. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

先来简单看一下什么是运算符重载,举个简单的例子:

// 全局的operator
// 比较日期大小
class Date
{
public:
    Date(int year = 0,int month=0,int day=0)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    int _year;
    int _month;
    int _day;
};
bool operator < (const Date& d1,const Date& d2)
{
    if (d1._year < d2._year)
        return true;
    else if (d1._year == d2._year && d1._month < d2._month)
        return true;
    else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day)
        return true;
    else
        return false;
}
int main()
{
    Date d1(2023, 12, 10);
    Date d2(2022, 12, 10);
    cout << (d1 < d2) << endl;
    cout << (d2 < d1) << endl;
    return 0;
}

但是这样写出的代码无疑看起来怪怪的,在用opeartor运算符重载的时候,访问了类里面的成员,实际上我们的成员变量本应该是私有的,这里为了演示才用了公有,所以这样我们当然不会这样写,那么怎么解决呢?自然而然地就可以想到将重载函数写到类里面。但是要写到类里面又有一些其他的细节,我们看下面:

直接复制粘贴到类里面就会发现编译器报错了,

image-20230503153903265

编译器告诉我们参数太多,是否有点莫名其妙,但是仔细想想,参数有几个呢?真的是两个吗?类的内部成员函数的this指针可不要忘了,没错,此时参数确实多了,那么怎么改呢?

class Date
{
public:
    Date(int year = 0, int month = 0, int day = 0)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    bool 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;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2021, 12, 10);
    Date d2(2022, 12, 10);
    cout << (d1 < d2) << endl;
    cout << d1.operator<(d2);
    return 0;
}

赋值运算符重载

  • 赋值运算符重载格式

    1. 参数类型:const T&,传递引用可以提高传参效率

    2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

    3. 检测是否自己给自己赋值

    4. 返回*this :要复合连续赋值的含义

    5. 赋值运算符只能重载成类的成员函数不能重载成全局函数,原因:用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

      ps:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
      重载完成赋值

    6. 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现

  • 有返回值是为了支持连续赋值
class Date
{
public:
    Date(int year = 0, int month=0, int day=0)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    /*注意这段代码中的细节*/
    Date& operator=(const Date& d)
    {
        if (this != &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2023, 5, 3);
    Date d2(2020, 3, 3);
    Date d3(2022, 6, 6);
    d1 = d2 = d3;
    return 0;
}
  • 默认赋值运算符重载

内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值

class Time
{
public:
    Time()
    {
        _hour = 10;
        _minute = 10;
        _second = 10;
    }
    Time& operator=(const Time& t)
    {
        if (this != &t)
        {
            _hour = t._hour;
            _minute = t._minute;
            _second = t._second;
        }
        return *this;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
public:
    Date(int year = 0, int month=0, int day=0)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};
int main()
{
    Date d1(2020, 2, 2);
    Date d2(2021, 3, 3);
    d1 = d2;
    return 0;
}
  • 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现

例如日期这样的只需要浅拷贝就可以解决的问题,使用默认赋值运算符重载是没有任何问题的,但是一旦涉及到资源的申请,例如栈这种类,就必须自己动手写,通过深拷贝来解决问题。

前置++和后置++重载

前置++和后置++怎么重载呢,这两个其实都是比较常用的,而且用它的特性的地方还是非常多的。直接写肯定是不行,都是operator++怎么区分呢,最后没有办法只能做一点操作用来区分,前置++不变,后置++要都写个参数类型int,来区分两者。

C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //前置++
    Date& operator++()
    {
        _day += 1;
        return *this;
    }
    //后置++
    Date operator++(int)
    {
        Date temp(*this);
        _day += 1;
        return temp;
    }
private:
    int _year ;
    int _month ;
    int _day ;
};

其实从效率也可以看出来,后置++需要额外创建变量,效率显然是不及前置++的,所以选择了让后置++做出一点牺牲。

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

image-20230507102739133

可以用下面代码来验证一下一些const和非const修饰的函数的问题,其实无非就是一个权限的问题,权限只能缩小或者平移,但是绝对不能被放大。

  1. const对象可以调用非const成员函数吗?

    用const修饰的对象是不可被修改的,所以不能调用非const修饰的函数。

    1. 非const对象可以调用const成员函数吗?

    可以。

  2. const成员函数内可以调用其它的非const成员函数吗?

    不可以。

  3. 非const成员函数内可以调用其它的const成员函数吗?

    可以。

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << "Print()" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
    void Print() const
    {
        cout << "Print()const" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
private:
    int _year; // 年
    int _month; // 月
    int _day; // 日
};
void Test()
{
    Date d1(2022, 1, 13);
    d1.Print();
    const Date d2(2022, 1, 13);
    d2.Print();
}
int main()
{
    Test();
    return 0;
}

可以用一段代码来验证一下。

当然,其实用const的好处并不在于这些,否则我们直接全部都不加const不就好了,直接给最大权限,能读能写。其实有时候const还是非常必要的,权限给的大了,有时候未必是好事,例如下面情况:

image-20230507105943228

所以那些不需要改变变量的函数,const能加就尽量加上比较好,const修饰和非const修饰就都可以调用。

取地址操作符重载

通常分两种对对象的取地址,const对象的取地址,非const对象的取地址

class Date
{
public:
    Date& operator& ()
    {
        return *this;
    }
    const Date& operator&() const 
    {
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};

这两个一般不需要自己写,编译器会自动生成,除非某种非常特殊的情况,例如想让别获得指定内容。

日期类的实现

到这里,类和对象二就算暂告一段落了,当然其实还有类和对象三,(狗头),到这里已经差不多85%的内容了吧,类和对象这块确实是个大杂烩,很多很杂,但是走过一段基础打好,后面的学习就会轻松不少,最后就将日期类的完整代码放在这里,有需要的可以自行复制。

Date.cpp

#include "Date.h"

Date::Date(int year = 0, int month = 0, int day = 0)
{
    _year = year;
    _month = month;
    _day = day;
}

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

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

bool Date::operator<(const Date& d) const
{
    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) const
{
    if (_year == d._year && _month == d._month && _day == d._day)
        return true;
    else
        return false;
}

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::GetMonthDay(const int year, const int month)
{
    static int date[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)))
        return 29;
    else
        return date[month];
}

Date& Date::operator+=(const int day) 
{
    if (day < 0)
    {
        return *this -= day;
    }

    _day += day;
    while (_day > GetMonthDay(_year, _month))
    {
        _day -= GetMonthDay(_year, _month);
        ++_month;
        if (_month == 13)
        {
            ++_year;
            _month = 1;
        }
    }
    return *this;
}

Date Date::operator+(const int day) const
{
    Date temp(*this);
    temp += day;
    return temp;
}

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

Date Date::operator++(int)
{
    Date temp(*this);
    *this += 1;
    return temp;
}

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

Date Date::operator--(int)
{
    Date temp(*this);
    *this -= 1;
    return temp;
}

//Date& Date::operator--()
//{
//    _day -= 1;
//    if (_day < 1)
//    {
//        _month -= 1;
//        _day += GetMonthDay(_year, _month);
//        if (_month < 1)
//        {
//            _year -= 1;
//            _month = 12;
//        }
//    }
//    return *this;
//}

Date& Date::operator-=(const int day)
{
    if (day > 0)
    {
        return *this += day;
    }

    _day -= day;
    while (_day < 1)
    {
        --_month;
        if (_month < 1)
        {
            _month = 12;
            --_year;
        }
        _day += GetMonthDay(_year, _month);
    }
    return *this;
}

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

int Date::operator-(const Date& d) const
{
    Date max = *this;
    Date min = d;
    int flag = 1;
    if (max < min)
    {
        max = d;
        min = *this;
        flag = -1;
    }
    int count = 0;
    while (min != max)
    {
        ++min;
        ++count;
    }
    return count * flag;
}

ostream& operator<<(ostream& out, Date& d) 
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

istream& operator>>(istream& in, Date& d) 
{
    cout << "Please Enter date:>" << endl;
    int year, month, day;
    in >> year >> month >> day;
    if (month > 0 && month < 13
        && day > 0 && day <= d.GetMonthDay(year, month))
    {
        d._year = year;
        d._month = month;
        d._day = day;
    }
    else
    {
        cout << "非法日期" << endl;
        assert(false);
    }
    return in;

    /*in >> d._year >> d._month >> d._day;
    return in;*/
}

Date.h

#pragma once
#include <assert.h>
#include <iostream>
using namespace std;

class Date
{
    friend ostream& operator<<(ostream& out, Date& d);
    friend istream& operator>>(istream& in, Date& d);
public:
    //构造函数
    Date(int year, int month, int day);
    //拷贝构造
    Date(const Date& d);
    //赋值运算符重载
    Date& operator=(const Date& d);
    //析构
    ~Date() {};
    //打印
    void Show()
    {
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }
    int GetMonthDay(const int year, const int month);

    // < 运算符重载
    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+=(const int day);
    //日期+天数
    Date operator+(const int day)const;
    //日期-天数
    Date operator-(const int day)const;
    //日期-=天数
    Date& operator-=(const int day);

    //前置++
    Date& operator++();
    //后置++
    Date operator++(int);
    //前置--
    Date& operator--();
    //后置--
    Date operator--(int);

    //日期-日期,返回天数
    int operator-(const Date& d)const;

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

ostream& operator<<(ostream& out, Date& d);

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