跳转至

C++默认成员函数

约 6657 个字 2780 行代码 3 张图片 预计阅读时间 57 分钟

默认成员函数

默认成员函数:指用户没有显式地定义,编译器会自动生成的成员函数

在C++中,每一个类都会默认生成6个成员函数

  1. 默认构造函数
  2. 默认析构函数
  3. 默认拷贝构造函数
  4. 赋值重载
  5. 普通对象取地址
  6. const对象取地址

构造函数

构造函数介绍

在C语言中,如果要给一个整体初始化时,需要使用一个函数,并且需要自行调用

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
typedef struct Date
{
    int year;
    int month;
    int day;
}Date;

//初始化函数
void InitDate(Date* d)
{
    d->day = 20;
    d->month = 3;
    d->year = 2024;
}

int main()
{
    Date d = {0};
    InitDate(&d);
    printf("%d/%d/%d", d.year, d.month, d.day);
    return 0;
}
输出结果
2024/3/20

在C++中也可以像C语言一样将初始化函数封装到类中

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _year;
    int _month;
public:
    void Init(int year = 2024, int month = 3, int day = 20)
    {
        _day = day;
        _year = year;
        _month = month;
    }

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;
    d.Init();//全缺省不给实参默认为缺省值
    d.print();

    return 0;
}
输出结果
2024-3-20

但是,这种方法每次对成员变量进行初始化都需要调用Init()函数,难写不说还容易忘记,所以在C++中支持在对象实例化的同时调用的一种函数,该函数即为构造函数,此时上面的代码可以改成

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 20)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;//对象实例化时自动调用构造函数,不给任何参数时使用全缺省值
    d.print();

    return 0;
}
输出结果
2024-3-20

构造函数使用

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

C++中的构造函数有下面的特点:

  1. 构造函数与类名相同
  2. 构造函数无返回值(没有也不能写void
  3. 对象实例化时会自动调用对应的默认构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  6. 默认构造函数不会对内置类型做任何改变,但是对于自定义类型来说,会调用自定义类型的构造函数。但是在,C++11 中针对内置类型成员不初始化的缺陷,内置类型成员变量在类中声明时可以给默认值

Note

无参构造函数、全缺省构造函数以及编译器默认生成的构造函数,都可以认为是默认构造函数,并且默认构造函数只能有一个。

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date()
    {
        //无任何参数的也称为默认构造函数
        cout << "无参构造函数调用" << endl;
    }

    //有参但不是缺省参数的构造函数重载
    Date(int year, int day, int month)
    {
        _year = year;
        _month = month;
        _day = day;
        cout << "有参构造函数调用" << endl;
    }

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;//不写参数时调用无参构造,不给参数时不用写括号,否则编译器将无法辨认是函数声明是对象实例化
    Date d1(2024, 3, 20);
    d.print();
    d1.print();
    return 0;
}
输出结果
无参构造函数调用
有参构造函数调用
-858993460--858993460--858993460
2024-3-20

在上面的代码中,默认构造函数并没有对成员变量做出改变,在实例化后,成员变量中的值为随机值,该现象也同样使用编译器默认生成的构造函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    //编译器自动生成构造函数

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;//不写参数时调用编译器自动生成的构造
    d.print();
    return 0;
}
输出结果
-858993460--858993460--858993460

Note

需要注意的是,因为默认构造函数一个类中只能有一个,所以全缺省构造函数和无参构造函数不能同时被运行调用,因为编译器无法区分调用哪一个默认构造函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date()
    {
        //无任何参数的也称为默认构造函数
        cout << "无参构造函数调用" << endl;
    }

    //全缺省默认构造函数
    Date(int year = 2024, int month = 3, int day = 20)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;//不写参数时调用无参构造以及编译器自动生成的构造
    d.print();
    return 0;
}
报错信息
 "Date" 包含多个默认构造函数

构造函数细节知识

  • 对于调用无参或者全缺省的默认构造函数的理解

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <iostream>
    using namespace std;
    
    class Date
    {
    private:
        int _year;
        int _month;
        int _day;
    public:
        Date(int year = 2024, int month = 3, int day = 20)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        void print()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    };
    
    int main()
    {
        Date d;
        //不可以理解为d.Date();
        d.print();
        return 0;
    }
    输出结果
    

    在上面的代码中,因为实例化的对象并没有传递参数,所以使用构造函数的缺省值,但是注意对比下面的print函数,print函数时对象在显式调用,但是不建议理解为构造函数也是对象显式调用,即d.Date();理解是不准确的,因为Date d;本身就是在创建对象,此时对象还没出现,如果直接理解为显式调用,那么和前面显式使用init()初始化对象没有区别

  • 成员变量给初始值的理解

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <iostream>
    using namespace std;
    
    class Date
    {
    private:
        int _year = 0;
        int _month = 0;
        int _day = 0;
    public:
        Date(int year = 2024, int month = 3, int day = 20)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        void print()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    };
    
    int main()
    {
        Date d;
        d.print();
        return 0;
    }
    输出结果
    2024-3-20
    

    在上面的代码中,成员变量都给了初始值0,但是这里并不是初始化,而是类似全缺省函数一样给的缺省值,因为前面的知识已经说明类中的成员变量只有在实例化之后才有实际空间。如果此处理解为初始化,因为类还没有被实例化没有实际的地址空间,这个变量将没有空间存放,而初始化则是将初始化的值存入变量对应的地址空间,则与初始化的含义相违背

构造函数初始化列表

在C++中,可以在构造函数的函数体中为变量进行初始化,但是实际上该过程并不是初始化,可以理解为赋值,因为对象还没有真正创建,并且初始化只能初始化一次,但是赋值可以执行多次,而当对象创建时,所有成员变量就是当成整体创建,那么每一个变量在和何处被初始化变成了一个问题,为了解决这个问题,C++标准中引入构造函数初始化列表,只要是本类的成员变量都是在初始化列表处初始化,具体初始化的内容由程序员自己决定

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 23)
        :_year(year)
        , _month(month)
        , _day(day)
    {}

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

int main()
{
    Date d;
    d.print();
    return 0;
}
输出结果
2024/3/23

构造函数初始化列表的使用

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式,语法如下

C++
1
2
3
4
5
类名(参数列表)
:成员变量()
,成员变量()
,成员变量()
{}

Note

注意,并不是所有成员变量都需要写进初始化列表,没写入初始化列表的成员变量也会像写入初始化列表中的成员变量一样走一遍初始化列表,只是没有显式

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

    1. 引用成员变量

      C++
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      #include <iostream>
      #include <cstdlib>
      #include <cassert>
      using namespace std;
      
      class Array
      {
      private:
          int* _arr;
          int _size;
          int& ref;
      public:
          Array()
              :_arr(nullptr)
              , _size(4)
              , ref(_size)//引用类型必须初始化
          {
              _arr = (int*)malloc(sizeof(int) * _size);
              assert(_arr);
          }
      
          Array(const Array& data)
              :ref(_size)//拷贝构造中,引用类型也必须初始化
          {
              _arr = (int*)malloc(sizeof(int) * data._size);
              assert(_arr);
      
              for (int i = 0; i < data._size; i++)
              {
                  _arr[i] = data[i];
              }
          }
      
          //重载[]
          int& operator[](int i)
          {
              return _arr[i];
          }
      
          //const类型的引用,不可以通过返回的引用改变数组中的值
          const int& operator[](int i) const
          {
              return _arr[i];
          }
      };
      
      int main()
      {
          Array a;
          for (int i = 0; i < 4; i++)
          {
              a[i] = i + 1;
          }
      
          const Array p(a);
          for (int i = 0; i < 4; i++)
          {
              cout << p[i] << " ";
          }
          return 0;
      }
      输出结果
      1 2 3 4
      
    2. const成员变量

    3. 自定义类型成员(且该类没有默认构造函数时)

      C++
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      #include <iostream>
      using namespace std;
      
      class Time
      {
      private:
          int _time;
      public:
          Time(int time)
          {
      
          }
      };
      
      class Date
      {
      private:
          int _year;
          int _month;
          int _day;
          Time _t;
      public:
          Date()
              :_year(2023)
              ,_month(3)
              ,_day(21)
          {}
      };
      
      int main()
      {
          Date d;
      
          return 0;
      }
      报错信息
       "Time" 不存在默认构造函数
      

      在C++11标准规范中,可以在成员变量创建的同时给缺省值,此时如果给了这个缺省值,想使用缺省值时就不需要再将该成员变量写入初始化列表中,如果不想使用缺省值,则将成员变量写入初始化列表中并给定初始值,否则默认初始值为0

      C++
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      #include <iostream>
      using namespace std;
      
      //构造函数初始化列表
      class Date
      {
      private:
          int _year = 2023;
          int _month = 2;
          int _day = 28;
      public:
          Date()
              //_year没有写入初始化列表,使用缺省值
              : _month(3)//写入初始化列表中,给了初始值为3,使用初始值
              , _day()//写入初始化列表中,但是没给初始值,默认初始值为0
          {}
      
          void print()
          {
              cout << _year << "/" << _month << "/" << _day << endl;
          }
      };
      
      int main()
      {
          Date d;
          d.print();
          return 0;
      }
      输出结果
      2023/3/0
      

      在初始化列表中无法处理例如动态申请内存的行为,此时可以在函数体内完成,例如

      C++
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      #include <iostream>
      #include <cstdlib>
      #include <cassert>
      using namespace std;
      
      class Array
      {
      private:
          int* _arr;
          int _size;
      public:
          Array()
              :_arr(nullptr)
              ,_size(4)
          {
              _arr = (int*)malloc(sizeof(int) * _size);//在构造函数体中分配空间
              assert(_arr);
          }
      
          Array(const Array& data)
          {
              _arr = (int*)malloc(sizeof(int) * data._size);
              assert(_arr);
      
              for (int i = 0; i < data._size; i++)
              {
                  _arr[i] = data[i];
              }
          }
      
          //重载[]
          int& operator[](int i)
          {
              return _arr[i];
          }
      
          const int& operator[](int i) const
          {
              return _arr[i];
          }
      };
      
      int main()
      {
          Array a;
          for (int i = 0; i < 4; i++)
          {
              a[i] = i + 1;
          }
      
          const Array& p(a);
          for (int i = 0; i < 4; i++)
          {
              cout << p[i] << " ";
          }
          return 0;
      }
      输出结果
      1 2 3 4
      

      所以,如果不是要使用缺省值,尽量使用初始化列表初始化,因为不管是否使用初始化列表,对于自定义类型的成员变量,一定会先使用初始化列表初始化

      Note

      注意:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

      C++
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      class A
      {
      public:
          A(int a)
              :_a1(a)
              ,_a2(_a1)
          {}
      
          void Print() {
              cout<<_a1<<" "<<_a2<<endl;
          }
      private:
          int _a2;
          int _a1;
      };
      int main() {
          A aa(1);
          aa.Print();
      }
      输出结果
      1 -858993460
      

      在上面的代码中,因为成员变量_a2_a1先声明,所以在初始化时先走_a2(_a1),所以_a2被初始化为随机值,接着再初始化_a1,所以_a1为1

析构函数

在使用C语言实现一些需要动态内存管理开辟的空间时,都需要写一个Destroy函数,再最后程序结束之前调用该函数来释放对应的空间,如果没有释放空间可能会因为程序长时间运行导致出现内存泄漏。但是在一些需要大量调用销毁函数的时候容易忘记调用Destroy函数最后导致内存泄漏。

在C++中,为了解决上面的问题,出现了析构函数,析构函数的作用是销毁对象中资源的清理工作

Note

注意,此处是对象中的资源清理工作,并不是直接销毁对象,因为对象是在当前函数栈帧空间销毁时才销毁

析构函数使用

析构函数:与构造函数功能相反,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

析构函数的特点:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <iostream>
    using namespace std;
    
    //顺序表类
    class SeqList
    {
    private:
        int* data;
        int _size;//有效数据个数
        int _capacity;//顺序表容量大小
    public:
        //构造函数初始化
        SeqList(int capacity = 4)
        {
            int* tmp = (int*)malloc(sizeof(int) * capacity);    
            if (!tmp)
            {
                perror("malloc fail");
            }
            else
            {
                data = tmp;
            }
            _size = 0;
            _capacity = capacity;
        }
    
        //析构函数释放空间
        ~SeqList()
        {
            free(data);
            _size = 0;
            _capacity = 0;
        }
    };
    
    int main()
    {
        SeqList s;//对象实例化时会调用构造函数
    
        //对象准备销毁时会调用析构函数
        return 0;
    }
    
  5. 对于编译器生成的默认析构函数,不处理内置类型,对自定义类型成员调用它的析构函数

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #include <iostream>
    using namespace std;
    
    class Time
    {
    private:
        int _time;
    public:
        Time()
        {
            _time = 0;
        }
        ~Time()
        {
    
        }
    };
    
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
        Time _t;
    public:
        Date(int day = 20, int month = 3, int year = 2024)
        {
            _day = day;
            _month = month;
            _year = year;
        }
    
    };
    
    int main()
    {
        Date d;
    
    //因为d对象实例化时会创建对象_t,所以在对象d销毁之前,需要销毁d对象的资源和_t对象中的资源,而因为d对象没有显式的析构函数,此时编译器会默认生成析构函数,在销毁自定义类型对象_t时d对象的默认析构函数会调用_t对象的析构函数销毁其中的资源
        return 0;
    }
    
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成内存泄漏

拷贝构造函数

拷贝构造函数使用

在对象实例化时,有时可能需要创建出两个一模一样的对象,如果多次实例化,那么每一次传参就会变得很麻烦,为了解决这个问题,C++的默认构造函数中提供一个构造函数为拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

Note

使用const关键字进行修饰可以防止被拷贝对象被修改

拷贝构造函数的特点:

  1. 拷贝构造函数是构造函数的一个重载形式

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #include <iostream>
    using namespace std;
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
    public:
        //初始化构造函数
        Date(int year = 2024, int month = 3, int day = 20)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        //拷贝对象
        Date(const Date& d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    
        void print()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    };
    
    int main()
    {
        //第一个Date对象
        Date d1(2024, 3, 21);
        Date d2(d1);//拷贝d1对象的值
        //也可以写成下面的形式
        Date d3 = d2;
        d1.print();
        d2.print();
        d3.print();
        return 0;
    }
    输出结果
    2024-3-21
    2024-3-21
    2024-3-21
    
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #include <iostream>
    using namespace std;
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
    public:
        //初始化构造函数
        Date(int year = 2024, int month = 3, int day = 20)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        //拷贝对象
        Date(Date d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    
        void print()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    };
    
    int main()
    {
        //第一个Date对象
        Date d1(2024, 3, 21);
        Date d2(d1);//拷贝d1对象的值
        d1.print();
        d2.print();
        return 0;
    }
    报错信息
     "Date" 的复制构造函数不能带有 "Date" 类型的参数
    

    在上面的代码中,拷贝构造函数的形参为Date类型的对象,Date类型的对象需要拷贝时将再次调用Date类型的拷贝构造,此时因为Date类型的拷贝构造形参依旧是Date类型的对象,故继续调用Date类型的拷贝构造,如此往复无穷进行,如下图所示

  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝,而深拷贝是指具体的拷贝方式需要自行实现,例如栈、链表等的拷贝

    Note

    注意,拷贝构造函数中的形参不可以使用指针类型,虽然实现的结果相同,但是此时编译器不认为是拷贝构造函数,而只是一个普通的成员函数

  4. 因为编译器会默认生成拷贝构造函数,而默认的拷贝构造与前面的析构和构造不同,拷贝构造会对内置类型按照字节方式直接拷贝,而对自定义类型来说,与前面的析构和构造相同,都会调用对应类型的拷贝构造函数,但是如果自定义类型没有实现拷贝构造,那么将直接按照字节序进行拷贝

在日期类中,可以直接使用默认的拷贝构造函数而不需要自己去实现,但是对于下面的情况则需要自己实现拷贝构造

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 10)
    {
        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array)
        {
            perror("malloc申请空间失败");
            return;
        }

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

在上面的代码中,定义了一个栈类,而栈类中的成员变量有一个指针类型和两个内置类型,当Stack s2(s1)需要调用拷贝构造函数时,因为Stack类中并未实现拷贝构造,编译器会默认生成拷贝构造,此时将按照字节方式进行浅拷贝,此时对于内置类型的_size_capacity的拷贝不会有任何问题,但是对于*_array来说,因为进行字节方式的浅拷贝,所以s2对象中的*_arrays1对象中的*_array都指向同一块空间,而因为_size_capacity是各自独有的空间,s1对象中的改变不影响s2对象,现在插入了4个数据,s1中的_size = 4s2对象中的_size依旧为0,如果s2需要插入数据,那么将从栈底开始插入,此时会覆盖掉原来s1中的数据。

另外,因为s1s2对象都出现了资源申请,所以最后在程序即将销毁两个对象时会调用析构函数进行对象资源的释放,因为s2对象后实例化,所以在资源清理时先进行清理,此时会释放掉s2*_array指向的空间,此时s1对象中的*_array指向的空间也同时被销毁,但是s2*_array置为了空指针,而s1*_array并不是空指针(依旧指向原来空间的位置),当释放完s2的资源时将释放s1的资源,此时释放s1的资源时将出现非法访问,因为s1对象的*_array指向的空间已经归还给操作系统了,从而会出现程序运行崩溃

综上所述,在对象有资源申请时,需要自行实现拷贝构造,否则直接使用编译器自动生成的拷贝构造函数即可

拷贝构造函数使用实例

C++
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#include <iostream>
using namespace std;

//生成指定天数后的某一天
class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 20)
    {
        _year = year;
        _month = month;
        _day = day;
    }

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

    int GetMonthDays(int year, int month)
    {
        int monthDay[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 monthDay[month] + 1;
        }
        return monthDay[month];
    }

    //获取指定天数后的某一天
    //返回引用
    Date& GetAfterXDate_pe(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;//返回调用本函数的对象的引用
    }

    //返回自定义类型值
    Date GetAfterXDate_p(int days)
    {
        Date tmp(*this);//使用本类的拷贝构造复制对象中的内容
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;//返回自定义类型的值
    }

    void print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
};

int main()
{
    Date d;//当天的日期
    d.print();
    d.GetAfterXDate_p(100).print();//获取一百天以后的当天,将返回值作为对象调用print函数,但是不改变初始的日期
    d.print();

    cout << endl;

    Date d1;
    d1.print();
    d1.GetAfterXDate_pe(100).print();//获取一百天后的当天,但会改变初始的日期
    d1.print();

    return 0;
}
输出结果
2024-3-20
2024-6-28
2024-3-20

2024-3-20
2024-6-28
2024-6-28

在上面的代码中,对于返回对象的引用操作并不是非法引用,尽管this指针的作用域只在成员函数中,但是因为this指向的是实例化的对象,而实例化的对象只有在其所在空间被销毁才会销毁,所以可以理解为this指针并未被销毁,所以可以返回this的引用操作

拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

Date Test(Date d)
{
    Date temp(d);
    return temp;
}

int main()
{
    Date d1(2022,1,13);
    Test(d1);
    return 0;
}
输出结果
Date(int,int,int):000000B51D1DFC58
Date(const Date& d):000000B51D1DFD48
Date(const Date& d):000000B51D1DFD94
~Date():000000B51D1DFD48
~Date():000000B51D1DFD94
~Date():000000B51D1DFC58

在上面的代码中,首先创建了一个d1对象,此时会调用Date类中的构造函数,接下来Test函数传入实参为自定义类型对象d1,因为Test函数的形参是自定义类型,并且是值传递,所以此时形参d需要拷贝传入的实参对象d1的内容,故会调用Date的拷贝构造函数进行对象拷贝,在Test函数体中出现了temp对象的创建和拷贝,故此时依旧会调用拷贝构造函数拷贝d对象中的内容给temp对象,所以总共调用了两次拷贝构造函数,而因为temp对象出了Test函数将进行销毁,故会调用Date类中的析构函数,而因为形参d也需要被销毁,故也会调用Date类的析构函数,最后就是d1对象的销毁,该对象销毁也需要调用析构函数,故总共有三次析构函数的调用

如果将上面的代码中Test函数中的值形参改成引用形参时,在实参传入时将不会调用拷贝函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;

class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

Date Test(Date& d)
{
    Date temp(d);
    return temp;
}

int main()
{
    Date d1(2022, 1, 13);
    Test(d1);
    return 0;
}
输出结果
Date(int,int,int):0000002DC037F748
Date(const Date& d):0000002DC037F834
~Date():0000002DC037F834
~Date():0000002DC037F748

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用

赋值运算符重载函数

运算符重载函数介绍

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

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//自定义类型无法使用内置的关系运算符进行比较

    return 0;
}
报错信息
二进制==:Date不定义该运算符或到预定义运算符可接收的类型的转换

为了自定义类型的对象之间可以进行关系运算,可以使用运算符重载,如下面代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int getYear()
    {
        return _year;
    }

    int getMonth()
    {
        return _month;
    }

    int getDay()
    {
        return _day;
    }
};

bool operator==(Date& d1, Date& d2)
{
    return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//有重载==的函数时可以比较

    return 0;
}
输出结果
1

运算符重载函数的使用

对于运算符重载函数来说,其函数名为:operator+需要重载的运算符,而该函数的原型如下:

C++
1
函数返回类型 operator运算符(参数列表)

定义运算符重载函数时,需要注意下面的问题

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* :: sizeof ?: . :注意以上5个运算符不能重载

对于下面的代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int getYear()
    {
        return _year;
    }

    int getMonth()
    {
        return _month;
    }

    int getDay()
    {
        return _day;
    }
};

//全局运算符重载函数
bool operator==(Date& d1, Date& d2)
{
    return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//有重载==的函数时可以比较

    return 0;
}
输出结果
1

在上面的代码中,因为运算符重载函数不在类中,并且因为类的成员变量为private,所以需要调用获取函数来得到当前对象的成员变量中的值,并且因为在全局中,并不存在哪一个对象调用函数,所以没有this指针,此时形参的个数对应运算符的操作数的个数

Note

注意上面的全局运算符重载函数中形参不可以使用const修饰,因为如果使用了const修饰,那么就是d1d2都是const修饰的对象,而this只是*const,而不是const*,本来是d1d2const修饰不可以修改引用的对象的值,但是如果传递给了this可能会出现通过this改变d1d2引用的对象的值,所以此处涉及到将引用的权限放大

考虑到如果将运算符重载函数写在类外需要额外写三个函数来获取到指定的值,所以可以将运算符重载函数写进类中

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    //类中的运算符重载函数
    bool operator==(const Date& d)
    {
        return _year == d._year && _month == d._month && _day == d._day;
    }
};

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//直接调用类中实现的运算符重载函数
    //上面的代码相当于:cout << d1.operator==(d2) << endl;
    return 0;
}
输出结果:
1

在上面的代码中,因为运算符重载函数在类中,所以存在this指针,所以只需要传递一个参数(加上this指针参数和额外的参数一共两个参数对应==操作符的操作数个数),并且形参对象引用d指的是第二个操作数,因为d1 == d2等价于d1.operator==(d2),因为是d1在调用运算符重载函数,所以this指针指向的对象即为d1

赋值运算符重载函数

赋值运算符重载函数也是运算符重载函数中的一种,因为重载的运算符为赋值运算符=,重载赋值运算符时,首先不能改变赋值运算符的特性,包括连续赋值

赋值运算符重载函数的使用

赋值运算符重载函数的格式

  • 参数类型:const T&,传递引用可以提高传参效率(T为类名)
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,并且需要检测是否自己给自己赋值,以减少赋值次数
  • 返回*this :要复合连续赋值的含义
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

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

int main()
{
    Date d(2024, 2, 28);
    Date d1;

    d1 = d;
    d1.print();

    return 0;
}
输出结果
2024/2/28

在上面的代码中,类Date中对赋值运算符进行了重载,将引用指向的对象中的值给调用该运算符重载函数的对象,但是上面的代码无法实现赋值运算符的连续赋值,因为没有返回值

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

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

int main()
{
    Date d(2024, 2, 28);
    Date d1;
    Date d2;

    d1 = d;
    d1.print();
    d2 = d1 = d;

    return 0;
}
报错信息
二元=: 没有找到接受void类型的右操作数的运算符(或没有可接受的转换)

所以,为了解决这个问题,赋值运算符重载函数需要给定返回值为类类型

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

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

        return *this;
    }
};

int main()
{
    Date d(2024, 2, 28);
    Date d1;
    Date d2;

    d2 = d1 = d;
    d1.print();
    d2.print();
    return 0;
}
输出结果
2024/2/28
2024/2/28

在上面的代码中,赋值运算符重载函数给了返回值为Date类型的引用,此时可以使用连续赋值,因为赋值运算符从右往左结合,所以具体过程为d对象赋值给d1,d1对象的值赋值给d2,从函数调用的角度理解为d2.operator=(d1.operator=(d));(注意不是d2.operator=(d1).operator=(d);,本句理解为d2被赋值为d1中的内容,然后再被赋值为d中的内容,相当于d2 = d1; d2 = d;

Note

赋值运算符重载函数的返回值也可以不用引用,但是此时在返回时会调用拷贝构造函数将返回值的内容拷贝给调用赋值运算符重载函数的对象,为了减少调用拷贝构造的次数,更推荐使用引用,该解释同样适用于形参

另外,还有一个小问题,如果两个相同的对象进行赋值,那么将产生额外的一次赋值,对于这个问题,在赋值时需要判断形参引用的对象和this指针指向的对象是否是同一个地址的对象

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    Date& operator=(const Date& d)
    {
        if (this != &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }

        return *this;
    }
};

int main()
{
    Date d(2024, 2, 28);
    d = d;
    return 0;
}

在上面的代码中,判断this指针指向的对象的地址和引用的对象地址是否相等,如果二者相等,则证明是同一个对象,不需要进行赋值直接返回即可,注意形参的Date &d为创建对象的引用,而if语句中的&d是取引用的地址

注意,赋值运算符重载函数必须作为成员函数,不能作为全局函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class Date
{
public:
    int _year;
    int _month;
    int _day;
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};
Date& operator=(Date& d1, Date& d)
{    
    if (&d1 != &d)
    {
        d1._year = d._year;
        d1._month = d._month;
        d1._day = d._day;
    }

    return d1;
}
int main()
{
    Date d(2024, 2, 28);
    Date d1;
    d1 = d;

    return 0;
}
报错信息
operator=必须是成员函数

赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,所以赋值运算符重载只能是类的成员函数

拷贝构造函数和赋值运算符重载函数

与拷贝构造函数类似,赋值运算符重载函数如果用户没有实现,编译器会自动实现。默认如果不自主实现还是按照字节拷贝,按照字节方式拷贝也会遇到像拷贝函数一样的问题(指对象中有资源申请时)

  1. 一个对象改变,另一个对象也会跟着改变,严重者会数据覆盖
  2. 释放资源时会因为多次释放一个空间导致程序崩溃
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

};

int main()
{
    Date d(2024, 2, 28);
    Date d1;
    d1 = d;//编译器自动生成的默认赋值运算符重载函数
    Date d2(d);//编译器自动生成的拷贝构造函数
    d1.print();
    d2.print();
    return 0;
}
输出结果
2024/2/28
2024/2/28

与拷贝构造函数一样,如果类对象中有涉及到资源申请,那么需要自己实现赋值运算符重载函数,否则直接使用默认的即可

重载前置++和后置++

前置++

对于运算符重载函数的使用规则,那么可以很容易写出++的重载函数,如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    void operator++()
    {
        _day += 1;
    }
};

int main()
{
    Date d;
    ++d;
    d.print();
    return 0;
}
输出结果
2024/3/23

因为前++相当于计算+=1,而因为前面实现过获取X天后的日期的函数GetAfterXDays_plusEqual,所以可以直接用该函数进行复用,从而实现++操作

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int GetMonthDays(int year, int month)
    {
        int monthDays[] = { 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 monthDays[month] + 1;
        }   
        else
        {
            return monthDays[month];
        }
    }

    Date& GetAfterXDays_plusEqual(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    Date GetAfterXDays_plus(int days)
    {
        Date tmp(*this);
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;
    }

    void operator++()
    {
        //因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
        *this = GetAfterXDays_plusEqual(1);
    }
};

int main()
{
    Date d;
    ++d;
    d.print();
    return 0;
}
输出结果
2024/3/23

注意到上面实现的++是无返回值的++运算符重载函数,但是如果函数没有返回值,将无法将++后的值给另外一个对象

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int GetMonthDays(int year, int month)
    {
        int monthDays[] = { 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 monthDays[month] + 1;
        }   
        else
        {
            return monthDays[month];
        }
    }

    Date& GetAfterXDays_plusEqual(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    Date GetAfterXDays_plus(int days)
    {
        Date tmp(*this);
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;
    }

    void operator++()
    {
        //_day += 1;
        //因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
        *this = GetAfterXDays_plusEqual(1);
        //return *this;
    }

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

int main()
{
    Date d;
    ++d;
    d.print();
    Date d1;
    d1 = ++d;
    return 0;
}
报错信息
二元=: 没有找到接受void类型的右操作数的运算符(或没有可接受的转换)

所以为了解决这种问题将考虑为++运算符重载函数加上返回值为类对象引用类型

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int GetMonthDays(int year, int month)
    {
        int monthDays[] = { 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 monthDays[month] + 1;
        }   
        else
        {
            return monthDays[month];
        }
    }

    Date& GetAfterXDays_plusEqual(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    Date GetAfterXDays_plus(int days)
    {
        Date tmp(*this);
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;
    }

    Date& operator++()
    {
        //_day += 1;
        //因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
        *this = GetAfterXDays_plusEqual(1);
        return *this;
    }

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

int main()
{
    Date d;
    ++d;
    d.print();
    Date d1;
    d1 = ++d;
    return 0;
}
输出结果
2024/3/23

后置++

上面的函数中实现了前置++,但是并没有实现后置++,如果在没有实现后置++时,使用后置++,则会出现下面的情况

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int GetMonthDays(int year, int month)
    {
        int monthDays[] = { 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 monthDays[month] + 1;
        }
        else
        {
            return monthDays[month];
        }
    }

    Date& GetAfterXDays_plusEqual(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    Date GetAfterXDays_plus(int days)
    {
        Date tmp(*this);
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;
    }

    Date& operator++()
    {
        //_day += 1;
        //因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
        *this = GetAfterXDays_plusEqual(1);
        return *this;
    }

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

int main()
{
    Date d;
    d++;
    return 0;
}
报错信息
二进制++:Date不定义该运算符或到预定义运算符可接收的类型的转换

所以有前置++的实现并不能同时应用于后置++,对于后置++来说,编译器为了与前置++作区分,需要在形参位置添加一个整型占位形参,如下

C++
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int GetMonthDays(int year, int month)
    {
        int monthDays[] = { 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 monthDays[month] + 1;
        }
        else
        {
            return monthDays[month];
        }
    }

    Date& GetAfterXDays_plusEqual(int days)
    {
        _day += days;
        while (_day > GetMonthDays(_year, _month))
        {
            _day -= GetMonthDays(_year, _month);
            _month++;
            if (_month == 13)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    Date GetAfterXDays_plus(int days)
    {
        Date tmp(*this);
        tmp._day += days;
        while (tmp._day > GetMonthDays(tmp._year, tmp._month))
        {
            tmp._day -= GetMonthDays(tmp._year, tmp._month);
            tmp._month++;
            if (tmp._month == 13)
            {
                tmp._year++;
                tmp._month = 1;
            }
        }
        return tmp;
    }

    //前置++
    Date& operator++()
    {
        //_day += 1;
        //因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
        *this = GetAfterXDays_plusEqual(1);
        return *this;
    }

    //后置++,但是为了不同于前置++,在形参处加入int形参作为占位便于编译器区分
    Date operator++(int)
    {
        //后置++满足先使用再++,所以返回值为原值
        Date tmp(*this);
        *this = GetAfterXDays_plusEqual(1);
        return tmp;
    }

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

int main()
{
    Date d;
    d++;
    d.print();
    return 0;
}
输出结果
2024/3/23

在上面的代码中,对于后置++重载函数来说,在形参处添加了一个int类型形参作为占位符,这个形参可以不给形参名,因为只是编译器用于区分

重载流插入<<与流提取>>

在C++标准库中,coutcin是属于iostreamostreamistream的对象,对于流插入<<运算符,之所以cout输出可以不用指定占位符编译器可以自动匹配的原因是ostream<<的运算符重载和函数重载,对于内置类型来说,有下面的函数重载

同样对于流提取运算符>>来说也是如此,如下图所示

但是流插入和流提取运算符并没有对自定义类型有函数重载,所以可以对流提取运算符和流插入运算符进行函数重载

流插入运算符<<重载

按照前面的运算符重载思路可以写出下面的代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void operator<<(ostream& cout)
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

int main()
{
    Date d;
    cout << d;
    return 0;
}
报错信息
二元<<: 没有找到接受Date类型的右操作数的运算符(或没有可接受的转换)

在上面的代码中,虽然重载了<<,但是形参是ostream流的对象,而隐含的形参是this,而在运算符重载函数形参列表的规则中,对于有两个操作数的运算符重载来说,第一个参数为左操作数,第二个参数为右操作数,所以上面的代码调用应该为d << cout

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void operator<<(ostream& cout)
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

int main()
{
    Date d;
    //cout << d;
    d << cout;
    return 0;
}
输出结果
2024/3/22

但是和正常的输出刚好顺序相反,所以这种方法需要改变,但是因为不能改变this在形参的位置,所以考虑到将<<重载放置到全局中,此时可以决定两个操作数的顺序,但是这个方法就不能使用this指针,并且需要考虑到成员变量的private属性,本处给出一种解决方案是使用get函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    int getYear()
    {
        return _year;
    }

    int getMonth()
    {
        return _month;
    }

    int getDay()
    {
        return _day;
    }
};

void operator<<(ostream& cout, Date& d)
{
    cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
}

int main()
{
    Date d;
    cout << d;
    return 0;
}
输出结果
2024/3/22

在上面的代码中,将流插入运算符重载函数放置到全局中可以控制coutd的顺序,此时即可写为cout << d,但是因为上面的<<并没有返回值,所以不可以连续插入,所以可以改进为如下

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    int getYear()
    {
        return _year;
    }

    int getMonth()
    {
        return _month;
    }

    int getDay()
    {
        return _day;
    }
};

ostream& operator<<(ostream& cout, Date& d)
{
    cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
    return cout;
}

int main()
{
    Date d;
    Date d1;
    cout << d << d1 << endl;
    return 0;
}
输出结果
2024/3/22
2024/3/22

流提取运算符>>重载

对于流提取运算符>>的重载类似于流插入运算符<<,但是此时不能使用获取函数,所以对于流提取运算符的重载来说,考虑用友元解决,使用友元可以让全局函数中的对象获取到对应类private属性变量

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    //友元
    friend istream& operator>>(istream& cin, Date& d);

    int getYear()
    {
        return _year;
    }

    int getMonth()
    {
        return _month;
    }

    int getDay()
    {
        return _day;
    }
};

ostream& operator<<(ostream& cout, Date& d)
{
    cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
    return cout;
}

istream& operator>>(istream& cin, Date& d)
{
    cin >> d._year >> d._month >> d._day;
    return cin;
}

int main()
{
    Date d;
    Date d1;
    cin >> d >> d1;
    cout << d << d1 << endl;
    return 0;
}
输入
2024 2 28 2024 3 31
输出结果
2024/2/28
2024/3/31

同样可以使用友元解决流插入运算符的重载函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    Date(int year = 2024, int month = 3, int day = 22)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //友元
    friend istream& operator>>(istream& cin, Date& d);
    friend ostream& operator<<(ostream& cout, Date& d);
};

ostream& operator<<(ostream& cout, Date& d)
{
    cout << d._year << "/" << d._month << "/" << d._day << endl;
    return cout;
}

istream& operator>>(istream& cin, Date& d)
{
    cin >> d._year >> d._month >> d._day;
    return cin;
}

int main()
{
    Date d;
    Date d1;
    cin >> d >> d1;
    cout << d << d1 << endl;
    return 0;
}
输入
2024 2 23 2022 3 31
输出结果
2024/2/23
2022/3/31

const成员函数

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

在前面的运算符重载函数中,当运算符重载函数在全局时不可以使用const修饰形式参数,因为const成员变量传递给成员函数时涉及到引用权限放大,那么const成员函数就是用来解决这种权限放大问题,可以将权限保持不变或者缩小,例如在下面的代码中

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int getYear() const
    {
        return _year;
    }

    int getMonth() const
    {
        return _month;
    }

    int getDay() const
    {
        return _day;
    }
};

//全局运算符重载函数,const形参
bool operator==(const Date& d1, const Date& d2)
{
    return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//有重载==的函数时可以比较

    return 0;
}
输出结果
1

在上面的代码中,使用const修饰了==运算符重载函数的两个形式参数,此时d1d2不可以被修改,当对象d1d2调用get系列函数时,成员函数的形式参数需要保证获得的权限不被放大,所以需要修饰形式参数,但是因为this指针不可以直接显式做形式参数,所以不可以使用const显式对this指针进行修饰,此时就需要将const放置到函数名后,作为修饰this指针的const以满足指针及引用权限不被放大

但是,如果==运算符重载函数中的两个形式参数并不是const修饰的变量,此时调用const成员函数也不会有错,因为此时是权限的缩小(从可修改到成员函数的不可修改),如下面的代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    int getYear() const
    {
        return _year;
    }

    int getMonth() const
    {
        return _month;
    }

    int getDay() const
    {
        return _day;
    }
};

//全局运算符重载函数,非const形参
bool operator==(Date& d1, Date& d2)
{
    return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}

int main()
{
    Date d1;
    Date d2(d1);
    cout << (d1 == d2) << endl;//有重载==的函数时可以比较

    return 0;
}

对于const成员函数和非const成员函数之间也是如此

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    //非const成员函数
    void printYear()
    {
        cout << _year;
    }    

    int getYear() const
    {
        printYear();
        return _year;
    }
};

int main()
{
    Date d1;
    Date d2(d1);

    return 0;
}
报错信息
void Date::printYear(void): 不能将this指针从const Date转换为Date &

而对于const成员函数和const成员函数之间可以直接调用

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    //非const成员函数
    void printYear() const
    {
        cout << _year;
    }    

    int getYear() const
    {
        printYear();
        return _year;
    }
};

int main()
{
    Date d1;
    Date d2(d1);

    return 0;
}

同样非const成员函数可以调用const成员函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    //非const成员函数
    void printYear() const
    {
        cout << _year;
    }    

    int getYear() 
    {
        printYear();
        return _year;
    }
};

int main()
{
    Date d1;
    Date d2(d1);

    return 0;
}

取地址操作符重载与const成员取地址操作符重载

在C++中,这两个运算符重载函数可以不用显式定义,编译器会默认生成

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

};

int main()
{
    Date d1;
    Date d2;
    const Date d3;

    cout << &d1 << endl;
    cout << &d2 << endl;
    cout << &d3 << endl;

    return 0;
}
输出结果
00000031E9FEF628
00000031E9FEF658
00000031E9FEF688

也可以显式定义

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    // 显式定义
    Date* operator&()
    {
        return this;
    }

    const Date* operator&()const
    {
        return this;
    }

};

int main()
{
    Date d1;
    Date d2;
    const Date d3;

    cout << &d1 << endl;
    cout << &d2 << endl;
    cout << &d3 << endl;

    return 0;
}
输出结果
0000006B086FFC38
0000006B086FFC68
0000006B086FFC98

只有特殊情况,才需要重载这两个函数,比如想让别人获取到指定的内容,让其访问非法地址

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
using namespace std;

class Date
{
private:
    int _year;
    int _month;
    int _day;
public:
    Date(int year = 2024, int month = 3, int day = 21)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    Date* operator&()
    {
        return (Date*)0;
    }

    const Date* operator&()const
    {
        return (Date*)0;
    }

};

int main()
{
    Date d1;
    Date d2;
    const Date d3;

    cout << &d1 << endl;
    cout << &d2 << endl;
    cout << &d3 << endl;

    return 0;
}
输出结果
0000000000000000
0000000000000000
0000000000000000

实现日期类练习

C++
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//头文件
#pragma once

#include <iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;

public:
    //构造函数
    Date(int year = 2024, int month = 3, int day = 23)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}

    friend inline istream& operator>>(istream& cin, Date& d);
    friend inline ostream& operator<<(ostream& cout, Date& d);

    //获取月份日期函数
    int GetMonthDays(int year, int month);

    //+=运算符重载
    Date& operator+=(int days);

    //+运算符重载
    Date operator+(int days)
    {
        Date tmp(*this);
        //复用+=重载
        tmp += days;
        return tmp;
    }

    //赋值运算符重载
    Date& operator=(const Date& d);

    //前置++运算符重载
    Date& operator++()
    {
        //复用+=重载
        *this += 1;
        return *this;
    }

    //后置++运算符重载
    Date operator++(int)
    {
        //复用+=函数
        Date tmp(*this);
        tmp += 1;
        return tmp;
    }

    //>=运算符重载
    bool operator>=(const Date& d) const;

    //<=运算符重载
    bool operator<=(const Date& d) const;

    //<运算符重载
    bool operator<(const Date& d) const
    {
        //<的对立事件为>=,故直接对>=取反
        return !(*this >= d);
    }

    //>运算符重载
    bool operator>(const Date& d) const
    {
        //>的对立事件为<=,故直接对<=取反
        return !(*this <= d);
    }

    //==运算符重载
    bool operator==(const Date& d) const
    {
        return _year == d._year && _month == d._month && _day == d._day;
    }

    //!=运算符重载
    bool operator!=(const Date& d) const
    {
        //!=的对立事件为==,故直接对==取反
        return !(*this == d);
    }

    //-=运算符重载
    Date& operator-=(int days);

    //-运算符重载
    Date operator-(int days)
    {
        Date tmp(*this);
        tmp._day -= days;
        return tmp;
    }

    //前置--运算符重载
    Date& operator--()
    {
        //复用-=重载函数
        *this -= 1;
        return *this;
    }

    //后置--运算符重载
    Date operator--(int)
    {
        //复用-=重载
        Date tmp(*this);
        *this -= 1;
        return tmp;
    }

    //日期-日期
    int operator-(const Date& d);
};

inline ostream& operator<<(ostream& cout, Date& d)
{
    cout << d._year << "/" << d._month << "/" << d._day << endl;
    return cout;
}

inline istream& operator>>(istream& cin, Date& d)
{
    cin >> d._year >> d._month >> d._day;
    return cin;
}

//实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"

//获取月份日期函数
int Date::GetMonthDays(int year, int month)
{
    int monthDays[] = { 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 monthDays[month] + 1;
    }
    return monthDays[month];
}

//+=运算符重载
Date& Date::operator+=(int days)
{
    _day += days;
    while (_day > GetMonthDays(_year, _month))
    {
        _day -= GetMonthDays(_year, _month);
        _month++;
        if (_month == 13)
        {
            _year++;
            _month = 1;
        }
    }
    return *this;
}

//赋值运算符重载
Date& Date::operator=(const Date& d)
{
    if (this != &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    return *this;
}

//>=运算符重载
bool Date::operator>=(const Date& d) const
{
    //如果年大就直接返回true
    if (_year>d._year)
    {
        return true;
    }
    else if(_year == d._year && _month > d._month)//年相等时比较月份,月份大就直接返回true
    {
        return true;
    }
    else if (_year == d._year && _month == d._month && _day > d._day)//年相等,月份相等时,天大就直接返回true
    {
        return true;
    }
    else//其他情况均返回false
    {
        return false;
    }
}

//<=运算符重载
bool Date::operator<=(const Date& d) const
{
    //如果年小就直接返回true
    if (_year < d._year)
    {
        return true;
    }
    else if (_year == d._year && _month < d._month)//年相等时比较月份,月份小就直接返回true
    {
        return true;
    }
    else if (_year == d._year && _month == d._month && _day < d._day)//年相等,月份相等时,天小就直接返回true
    {
        return true;
    }
    else//其他情况均返回false
    {
        return false;
    }
}

//-=运算符重载
Date& Date::operator-=(int days)
{
    _day -= days;
    while (_day <= 0)
    {
        _month--;
        if (_month == 0)
        {
            _year--;
            _month = 12;
        }
        _day += GetMonthDays(_year, _month);
    }
    return *this;
}

//日期-日期
int Date::operator-(const Date& d)
{
    Date maxYear = *this;
    Date minYear = d;
    int flag = 1;

    if (maxYear < minYear)
    {
        maxYear = d;
        minYear = *this;
        flag = -1;
    }

    int count = 0;
    while (maxYear != minYear)
    {
        count++;
        ++minYear;
    }

    return count * flag;
}

//测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"

int main()
{
    Date d;
    Date d1(d);

    Date d2(2023, 1, 1);
    d--;
    cout << d;
    cout << (d != d1) << endl;
    cout << (d >= d1) << endl;
    Date d3;
    d3 = --d2;
    cout << d3;
    Date d4(2023, 2, 7);
    d4 -= 100;
    cout << d4;
    cout << d4 - d1 << endl;

    Date d5;
    Date d6;
    cin >> d5 >> d6;
    cout << d5 << d6 << endl;
    return 0;
}
输入
2024 3 23
2023 2 23
输出结果
2024/3/22
1
0
2022/12/31
2022/10/30
-510
2024/3/23
2023/2/23