类和对象(深入)类的6个默认成员函数

打印 上一主题 下一主题

主题 1516|帖子 1516|积分 4558

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
来吧,开始记笔记!!!
 类的默认成员函数

默认成员函数,简单来说,就是我们不写,编译器会主动生成的一些函数,没有显示的实现
      默认成员函数就是⽤⼾没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我   们不写的环境下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最   后两个取地点重载不重要,   其次就是C++11以后还会增加两个默认成员函数,   移动构造和移动赋值,这两个背面在说。               默认成员函数很重要,也⽐较复杂,我们要从两个⽅⾯     去学习:                  •      第⼀:我们不写时,编译器默认⽣成的函数⾏为是什么,是否满⾜我们的需求。              •      第⼆:编译器默认⽣成的函数不满⾜我们的需求,我们需要⾃⼰实现,那么如何⾃⼰实现?                 只有知道第一点,我们才知道我们到底需不需要写(默认生成的函数是否符合我们的需求)                 总体框架:                 
            1.构造函数(最复杂)

            构造函数是特殊的成员函数,需要注意的是,构造函数固然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数(初始化)的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。         构造函数的特点:        1.    函数名与类名相同;         2.    ⽆返回值; (返回值啥都不需要给,也不需要写void,不要纠结,C++规定云云)          3.    对象实例化时系统会⾃动调⽤对应的构造函数;         4.    构造函数可以重载;(也就是可以函数重载)         代码演示:        
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6.         // 1.⽆参构造函数
  7.         Date()
  8.         {
  9.                 _year = 1;
  10.                 _month = 1;
  11.                 _day = 1;
  12.         }
  13.         // 2.带参构造函数
  14.         Date(int year, int month, int day)
  15.         {
  16.                 _year = year;
  17.                 _month = month;
  18.                 _day = day;
  19.         }
  20.        
  21.         void Print()
  22.         {
  23.                 cout << _year << "/" << _month << "/" << _day << endl;
  24.         }
  25. private:
  26.         int _year;
  27.         int _month;
  28.         int _day;
  29. };
  30. int main()
  31. {
  32.         //调用第一个:默认构造函数
  33.         Date d1;
  34.         d1.Print();
  35.         //调用第二个:带参的构造函数(函数重载),可以按F11去看看
  36.         Date d2(2024, 7, 15);
  37.         d2.Print();
  38.         //注意:无参不能加括号,对象没有被定义出来,因为和函数声明区分不开
  39.         //Date d3();
  40.         //d3.Print();
  41.         //函数声明样例:
  42.         //Date func();
  43.         //func.Print();
  44.         return 0;
  45. }
复制代码
     
  1. class Date
  2. {
  3. public:
  4.         // 全缺省构造函数(全缺省和无参,是函数重载,但存在调用歧义)(可以兼备前两个)
  5.         Date(int year = 1, int month = 1, int day = 1)
  6.         {
  7.                 _year = year;
  8.                 _month = month;
  9.                 _day = day;
  10.         }
  11.         void Print()
  12.         {
  13.                 cout << _year << "/" << _month << "/" << _day << endl;
  14.         }
  15. private:
  16.         int _year;
  17.         int _month;
  18.         int _day;
  19. };
  20. int main()
  21. {
  22.         Date d1;
  23.         d1.Print();
  24.         Date d2(2024, 7, 15);
  25.         d2.Print();
  26.         Date d3(2024);
  27.         d3.Print();
  28.         return 0;
  29. }
复制代码
      5.    如果类中没有显式界说构造函数,则C++编译器会⾃动⽣成⼀个⽆参      默认构造函数(注意全缺省也算),⼀旦用户   显式   界说编译器将不再⽣成;             6.     ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。    ⽆参构造函数和全缺省构造函数固然构成函数重载,但是调⽤时会存在歧义。实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调⽤的构作育叫默认构造;             7.     我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃界说类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤    初始化列表    才气解决;             这是我的VS2022编译器:            
                        阐明:C++把类型分成内置类型(根本类型)和⾃界说类型。内置类型就是语⾔提供的原⽣数据类型,      如:int/char/double/指针等,⾃界说类型就是我们使⽤class/struct等关键字⾃⼰界说的类型                   不难发现,第一个题目可以看出,默认的体现并不能完全满意我们的要求             看看吧:(栈的构造函数):         
  1. #include<iostream>
  2. using namespace std;
  3. typedef int STDataType;
  4. class Stack
  5. {
  6. public:
  7.         //当没有默认构造时:Stack(int n)//会报错:这自己生成不了了·······
  8.         Stack(int n = 4)//因为缺省,知道多少给多少,不知道的就默认给4,有效避免了扩容带来的空间浪费
  9.         {
  10.                 _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11.                 if (nullptr == _a)
  12.                 {
  13.                         perror("malloc申请空间失败");
  14.                         return;
  15.                 }
  16.                 _capacity = n;
  17.                 _top = 0;
  18.         }
  19.         // ...
  20. private://栈不自己写,下面给随机值,这不就扯蛋吗
  21.         STDataType* _a;
  22.         size_t _capacity;
  23.         size_t _top;
  24.         int size;//自动生成的构造对内置类型的初始化是不确定的//加这句就是一个坑,但也有方法可以解决
  25. };
  26. // 两个Stack实现队列
  27. class MyQueue
  28. {
  29. public:
  30.         //编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
  31. private:
  32.         Stack pushst;//重Stack中找,我们写的全缺省Stack就是他要调用的默认构造函数
  33.         Stack popst;
  34. };
  35. int main()
  36. {
  37.         MyQueue mq;
  38.         return 0;
  39. }
复制代码
         总结:          大多数环境,构造函数都需要我们自己去实现,少数环境雷同MyQueue且Stack有默认构造时,MyQueue主动生成就可以用。             ---------应写尽写(构造函数的特点)         2.析构函数

         析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,⽽像Date没有Destroy,其实就是没有资源需要释放,所以严酷说Date是不需要析构函数的。     析构函数的特点:(针对有资源申请的类)
   1.   析构函数名是在类名前加上字符 ~;(联想按位取反“~”)     2.   ⽆参数⽆返回值; (这⾥跟构造雷同,也不需要加void)      3.   ⼀个类只能有⼀个析构函数,若未显式界说,系统会⾃动⽣成默认的析构函数;     4.   对象⽣命周期结束时,系统会⾃动调⽤析构函数;     5.   跟构造函数雷同,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,⾃定类型成员会调⽤他的析构函数;     6.   还需要注意的是我们显⽰写析构函数,对于⾃界说类型成员也会调⽤他的析构,也就是说⾃界说类型成员⽆论什么环境都会⾃动调⽤析构函数;        7.    如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认⽣成的析构就可以⽤,也就不需要显⽰写析构,如MyQueue;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源走漏,如Stack;         8.    ⼀个局部域的多个对象,C++规定后界说的先析构;        
  1. #include<iostream>
  2. using namespace std;
  3. typedef int STDataType;
  4. class Stack
  5. {
  6. public:
  7.         Stack(int n = 4)
  8.         {
  9.                 _a = (STDataType*)malloc(sizeof(STDataType) * n);
  10.                 if (nullptr == _a)
  11.                 {
  12.                         perror("malloc申请空间失败");
  13.                         return;
  14.                 }
  15.                 _capacity = n;
  16.                 _top = 0;
  17.         }
  18.         ~Stack()
  19.         {
  20.                 cout << "~Stack()" << endl;
  21.                 free(_a);
  22.                 _a = nullptr;
  23.                 _top = _capacity = 0;
  24.         }//不写就内存泄漏了
  25. private:
  26.         STDataType* _a;
  27.         size_t _capacity;
  28.         size_t _top;
  29. };
  30. // 两个Stack实现队列
  31. class MyQueue
  32. {
  33. public:
  34.         //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
  35.         //显⽰写析构,也会⾃动调⽤Stack的析构
  36.         //还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数;
  37.     /*~MyQueue()
  38.     {}*/
  39. private:
  40.         Stack pushst;
  41.         Stack popst;
  42. };
  43. int main()//在类中想要看成员变量,在调试的时候直接写一个this,就可以看到成员了
  44. {
  45.         Stack st;
  46.         MyQueue mq;//如果有多个对象,后定义的先析构(规定)
  47.         return 0;
  48. }
复制代码
  3.拷贝构造函数(复制构造函数)

            如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤(只要求是第一个),且任何额外的参数都有默认值(缺省值),则此构造函数也叫做拷⻉构造函数,也就是说拷⻉构造是⼀个特殊的构造函数             1.   拷⻉构造函数是构造函数的⼀个重载;   
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6.         Date(int year = 1, int month = 1, int day = 1)
  7.         {
  8.                 _year = year;
  9.                 _month = month;
  10.                 _day = day;
  11.         }
  12.         //拷贝函数
  13.         //第一的参数:类类型的引用
  14.         //Date(const Date& d , int x = 0)//其他的一定要是默认值/缺省值,但一般不用
  15.         //Date(const Date)//使用传值方式编译器会报错
  16.         Date(const Date& d)
  17.         {
  18.                 _year = d._year;
  19.                 _month = d._month;
  20.                 _day = d._day;
  21.         }
  22.         void Print()
  23.         {
  24.                 cout << _year << "/" << _month << "/" << _day << endl;
  25.         }
  26. private:
  27.         int _year;
  28.         int _month;
  29.         int _day;
  30. };
  31. int main()
  32. {
  33.         //用常规值初始化
  34.         Date d1(2024, 7, 16);
  35.         d1.Print();
  36.         //用d1拷贝d2
  37.         //调用的就是拷贝构造
  38.         Date d2(d1);
  39.         d2.Print();
  40.         return 0;
  41. }
复制代码
    2.   拷⻉构造函数的第一个参数必须是类类型对象的引⽤(由于要完成当前对象的拷贝),使⽤传值⽅式编译器直接报错,由于语法逻辑上会引发⽆穷递归调⽤;     3.   C++规定⾃界说类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃界说类型传值传参和传值返回都会调⽤拷⻉构造完成;     探究本质:   
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6.         Date(int year = 1, int month = 1, int day = 1)
  7.         {
  8.                 _year = year;
  9.                 _month = month;
  10.                 _day = day;
  11.         }
  12.         Date(const Date& d)
  13.         {
  14.                 _year = d._year;
  15.                 _month = d._month;
  16.                 _day = d._day;
  17.         }
  18.         void Print()
  19.         {
  20.                 cout << _year << "/" << _month << "/" << _day << endl;
  21.         }
  22. private:
  23.         int _year;
  24.         int _month;
  25.         int _day;
  26. };
  27. //为什么必须是引用?
  28. void Func1(Date d)
  29. {
  30.         cout << &d << endl;
  31.         d.Print();
  32. }
  33. int main()
  34. {
  35.         Date d1(2024, 7, 16);
  36.         d1.Print();
  37.         //C++规定传值传参要调用拷贝构造函数,C语言传直接按字节拷贝过去了
  38.         Func1(d1);//调试发现,按F11并没有跳到Func1函数中,而是跳到了拷贝构造函数中,再跳到Func,本质是调用了两个函数
  39. }
复制代码
调试图像演示:
由于传参会构成一个新的拷贝构造,这也就是为什么拷贝构造函数不可以以传值方式构造,而是要用引用 :由于用传值方式构造会导致无限递归。
图解:

  用引用的话,d是d1的别名,所以传参的时候不会形成新的拷贝构造 
   4.   若未显式界说拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型(构造函数:(初始化不确定)和析构函数:(不处理)对内置类型是不做处理的)成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃界说类型成员变量会调⽤他的拷⻉构造;(内置类型拷贝构造也管)   
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6.         Date(int year = 1, int month = 1, int day = 1)
  7.         {
  8.                 _year = year;
  9.                 _month = month;
  10.                 _day = day;
  11.         }
  12.         /*Date(const Date& d)
  13.         {
  14.                 _year = d._year;
  15.                 _month = d._month;
  16.                 _day = d._day;
  17.         }*/
  18.         void Print()
  19.         {
  20.                 cout << _year << "/" << _month << "/" << _day << endl;
  21.         }
  22. private:
  23.         int _year;
  24.         int _month;
  25.         int _day;
  26. };
  27. int main()
  28. {
  29.         //⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉
  30.         //照样可以拷贝
  31.         Date d1(2024, 7, 17);
  32.         d1.Print();
  33.         Date d2(d1);
  34.         d2.Print();
  35.         //2024 / 7 / 17
  36.         //2024 / 7 / 17
  37.         return 0;
  38. }
复制代码
    5.   像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构作育可以完成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。像Stack这样的类,固然也都是内置类型,  但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)(也就是深拷贝)  。像MyQueue这样的类型内部主要是⾃界说类型Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写拷⻉构造,否则就不需要;     浅拷贝(编译器主动生成的)案例:   
  1. #include<iostream>
  2. using namespace std;
  3. typedef int STDataType;
  4. class Stack
  5. {
  6. public:
  7.         Stack(int n = 4)
  8.         {
  9.                 _a = (STDataType*)malloc(sizeof(STDataType) * n);
  10.                 if (nullptr == _a)
  11.                 {
  12.                         perror("malloc申请空间失败");
  13.                         return;
  14.                 }
  15.                 _capacity = n;
  16.                 _top = 0;
  17.         }
  18.         void Push(STDataType x)
  19.         {
  20.                 if (_top == _capacity)
  21.                 {
  22.                         int newcapacity = _capacity * 2;
  23.                         STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
  24.                                 sizeof(STDataType));
  25.                         if (tmp == NULL)
  26.                         {
  27.                                 perror("realloc fail");
  28.                                 return;
  29.                         }
  30.                         _a = tmp;
  31.                         _capacity = newcapacity;
  32.                 }
  33.                 _a[_top++] = x;
  34.         }
  35.         ~Stack()
  36.         {
  37.                 cout << "~Stack()" << endl;
  38.                 free(_a);
  39.                 _a = nullptr;
  40.                 _top = _capacity = 0;
  41.         }
  42. private:
  43.         STDataType* _a;
  44.         size_t _capacity;
  45.         size_t _top;
  46. };
  47. int main()
  48. {
  49.         Stack st1;
  50.         st1.Push(1);
  51.         st1.Push(2);
  52.         // Stack不显⽰实现拷⻉构造,⽤⾃动⽣成的拷⻉构造完成浅拷⻉
  53.         // 会导致st1和st2⾥⾯的_a指针指向同⼀块资源,析构时会析构两次,程序崩溃
  54.         Stack st2(st1);
  55.     //Stack st3 = st1;//拷贝构造也可以这么写
  56.         return 0;
  57. }
复制代码
通过调试:
  

  发现题目:
 解决题目:
在public下,自己写一个拷贝构造:
  1. Stack(const Stack& st)
  2. {
  3.         // 需要对_a指向资源创建同样⼤的资源再拷⻉值
  4.         _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
  5.         if (nullptr == _a)
  6.         {
  7.                 perror("malloc申请空间失败!!!");
  8.                 return;
  9.         }
  10.         memcpy(_a, st._a, sizeof(STDataType) * st._top);
  11.         _top = st._top;
  12.         _capacity = st._capacity;
  13. }
复制代码

引用的好处:(体现) 
  1. #include<iostream>
  2. using namespace std;
  3. typedef int STDataType;
  4. class Stack
  5. {
  6. public:
  7.         Stack(int n = 4)
  8.         {
  9.                 _a = (STDataType*)malloc(sizeof(STDataType) * n);
  10.                 if (nullptr == _a)
  11.                 {
  12.                         perror("malloc申请空间失败");
  13.                         return;
  14.                 }
  15.                 _capacity = n;
  16.                 _top = 0;
  17.         }
  18.         Stack(const Stack& st)
  19.         {
  20.                 // 需要对_a指向资源创建同样⼤的资源再拷⻉值
  21.                 _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
  22.                 if (nullptr == _a)
  23.                 {
  24.                         perror("malloc申请空间失败!!!");
  25.                         return;
  26.                 }
  27.                 memcpy(_a, st._a, sizeof(STDataType) * st._top);
  28.                 _top = st._top;
  29.                 _capacity = st._capacity;
  30.         }
  31.         void Push(STDataType x)
  32.         {
  33.                 if (_top == _capacity)
  34.                 {
  35.                         int newcapacity = _capacity * 2;
  36.                         STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
  37.                                 sizeof(STDataType));
  38.                         if (tmp == NULL)
  39.                         {
  40.                                 perror("realloc fail");
  41.                                 return;
  42.                         }
  43.                         _a = tmp;
  44.                         _capacity = newcapacity;
  45.                 }
  46.                 _a[_top++] = x;
  47.         }
  48.         ~Stack()
  49.         {
  50.                 cout << "~Stack()" << endl;
  51.                 free(_a);
  52.                 _a = nullptr;
  53.                 _top = _capacity = 0;
  54.         }
  55. private:
  56.         STDataType* _a;
  57.         size_t _capacity;
  58.         size_t _top;
  59. };
  60. void Func(Stack st)
  61. {
  62. }
  63. int main()
  64. {
  65.         Stack st1;
  66.         st1.Push(1);
  67.         st1.Push(2);
  68.         Func(st1);//这样是不行的,这时候st是st1的深拷贝,st的改变不会影响st1,另一个层度警示我们:自定义类型用传值传参不好,尽可能用引用,不改变时用const引用
  69.         // Stack不显⽰实现拷⻉构造,⽤⾃动⽣成的拷⻉构造完成浅拷⻉
  70.         // 会导致st1和st2⾥⾯的_a指针指向同⼀块资源,析构时会析构两次,程序崩溃
  71.         //Stack st2(st1);
  72.         return 0;
  73. }
复制代码
  6.   传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有题目的,这时的引⽤相称于⼀个野引⽤,雷同⼀个野指针⼀样。传引⽤返回可以淘汰拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才气⽤引⽤返回;     传值返回:   
     传引用返回:   
    从栈帧上明确:   
  确保出了作用域,st还在:传引用返回还淘汰了拷贝
  1. Stack& func2()
  2. {
  3.         static Stack st;//这时候是静态对象,全局对象了
  4.         return st;
  5. }
  6. int main()
  7. {
  8.         Stack ret = func2();
  9.         return 0;
  10. }
复制代码
还可以:
  1. Stack func2(Stack& st)
  2. {
  3.         st.Push(1);
  4.         st.Push(1);
  5.         st.Push(1);
  6.         return st;
  7. }
  8. int main()
  9. {
  10.         Stack st1;
  11.         func2(st1);
  12.         return 0;
  13. }
复制代码
 可以在拷贝构造函数中添加一行代码,进行更好的观察:没有调用拷贝构造:
  1. cout << "Stack(const Stack& st)" << endl;//检验是否调用拷贝构造
复制代码
4.赋值函数

1.运算符重载 

   •   当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的情势指定新的寄义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错;(运算符重载转化成一个函数)     •   运算符重载是具有特殊名字的函数,他的名字是由operator(关键字)和后⾯要界说的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体;(格式:operator+运算符 构成函数名)     •   重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多,⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数;(++/- -/*(解引用)/.....就是一元)     •   如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个;     •   运算符重载以后,其优先级和联合性与对应的内置类型运算符保持⼀致;   
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6.         Date(int year = 1, int month = 1, int day = 1)
  7.         {
  8.                 _year = year;
  9.                 _month = month;
  10.                 _day = day;
  11.         }
  12.         void Print()
  13.         {
  14.                 cout << _year << "/" << _month << "/" << _day << endl;
  15.         }
  16.         //Get函数
  17.         int Get_year()
  18.         {
  19.                 return _year;
  20.         }
  21.         int Get_month()
  22.         {
  23.                 return _month;
  24.         }
  25.         int Get_day()
  26.         {
  27.                 return _day;
  28.         }
  29.         //因为存在this指针,所以应当少一个
  30.         bool operator==(Date d2)
  31.         {
  32.                 return _year == d2._year
  33.                         && _month == d2._month
  34.                         && _day == d2._day;
  35.         }
  36. private:
  37.         int _year;
  38.         int _month;
  39.         int _day;
  40.         //自定义类型怎么去比较?他的行为应该是我们自己去定义的,而不是系统去定义的
  41. };
  42. //比较大小的返回值是一个bool值
  43. bool operator<(Date d1, Date d2)
  44. {
  45. }
  46. //重载成全局的,方式是最正的:二元的:左操作数传给第一个参数,右操作数传给第二个参数
  47. //bool operator==(Date d1, Date d2)
  48. //{
  49. //        return d1._year == d2._year
  50. //                && d1._month == d2._month
  51. //                && d1._day == d2._day;
  52. //        //没有访问权限,1:可以直接将private的内容包含于public
  53. //        //·······2:提供Get函数
  54. //        //·······3:友元
  55. //        //·······4:放在类里面成为成员函数
  56. //}
  57. int main()
  58. {
  59.         Date x1(2024, 7, 16);
  60.         Date x2(2024, 7, 16);
  61.         到底是x1对应d1,还是d2
  62.         可以显现调用(跟一个普通函数一样):
  63.         //operator==(x1, x2);
  64.         还可以:
  65.         //x1 == x2;
  66.     //全局的和成员的都有,会优先调用成员的
  67.         //改后:
  68.         x1.operator==(x2);
  69.         x1 == x2;
  70.         return 0;
  71. }
复制代码
   •   不能通过连接语法中没有的符号来创建新的操作符/运算符:⽐如operator@;     •   '' .* ''      '' : : ''     '' sizeof ''      '' ?: ''     '' .''   注意以上5个运算符不能重载。(选择题⾥⾯常考,⼤家要记⼀下)重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的寄义,如: int operator+(int x, int y);     " .* ":案例:   
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         void func()
  7.         {
  8.                 cout << "A::func()" << endl;
  9.         }
  10. };
  11. //用回调的方式去调用成员函数的指针
  12. //普通函数指针;void(*)();
  13. typedef void(A::* PF)(); //成员函数指针类型
  14. int main()
  15. {
  16.         //void(A:: * PF)() = nullptr;
  17.         PF pf = nullptr;
  18.         //实现回调
  19.         // C++规定成员函数要加&才能取到函数指针
  20.         pf = &A::func;
  21.         //普通的,全局的函数是可以回调的
  22.         //(*pf)();//成员函数回调不了,因为有隐含的this指针
  23.        
  24.         //要调用函数指针,要传隐含的this指针这个实参
  25.         A aa;
  26.         //(*pf)(&aa);//这样也不行,因为this指针在形参和实参的位置不能显示
  27.         //C++规定:回调成员函数的指针要这么回调:
  28.         (aa.*pf)();//aa就悄悄传给this
  29. }
复制代码
   •   ⼀个类需要重载哪些运算符,是看哪些运算符重载后故意义,⽐如Date类重载operator-就故意     义(天数),但是重载operator+就没故意义;     日期加日期没故意义,不会构成重载;又由于没有要求运算符双方必须要同类型,而是只要求至少有一个类类型的参数:   
  1. d1 + 100;//100天后日期是多少
  2. d1 - 100;//100天前日期是多少
  3. d1 - d2;//间隔天数
复制代码
运算符重载:
  1. //d1+100
  2. Date operator+(int day);
  3. //d1-100
  4. Date operator-(int day);
  5. //d1-d2
  6. int operator-(const Date& d);
复制代码
 注:运算符重载和函数重载都用了重载,但他们没有关系;函数重载是函数名相同,参数差别;运算符重载是重新界说运算符的行为,另外,两个运算符重载函数可以构成函数重载
      •    重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分,       C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分;         •    重载<<和>>时,需要重载为全局函数,由于重载为成员函数,this指针默认抢占了第⼀个形参位        置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。        重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对        象;      2.赋值运算符重载 

      赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟拷⻉构造区分,拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象   
  1. int main()
  2. {
  3.         Date d1(2024, 7, 5);
  4.         Date d2(2024, 7, 6);
  5.        
  6.         //赋值重载拷贝
  7.         d1 = d2;
  8.         //拷贝构造
  9.         Date d3(d2);
  10.         Date d4 = d2;
  11.         return 0;
  12. }
复制代码
赋值运算符重载的特点:
   1.   赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数(不可以是全局)。赋值运算重载的参数建议写成 const 当前类类型引⽤,否则会传值传参会有拷⻉;   
  1. void operator=(const Date& d)
  2. {
  3.         //尽管用传值传参,不会产生所谓的无穷递归,现在是赋值重载,传值传参,把d2传给d,调用拷贝构造,调用完回来了,接着调用赋值
  4.         //operator赋值//但是建议用引用
  5.         _year = d._year;
  6.         _month = d._month;
  7.         _day = d._day;
  8. }
  9. bool operator==(const Date& d)
  10. {
  11.         //拷贝构造赋值重载拷贝去传值传参形成新的拷贝构造。。。。。无穷递归
  12.         //operator等于
  13. }
复制代码
由于:拷贝构造和复制重载两者本来就不一样!!! 
   2.   有返回值  ,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,  有返回值⽬的是为了⽀持连赋     值场景  ;   
  1. Date operator=(const Date& d)
  2. {
  3.         _year = d._year;
  4.         _month = d._month;
  5.         _day = d._day;
  6.         //d3=d1
  7.         //this是d3的地址
  8.         return *this;//类里面可以显示写
  9. }
复制代码
优化:传值返回也会生成一个拷贝,就调用拷贝构造,白白生成一个拷贝,付出了代价:用印用来解决:进步效率:
  1. Date& operator=(const Date& d)
  2. {
  3.         _year = d._year;
  4.         _month = d._month;
  5.         _day = d._day;
  6.         return *this;
  7. }
复制代码
   3.   没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认的拷贝构造函数雷同,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃界说类型成员变量会调⽤他的赋值重载;     4.   像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,固然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃界说类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要;  
3.日期类实现

补充:两个日期间隔多少天(有算正负数)-----算法:图解:

另外:还可以尽可能复用逻辑(代码中会演示到):取小日期(比较巨细),让小的日期不断++,真到与大日期相称:(不过相比于上图方法,会相对慢,但cpu太快了,无所谓)
代码:
头文件:Date.h
  1. #pragma once
  2. #include<iostream>
  3. using namespace std;
  4. #include<assert.h>
  5. class Date
  6. {
  7.        
  8.         //我和小笨蛋不认识,所以不可以去他家玩,但我很想去,因为他爸爸会数分,就给小笨蛋吹彩虹屁,就和他成为朋友了
  9.         //友元声明:friend+函数声明
  10.         friend ostream& operator<<(ostream& out, const Date& d);
  11.         friend istream& operator>>(istream& in, Date& d);
  12. public:
  13.         bool CheckDate();
  14.         Date(int year = 1900, int month = 1, int day = 1);
  15.         void Print()const;
  16.         //频繁调用的小函数
  17.         //没有做声明和定义分离, 因为定义在类里面的成员函数默认是内联inline
  18.         int GetMonthDay(int year, int month)
  19.         {
  20.                 //防止month在减的时候是从一月减到零月:
  21.                 assert(month > 0 && month < 13);
  22.                 //因为要频繁调用,所以放到静态区去
  23.                 static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
  24.                 if ((month == 2) && (year % 100 != 0 && year % 4 == 0) || (year % 400 == 0))
  25.                 {
  26.                         return 29;
  27.                 }
  28.                 return monthDayArray[month];
  29.         }
  30.         bool operator<(const Date& d)const;
  31.         bool operator<=(const Date& d)const;
  32.         bool operator>(const Date& d)const;
  33.         bool operator>=(const Date& d)const;
  34.         bool operator==(const Date& d)const;
  35.         bool operator!=(const Date& d)const;
  36.         Date operator+(int day)const;
  37.         Date& operator+=(int day);
  38.         Date operator-(int day)const;
  39.         Date& operator-=(int day);
  40.         d1++
  41.         //Date operator++();
  42.         ++d1
  43.         //Date operator++();
  44.         //这么区分这两个(d1++,++d1):重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。
  45.         //C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
  46.         //d1++:后置++
  47.         Date operator++(int);
  48.         //++d1:前置++
  49.         Date& operator++();
  50.         Date operator--(int);
  51.         Date& operator--();
  52.         //d1-d2
  53.         int operator-(const Date& d);
  54.         //printf与scanf他们直接适应的是内置类型
  55.         //流插入之所以可以自定识别类型,是因为本质是函数重载
  56.         //void operator<<(ostream& out);//写成成员函数是为了访问私有,但是这个实现形式倒反天罡
  57. private:
  58.         int _year;
  59.         int _month;
  60.         int _day;
  61. };
  62. //全局
  63. //流插入
  64. ostream& operator<<(ostream& out, const Date& d);
  65. //用友元声明:让类外面的函数访问类里面的元素
  66. //可是连续输出又不行:EG:cout<<d1<<d2
  67. //流插入是从左到右(赋值相反),所以应该返回out
  68. //流提取
  69. istream& operator>>(istream& in, Date& d);//提取的值要放到Date对象中,所以不加const
复制代码
源文件:Date.cpp
  1. #include"Date.h"
  2. bool Date::CheckDate()
  3. {
  4.         if (_month < 1 || _month > 12
  5.                 || _day < 1 || _day > GetMonthDay(_year, _month))
  6.         {
  7.                 return false;
  8.         }
  9.         else
  10.         {
  11.                 return true;
  12.         }
  13. }
  14. Date::Date(int year, int month, int day)
  15. {
  16.         _year = year;
  17.         _month = month;
  18.         _day = day;
  19.         if (!CheckDate())
  20.         {
  21.                 cout << "非法日期" << endl;
  22.                 Print();
  23.         }
  24. }
  25. void Date::Print() const
  26. {
  27.         cout << _year << "-" << _month << "-" << _day << endl;
  28. }
  29. //这是+=:会改变_day
  30. Date& Date::operator+=(int day)
  31. {
  32.         //有坑:当day为负数时:
  33.         // 解决:
  34.         if (day < 0)
  35.         {
  36.                 return *this -= (-day);
  37.         }
  38.         //与加法进数一样,天满了往月上进位,月满了往年进位
  39.         _day += day;
  40.         while (_day > GetMonthDay(_year, _month))
  41.         {
  42.                 _day -= GetMonthDay(_year, _month);
  43.                 _month++;
  44.                 if (_month == 13)
  45.                 {
  46.                         _year++;
  47.                         _month = 1;
  48.                 }
  49.         }
  50.         return*this;
  51. }
  52. //d1+100:不会改变d1
  53. Date Date::operator+(int day)const
  54. {
  55.         //拷贝构造的场景
  56.         Date tmp = *this;
  57.         //+=就会往tmp上走
  58.         /*tmp._day += day;
  59.         while (tmp._day > GetMonthDay(tmp._year, tmp._month))
  60.         {
  61.                 tmp._day -= GetMonthDay(tmp._year, tmp._month);
  62.                 tmp._month++;
  63.                 if (tmp._month == 13)
  64.                 {
  65.                         tmp._year++;
  66.                         tmp._month = 1;
  67.                 }
  68.         }*/
  69.         //复用:
  70.         tmp += day;
  71.         return tmp;//tmp为局部对象,出了作用域销毁,不能用引用返回,即使会多进行一次拷贝
  72. }
  73. Date& Date:: operator-=(int day)
  74. {
  75.         if (day < 0)
  76.         {
  77.                 return *this += (-day);
  78.         }
  79.         _day -= day;
  80.         while (_day <= 0)
  81.         {
  82.                 _month--;
  83.                 if (_month == 0)
  84.                 {
  85.                         _month = 12;
  86.                 }
  87.                 _day += GetMonthDay(_year, _month);
  88.         }
  89.         return *this;
  90. }
  91. Date Date:: operator-(int day)const
  92. {
  93.         Date tmp = *this;
  94.         /*tmp._day -= day;
  95.         while (tmp._day <= 0)
  96.         {
  97.                 tmp._month--;
  98.                 if (tmp._month == 0)
  99.                 {
  100.                         tmp._month = 12;
  101.                 }
  102.                 tmp._day += GetMonthDay(tmp._year, tmp._month);
  103.         }*/
  104.         tmp -= day;
  105.         return tmp;
  106. }
  107. d1-=100
  108. 用-实现-=:d1改变
  109. //Date& Date:: operator-=(int day)
  110. //{
  111. //        /*Date tmp = *this - day;
  112. //        *this = tmp;*/
  113. //        *this = *this - day;
  114. //        return *this;
  115. //}
  116. //其实-复用-=更好:因为-的效率本身是更低的
  117. //:
  118. //Date tmp = *this;是一次拷贝,return *this 传值返回也是一次拷贝
  119. //-复用-=或-=复用-,在“-”中,都要进行两次构造,所以,真正的区别在于-=
  120. //如果-=是自己实现,全程没有拷贝;
  121. //但是-=去复用-,就会加上-带来的两次拷贝和多加的赋值拷贝,很亏
  122. //至少实现<+=或>+=
  123. bool Date:: operator<(const Date& d)const
  124. {
  125.         if (_year < d._year)
  126.         {
  127.                 return true;
  128.         }
  129.         else if (_year == d._year)
  130.         {
  131.                 if (_month < d._month)
  132.                 {
  133.                         return true;
  134.                 }
  135.                 else if (_month == d._month)
  136.                 {
  137.                         return _day < d._day;
  138.                 }
  139.         }
  140.         return false;
  141. }
  142. //d1<=d2
  143. bool Date:: operator<=(const Date& d)const
  144. {
  145.         return *this < d || *this == d;
  146. }
  147. bool Date:: operator>(const Date& d)const
  148. {
  149.         /*if (_year > d._year)
  150.         {
  151.                 return true;
  152.         }
  153.         else if (_year == d._year)
  154.         {
  155.                 if (_month > d._month)
  156.                 {
  157.                         return true;
  158.                 }
  159.                 else if (_month == d._month)
  160.                 {
  161.                         return _day > d._day;
  162.                 }
  163.         }
  164.         return false;*/
  165.         return !(*this <= d);
  166. }
  167. bool Date:: operator>=(const Date& d)const
  168. {
  169.         return !(*this < d);
  170. }
  171. bool Date:: operator==(const Date& d)const
  172. {
  173.         return _year == d._year && _month == d._month && _day == d._day;
  174. }
  175. bool Date:: operator!=(const Date& d)const
  176. {
  177.         return !(*this == d);
  178. }
  179. //d1++
  180. //d1.operator(0);
  181. Date Date::operator++(int)
  182. {
  183.         Date tmp = *this;
  184.         *this += 1;
  185.         return tmp;
  186. }
  187. //++d1
  188. //d1.operator();
  189. Date& Date::operator++()
  190. {
  191.         *this += 1;
  192.         return *this;
  193. }
  194. //所以:能用前置就用前置:引用返回:少拷贝
  195. Date Date::operator--(int)
  196. {
  197.         Date tmp = *this;
  198.         *this -= 1;
  199.         return tmp;
  200. }
  201. Date& Date::operator--()
  202. {
  203.         *this -= 1;
  204.         return *this;
  205. }
  206. //天数的实现
  207. int Date:: operator-(const Date& d)
  208. {
  209.         int flag = 1;
  210.         int n = 0;
  211.         //假设法
  212.         Date max = *this;
  213.         Date min = d;
  214.         if (*this < d)
  215.         {
  216.                 max = d;
  217.                 min = *this;
  218.                 flag = -1;
  219.         }
  220.         while (min != max)
  221.         {
  222.                 ++min;
  223.                 ++n;
  224.         }
  225.         return n * flag;
  226. }
  227. //倒反天罡
  228. //void Date::operator<<(ostream& out)
  229. //{
  230. //        //out就是cout
  231. //        //对于二元函数,默认左操作数对应第一个参数
  232. //        out << _year << "年" << _month << "月" << _day << "日" << endl;
  233. //        //要这么配得上:d1<<out//d1.operator<<(cout)
  234. //}
  235. //因为流对象中含有指向IO缓冲区的指针,假如流对象可以复制,那么将会有两个指针同时操作缓冲区,如何释放、如何修改都会有冲突同步问题,因此流对象无法复制。
  236. ostream& operator<<(ostream& out, const Date& d)
  237. {
  238.         out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
  239.         return out;
  240. }
  241. istream& operator>>(istream& in, Date& d)
  242. {
  243.         while (1)
  244.         {
  245.                 cout << "请依次输入年月日:>";
  246.                 in >> d._year >> d._month >> d._day;
  247.                 //在进行流提取是,cout的buffer刷新了,be flushed了
  248.                 if (!d.CheckDate())
  249.                 {
  250.                         cout << "输入日期非法";
  251.                         d.Print();
  252.                         cout << "请重新输入:" << endl;
  253.                 }
  254.                 else
  255.                 {
  256.                         break;
  257.                 }
  258.         }
  259.         return in;
  260. }
复制代码
5.取地点运算符重载

1.const成员函数

      •    将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后⾯;       •    const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this    变为    const        Date* const this   
  1. void Date::Print()
  2. {
  3.         cout << _year << "-" << _month << "-" << _day << endl;
  4. }
  5. const Date d1(2024, 7, 19);
  6. d1.Print();
  7. //权限放大
复制代码
解决:(const修饰后不能修改)
  1. void Date::Print() const
  2. {
  3.         cout << _year << "-" << _month << "-" << _day << endl;
  4. }
  5. const Date d1(2024, 7, 19);
  6. d1.Print();
  7. Date d2(2024, 7, 20);
  8. d2.Print();
  9. //权限可以缩小
复制代码
 所以:以后不修改的只管加上const
2.取地点运算符重载

      取地点运算符重载分为普通取地点运算符重载和const取地点运算符重载,⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地点,就可以⾃⼰实现⼀份,胡乱返回⼀个地点   
  1. #include<iostream>
  2. using namespace std;
  3. class Date
  4. {
  5. public:
  6. //两个都写,优先取合适的
  7.         Date* operator&()
  8.         {
  9.                 return this;
  10.                 // return nullptr;
  11.                 //使坏:
  12.                 //return (Date*)0x2673FF40;
  13.         }
  14.         const Date* operator&()const
  15.         {
  16.                 return this;
  17.                 // return nullptr;
  18.                 //return (Date*)0x2673FF40;
  19.         }
  20. private:
  21.         int _year; // 年
  22.         int _month; // ⽉
  23.         int _day; // ⽇
  24. };
复制代码

 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表